iamapi_management_handlers.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. package iamapi
  2. import (
  3. "crypto/sha1"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/chrislusf/seaweedfs/weed/glog"
  7. "github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
  8. "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
  9. "github.com/chrislusf/seaweedfs/weed/s3api/s3err"
  10. "math/rand"
  11. "net/http"
  12. "net/url"
  13. "reflect"
  14. "strings"
  15. "sync"
  16. "time"
  17. "github.com/aws/aws-sdk-go/service/iam"
  18. )
  19. const (
  20. charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  21. charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/"
  22. policyDocumentVersion = "2012-10-17"
  23. StatementActionAdmin = "*"
  24. StatementActionWrite = "Put*"
  25. StatementActionRead = "Get*"
  26. StatementActionList = "List*"
  27. StatementActionTagging = "Tagging*"
  28. )
  29. var (
  30. seededRand *rand.Rand = rand.New(
  31. rand.NewSource(time.Now().UnixNano()))
  32. policyDocuments = map[string]*PolicyDocument{}
  33. policyLock = sync.RWMutex{}
  34. )
  35. func MapToStatementAction(action string) string {
  36. switch action {
  37. case StatementActionAdmin:
  38. return s3_constants.ACTION_ADMIN
  39. case StatementActionWrite:
  40. return s3_constants.ACTION_WRITE
  41. case StatementActionRead:
  42. return s3_constants.ACTION_READ
  43. case StatementActionList:
  44. return s3_constants.ACTION_LIST
  45. case StatementActionTagging:
  46. return s3_constants.ACTION_TAGGING
  47. default:
  48. return ""
  49. }
  50. }
  51. func MapToIdentitiesAction(action string) string {
  52. switch action {
  53. case s3_constants.ACTION_ADMIN:
  54. return StatementActionAdmin
  55. case s3_constants.ACTION_WRITE:
  56. return StatementActionWrite
  57. case s3_constants.ACTION_READ:
  58. return StatementActionRead
  59. case s3_constants.ACTION_LIST:
  60. return StatementActionList
  61. case s3_constants.ACTION_TAGGING:
  62. return StatementActionTagging
  63. default:
  64. return ""
  65. }
  66. }
  67. type Statement struct {
  68. Effect string `json:"Effect"`
  69. Action []string `json:"Action"`
  70. Resource []string `json:"Resource"`
  71. }
  72. type Policies struct {
  73. Policies map[string]PolicyDocument `json:"policies"`
  74. }
  75. type PolicyDocument struct {
  76. Version string `json:"Version"`
  77. Statement []*Statement `json:"Statement"`
  78. }
  79. func (p PolicyDocument) String() string {
  80. b, _ := json.Marshal(p)
  81. return string(b)
  82. }
  83. func Hash(s *string) string {
  84. h := sha1.New()
  85. h.Write([]byte(*s))
  86. return fmt.Sprintf("%x", h.Sum(nil))
  87. }
  88. func StringWithCharset(length int, charset string) string {
  89. b := make([]byte, length)
  90. for i := range b {
  91. b[i] = charset[seededRand.Intn(len(charset))]
  92. }
  93. return string(b)
  94. }
  95. func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListUsersResponse) {
  96. for _, ident := range s3cfg.Identities {
  97. resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &iam.User{UserName: &ident.Name})
  98. }
  99. return resp
  100. }
  101. func (iama *IamApiServer) ListAccessKeys(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListAccessKeysResponse) {
  102. status := iam.StatusTypeActive
  103. userName := values.Get("UserName")
  104. for _, ident := range s3cfg.Identities {
  105. if userName != "" && userName != ident.Name {
  106. continue
  107. }
  108. for _, cred := range ident.Credentials {
  109. resp.ListAccessKeysResult.AccessKeyMetadata = append(resp.ListAccessKeysResult.AccessKeyMetadata,
  110. &iam.AccessKeyMetadata{UserName: &ident.Name, AccessKeyId: &cred.AccessKey, Status: &status},
  111. )
  112. }
  113. }
  114. return resp
  115. }
  116. func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateUserResponse) {
  117. userName := values.Get("UserName")
  118. resp.CreateUserResult.User.UserName = &userName
  119. s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName})
  120. return resp
  121. }
  122. func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp DeleteUserResponse, err error) {
  123. for i, ident := range s3cfg.Identities {
  124. if userName == ident.Name {
  125. s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
  126. return resp, nil
  127. }
  128. }
  129. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  130. }
  131. func (iama *IamApiServer) GetUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp GetUserResponse, err error) {
  132. for _, ident := range s3cfg.Identities {
  133. if userName == ident.Name {
  134. resp.GetUserResult.User = iam.User{UserName: &ident.Name}
  135. return resp, nil
  136. }
  137. }
  138. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  139. }
  140. func GetPolicyDocument(policy *string) (policyDocument PolicyDocument, err error) {
  141. if err = json.Unmarshal([]byte(*policy), &policyDocument); err != nil {
  142. return PolicyDocument{}, err
  143. }
  144. return policyDocument, err
  145. }
  146. func (iama *IamApiServer) CreatePolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreatePolicyResponse, err error) {
  147. policyName := values.Get("PolicyName")
  148. policyDocumentString := values.Get("PolicyDocument")
  149. policyDocument, err := GetPolicyDocument(&policyDocumentString)
  150. if err != nil {
  151. return CreatePolicyResponse{}, err
  152. }
  153. policyId := Hash(&policyDocumentString)
  154. arn := fmt.Sprintf("arn:aws:iam:::policy/%s", policyName)
  155. resp.CreatePolicyResult.Policy.PolicyName = &policyName
  156. resp.CreatePolicyResult.Policy.Arn = &arn
  157. resp.CreatePolicyResult.Policy.PolicyId = &policyId
  158. policies := Policies{}
  159. policyLock.Lock()
  160. defer policyLock.Unlock()
  161. if err = iama.s3ApiConfig.GetPolicies(&policies); err != nil {
  162. return resp, err
  163. }
  164. policies.Policies[policyName] = policyDocument
  165. if err = iama.s3ApiConfig.PutPolicies(&policies); err != nil {
  166. return resp, err
  167. }
  168. return resp, nil
  169. }
  170. func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) {
  171. userName := values.Get("UserName")
  172. policyName := values.Get("PolicyName")
  173. policyDocumentString := values.Get("PolicyDocument")
  174. policyDocument, err := GetPolicyDocument(&policyDocumentString)
  175. if err != nil {
  176. return PutUserPolicyResponse{}, err
  177. }
  178. policyDocuments[policyName] = &policyDocument
  179. actions := GetActions(&policyDocument)
  180. for _, ident := range s3cfg.Identities {
  181. if userName == ident.Name {
  182. for _, action := range actions {
  183. ident.Actions = append(ident.Actions, action)
  184. }
  185. break
  186. }
  187. }
  188. return resp, nil
  189. }
  190. func (iama *IamApiServer) GetUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp GetUserPolicyResponse, err error) {
  191. userName := values.Get("UserName")
  192. policyName := values.Get("PolicyName")
  193. for _, ident := range s3cfg.Identities {
  194. if userName != ident.Name {
  195. continue
  196. }
  197. resp.GetUserPolicyResult.UserName = userName
  198. resp.GetUserPolicyResult.PolicyName = policyName
  199. if len(ident.Actions) == 0 {
  200. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  201. }
  202. policyDocument := PolicyDocument{Version: policyDocumentVersion}
  203. statements := make(map[string][]string)
  204. for _, action := range ident.Actions {
  205. // parse "Read:EXAMPLE-BUCKET"
  206. act := strings.Split(action, ":")
  207. resource := "*"
  208. if len(act) == 2 {
  209. resource = fmt.Sprintf("arn:aws:s3:::%s/*", act[1])
  210. }
  211. statements[resource] = append(statements[resource],
  212. fmt.Sprintf("s3:%s", MapToIdentitiesAction(act[0])),
  213. )
  214. }
  215. for resource, actions := range statements {
  216. isEqAction := false
  217. for i, statement := range policyDocument.Statement {
  218. if reflect.DeepEqual(statement.Action, actions) {
  219. policyDocument.Statement[i].Resource = append(
  220. policyDocument.Statement[i].Resource, resource)
  221. isEqAction = true
  222. break
  223. }
  224. }
  225. if isEqAction {
  226. continue
  227. }
  228. policyDocumentStatement := Statement{
  229. Effect: "Allow",
  230. Action: actions,
  231. }
  232. policyDocumentStatement.Resource = append(policyDocumentStatement.Resource, resource)
  233. policyDocument.Statement = append(policyDocument.Statement, &policyDocumentStatement)
  234. }
  235. resp.GetUserPolicyResult.PolicyDocument = policyDocument.String()
  236. return resp, nil
  237. }
  238. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  239. }
  240. func (iama *IamApiServer) DeleteUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) {
  241. userName := values.Get("UserName")
  242. for i, ident := range s3cfg.Identities {
  243. if ident.Name == userName {
  244. s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
  245. return resp, nil
  246. }
  247. }
  248. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  249. }
  250. func GetActions(policy *PolicyDocument) (actions []string) {
  251. for _, statement := range policy.Statement {
  252. if statement.Effect != "Allow" {
  253. continue
  254. }
  255. for _, resource := range statement.Resource {
  256. // Parse "arn:aws:s3:::my-bucket/shared/*"
  257. res := strings.Split(resource, ":")
  258. if len(res) != 6 || res[0] != "arn" || res[1] != "aws" || res[2] != "s3" {
  259. glog.Infof("not match resource: %s", res)
  260. continue
  261. }
  262. for _, action := range statement.Action {
  263. // Parse "s3:Get*"
  264. act := strings.Split(action, ":")
  265. if len(act) != 2 || act[0] != "s3" {
  266. glog.Infof("not match action: %s", act)
  267. continue
  268. }
  269. statementAction := MapToStatementAction(act[1])
  270. if res[5] == "*" {
  271. actions = append(actions, statementAction)
  272. continue
  273. }
  274. // Parse my-bucket/shared/*
  275. path := strings.Split(res[5], "/")
  276. if len(path) != 2 || path[1] != "*" {
  277. glog.Infof("not match bucket: %s", path)
  278. continue
  279. }
  280. actions = append(actions, fmt.Sprintf("%s:%s", statementAction, path[0]))
  281. }
  282. }
  283. }
  284. return actions
  285. }
  286. func (iama *IamApiServer) CreateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateAccessKeyResponse) {
  287. userName := values.Get("UserName")
  288. status := iam.StatusTypeActive
  289. accessKeyId := StringWithCharset(21, charsetUpper)
  290. secretAccessKey := StringWithCharset(42, charset)
  291. resp.CreateAccessKeyResult.AccessKey.AccessKeyId = &accessKeyId
  292. resp.CreateAccessKeyResult.AccessKey.SecretAccessKey = &secretAccessKey
  293. resp.CreateAccessKeyResult.AccessKey.UserName = &userName
  294. resp.CreateAccessKeyResult.AccessKey.Status = &status
  295. changed := false
  296. for _, ident := range s3cfg.Identities {
  297. if userName == ident.Name {
  298. ident.Credentials = append(ident.Credentials,
  299. &iam_pb.Credential{AccessKey: accessKeyId, SecretKey: secretAccessKey})
  300. changed = true
  301. break
  302. }
  303. }
  304. if !changed {
  305. s3cfg.Identities = append(s3cfg.Identities,
  306. &iam_pb.Identity{Name: userName,
  307. Credentials: []*iam_pb.Credential{
  308. {
  309. AccessKey: accessKeyId,
  310. SecretKey: secretAccessKey,
  311. },
  312. },
  313. },
  314. )
  315. }
  316. return resp
  317. }
  318. func (iama *IamApiServer) DeleteAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteAccessKeyResponse) {
  319. userName := values.Get("UserName")
  320. accessKeyId := values.Get("AccessKeyId")
  321. for _, ident := range s3cfg.Identities {
  322. if userName == ident.Name {
  323. for i, cred := range ident.Credentials {
  324. if cred.AccessKey == accessKeyId {
  325. ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...)
  326. break
  327. }
  328. }
  329. break
  330. }
  331. }
  332. return resp
  333. }
  334. func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
  335. if err := r.ParseForm(); err != nil {
  336. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  337. return
  338. }
  339. values := r.PostForm
  340. var s3cfgLock sync.RWMutex
  341. s3cfgLock.RLock()
  342. s3cfg := &iam_pb.S3ApiConfiguration{}
  343. if err := iama.s3ApiConfig.GetS3ApiConfiguration(s3cfg); err != nil {
  344. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  345. return
  346. }
  347. s3cfgLock.RUnlock()
  348. glog.V(4).Infof("DoActions: %+v", values)
  349. var response interface{}
  350. var err error
  351. changed := true
  352. switch r.Form.Get("Action") {
  353. case "ListUsers":
  354. response = iama.ListUsers(s3cfg, values)
  355. changed = false
  356. case "ListAccessKeys":
  357. response = iama.ListAccessKeys(s3cfg, values)
  358. changed = false
  359. case "CreateUser":
  360. response = iama.CreateUser(s3cfg, values)
  361. case "GetUser":
  362. userName := values.Get("UserName")
  363. response, err = iama.GetUser(s3cfg, userName)
  364. if err != nil {
  365. writeIamErrorResponse(w, r, err, "user", userName, nil)
  366. return
  367. }
  368. changed = false
  369. case "DeleteUser":
  370. userName := values.Get("UserName")
  371. response, err = iama.DeleteUser(s3cfg, userName)
  372. if err != nil {
  373. writeIamErrorResponse(w, r, err, "user", userName, nil)
  374. return
  375. }
  376. case "CreateAccessKey":
  377. response = iama.CreateAccessKey(s3cfg, values)
  378. case "DeleteAccessKey":
  379. response = iama.DeleteAccessKey(s3cfg, values)
  380. case "CreatePolicy":
  381. response, err = iama.CreatePolicy(s3cfg, values)
  382. if err != nil {
  383. glog.Errorf("CreatePolicy: %+v", err)
  384. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  385. return
  386. }
  387. case "PutUserPolicy":
  388. response, err = iama.PutUserPolicy(s3cfg, values)
  389. if err != nil {
  390. glog.Errorf("PutUserPolicy: %+v", err)
  391. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  392. return
  393. }
  394. case "GetUserPolicy":
  395. response, err = iama.GetUserPolicy(s3cfg, values)
  396. if err != nil {
  397. writeIamErrorResponse(w, r, err, "user", values.Get("UserName"), nil)
  398. return
  399. }
  400. changed = false
  401. case "DeleteUserPolicy":
  402. if response, err = iama.DeleteUserPolicy(s3cfg, values); err != nil {
  403. writeIamErrorResponse(w, r, err, "user", values.Get("UserName"), nil)
  404. }
  405. default:
  406. errNotImplemented := s3err.GetAPIError(s3err.ErrNotImplemented)
  407. errorResponse := ErrorResponse{}
  408. errorResponse.Error.Code = &errNotImplemented.Code
  409. errorResponse.Error.Message = &errNotImplemented.Description
  410. s3err.WriteXMLResponse(w, r, errNotImplemented.HTTPStatusCode, errorResponse)
  411. return
  412. }
  413. if changed {
  414. s3cfgLock.Lock()
  415. err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg)
  416. s3cfgLock.Unlock()
  417. if err != nil {
  418. writeIamErrorResponse(w, r, fmt.Errorf(iam.ErrCodeServiceFailureException), "", "", err)
  419. return
  420. }
  421. }
  422. s3err.WriteXMLResponse(w, r, http.StatusOK, response)
  423. }