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.

449 lines
13 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
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. "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. for _, ident := range s3cfg.Identities {
  104. for _, cred := range ident.Credentials {
  105. resp.ListAccessKeysResult.AccessKeyMetadata = append(resp.ListAccessKeysResult.AccessKeyMetadata,
  106. &iam.AccessKeyMetadata{UserName: &ident.Name, AccessKeyId: &cred.AccessKey, Status: &status},
  107. )
  108. }
  109. }
  110. return resp
  111. }
  112. func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateUserResponse) {
  113. userName := values.Get("UserName")
  114. resp.CreateUserResult.User.UserName = &userName
  115. s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName})
  116. return resp
  117. }
  118. func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp DeleteUserResponse, err error) {
  119. for i, ident := range s3cfg.Identities {
  120. if userName == ident.Name {
  121. s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
  122. return resp, nil
  123. }
  124. }
  125. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  126. }
  127. func (iama *IamApiServer) GetUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp GetUserResponse, err error) {
  128. for _, ident := range s3cfg.Identities {
  129. if userName == ident.Name {
  130. resp.GetUserResult.User = iam.User{UserName: &ident.Name}
  131. return resp, nil
  132. }
  133. }
  134. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  135. }
  136. func GetPolicyDocument(policy *string) (policyDocument PolicyDocument, err error) {
  137. if err = json.Unmarshal([]byte(*policy), &policyDocument); err != nil {
  138. return PolicyDocument{}, err
  139. }
  140. return policyDocument, err
  141. }
  142. func (iama *IamApiServer) CreatePolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreatePolicyResponse, err error) {
  143. policyName := values.Get("PolicyName")
  144. policyDocumentString := values.Get("PolicyDocument")
  145. policyDocument, err := GetPolicyDocument(&policyDocumentString)
  146. if err != nil {
  147. return CreatePolicyResponse{}, err
  148. }
  149. policyId := Hash(&policyDocumentString)
  150. arn := fmt.Sprintf("arn:aws:iam:::policy/%s", policyName)
  151. resp.CreatePolicyResult.Policy.PolicyName = &policyName
  152. resp.CreatePolicyResult.Policy.Arn = &arn
  153. resp.CreatePolicyResult.Policy.PolicyId = &policyId
  154. policies := Policies{}
  155. policyLock.Lock()
  156. defer policyLock.Unlock()
  157. if err = iama.s3ApiConfig.GetPolicies(&policies); err != nil {
  158. return resp, err
  159. }
  160. policies.Policies[policyName] = policyDocument
  161. if err = iama.s3ApiConfig.PutPolicies(&policies); err != nil {
  162. return resp, err
  163. }
  164. return resp, nil
  165. }
  166. func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) {
  167. userName := values.Get("UserName")
  168. policyName := values.Get("PolicyName")
  169. policyDocumentString := values.Get("PolicyDocument")
  170. policyDocument, err := GetPolicyDocument(&policyDocumentString)
  171. if err != nil {
  172. return PutUserPolicyResponse{}, err
  173. }
  174. policyDocuments[policyName] = &policyDocument
  175. actions := GetActions(&policyDocument)
  176. for _, ident := range s3cfg.Identities {
  177. if userName == ident.Name {
  178. for _, action := range actions {
  179. ident.Actions = append(ident.Actions, action)
  180. }
  181. break
  182. }
  183. }
  184. return resp, nil
  185. }
  186. func (iama *IamApiServer) GetUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp GetUserPolicyResponse, err error) {
  187. userName := values.Get("UserName")
  188. policyName := values.Get("PolicyName")
  189. for _, ident := range s3cfg.Identities {
  190. if userName != ident.Name {
  191. continue
  192. }
  193. resp.GetUserPolicyResult.UserName = userName
  194. resp.GetUserPolicyResult.PolicyName = policyName
  195. if len(ident.Actions) == 0 {
  196. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  197. }
  198. policyDocument := PolicyDocument{Version: policyDocumentVersion}
  199. statements := make(map[string][]string)
  200. for _, action := range ident.Actions {
  201. // parse "Read:EXAMPLE-BUCKET"
  202. act := strings.Split(action, ":")
  203. resource := "*"
  204. if len(act) == 2 {
  205. resource = fmt.Sprintf("arn:aws:s3:::%s/*", act[1])
  206. }
  207. statements[resource] = append(statements[resource],
  208. fmt.Sprintf("s3:%s", MapToIdentitiesAction(act[0])),
  209. )
  210. }
  211. for resource, actions := range statements {
  212. isEqAction := false
  213. for i, statement := range policyDocument.Statement {
  214. if reflect.DeepEqual(statement.Action, actions) {
  215. policyDocument.Statement[i].Resource = append(
  216. policyDocument.Statement[i].Resource, resource)
  217. isEqAction = true
  218. break
  219. }
  220. }
  221. if isEqAction {
  222. continue
  223. }
  224. policyDocumentStatement := Statement{
  225. Effect: "Allow",
  226. Action: actions,
  227. }
  228. policyDocumentStatement.Resource = append(policyDocumentStatement.Resource, resource)
  229. policyDocument.Statement = append(policyDocument.Statement, &policyDocumentStatement)
  230. }
  231. resp.GetUserPolicyResult.PolicyDocument = policyDocument.String()
  232. return resp, nil
  233. }
  234. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  235. }
  236. func (iama *IamApiServer) DeleteUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) {
  237. userName := values.Get("UserName")
  238. for i, ident := range s3cfg.Identities {
  239. if ident.Name == userName {
  240. s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
  241. return resp, nil
  242. }
  243. }
  244. return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
  245. }
  246. func GetActions(policy *PolicyDocument) (actions []string) {
  247. for _, statement := range policy.Statement {
  248. if statement.Effect != "Allow" {
  249. continue
  250. }
  251. for _, resource := range statement.Resource {
  252. // Parse "arn:aws:s3:::my-bucket/shared/*"
  253. res := strings.Split(resource, ":")
  254. if len(res) != 6 || res[0] != "arn" || res[1] != "aws" || res[2] != "s3" {
  255. glog.Infof("not match resource: %s", res)
  256. continue
  257. }
  258. for _, action := range statement.Action {
  259. // Parse "s3:Get*"
  260. act := strings.Split(action, ":")
  261. if len(act) != 2 || act[0] != "s3" {
  262. glog.Infof("not match action: %s", act)
  263. continue
  264. }
  265. statementAction := MapToStatementAction(act[1])
  266. if res[5] == "*" {
  267. actions = append(actions, statementAction)
  268. continue
  269. }
  270. // Parse my-bucket/shared/*
  271. path := strings.Split(res[5], "/")
  272. if len(path) != 2 || path[1] != "*" {
  273. glog.Infof("not match bucket: %s", path)
  274. continue
  275. }
  276. actions = append(actions, fmt.Sprintf("%s:%s", statementAction, path[0]))
  277. }
  278. }
  279. }
  280. return actions
  281. }
  282. func (iama *IamApiServer) CreateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateAccessKeyResponse) {
  283. userName := values.Get("UserName")
  284. status := iam.StatusTypeActive
  285. accessKeyId := StringWithCharset(21, charsetUpper)
  286. secretAccessKey := StringWithCharset(42, charset)
  287. resp.CreateAccessKeyResult.AccessKey.AccessKeyId = &accessKeyId
  288. resp.CreateAccessKeyResult.AccessKey.SecretAccessKey = &secretAccessKey
  289. resp.CreateAccessKeyResult.AccessKey.UserName = &userName
  290. resp.CreateAccessKeyResult.AccessKey.Status = &status
  291. changed := false
  292. for _, ident := range s3cfg.Identities {
  293. if userName == ident.Name {
  294. ident.Credentials = append(ident.Credentials,
  295. &iam_pb.Credential{AccessKey: accessKeyId, SecretKey: secretAccessKey})
  296. changed = true
  297. break
  298. }
  299. }
  300. if !changed {
  301. s3cfg.Identities = append(s3cfg.Identities,
  302. &iam_pb.Identity{Name: userName,
  303. Credentials: []*iam_pb.Credential{
  304. {
  305. AccessKey: accessKeyId,
  306. SecretKey: secretAccessKey,
  307. },
  308. },
  309. },
  310. )
  311. }
  312. return resp
  313. }
  314. func (iama *IamApiServer) DeleteAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteAccessKeyResponse) {
  315. userName := values.Get("UserName")
  316. accessKeyId := values.Get("AccessKeyId")
  317. for _, ident := range s3cfg.Identities {
  318. if userName == ident.Name {
  319. for i, cred := range ident.Credentials {
  320. if cred.AccessKey == accessKeyId {
  321. ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...)
  322. break
  323. }
  324. }
  325. break
  326. }
  327. }
  328. return resp
  329. }
  330. func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
  331. if err := r.ParseForm(); err != nil {
  332. writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
  333. return
  334. }
  335. values := r.PostForm
  336. var s3cfgLock sync.RWMutex
  337. s3cfgLock.RLock()
  338. s3cfg := &iam_pb.S3ApiConfiguration{}
  339. if err := iama.s3ApiConfig.GetS3ApiConfiguration(s3cfg); err != nil {
  340. writeErrorResponse(w, s3err.ErrInternalError, r.URL)
  341. return
  342. }
  343. s3cfgLock.RUnlock()
  344. glog.V(4).Infof("DoActions: %+v", values)
  345. var response interface{}
  346. var err error
  347. changed := true
  348. switch r.Form.Get("Action") {
  349. case "ListUsers":
  350. response = iama.ListUsers(s3cfg, values)
  351. changed = false
  352. case "ListAccessKeys":
  353. response = iama.ListAccessKeys(s3cfg, values)
  354. changed = false
  355. case "CreateUser":
  356. response = iama.CreateUser(s3cfg, values)
  357. case "GetUser":
  358. userName := values.Get("UserName")
  359. response, err = iama.GetUser(s3cfg, userName)
  360. if err != nil {
  361. writeIamErrorResponse(w, err, "user", userName, nil)
  362. return
  363. }
  364. changed = false
  365. case "DeleteUser":
  366. userName := values.Get("UserName")
  367. response, err = iama.DeleteUser(s3cfg, userName)
  368. if err != nil {
  369. writeIamErrorResponse(w, err, "user", userName, nil)
  370. return
  371. }
  372. case "CreateAccessKey":
  373. response = iama.CreateAccessKey(s3cfg, values)
  374. case "DeleteAccessKey":
  375. response = iama.DeleteAccessKey(s3cfg, values)
  376. case "CreatePolicy":
  377. response, err = iama.CreatePolicy(s3cfg, values)
  378. if err != nil {
  379. glog.Errorf("CreatePolicy: %+v", err)
  380. writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
  381. return
  382. }
  383. case "PutUserPolicy":
  384. response, err = iama.PutUserPolicy(s3cfg, values)
  385. if err != nil {
  386. glog.Errorf("PutUserPolicy: %+v", err)
  387. writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
  388. return
  389. }
  390. case "GetUserPolicy":
  391. response, err = iama.GetUserPolicy(s3cfg, values)
  392. if err != nil {
  393. writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil)
  394. return
  395. }
  396. changed = false
  397. case "DeleteUserPolicy":
  398. if response, err = iama.DeleteUserPolicy(s3cfg, values); err != nil {
  399. writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil)
  400. }
  401. default:
  402. errNotImplemented := s3err.GetAPIError(s3err.ErrNotImplemented)
  403. errorResponse := ErrorResponse{}
  404. errorResponse.Error.Code = &errNotImplemented.Code
  405. errorResponse.Error.Message = &errNotImplemented.Description
  406. writeResponse(w, errNotImplemented.HTTPStatusCode, encodeResponse(errorResponse), mimeXML)
  407. return
  408. }
  409. if changed {
  410. s3cfgLock.Lock()
  411. err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg)
  412. s3cfgLock.Unlock()
  413. if err != nil {
  414. writeIamErrorResponse(w, fmt.Errorf(iam.ErrCodeServiceFailureException), "", "", err)
  415. return
  416. }
  417. }
  418. writeSuccessResponseXML(w, encodeResponse(response))
  419. }