diff options
Diffstat (limited to 'weed/iamapi')
| -rw-r--r-- | weed/iamapi/iamapi_handlers.go | 8 | ||||
| -rw-r--r-- | weed/iamapi/iamapi_management_handlers.go | 87 | ||||
| -rw-r--r-- | weed/iamapi/iamapi_response.go | 5 | ||||
| -rw-r--r-- | weed/iamapi/iamapi_server.go | 54 | ||||
| -rw-r--r-- | weed/iamapi/iamapi_test.go | 43 |
5 files changed, 142 insertions, 55 deletions
diff --git a/weed/iamapi/iamapi_handlers.go b/weed/iamapi/iamapi_handlers.go index 7765d9e95..a59834e88 100644 --- a/weed/iamapi/iamapi_handlers.go +++ b/weed/iamapi/iamapi_handlers.go @@ -8,7 +8,7 @@ import ( "net/http" ) -func writeIamErrorResponse(w http.ResponseWriter, err error, object string, value string, msg error) { +func writeIamErrorResponse(w http.ResponseWriter, r *http.Request, err error, object string, value string, msg error) { errCode := err.Error() errorResp := ErrorResponse{} errorResp.Error.Type = "Sender" @@ -22,10 +22,10 @@ func writeIamErrorResponse(w http.ResponseWriter, err error, object string, valu case iam.ErrCodeNoSuchEntityException: msg := fmt.Sprintf("The %s with name %s cannot be found.", object, value) errorResp.Error.Message = &msg - s3err.WriteXMLResponse(w, http.StatusNotFound, errorResp) + s3err.WriteXMLResponse(w, r, http.StatusNotFound, errorResp) case iam.ErrCodeServiceFailureException: - s3err.WriteXMLResponse(w, http.StatusInternalServerError, errorResp) + s3err.WriteXMLResponse(w, r, http.StatusInternalServerError, errorResp) default: - s3err.WriteXMLResponse(w, http.StatusInternalServerError, errorResp) + s3err.WriteXMLResponse(w, r, http.StatusInternalServerError, errorResp) } } diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index 0826ce336..488e92aa5 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -4,10 +4,6 @@ import ( "crypto/sha1" "encoding/json" "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" @@ -16,6 +12,11 @@ import ( "sync" "time" + "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" + "github.com/aws/aws-sdk-go/service/iam" ) @@ -155,6 +156,22 @@ func (iama *IamApiServer) GetUser(s3cfg *iam_pb.S3ApiConfiguration, userName str return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException) } +func (iama *IamApiServer) UpdateUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp UpdateUserResponse, err error) { + userName := values.Get("UserName") + newUserName := values.Get("NewUserName") + if newUserName != "" { + for _, ident := range s3cfg.Identities { + if userName == ident.Name { + ident.Name = newUserName + return resp, nil + } + } + } else { + return resp, nil + } + return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException) +} + func GetPolicyDocument(policy *string) (policyDocument PolicyDocument, err error) { if err = json.Unmarshal([]byte(*policy), &policyDocument); err != nil { return PolicyDocument{}, err @@ -360,9 +377,38 @@ func (iama *IamApiServer) DeleteAccessKey(s3cfg *iam_pb.S3ApiConfiguration, valu return resp } +// handleImplicitUsername adds username who signs the request to values if 'username' is not specified +// According to https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/create-access-key.html/ +// "If you do not specify a user name, IAM determines the user name implicitly based on the Amazon Web +// Services access key ID signing the request." +func handleImplicitUsername(r *http.Request, values url.Values) { + if len(r.Header["Authorization"]) == 0 || values.Get("UserName") != "" { + return + } + // get username who signs the request. For a typical Authorization: + // "AWS4-HMAC-SHA256 Credential=197FSAQ7HHTA48X64O3A/20220420/test1/iam/aws4_request, SignedHeaders=content-type; + // host;x-amz-date, Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8", + // the "test1" will be extracted as the username + glog.V(4).Infof("Authorization field: %v", r.Header["Authorization"][0]) + s := strings.Split(r.Header["Authorization"][0], "Credential=") + if len(s) < 2 { + return + } + s = strings.Split(s[1], ",") + if len(s) < 2 { + return + } + s = strings.Split(s[0], "/") + if len(s) < 5 { + return + } + userName := s[2] + values.Set("UserName", userName) +} + func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { - s3err.WriteErrorResponse(w, s3err.ErrInvalidRequest, r) + s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest) return } values := r.PostForm @@ -370,7 +416,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { s3cfgLock.RLock() s3cfg := &iam_pb.S3ApiConfiguration{} if err := iama.s3ApiConfig.GetS3ApiConfiguration(s3cfg); err != nil { - s3err.WriteErrorResponse(w, s3err.ErrInternalError, r) + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) return } s3cfgLock.RUnlock() @@ -384,6 +430,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { response = iama.ListUsers(s3cfg, values) changed = false case "ListAccessKeys": + handleImplicitUsername(r, values) response = iama.ListAccessKeys(s3cfg, values) changed = false case "CreateUser": @@ -392,52 +439,62 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { userName := values.Get("UserName") response, err = iama.GetUser(s3cfg, userName) if err != nil { - writeIamErrorResponse(w, err, "user", userName, nil) + writeIamErrorResponse(w, r, err, "user", userName, nil) return } changed = false + case "UpdateUser": + response, err = iama.UpdateUser(s3cfg, values) + if err != nil { + glog.Errorf("UpdateUser: %+v", err) + s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest) + return + } case "DeleteUser": userName := values.Get("UserName") response, err = iama.DeleteUser(s3cfg, userName) if err != nil { - writeIamErrorResponse(w, err, "user", userName, nil) + writeIamErrorResponse(w, r, err, "user", userName, nil) return } case "CreateAccessKey": + handleImplicitUsername(r, values) response = iama.CreateAccessKey(s3cfg, values) case "DeleteAccessKey": + handleImplicitUsername(r, values) response = iama.DeleteAccessKey(s3cfg, values) case "CreatePolicy": response, err = iama.CreatePolicy(s3cfg, values) if err != nil { glog.Errorf("CreatePolicy: %+v", err) - s3err.WriteErrorResponse(w, s3err.ErrInvalidRequest, r) + s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest) return } case "PutUserPolicy": response, err = iama.PutUserPolicy(s3cfg, values) if err != nil { glog.Errorf("PutUserPolicy: %+v", err) - s3err.WriteErrorResponse(w, s3err.ErrInvalidRequest, r) + s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest) return } case "GetUserPolicy": response, err = iama.GetUserPolicy(s3cfg, values) if err != nil { - writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil) + writeIamErrorResponse(w, r, err, "user", values.Get("UserName"), nil) return } changed = false case "DeleteUserPolicy": if response, err = iama.DeleteUserPolicy(s3cfg, values); err != nil { - writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil) + writeIamErrorResponse(w, r, err, "user", values.Get("UserName"), nil) + return } default: errNotImplemented := s3err.GetAPIError(s3err.ErrNotImplemented) errorResponse := ErrorResponse{} errorResponse.Error.Code = &errNotImplemented.Code errorResponse.Error.Message = &errNotImplemented.Description - s3err.WriteXMLResponse(w, errNotImplemented.HTTPStatusCode, errorResponse) + s3err.WriteXMLResponse(w, r, errNotImplemented.HTTPStatusCode, errorResponse) return } if changed { @@ -445,9 +502,9 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg) s3cfgLock.Unlock() if err != nil { - writeIamErrorResponse(w, fmt.Errorf(iam.ErrCodeServiceFailureException), "", "", err) + writeIamErrorResponse(w, r, fmt.Errorf(iam.ErrCodeServiceFailureException), "", "", err) return } } - s3err.WriteXMLResponse(w, http.StatusOK, response) + s3err.WriteXMLResponse(w, r, http.StatusOK, response) } diff --git a/weed/iamapi/iamapi_response.go b/weed/iamapi/iamapi_response.go index 77328b608..df9443f0d 100644 --- a/weed/iamapi/iamapi_response.go +++ b/weed/iamapi/iamapi_response.go @@ -66,6 +66,11 @@ type GetUserResponse struct { } `xml:"GetUserResult"` } +type UpdateUserResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ UpdateUserResponse"` +} + type CreateAccessKeyResponse struct { CommonResponse XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateAccessKeyResponse"` diff --git a/weed/iamapi/iamapi_server.go b/weed/iamapi/iamapi_server.go index ec718bd41..62c7f867c 100644 --- a/weed/iamapi/iamapi_server.go +++ b/weed/iamapi/iamapi_server.go @@ -18,7 +18,6 @@ import ( "github.com/gorilla/mux" "google.golang.org/grpc" "net/http" - "strings" ) type IamS3ApiConfig interface { @@ -34,11 +33,10 @@ type IamS3ApiConfigure struct { } type IamServerOption struct { - Masters string - Filer string - Port int - FilerGrpcAddress string - GrpcDialOption grpc.DialOption + Masters map[string]pb.ServerAddress + Filer pb.ServerAddress + Port int + GrpcDialOption grpc.DialOption } type IamApiServer struct { @@ -51,7 +49,7 @@ var s3ApiConfigure IamS3ApiConfig func NewIamApiServer(router *mux.Router, option *IamServerOption) (iamApiServer *IamApiServer, err error) { s3ApiConfigure = IamS3ApiConfigure{ option: option, - masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", 0, "", strings.Split(option.Masters, ",")), + masterClient: wdclient.NewMasterClient(option.GrpcDialOption, "", "iam", "", "", option.Masters), } s3Option := s3api.S3ApiServerOption{Filer: option.Filer} iamApiServer = &IamApiServer{ @@ -78,7 +76,7 @@ func (iama *IamApiServer) registerRouter(router *mux.Router) { func (iam IamS3ApiConfigure) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { var buf bytes.Buffer - err = pb.WithGrpcFilerClient(iam.option.FilerGrpcAddress, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { + err = pb.WithGrpcFilerClient(false, iam.option.Filer, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { if err = filer.ReadEntry(iam.masterClient, client, filer.IamConfigDirecotry, filer.IamIdentityFile, &buf); err != nil { return err } @@ -100,24 +98,20 @@ func (iam IamS3ApiConfigure) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfigurat if err := filer.ProtoToText(&buf, s3cfg); err != nil { return fmt.Errorf("ProtoToText: %s", err) } - return pb.WithGrpcFilerClient( - iam.option.FilerGrpcAddress, - iam.option.GrpcDialOption, - func(client filer_pb.SeaweedFilerClient) error { - err = util.Retry("saveIamIdentity", func() error { - return filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamIdentityFile, buf.Bytes()) - }) - if err != nil { - return err - } - return nil - }, - ) + return pb.WithGrpcFilerClient(false, iam.option.Filer, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { + err = util.Retry("saveIamIdentity", func() error { + return filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamIdentityFile, buf.Bytes()) + }) + if err != nil { + return err + } + return nil + }) } func (iam IamS3ApiConfigure) GetPolicies(policies *Policies) (err error) { var buf bytes.Buffer - err = pb.WithGrpcFilerClient(iam.option.FilerGrpcAddress, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { + err = pb.WithGrpcFilerClient(false, iam.option.Filer, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { if err = filer.ReadEntry(iam.masterClient, client, filer.IamConfigDirecotry, filer.IamPoliciesFile, &buf); err != nil { return err } @@ -141,14 +135,10 @@ func (iam IamS3ApiConfigure) PutPolicies(policies *Policies) (err error) { if b, err = json.Marshal(policies); err != nil { return err } - return pb.WithGrpcFilerClient( - iam.option.FilerGrpcAddress, - iam.option.GrpcDialOption, - func(client filer_pb.SeaweedFilerClient) error { - if err := filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamPoliciesFile, b); err != nil { - return err - } - return nil - }, - ) + return pb.WithGrpcFilerClient(false, iam.option.Filer, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { + if err := filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamPoliciesFile, b); err != nil { + return err + } + return nil + }) } diff --git a/weed/iamapi/iamapi_test.go b/weed/iamapi/iamapi_test.go index 09aaf0ac8..375e9a2f3 100644 --- a/weed/iamapi/iamapi_test.go +++ b/weed/iamapi/iamapi_test.go @@ -2,6 +2,11 @@ package iamapi import ( "encoding/xml" + "net/http" + "net/http/httptest" + "net/url" + "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/iam" @@ -9,9 +14,6 @@ import ( "github.com/gorilla/mux" "github.com/jinzhu/copier" "github.com/stretchr/testify/assert" - "net/http" - "net/http/httptest" - "testing" ) var GetS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error) @@ -161,8 +163,20 @@ func TestGetUserPolicy(t *testing.T) { assert.Equal(t, http.StatusOK, response.Code) } -func TestDeleteUser(t *testing.T) { +func TestUpdateUser(t *testing.T) { userName := aws.String("Test") + newUserName := aws.String("Test-New") + params := &iam.UpdateUserInput{NewUserName: newUserName, UserName: userName} + req, _ := iam.New(session.New()).UpdateUserRequest(params) + _ = req.Build() + out := UpdateUserResponse{} + response, err := executeRequest(req.HTTPRequest, out) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) +} + +func TestDeleteUser(t *testing.T) { + userName := aws.String("Test-New") params := &iam.DeleteUserInput{UserName: userName} req, _ := iam.New(session.New()).DeleteUserRequest(params) _ = req.Build() @@ -179,3 +193,24 @@ func executeRequest(req *http.Request, v interface{}) (*httptest.ResponseRecorde apiRouter.ServeHTTP(rr, req) return rr, xml.Unmarshal(rr.Body.Bytes(), &v) } + +func TestHandleImplicitUsername(t *testing.T) { + var tests = []struct { + r *http.Request + values url.Values + userName string + }{ + {&http.Request{}, url.Values{}, ""}, + {&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 Credential=197FSAQ7HHTA48X64O3A/20220420/test1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8"}}}, url.Values{}, "test1"}, + {&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 =197FSAQ7HHTA48X64O3A/20220420/test1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8"}}}, url.Values{}, ""}, + {&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 Credential=197FSAQ7HHTA48X64O3A/20220420/test1/iam/aws4_request SignedHeaders=content-type;host;x-amz-date Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8"}}}, url.Values{}, ""}, + {&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 Credential=197FSAQ7HHTA48X64O3A/20220420/test1/iam, SignedHeaders=content-type;host;x-amz-date, Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8"}}}, url.Values{}, ""}, + } + + for i, test := range tests { + handleImplicitUsername(test.r, test.values) + if un := test.values.Get("UserName"); un != test.userName { + t.Errorf("No.%d: Got: %v, Expected: %v", i, un, test.userName) + } + } +} |
