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.

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