You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

322 lines
9.2 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  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. "strings"
  14. "sync"
  15. "time"
  16. "github.com/aws/aws-sdk-go/service/iam"
  17. )
  18. const (
  19. charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  20. charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/"
  21. )
  22. var (
  23. seededRand *rand.Rand = rand.New(
  24. rand.NewSource(time.Now().UnixNano()))
  25. policyDocuments = map[string]*PolicyDocument{}
  26. )
  27. type Statement struct {
  28. Effect string `json:"Effect"`
  29. Action []string `json:"Action"`
  30. Resource []string `json:"Resource"`
  31. }
  32. type PolicyDocument struct {
  33. Version string `json:"Version"`
  34. Statement []*Statement `json:"Statement"`
  35. }
  36. func Hash(s *string) string {
  37. h := sha1.New()
  38. h.Write([]byte(*s))
  39. return fmt.Sprintf("%x", h.Sum(nil))
  40. }
  41. func StringWithCharset(length int, charset string) string {
  42. b := make([]byte, length)
  43. for i := range b {
  44. b[i] = charset[seededRand.Intn(len(charset))]
  45. }
  46. return string(b)
  47. }
  48. func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListUsersResponse) {
  49. for _, ident := range s3cfg.Identities {
  50. resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &iam.User{UserName: &ident.Name})
  51. }
  52. return resp
  53. }
  54. func (iama *IamApiServer) ListAccessKeys(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListAccessKeysResponse) {
  55. status := iam.StatusTypeActive
  56. for _, ident := range s3cfg.Identities {
  57. for _, cred := range ident.Credentials {
  58. resp.ListAccessKeysResult.AccessKeyMetadata = append(resp.ListAccessKeysResult.AccessKeyMetadata,
  59. &iam.AccessKeyMetadata{UserName: &ident.Name, AccessKeyId: &cred.AccessKey, Status: &status},
  60. )
  61. }
  62. }
  63. return resp
  64. }
  65. func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateUserResponse) {
  66. userName := values.Get("UserName")
  67. resp.CreateUserResult.User.UserName = &userName
  68. s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName})
  69. return resp
  70. }
  71. func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp DeleteUserResponse, err error) {
  72. for i, ident := range s3cfg.Identities {
  73. if userName == ident.Name {
  74. ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...)
  75. return resp, nil
  76. }
  77. }
  78. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  79. }
  80. func (iama *IamApiServer) GetUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp GetUserResponse, err error) {
  81. for _, ident := range s3cfg.Identities {
  82. if userName == ident.Name {
  83. resp.GetUserResult.User = iam.User{UserName: &ident.Name}
  84. return resp, nil
  85. }
  86. }
  87. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  88. }
  89. func GetPolicyDocument(policy *string) (policyDocument PolicyDocument, err error) {
  90. if err = json.Unmarshal([]byte(*policy), &policyDocument); err != nil {
  91. return PolicyDocument{}, err
  92. }
  93. return policyDocument, err
  94. }
  95. func (iama *IamApiServer) CreatePolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreatePolicyResponse, err error) {
  96. policyName := values.Get("PolicyName")
  97. policyDocumentString := values.Get("PolicyDocument")
  98. policyDocument, err := GetPolicyDocument(&policyDocumentString)
  99. if err != nil {
  100. return CreatePolicyResponse{}, err
  101. }
  102. policyId := Hash(&policyDocumentString)
  103. arn := fmt.Sprintf("arn:aws:iam:::policy/%s", policyName)
  104. resp.CreatePolicyResult.Policy.PolicyName = &policyName
  105. resp.CreatePolicyResult.Policy.Arn = &arn
  106. resp.CreatePolicyResult.Policy.PolicyId = &policyId
  107. policyDocuments[policyName] = &policyDocument
  108. return resp, nil
  109. }
  110. func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) {
  111. userName := values.Get("UserName")
  112. policyName := values.Get("PolicyName")
  113. policyDocumentString := values.Get("PolicyDocument")
  114. policyDocument, err := GetPolicyDocument(&policyDocumentString)
  115. if err != nil {
  116. return PutUserPolicyResponse{}, err
  117. }
  118. policyDocuments[policyName] = &policyDocument
  119. actions := GetActions(&policyDocument)
  120. for _, ident := range s3cfg.Identities {
  121. if userName == ident.Name {
  122. for _, action := range actions {
  123. ident.Actions = append(ident.Actions, action)
  124. }
  125. break
  126. }
  127. }
  128. return resp, nil
  129. }
  130. func MapAction(action string) string {
  131. switch action {
  132. case "*":
  133. return s3_constants.ACTION_ADMIN
  134. case "Put*":
  135. return s3_constants.ACTION_WRITE
  136. case "Get*":
  137. return s3_constants.ACTION_READ
  138. case "List*":
  139. return s3_constants.ACTION_LIST
  140. default:
  141. return s3_constants.ACTION_TAGGING
  142. }
  143. }
  144. func GetActions(policy *PolicyDocument) (actions []string) {
  145. for _, statement := range policy.Statement {
  146. if statement.Effect != "Allow" {
  147. continue
  148. }
  149. for _, resource := range statement.Resource {
  150. // Parse "arn:aws:s3:::my-bucket/shared/*"
  151. res := strings.Split(resource, ":")
  152. if len(res) != 6 || res[0] != "arn" || res[1] != "aws" || res[2] != "s3" {
  153. glog.Infof("not match resource: %s", res)
  154. continue
  155. }
  156. for _, action := range statement.Action {
  157. // Parse "s3:Get*"
  158. act := strings.Split(action, ":")
  159. if len(act) != 2 || act[0] != "s3" {
  160. glog.Infof("not match action: %s", act)
  161. continue
  162. }
  163. if res[5] == "*" {
  164. actions = append(actions, MapAction(act[1]))
  165. continue
  166. }
  167. // Parse my-bucket/shared/*
  168. path := strings.Split(res[5], "/")
  169. if len(path) != 2 || path[1] != "*" {
  170. glog.Infof("not match bucket: %s", path)
  171. continue
  172. }
  173. actions = append(actions, fmt.Sprintf("%s:%s", MapAction(act[1]), path[0]))
  174. }
  175. }
  176. }
  177. return actions
  178. }
  179. func (iama *IamApiServer) CreateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateAccessKeyResponse) {
  180. userName := values.Get("UserName")
  181. status := iam.StatusTypeActive
  182. accessKeyId := StringWithCharset(21, charsetUpper)
  183. secretAccessKey := StringWithCharset(42, charset)
  184. resp.CreateAccessKeyResult.AccessKey.AccessKeyId = &accessKeyId
  185. resp.CreateAccessKeyResult.AccessKey.SecretAccessKey = &secretAccessKey
  186. resp.CreateAccessKeyResult.AccessKey.UserName = &userName
  187. resp.CreateAccessKeyResult.AccessKey.Status = &status
  188. changed := false
  189. for _, ident := range s3cfg.Identities {
  190. if userName == ident.Name {
  191. ident.Credentials = append(ident.Credentials,
  192. &iam_pb.Credential{AccessKey: accessKeyId, SecretKey: secretAccessKey})
  193. changed = true
  194. break
  195. }
  196. }
  197. if !changed {
  198. s3cfg.Identities = append(s3cfg.Identities,
  199. &iam_pb.Identity{Name: userName,
  200. Credentials: []*iam_pb.Credential{
  201. {
  202. AccessKey: accessKeyId,
  203. SecretKey: secretAccessKey,
  204. },
  205. },
  206. },
  207. )
  208. }
  209. return resp
  210. }
  211. func (iama *IamApiServer) DeleteAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteAccessKeyResponse) {
  212. userName := values.Get("UserName")
  213. accessKeyId := values.Get("AccessKeyId")
  214. for _, ident := range s3cfg.Identities {
  215. if userName == ident.Name {
  216. for i, cred := range ident.Credentials {
  217. if cred.AccessKey == accessKeyId {
  218. ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...)
  219. break
  220. }
  221. }
  222. break
  223. }
  224. }
  225. return resp
  226. }
  227. func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
  228. if err := r.ParseForm(); err != nil {
  229. writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
  230. return
  231. }
  232. values := r.PostForm
  233. var s3cfgLock sync.RWMutex
  234. s3cfgLock.RLock()
  235. s3cfg := &iam_pb.S3ApiConfiguration{}
  236. if err := iama.s3ApiConfig.GetS3ApiConfiguration(s3cfg); err != nil {
  237. writeErrorResponse(w, s3err.ErrInternalError, r.URL)
  238. return
  239. }
  240. s3cfgLock.RUnlock()
  241. glog.V(4).Infof("DoActions: %+v", values)
  242. var response interface{}
  243. var err error
  244. changed := true
  245. switch r.Form.Get("Action") {
  246. case "ListUsers":
  247. response = iama.ListUsers(s3cfg, values)
  248. changed = false
  249. case "ListAccessKeys":
  250. response = iama.ListAccessKeys(s3cfg, values)
  251. changed = false
  252. case "CreateUser":
  253. response = iama.CreateUser(s3cfg, values)
  254. case "GetUser":
  255. userName := values.Get("UserName")
  256. response, err = iama.GetUser(s3cfg, userName)
  257. if err != nil {
  258. writeIamErrorResponse(w, err, "user", userName)
  259. return
  260. }
  261. case "DeleteUser":
  262. userName := values.Get("UserName")
  263. response, err = iama.DeleteUser(s3cfg, userName)
  264. if err != nil {
  265. writeIamErrorResponse(w, err, "user", userName)
  266. return
  267. }
  268. case "CreateAccessKey":
  269. response = iama.CreateAccessKey(s3cfg, values)
  270. case "DeleteAccessKey":
  271. response = iama.DeleteAccessKey(s3cfg, values)
  272. case "CreatePolicy":
  273. response, err = iama.CreatePolicy(s3cfg, values)
  274. if err != nil {
  275. glog.Errorf("CreatePolicy: %+v", err)
  276. writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
  277. return
  278. }
  279. case "PutUserPolicy":
  280. response, err = iama.PutUserPolicy(s3cfg, values)
  281. if err != nil {
  282. glog.Errorf("PutUserPolicy: %+v", err)
  283. writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
  284. return
  285. }
  286. default:
  287. writeErrorResponse(w, s3err.ErrNotImplemented, r.URL)
  288. return
  289. }
  290. if changed {
  291. s3cfgLock.Lock()
  292. err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg)
  293. s3cfgLock.Unlock()
  294. if err != nil {
  295. writeErrorResponse(w, s3err.ErrInternalError, r.URL)
  296. return
  297. }
  298. }
  299. writeSuccessResponseXML(w, encodeResponse(response))
  300. }