diff options
Diffstat (limited to 'weed/s3api/s3api_embedded_iam_test.go')
| -rw-r--r-- | weed/s3api/s3api_embedded_iam_test.go | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/weed/s3api/s3api_embedded_iam_test.go b/weed/s3api/s3api_embedded_iam_test.go index 81839084b..1b44df64c 100644 --- a/weed/s3api/s3api_embedded_iam_test.go +++ b/weed/s3api/s3api_embedded_iam_test.go @@ -137,6 +137,19 @@ func (e *EmbeddedIamApiForTest) DoActions(w http.ResponseWriter, r *http.Request e.writeIamErrorResponse(w, r, iamErr) return } + case "SetUserStatus": + response, iamErr = e.SetUserStatus(s3cfg, values) + if iamErr != nil { + e.writeIamErrorResponse(w, r, iamErr) + return + } + case "UpdateAccessKey": + e.handleImplicitUsername(r, values) + response, iamErr = e.UpdateAccessKey(s3cfg, values) + if iamErr != nil { + e.writeIamErrorResponse(w, r, iamErr) + return + } default: http.Error(w, "Not implemented", http.StatusNotImplemented) return @@ -1026,3 +1039,406 @@ func TestEmbeddedIamGetActionsFromPolicy(t *testing.T) { assert.Contains(t, actions, "Write:mybucket") } +// TestEmbeddedIamSetUserStatus tests enabling/disabling a user +func TestEmbeddedIamSetUserStatus(t *testing.T) { + api := NewEmbeddedIamApiForTest() + api.mockConfig = &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{ + {Name: "TestUser", Disabled: false}, + }, + } + + t.Run("DisableUser", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "SetUserStatus") + form.Set("UserName", "TestUser") + form.Set("Status", "Inactive") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + // Verify user is now disabled + assert.True(t, api.mockConfig.Identities[0].Disabled) + }) + + t.Run("EnableUser", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "SetUserStatus") + form.Set("UserName", "TestUser") + form.Set("Status", "Active") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + // Verify user is now enabled + assert.False(t, api.mockConfig.Identities[0].Disabled) + }) +} + +// TestEmbeddedIamSetUserStatusErrors tests error handling for SetUserStatus +func TestEmbeddedIamSetUserStatusErrors(t *testing.T) { + api := NewEmbeddedIamApiForTest() + api.mockConfig = &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{ + {Name: "TestUser"}, + }, + } + + t.Run("UserNotFound", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "SetUserStatus") + form.Set("UserName", "NonExistentUser") + form.Set("Status", "Inactive") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusNotFound, rr.Code) + }) + + t.Run("InvalidStatus", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "SetUserStatus") + form.Set("UserName", "TestUser") + form.Set("Status", "InvalidStatus") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusBadRequest, rr.Code) + }) + + t.Run("MissingUserName", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "SetUserStatus") + form.Set("Status", "Inactive") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusBadRequest, rr.Code) + }) +} + +// TestEmbeddedIamUpdateAccessKey tests updating access key status +func TestEmbeddedIamUpdateAccessKey(t *testing.T) { + api := NewEmbeddedIamApiForTest() + api.mockConfig = &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{ + { + Name: "TestUser", + Credentials: []*iam_pb.Credential{ + {AccessKey: "AKIATEST12345", SecretKey: "secret", Status: "Active"}, + }, + }, + }, + } + + t.Run("DeactivateAccessKey", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "UpdateAccessKey") + form.Set("UserName", "TestUser") + form.Set("AccessKeyId", "AKIATEST12345") + form.Set("Status", "Inactive") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + // Verify access key is now inactive + assert.Equal(t, "Inactive", api.mockConfig.Identities[0].Credentials[0].Status) + }) + + t.Run("ActivateAccessKey", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "UpdateAccessKey") + form.Set("UserName", "TestUser") + form.Set("AccessKeyId", "AKIATEST12345") + form.Set("Status", "Active") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + // Verify access key is now active + assert.Equal(t, "Active", api.mockConfig.Identities[0].Credentials[0].Status) + }) +} + +// TestEmbeddedIamUpdateAccessKeyErrors tests error handling for UpdateAccessKey +func TestEmbeddedIamUpdateAccessKeyErrors(t *testing.T) { + api := NewEmbeddedIamApiForTest() + api.mockConfig = &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{ + { + Name: "TestUser", + Credentials: []*iam_pb.Credential{ + {AccessKey: "AKIATEST12345", SecretKey: "secret"}, + }, + }, + }, + } + + t.Run("AccessKeyNotFound", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "UpdateAccessKey") + form.Set("UserName", "TestUser") + form.Set("AccessKeyId", "NONEXISTENT123") + form.Set("Status", "Inactive") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusNotFound, rr.Code) + }) + + t.Run("InvalidStatus", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "UpdateAccessKey") + form.Set("UserName", "TestUser") + form.Set("AccessKeyId", "AKIATEST12345") + form.Set("Status", "InvalidStatus") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusBadRequest, rr.Code) + }) + + t.Run("MissingUserName", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "UpdateAccessKey") + form.Set("AccessKeyId", "AKIATEST12345") + form.Set("Status", "Inactive") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusBadRequest, rr.Code) + }) + + t.Run("MissingAccessKeyId", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "UpdateAccessKey") + form.Set("UserName", "TestUser") + form.Set("Status", "Inactive") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusBadRequest, rr.Code) + }) + + t.Run("UserNotFound", func(t *testing.T) { + form := url.Values{} + form.Set("Action", "UpdateAccessKey") + form.Set("UserName", "NonExistentUser") + form.Set("AccessKeyId", "AKIATEST12345") + form.Set("Status", "Inactive") + + req, _ := http.NewRequest("POST", "/", nil) + req.PostForm = form + req.Form = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + apiRouter.Path("/").Methods(http.MethodPost).HandlerFunc(api.DoActions) + apiRouter.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusNotFound, rr.Code) + }) +} + +// TestEmbeddedIamListAccessKeysShowsStatus tests that ListAccessKeys returns the access key status +func TestEmbeddedIamListAccessKeysShowsStatus(t *testing.T) { + api := NewEmbeddedIamApiForTest() + api.mockConfig = &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{ + { + Name: "TestUser", + Credentials: []*iam_pb.Credential{ + {AccessKey: "AKIAACTIVE123", SecretKey: "secret1", Status: "Active"}, + {AccessKey: "AKIAINACTIVE1", SecretKey: "secret2", Status: "Inactive"}, + {AccessKey: "AKIADEFAULT12", SecretKey: "secret3"}, // No status set, should default to Active + }, + }, + }, + } + + params := &iam.ListAccessKeysInput{UserName: aws.String("TestUser")} + req, _ := iam.New(session.New()).ListAccessKeysRequest(params) + _ = req.Build() + out := iamListAccessKeysResponse{} + response, err := executeEmbeddedIamRequest(api, req.HTTPRequest, &out) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, response.Code) + + // Verify all three access keys are listed with correct status + assert.Len(t, out.ListAccessKeysResult.AccessKeyMetadata, 3) + + // Find each key and verify status + statusMap := make(map[string]string) + for _, meta := range out.ListAccessKeysResult.AccessKeyMetadata { + statusMap[*meta.AccessKeyId] = *meta.Status + } + + assert.Equal(t, "Active", statusMap["AKIAACTIVE123"]) + assert.Equal(t, "Inactive", statusMap["AKIAINACTIVE1"]) + assert.Equal(t, "Active", statusMap["AKIADEFAULT12"]) // Default to Active +} + +// TestDisabledUserLookupFails tests that disabled users cannot authenticate +func TestDisabledUserLookupFails(t *testing.T) { + iam := &IdentityAccessManagement{} + testConfig := &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{ + { + Name: "enabledUser", + Disabled: false, + Credentials: []*iam_pb.Credential{ + {AccessKey: "AKIAENABLED123", SecretKey: "secret1"}, + }, + }, + { + Name: "disabledUser", + Disabled: true, + Credentials: []*iam_pb.Credential{ + {AccessKey: "AKIADISABLED12", SecretKey: "secret2"}, + }, + }, + }, + } + err := iam.LoadS3ApiConfigurationFromBytes(mustMarshalJSON(testConfig)) + assert.NoError(t, err) + + // Enabled user should be found + identity, cred, found := iam.LookupByAccessKey("AKIAENABLED123") + assert.True(t, found) + assert.NotNil(t, identity) + assert.NotNil(t, cred) + assert.Equal(t, "enabledUser", identity.Name) + + // Disabled user should NOT be found + identity, cred, found = iam.LookupByAccessKey("AKIADISABLED12") + assert.False(t, found) + assert.Nil(t, identity) + assert.Nil(t, cred) +} + +// TestInactiveAccessKeyLookupFails tests that inactive access keys cannot authenticate +func TestInactiveAccessKeyLookupFails(t *testing.T) { + iam := &IdentityAccessManagement{} + testConfig := &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{ + { + Name: "testUser", + Credentials: []*iam_pb.Credential{ + {AccessKey: "AKIAACTIVE123", SecretKey: "secret1", Status: "Active"}, + {AccessKey: "AKIAINACTIVE1", SecretKey: "secret2", Status: "Inactive"}, + {AccessKey: "AKIADEFAULT12", SecretKey: "secret3"}, // No status = Active + }, + }, + }, + } + err := iam.LoadS3ApiConfigurationFromBytes(mustMarshalJSON(testConfig)) + assert.NoError(t, err) + + // Active key should be found + identity, cred, found := iam.LookupByAccessKey("AKIAACTIVE123") + assert.True(t, found) + assert.NotNil(t, identity) + assert.NotNil(t, cred) + + // Inactive key should NOT be found + identity, cred, found = iam.LookupByAccessKey("AKIAINACTIVE1") + assert.False(t, found) + assert.Nil(t, identity) + assert.Nil(t, cred) + + // Key with no status (default Active) should be found + identity, cred, found = iam.LookupByAccessKey("AKIADEFAULT12") + assert.True(t, found) + assert.NotNil(t, identity) + assert.NotNil(t, cred) +} + |
