iamapi_management_handlers.go 16 KB

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