diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index 2347c9a80..22bc8748a 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -1,14 +1,18 @@ package iamapi import ( + "crypto/sha1" + "encoding/json" "encoding/xml" "fmt" "github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants" "github.com/chrislusf/seaweedfs/weed/s3api/s3err" "math/rand" "net/http" "net/url" + "strings" "time" // "github.com/aws/aws-sdk-go/aws" @@ -21,11 +25,19 @@ const ( charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/" ) -var seededRand *rand.Rand = rand.New( - rand.NewSource(time.Now().UnixNano())) +var ( + seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) + policyDocuments = map[string]*PolicyDocument{} +) -type Response interface { - SetRequestId() +type PolicyDocument struct { + Version string `json:"Version"` + Statement []struct { + Effect string `json:"Effect"` + Action []string `json:"Action"` + Resource []string `json:"Resource"` + } `json:"Statement"` } type CommonResponse struct { @@ -62,6 +74,14 @@ type DeleteAccessKeyResponse struct { XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteAccessKeyResponse"` } +type CreatePolicyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreatePolicyResponse"` + CreatePolicyResult struct { + Policy iam.Policy `xml:"Policy"` + } `xml:"CreatePolicyResult"` +} + type CreateUserResponse struct { CommonResponse XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateUserResponse"` @@ -78,10 +98,21 @@ type CreateAccessKeyResponse struct { } `xml:"CreateAccessKeyResult"` } +type PutUserPolicyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ PutUserPolicyResponse"` +} + func (r *CommonResponse) SetRequestId() { r.ResponseMetadata.RequestId = fmt.Sprintf("%d", time.Now().UnixNano()) } +func Hash(s *string) string { + h := sha1.New() + h.Write([]byte(*s)) + return fmt.Sprintf("%x", h.Sum(nil)) +} + func StringWithCharset(length int, charset string) string { b := make([]byte, length) for i := range b { @@ -115,6 +146,96 @@ func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values ur s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName}) return resp } +func GetPolicyDocument(policy *string) (policyDocument PolicyDocument, err error) { + if err = json.Unmarshal([]byte(*policy), &policyDocument); err != nil { + return PolicyDocument{}, err + } + return policyDocument, err +} + +func (iama *IamApiServer) CreatePolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreatePolicyResponse, err error) { + policyName := values.Get("PolicyName") + policyDocumentString := values.Get("PolicyDocument") + policyDocument, err := GetPolicyDocument(&policyDocumentString) + if err != nil { + return CreatePolicyResponse{}, err + } + policyId := Hash(&policyDocumentString) + arn := fmt.Sprintf("arn:aws:iam:::policy/%s", policyName) + resp.CreatePolicyResult.Policy.PolicyName = &policyName + resp.CreatePolicyResult.Policy.Arn = &arn + resp.CreatePolicyResult.Policy.PolicyId = &policyId + policyDocuments[policyName] = &policyDocument + return resp, nil +} + +func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) { + userName := values.Get("UserName") + policyName := values.Get("PolicyName") + policyDocumentString := values.Get("PolicyDocument") + policyDocument, err := GetPolicyDocument(&policyDocumentString) + if err != nil { + return PutUserPolicyResponse{}, err + } + policyDocuments[policyName] = &policyDocument + actions := GetActions(&policyDocument) + for _, ident := range s3cfg.Identities { + if userName == ident.Name { + for _, action := range actions { + ident.Actions = append(ident.Actions, action) + } + break + } + } + return resp, nil +} + +func MapAction(action string) string { + switch action { + case "*": + return s3_constants.ACTION_ADMIN + case "Put*": + return s3_constants.ACTION_WRITE + case "Get*": + return s3_constants.ACTION_READ + case "List*": + return s3_constants.ACTION_LIST + default: + return s3_constants.ACTION_TAGGING + } +} + +func GetActions(policy *PolicyDocument) (actions []string) { + for _, statement := range policy.Statement { + if statement.Effect != "Allow" { + continue + } + for _, resource := range statement.Resource { + // Parse "arn:aws:s3:::my-bucket/shared/*" + res := strings.Split(resource, ":") + if len(res) != 6 || res[0] != "arn:" || res[1] != "aws" || res[2] != "s3" { + continue + } + for _, action := range statement.Action { + // Parse "s3:Get*" + act := strings.Split(action, ":") + if len(act) != 2 || act[0] != "s3" { + continue + } + if res[5] == "*" { + actions = append(actions, MapAction(act[1])) + continue + } + // Parse my-bucket/shared/* + path := strings.Split(res[5], "/") + if len(path) != 2 || path[1] != "*" { + actions = append(actions, fmt.Sprintf("%s:%s", MapAction(act[1]), path[0])) + } + } + } + } + return actions +} func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteUserResponse) { userName := values.Get("UserName") @@ -204,6 +325,20 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { response = iama.CreateAccessKey(s3cfg, values) case "DeleteAccessKey": response = iama.DeleteAccessKey(s3cfg, values) + case "CreatePolicy": + var err error + response, err = iama.CreatePolicy(s3cfg, values) + if err != nil { + writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) + return + } + case "PutUserPolicy": + var err error + response, err = iama.PutUserPolicy(s3cfg, values) + if err != nil { + writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) + return + } default: writeErrorResponse(w, s3err.ErrNotImplemented, r.URL) return