iamapi_management_handlers.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. package iamapi
  2. import (
  3. "crypto/sha1"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "math/rand"
  8. "net/http"
  9. "net/url"
  10. "reflect"
  11. "strings"
  12. "sync"
  13. "time"
  14. "github.com/seaweedfs/seaweedfs/weed/glog"
  15. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  16. "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
  17. "github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
  18. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  19. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  20. "github.com/aws/aws-sdk-go/service/iam"
  21. )
  22. const (
  23. charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  24. charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/"
  25. policyDocumentVersion = "2012-10-17"
  26. StatementActionAdmin = "*"
  27. StatementActionWrite = "Put*"
  28. StatementActionWriteAcp = "PutBucketAcl"
  29. StatementActionRead = "Get*"
  30. StatementActionReadAcp = "GetBucketAcl"
  31. StatementActionList = "List*"
  32. StatementActionTagging = "Tagging*"
  33. StatementActionDelete = "DeleteBucket*"
  34. )
  35. var (
  36. seededRand *rand.Rand = rand.New(
  37. rand.NewSource(time.Now().UnixNano()))
  38. policyDocuments = map[string]*policy_engine.PolicyDocument{}
  39. policyLock = sync.RWMutex{}
  40. )
  41. func MapToStatementAction(action string) string {
  42. switch action {
  43. case StatementActionAdmin:
  44. return s3_constants.ACTION_ADMIN
  45. case StatementActionWrite:
  46. return s3_constants.ACTION_WRITE
  47. case StatementActionWriteAcp:
  48. return s3_constants.ACTION_WRITE_ACP
  49. case StatementActionRead:
  50. return s3_constants.ACTION_READ
  51. case StatementActionReadAcp:
  52. return s3_constants.ACTION_READ_ACP
  53. case StatementActionList:
  54. return s3_constants.ACTION_LIST
  55. case StatementActionTagging:
  56. return s3_constants.ACTION_TAGGING
  57. case StatementActionDelete:
  58. return s3_constants.ACTION_DELETE_BUCKET
  59. default:
  60. return ""
  61. }
  62. }
  63. func MapToIdentitiesAction(action string) string {
  64. switch action {
  65. case s3_constants.ACTION_ADMIN:
  66. return StatementActionAdmin
  67. case s3_constants.ACTION_WRITE:
  68. return StatementActionWrite
  69. case s3_constants.ACTION_WRITE_ACP:
  70. return StatementActionWriteAcp
  71. case s3_constants.ACTION_READ:
  72. return StatementActionRead
  73. case s3_constants.ACTION_READ_ACP:
  74. return StatementActionReadAcp
  75. case s3_constants.ACTION_LIST:
  76. return StatementActionList
  77. case s3_constants.ACTION_TAGGING:
  78. return StatementActionTagging
  79. case s3_constants.ACTION_DELETE_BUCKET:
  80. return StatementActionDelete
  81. default:
  82. return ""
  83. }
  84. }
  85. const (
  86. USER_DOES_NOT_EXIST = "the user with name %s cannot be found."
  87. )
  88. type Policies struct {
  89. Policies map[string]policy_engine.PolicyDocument `json:"policies"`
  90. }
  91. func Hash(s *string) string {
  92. h := sha1.New()
  93. h.Write([]byte(*s))
  94. return fmt.Sprintf("%x", h.Sum(nil))
  95. }
  96. func StringWithCharset(length int, charset string) string {
  97. b := make([]byte, length)
  98. for i := range b {
  99. b[i] = charset[seededRand.Intn(len(charset))]
  100. }
  101. return string(b)
  102. }
  103. func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListUsersResponse) {
  104. for _, ident := range s3cfg.Identities {
  105. resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &iam.User{UserName: &ident.Name})
  106. }
  107. return resp
  108. }
  109. func (iama *IamApiServer) ListAccessKeys(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListAccessKeysResponse) {
  110. status := iam.StatusTypeActive
  111. userName := values.Get("UserName")
  112. for _, ident := range s3cfg.Identities {
  113. if userName != "" && userName != ident.Name {
  114. continue
  115. }
  116. for _, cred := range ident.Credentials {
  117. resp.ListAccessKeysResult.AccessKeyMetadata = append(resp.ListAccessKeysResult.AccessKeyMetadata,
  118. &iam.AccessKeyMetadata{UserName: &ident.Name, AccessKeyId: &cred.AccessKey, Status: &status},
  119. )
  120. }
  121. }
  122. return resp
  123. }
  124. func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateUserResponse) {
  125. userName := values.Get("UserName")
  126. resp.CreateUserResult.User.UserName = &userName
  127. s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName})
  128. return resp
  129. }
  130. func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp DeleteUserResponse, err *IamError) {
  131. for i, ident := range s3cfg.Identities {
  132. if userName == ident.Name {
  133. s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
  134. return resp, nil
  135. }
  136. }
  137. return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf(USER_DOES_NOT_EXIST, userName)}
  138. }
  139. func (iama *IamApiServer) GetUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp GetUserResponse, err *IamError) {
  140. for _, ident := range s3cfg.Identities {
  141. if userName == ident.Name {
  142. resp.GetUserResult.User = iam.User{UserName: &ident.Name}
  143. return resp, nil
  144. }
  145. }
  146. return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf(USER_DOES_NOT_EXIST, userName)}
  147. }
  148. func (iama *IamApiServer) UpdateUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp UpdateUserResponse, err *IamError) {
  149. userName := values.Get("UserName")
  150. newUserName := values.Get("NewUserName")
  151. if newUserName != "" {
  152. for _, ident := range s3cfg.Identities {
  153. if userName == ident.Name {
  154. ident.Name = newUserName
  155. return resp, nil
  156. }
  157. }
  158. } else {
  159. return resp, nil
  160. }
  161. return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf(USER_DOES_NOT_EXIST, userName)}
  162. }
  163. func GetPolicyDocument(policy *string) (policy_engine.PolicyDocument, error) {
  164. var policyDocument policy_engine.PolicyDocument
  165. if err := json.Unmarshal([]byte(*policy), &policyDocument); err != nil {
  166. return policy_engine.PolicyDocument{}, err
  167. }
  168. return policyDocument, nil
  169. }
  170. func (iama *IamApiServer) CreatePolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreatePolicyResponse, iamError *IamError) {
  171. policyName := values.Get("PolicyName")
  172. policyDocumentString := values.Get("PolicyDocument")
  173. policyDocument, err := GetPolicyDocument(&policyDocumentString)
  174. if err != nil {
  175. return CreatePolicyResponse{}, &IamError{Code: iam.ErrCodeMalformedPolicyDocumentException, Error: err}
  176. }
  177. policyId := Hash(&policyDocumentString)
  178. arn := fmt.Sprintf("arn:aws:iam:::policy/%s", policyName)
  179. resp.CreatePolicyResult.Policy.PolicyName = &policyName
  180. resp.CreatePolicyResult.Policy.Arn = &arn
  181. resp.CreatePolicyResult.Policy.PolicyId = &policyId
  182. policies := Policies{}
  183. policyLock.Lock()
  184. defer policyLock.Unlock()
  185. if err = iama.s3ApiConfig.GetPolicies(&policies); err != nil {
  186. return resp, &IamError{Code: iam.ErrCodeServiceFailureException, Error: err}
  187. }
  188. policies.Policies[policyName] = policyDocument
  189. if err = iama.s3ApiConfig.PutPolicies(&policies); err != nil {
  190. return resp, &IamError{Code: iam.ErrCodeServiceFailureException, Error: err}
  191. }
  192. return resp, nil
  193. }
  194. type IamError struct {
  195. Code string
  196. Error error
  197. }
  198. // https://docs.aws.amazon.com/IAM/latest/APIReference/API_PutUserPolicy.html
  199. func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, iamError *IamError) {
  200. userName := values.Get("UserName")
  201. policyName := values.Get("PolicyName")
  202. policyDocumentString := values.Get("PolicyDocument")
  203. policyDocument, err := GetPolicyDocument(&policyDocumentString)
  204. if err != nil {
  205. return PutUserPolicyResponse{}, &IamError{Code: iam.ErrCodeMalformedPolicyDocumentException, Error: err}
  206. }
  207. policyDocuments[policyName] = &policyDocument
  208. actions, err := GetActions(&policyDocument)
  209. if err != nil {
  210. return PutUserPolicyResponse{}, &IamError{Code: iam.ErrCodeMalformedPolicyDocumentException, Error: err}
  211. }
  212. // Log the actions
  213. glog.V(3).Infof("PutUserPolicy: actions=%v", actions)
  214. for _, ident := range s3cfg.Identities {
  215. if userName != ident.Name {
  216. continue
  217. }
  218. ident.Actions = actions
  219. return resp, nil
  220. }
  221. return PutUserPolicyResponse{}, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("the user with name %s cannot be found", userName)}
  222. }
  223. func (iama *IamApiServer) GetUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp GetUserPolicyResponse, err *IamError) {
  224. userName := values.Get("UserName")
  225. policyName := values.Get("PolicyName")
  226. for _, ident := range s3cfg.Identities {
  227. if userName != ident.Name {
  228. continue
  229. }
  230. resp.GetUserPolicyResult.UserName = userName
  231. resp.GetUserPolicyResult.PolicyName = policyName
  232. if len(ident.Actions) == 0 {
  233. return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: errors.New("no actions found")}
  234. }
  235. policyDocument := policy_engine.PolicyDocument{Version: policyDocumentVersion}
  236. statements := make(map[string][]string)
  237. for _, action := range ident.Actions {
  238. // parse "Read:EXAMPLE-BUCKET"
  239. act := strings.Split(action, ":")
  240. resource := "*"
  241. if len(act) == 2 {
  242. resource = fmt.Sprintf("arn:aws:s3:::%s/*", act[1])
  243. }
  244. statements[resource] = append(statements[resource],
  245. fmt.Sprintf("s3:%s", MapToIdentitiesAction(act[0])),
  246. )
  247. }
  248. for resource, actions := range statements {
  249. isEqAction := false
  250. for i, statement := range policyDocument.Statement {
  251. if reflect.DeepEqual(statement.Action.Strings(), actions) {
  252. policyDocument.Statement[i].Resource = policy_engine.NewStringOrStringSlice(append(
  253. policyDocument.Statement[i].Resource.Strings(), resource)...)
  254. isEqAction = true
  255. break
  256. }
  257. }
  258. if isEqAction {
  259. continue
  260. }
  261. policyDocumentStatement := policy_engine.PolicyStatement{
  262. Effect: policy_engine.PolicyEffectAllow,
  263. Action: policy_engine.NewStringOrStringSlice(actions...),
  264. Resource: policy_engine.NewStringOrStringSlice(resource),
  265. }
  266. policyDocument.Statement = append(policyDocument.Statement, policyDocumentStatement)
  267. }
  268. policyDocumentJSON, err := json.Marshal(policyDocument)
  269. if err != nil {
  270. return resp, &IamError{Code: iam.ErrCodeServiceFailureException, Error: err}
  271. }
  272. resp.GetUserPolicyResult.PolicyDocument = string(policyDocumentJSON)
  273. return resp, nil
  274. }
  275. return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf(USER_DOES_NOT_EXIST, userName)}
  276. }
  277. func (iama *IamApiServer) DeleteUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err *IamError) {
  278. userName := values.Get("UserName")
  279. for i, ident := range s3cfg.Identities {
  280. if ident.Name == userName {
  281. s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
  282. return resp, nil
  283. }
  284. }
  285. return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf(USER_DOES_NOT_EXIST, userName)}
  286. }
  287. func GetActions(policy *policy_engine.PolicyDocument) ([]string, error) {
  288. var actions []string
  289. for _, statement := range policy.Statement {
  290. if statement.Effect != policy_engine.PolicyEffectAllow {
  291. return nil, fmt.Errorf("not a valid effect: '%s'. Only 'Allow' is possible", statement.Effect)
  292. }
  293. for _, resource := range statement.Resource.Strings() {
  294. // Parse "arn:aws:s3:::my-bucket/shared/*"
  295. res := strings.Split(resource, ":")
  296. if len(res) != 6 || res[0] != "arn" || res[1] != "aws" || res[2] != "s3" {
  297. glog.Infof("not a valid resource: %s", res)
  298. continue
  299. }
  300. for _, action := range statement.Action.Strings() {
  301. // Parse "s3:Get*"
  302. act := strings.Split(action, ":")
  303. if len(act) != 2 || act[0] != "s3" {
  304. glog.Infof("not a valid action: %s", act)
  305. continue
  306. }
  307. statementAction := MapToStatementAction(act[1])
  308. if statementAction == "" {
  309. return nil, fmt.Errorf("not a valid action: '%s'", act[1])
  310. }
  311. path := res[5]
  312. if path == "*" {
  313. actions = append(actions, statementAction)
  314. continue
  315. }
  316. actions = append(actions, fmt.Sprintf("%s:%s", statementAction, path))
  317. }
  318. }
  319. }
  320. return actions, nil
  321. }
  322. func (iama *IamApiServer) CreateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateAccessKeyResponse) {
  323. userName := values.Get("UserName")
  324. status := iam.StatusTypeActive
  325. accessKeyId := StringWithCharset(21, charsetUpper)
  326. secretAccessKey := StringWithCharset(42, charset)
  327. resp.CreateAccessKeyResult.AccessKey.AccessKeyId = &accessKeyId
  328. resp.CreateAccessKeyResult.AccessKey.SecretAccessKey = &secretAccessKey
  329. resp.CreateAccessKeyResult.AccessKey.UserName = &userName
  330. resp.CreateAccessKeyResult.AccessKey.Status = &status
  331. changed := false
  332. for _, ident := range s3cfg.Identities {
  333. if userName == ident.Name {
  334. ident.Credentials = append(ident.Credentials,
  335. &iam_pb.Credential{AccessKey: accessKeyId, SecretKey: secretAccessKey})
  336. changed = true
  337. break
  338. }
  339. }
  340. if !changed {
  341. s3cfg.Identities = append(s3cfg.Identities,
  342. &iam_pb.Identity{
  343. Name: userName,
  344. Credentials: []*iam_pb.Credential{
  345. {
  346. AccessKey: accessKeyId,
  347. SecretKey: secretAccessKey,
  348. },
  349. },
  350. },
  351. )
  352. }
  353. return resp
  354. }
  355. func (iama *IamApiServer) DeleteAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteAccessKeyResponse) {
  356. userName := values.Get("UserName")
  357. accessKeyId := values.Get("AccessKeyId")
  358. for _, ident := range s3cfg.Identities {
  359. if userName == ident.Name {
  360. for i, cred := range ident.Credentials {
  361. if cred.AccessKey == accessKeyId {
  362. ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...)
  363. break
  364. }
  365. }
  366. break
  367. }
  368. }
  369. return resp
  370. }
  371. // handleImplicitUsername adds username who signs the request to values if 'username' is not specified
  372. // According to https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/create-access-key.html/
  373. // "If you do not specify a user name, IAM determines the user name implicitly based on the Amazon Web
  374. // Services access key ID signing the request."
  375. func handleImplicitUsername(r *http.Request, values url.Values) {
  376. if len(r.Header["Authorization"]) == 0 || values.Get("UserName") != "" {
  377. return
  378. }
  379. // get username who signs the request. For a typical Authorization:
  380. // "AWS4-HMAC-SHA256 Credential=197FSAQ7HHTA48X64O3A/20220420/test1/iam/aws4_request, SignedHeaders=content-type;
  381. // host;x-amz-date, Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8",
  382. // the "test1" will be extracted as the username
  383. glog.V(4).Infof("Authorization field: %v", r.Header["Authorization"][0])
  384. s := strings.Split(r.Header["Authorization"][0], "Credential=")
  385. if len(s) < 2 {
  386. return
  387. }
  388. s = strings.Split(s[1], ",")
  389. if len(s) < 2 {
  390. return
  391. }
  392. s = strings.Split(s[0], "/")
  393. if len(s) < 5 {
  394. return
  395. }
  396. userName := s[2]
  397. values.Set("UserName", userName)
  398. }
  399. func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
  400. if err := r.ParseForm(); err != nil {
  401. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  402. return
  403. }
  404. values := r.PostForm
  405. s3cfg := &iam_pb.S3ApiConfiguration{}
  406. if err := iama.s3ApiConfig.GetS3ApiConfiguration(s3cfg); err != nil && !errors.Is(err, filer_pb.ErrNotFound) {
  407. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  408. return
  409. }
  410. glog.V(4).Infof("DoActions: %+v", values)
  411. var response interface{}
  412. var iamError *IamError
  413. changed := true
  414. switch r.Form.Get("Action") {
  415. case "ListUsers":
  416. response = iama.ListUsers(s3cfg, values)
  417. changed = false
  418. case "ListAccessKeys":
  419. handleImplicitUsername(r, values)
  420. response = iama.ListAccessKeys(s3cfg, values)
  421. changed = false
  422. case "CreateUser":
  423. response = iama.CreateUser(s3cfg, values)
  424. case "GetUser":
  425. userName := values.Get("UserName")
  426. response, iamError = iama.GetUser(s3cfg, userName)
  427. if iamError != nil {
  428. writeIamErrorResponse(w, r, iamError)
  429. return
  430. }
  431. changed = false
  432. case "UpdateUser":
  433. response, iamError = iama.UpdateUser(s3cfg, values)
  434. if iamError != nil {
  435. glog.Errorf("UpdateUser: %+v", iamError.Error)
  436. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  437. return
  438. }
  439. case "DeleteUser":
  440. userName := values.Get("UserName")
  441. response, iamError = iama.DeleteUser(s3cfg, userName)
  442. if iamError != nil {
  443. writeIamErrorResponse(w, r, iamError)
  444. return
  445. }
  446. case "CreateAccessKey":
  447. handleImplicitUsername(r, values)
  448. response = iama.CreateAccessKey(s3cfg, values)
  449. case "DeleteAccessKey":
  450. handleImplicitUsername(r, values)
  451. response = iama.DeleteAccessKey(s3cfg, values)
  452. case "CreatePolicy":
  453. response, iamError = iama.CreatePolicy(s3cfg, values)
  454. if iamError != nil {
  455. glog.Errorf("CreatePolicy: %+v", iamError.Error)
  456. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  457. return
  458. }
  459. case "PutUserPolicy":
  460. var iamError *IamError
  461. response, iamError = iama.PutUserPolicy(s3cfg, values)
  462. if iamError != nil {
  463. glog.Errorf("PutUserPolicy: %+v", iamError.Error)
  464. writeIamErrorResponse(w, r, iamError)
  465. return
  466. }
  467. case "GetUserPolicy":
  468. response, iamError = iama.GetUserPolicy(s3cfg, values)
  469. if iamError != nil {
  470. writeIamErrorResponse(w, r, iamError)
  471. return
  472. }
  473. changed = false
  474. case "DeleteUserPolicy":
  475. if response, iamError = iama.DeleteUserPolicy(s3cfg, values); iamError != nil {
  476. writeIamErrorResponse(w, r, iamError)
  477. return
  478. }
  479. default:
  480. errNotImplemented := s3err.GetAPIError(s3err.ErrNotImplemented)
  481. errorResponse := ErrorResponse{}
  482. errorResponse.Error.Code = &errNotImplemented.Code
  483. errorResponse.Error.Message = &errNotImplemented.Description
  484. s3err.WriteXMLResponse(w, r, errNotImplemented.HTTPStatusCode, errorResponse)
  485. return
  486. }
  487. if changed {
  488. err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg)
  489. if err != nil {
  490. var iamError = IamError{Code: iam.ErrCodeServiceFailureException, Error: err}
  491. writeIamErrorResponse(w, r, &iamError)
  492. return
  493. }
  494. }
  495. s3err.WriteXMLResponse(w, r, http.StatusOK, response)
  496. }