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.

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