diff --git a/weed/iamapi/iamapi_handlers.go b/weed/iamapi/iamapi_handlers.go index c8eac8ef6..ae63310d9 100644 --- a/weed/iamapi/iamapi_handlers.go +++ b/weed/iamapi/iamapi_handlers.go @@ -34,7 +34,7 @@ func writeIamErrorResponse(w http.ResponseWriter, r *http.Request, iamError *Iam switch errCode { case iam.ErrCodeNoSuchEntityException: s3err.WriteXMLResponse(w, r, http.StatusNotFound, errorResp) - case iam.ErrCodeMalformedPolicyDocumentException: + case iam.ErrCodeMalformedPolicyDocumentException, iam.ErrCodeInvalidInputException: s3err.WriteXMLResponse(w, r, http.StatusBadRequest, errorResp) case iam.ErrCodeServiceFailureException: // We do not want to expose internal server error to the client diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index 899db7ff3..f67b71a8f 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -35,6 +35,8 @@ const ( StatementActionTagging = iamlib.StatementActionTagging StatementActionDelete = iamlib.StatementActionDelete USER_DOES_NOT_EXIST = iamlib.UserDoesNotExist + accessKeyStatusActive = iamlib.AccessKeyStatusActive + accessKeyStatusInactive = iamlib.AccessKeyStatusInactive ) var ( @@ -67,6 +69,17 @@ func stringSlicesEqual(a, b []string) bool { return iamlib.StringSlicesEqual(a, b) } +func validateAccessKeyStatus(status string) error { + switch status { + case accessKeyStatusActive, accessKeyStatusInactive: + return nil + case "": + return fmt.Errorf("Status parameter is required") + default: + return fmt.Errorf("Status must be '%s' or '%s'", accessKeyStatusActive, accessKeyStatusInactive) + } +} + func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListUsersResponse) { for _, ident := range s3cfg.Identities { resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &iam.User{UserName: &ident.Name}) @@ -75,15 +88,20 @@ func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url } func (iama *IamApiServer) ListAccessKeys(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListAccessKeysResponse) { - status := iam.StatusTypeActive userName := values.Get("UserName") for _, ident := range s3cfg.Identities { if userName != "" && userName != ident.Name { continue } for _, cred := range ident.Credentials { + status := cred.Status + if status == "" { + status = accessKeyStatusActive + } + identName := ident.Name + accessKey := cred.AccessKey resp.ListAccessKeysResult.AccessKeyMetadata = append(resp.ListAccessKeysResult.AccessKeyMetadata, - &iam.AccessKeyMetadata{UserName: &ident.Name, AccessKeyId: &cred.AccessKey, Status: &status}, + &iam.AccessKeyMetadata{UserName: &identName, AccessKeyId: &accessKey, Status: &status}, ) } } @@ -325,7 +343,7 @@ func (iama *IamApiServer) CreateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, valu for _, ident := range s3cfg.Identities { if userName == ident.Name { ident.Credentials = append(ident.Credentials, - &iam_pb.Credential{AccessKey: accessKeyId, SecretKey: secretAccessKey}) + &iam_pb.Credential{AccessKey: accessKeyId, SecretKey: secretAccessKey, Status: accessKeyStatusActive}) changed = true break } @@ -338,6 +356,7 @@ func (iama *IamApiServer) CreateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, valu { AccessKey: accessKeyId, SecretKey: secretAccessKey, + Status: accessKeyStatusActive, }, }, }, @@ -346,6 +365,37 @@ func (iama *IamApiServer) CreateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, valu return resp, nil } +// UpdateAccessKey updates the status of an access key (Active or Inactive). +func (iama *IamApiServer) UpdateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp UpdateAccessKeyResponse, err *IamError) { + userName := values.Get("UserName") + accessKeyId := values.Get("AccessKeyId") + status := values.Get("Status") + + if userName == "" { + return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("UserName is required")} + } + if accessKeyId == "" { + return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("AccessKeyId is required")} + } + if err := validateAccessKeyStatus(status); err != nil { + return resp, &IamError{Code: iam.ErrCodeInvalidInputException, Error: err} + } + + for _, ident := range s3cfg.Identities { + if ident.Name != userName { + continue + } + for _, cred := range ident.Credentials { + if cred.AccessKey == accessKeyId { + cred.Status = status + return resp, nil + } + } + return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("the access key with id %s for user %s cannot be found", accessKeyId, userName)} + } + return resp, &IamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf(USER_DOES_NOT_EXIST, userName)} +} + func (iama *IamApiServer) DeleteAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteAccessKeyResponse) { userName := values.Get("UserName") accessKeyId := values.Get("AccessKeyId") @@ -475,6 +525,13 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { case "DeleteAccessKey": iama.handleImplicitUsername(r, values) response = iama.DeleteAccessKey(s3cfg, values) + case "UpdateAccessKey": + iama.handleImplicitUsername(r, values) + response, iamError = iama.UpdateAccessKey(s3cfg, values) + if iamError != nil { + writeIamErrorResponse(w, r, iamError) + return + } case "CreatePolicy": response, iamError = iama.CreatePolicy(s3cfg, values) if iamError != nil { diff --git a/weed/iamapi/iamapi_response.go b/weed/iamapi/iamapi_response.go index 712e4196e..d2c1df996 100644 --- a/weed/iamapi/iamapi_response.go +++ b/weed/iamapi/iamapi_response.go @@ -19,6 +19,7 @@ type ( GetUserResponse = iamlib.GetUserResponse UpdateUserResponse = iamlib.UpdateUserResponse CreateAccessKeyResponse = iamlib.CreateAccessKeyResponse + UpdateAccessKeyResponse = iamlib.UpdateAccessKeyResponse PutUserPolicyResponse = iamlib.PutUserPolicyResponse DeleteUserPolicyResponse = iamlib.DeleteUserPolicyResponse GetUserPolicyResponse = iamlib.GetUserPolicyResponse diff --git a/weed/iamapi/iamapi_test.go b/weed/iamapi/iamapi_test.go index fa04d1ce9..acf864e11 100644 --- a/weed/iamapi/iamapi_test.go +++ b/weed/iamapi/iamapi_test.go @@ -84,6 +84,58 @@ func TestListAccessKeys(t *testing.T) { assert.Equal(t, http.StatusOK, response.Code) } +func TestUpdateAccessKey(t *testing.T) { + svc := iam.New(session.New()) + + createReq, _ := svc.CreateAccessKeyRequest(&iam.CreateAccessKeyInput{UserName: aws.String("Test")}) + _ = createReq.Build() + createOut := CreateAccessKeyResponse{} + response, err := executeRequest(createReq.HTTPRequest, createOut) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) + + var createResp CreateAccessKeyResponse + err = xml.Unmarshal(response.Body.Bytes(), &createResp) + assert.Equal(t, nil, err) + accessKeyId := createResp.CreateAccessKeyResult.AccessKey.AccessKeyId + if accessKeyId == nil { + t.Fatalf("expected access key id to be set") + } + + updateReq, _ := svc.UpdateAccessKeyRequest(&iam.UpdateAccessKeyInput{ + UserName: aws.String("Test"), + AccessKeyId: accessKeyId, + Status: aws.String("Inactive"), + }) + _ = updateReq.Build() + updateOut := UpdateAccessKeyResponse{} + response, err = executeRequest(updateReq.HTTPRequest, updateOut) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) + + listReq, _ := svc.ListAccessKeysRequest(&iam.ListAccessKeysInput{UserName: aws.String("Test")}) + _ = listReq.Build() + listOut := ListAccessKeysResponse{} + response, err = executeRequest(listReq.HTTPRequest, listOut) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) + + var listResp ListAccessKeysResponse + err = xml.Unmarshal(response.Body.Bytes(), &listResp) + assert.Equal(t, nil, err) + found := false + for _, key := range listResp.ListAccessKeysResult.AccessKeyMetadata { + if key.AccessKeyId != nil && *key.AccessKeyId == *accessKeyId { + found = true + if assert.NotNil(t, key.Status) { + assert.Equal(t, "Inactive", *key.Status) + } + break + } + } + assert.True(t, found) +} + func TestGetUser(t *testing.T) { userName := aws.String("Test") params := &iam.GetUserInput{UserName: userName}