diff --git a/.secrets.baseline b/.secrets.baseline index f06e9733b4..2128910f24 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -20,6 +20,9 @@ { "name": "CloudantDetector" }, + { + "name": "DiscordBotTokenDetector" + }, { "name": "GitHubTokenDetector" }, @@ -122,8 +125,7 @@ "filename": "config/jwks-file-static.json", "hashed_secret": "551c2aa179bc3c0e2e8176d4b458d077ed358e25", "is_verified": false, - "line_number": 8, - "is_secret": false + "line_number": 8 }, { "type": "Base64 High Entropy String", @@ -137,8 +139,7 @@ "filename": "config/jwks-file-static.json", "hashed_secret": "f05bf8a9b8521955a5fa259abd1d5a6d269273ec", "is_verified": false, - "line_number": 16, - "is_secret": false + "line_number": 16 }, { "type": "Base64 High Entropy String", @@ -152,8 +153,7 @@ "filename": "config/jwks-file-static.json", "hashed_secret": "e23d321e76d1144e48b7f1d05dfd0d5036031003", "is_verified": false, - "line_number": 24, - "is_secret": false + "line_number": 24 }, { "type": "Base64 High Entropy String", @@ -167,16 +167,14 @@ "filename": "config/jwks-file-static.json", "hashed_secret": "9b87ab16703bb0ccd78aee2f69bd0e604f7a42dc", "is_verified": false, - "line_number": 32, - "is_secret": false + "line_number": 32 }, { "type": "Base64 High Entropy String", "filename": "config/jwks-file-static.json", "hashed_secret": "3744e3d32aa35c3bb53d76d1832699b723f07812", "is_verified": false, - "line_number": 41, - "is_secret": false + "line_number": 41 } ], "config/jwks-file.json": [ @@ -192,8 +190,7 @@ "filename": "config/jwks-file.json", "hashed_secret": "551c2aa179bc3c0e2e8176d4b458d077ed358e25", "is_verified": false, - "line_number": 8, - "is_secret": false + "line_number": 8 }, { "type": "Base64 High Entropy String", @@ -207,8 +204,7 @@ "filename": "config/jwks-file.json", "hashed_secret": "f05bf8a9b8521955a5fa259abd1d5a6d269273ec", "is_verified": false, - "line_number": 16, - "is_secret": false + "line_number": 16 }, { "type": "Base64 High Entropy String", @@ -222,8 +218,7 @@ "filename": "config/jwks-file.json", "hashed_secret": "e23d321e76d1144e48b7f1d05dfd0d5036031003", "is_verified": false, - "line_number": 24, - "is_secret": false + "line_number": 24 }, { "type": "Base64 High Entropy String", @@ -237,8 +232,7 @@ "filename": "config/jwks-file.json", "hashed_secret": "9b87ab16703bb0ccd78aee2f69bd0e604f7a42dc", "is_verified": false, - "line_number": 32, - "is_secret": false + "line_number": 32 } ], "db_setup_docker.sql": [ @@ -247,8 +241,7 @@ "filename": "db_setup_docker.sql", "hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3", "is_verified": false, - "line_number": 1, - "is_secret": false + "line_number": 1 } ], "dev/env/manifests/shared/03-configmap-config.yaml": [ @@ -334,6 +327,15 @@ "line_number": 7 } ], + "e2e/e2e_test.go": [ + { + "type": "Secret Keyword", + "filename": "e2e/e2e_test.go", + "hashed_secret": "7f38822bc2b03e97325ff310099f457f6f788daf", + "is_verified": false, + "line_number": 267 + } + ], "fleetshard/pkg/central/cloudprovider/dbclient_moq.go": [ { "type": "Secret Keyword", @@ -352,30 +354,66 @@ "line_number": 1531 } ], + "internal/dinosaur/pkg/services/dinosaurservice_moq.go": [ + { + "type": "Secret Keyword", + "filename": "internal/dinosaur/pkg/services/dinosaurservice_moq.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 982 + }, + { + "type": "Secret Keyword", + "filename": "internal/dinosaur/pkg/services/dinosaurservice_moq.go", + "hashed_secret": "d035c0406b3e8286d3427e91db3497e0e17f0f83", + "is_verified": false, + "line_number": 983 + } + ], + "pkg/client/fleetmanager/api_moq.go": [ + { + "type": "Secret Keyword", + "filename": "pkg/client/fleetmanager/api_moq.go", + "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", + "is_verified": false, + "line_number": 567 + }, + { + "type": "Secret Keyword", + "filename": "pkg/client/fleetmanager/api_moq.go", + "hashed_secret": "0ff50155b4f57adeccae93f27dc23efe2a8b7824", + "is_verified": false, + "line_number": 568 + }, + { + "type": "Secret Keyword", + "filename": "pkg/client/fleetmanager/api_moq.go", + "hashed_secret": "5ce1b8d4fb9dae5c02b2017e39e7267a21cea37f", + "is_verified": false, + "line_number": 577 + } + ], "pkg/client/iam/client_moq.go": [ { "type": "Secret Keyword", "filename": "pkg/client/iam/client_moq.go", "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", "is_verified": false, - "line_number": 649, - "is_secret": false + "line_number": 649 }, { "type": "Secret Keyword", "filename": "pkg/client/iam/client_moq.go", "hashed_secret": "4595e0fe3be13544e523e5f6c1145f15007f7b58", "is_verified": false, - "line_number": 650, - "is_secret": false + "line_number": 650 }, { "type": "Secret Keyword", "filename": "pkg/client/iam/client_moq.go", "hashed_secret": "539fbe365f6c0db26d473d85a736d318c2f565e5", "is_verified": false, - "line_number": 991, - "is_secret": false + "line_number": 991 } ], "pkg/client/iam/gocloak_moq.go": [ @@ -384,48 +422,42 @@ "filename": "pkg/client/iam/gocloak_moq.go", "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", "is_verified": false, - "line_number": 9711, - "is_secret": false + "line_number": 9711 }, { "type": "Secret Keyword", "filename": "pkg/client/iam/gocloak_moq.go", "hashed_secret": "7f0b58c8f07c09a5ed45a784a8e1ea4d3e983d59", "is_verified": false, - "line_number": 9712, - "is_secret": false + "line_number": 9712 }, { "type": "Secret Keyword", "filename": "pkg/client/iam/gocloak_moq.go", "hashed_secret": "9b8b876c2782fa992fab14095267bb8757b9fabc", "is_verified": false, - "line_number": 13092, - "is_secret": false + "line_number": 13092 }, { "type": "Secret Keyword", "filename": "pkg/client/iam/gocloak_moq.go", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 13095, - "is_secret": false + "line_number": 13095 }, { "type": "Secret Keyword", "filename": "pkg/client/iam/gocloak_moq.go", "hashed_secret": "eb1b883e199141e362a143c51178ab8f09c87751", "is_verified": false, - "line_number": 13716, - "is_secret": false + "line_number": 13716 }, { "type": "Secret Keyword", "filename": "pkg/client/iam/gocloak_moq.go", "hashed_secret": "1b46ecc8fb47b1b39a420f00f08dbd58e0313188", "is_verified": false, - "line_number": 14023, - "is_secret": false + "line_number": 14023 } ], "pkg/client/redhatsso/api/api/openapi.yaml": [ @@ -443,8 +475,7 @@ "filename": "pkg/shared/secrets/secrets_test.go", "hashed_secret": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", "is_verified": false, - "line_number": 113, - "is_secret": false + "line_number": 113 } ], "templates/envoy-config-configmap.yml": [ @@ -549,8 +580,7 @@ "filename": "test/support/certs.json", "hashed_secret": "d59844c767c4c6c3840f8cabbc04b1e5ed2acc22", "is_verified": false, - "line_number": 8, - "is_secret": false + "line_number": 8 } ], "test/support/jwt_private_key.pem": [ @@ -559,10 +589,9 @@ "filename": "test/support/jwt_private_key.pem", "hashed_secret": "be4fc4886bd949b369d5e092eb87494f12e57e5b", "is_verified": false, - "line_number": 1, - "is_secret": false + "line_number": 1 } ] }, - "generated_at": "2024-01-10T15:22:39Z" + "generated_at": "2024-01-11T17:41:29Z" } diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index d61dc0ac91..ec69915b94 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "encoding/hex" + "errors" "fmt" "net" "net/url" @@ -240,29 +241,12 @@ var _ = Describe("Central", Ordered, func() { // TODO(ROX-11368): Create test to check Central is correctly exposed It("should restore secrets and deployment on namespace delete", func() { - previousNamespace := corev1.Namespace{} - Expect(assertNamespaceExists(ctx, &previousNamespace, namespaceName)()). - To(Succeed()) - // Using managedDB false here because e2e don't run with managed postgresql secretBackup := k8s.NewSecretBackup(k8sClient, false) expectedSecrets, err := secretBackup.CollectSecrets(ctx, namespaceName) Expect(err).ToNot(HaveOccurred()) - previousCreationTime := previousNamespace.CreationTimestamp - Expect(k8sClient.Delete(ctx, &previousNamespace)). - NotTo(HaveOccurred()) - - Eventually(func() error { - newNamespace := corev1.Namespace{} - if err := k8sClient.Get(ctx, ctrlClient.ObjectKey{Name: namespaceName}, &newNamespace); err != nil { - return err - } - if previousCreationTime.Equal(&newNamespace.CreationTimestamp) { - return fmt.Errorf("namespace found but was not yet deleted") - } - return nil - }).WithTimeout(waitTimeout).WithPolling(defaultPolling).Should(Succeed()) + deleteNamespaceAndWaitForRecreation(ctx, namespaceName, k8sClient) actualSecrets := map[string]*corev1.Secret{} Eventually(func() (err error) { @@ -270,13 +254,52 @@ var _ = Describe("Central", Ordered, func() { return err }).WithTimeout(waitTimeout).WithPolling(defaultPolling).Should(Succeed()) - Expect(actualSecrets).ToNot(BeEmpty()) - Expect(len(actualSecrets)).To(Equal(len(expectedSecrets))) - for secretName := range expectedSecrets { // pragma: allowlist secret - actualData := actualSecrets[secretName].StringData - expectedData := expectedSecrets[secretName].StringData - Expect(actualData).To(Equal(expectedData)) + assertEqualSecrets(actualSecrets, expectedSecrets) + }) + + It("should delete and recreate secret backup for admin reset API", func() { + secretBackup := k8s.NewSecretBackup(k8sClient, false) + oldSecrets, err := secretBackup.CollectSecrets(ctx, namespaceName) + Expect(err).ToNot(HaveOccurred()) + Expect(oldSecrets).ToNot(BeEmpty()) + + // modify secrets to later test that the backup was updated succesfully + for _, secret := range oldSecrets { + secret.Data["test"] = []byte("modified") + err := k8sClient.Update(ctx, secret) + Expect(err).ToNot(HaveOccurred()) } + + _, err = adminAPI.CentralRotateSecrets(ctx, centralRequestID, private.CentralRotateSecretsRequest{ResetSecretBackup: true}) + Expect(err).ToNot(HaveOccurred()) + + // Wait for secrets to be backed up again + Eventually(func() error { + central, _, err := client.PrivateAPI().GetCentral(ctx, centralRequestID) + Expect(err).ToNot(HaveOccurred()) + if len(central.Metadata.SecretsStored) == 0 { + return errors.New("secrets backup is empty") + } + + return nil + }). + WithTimeout(waitTimeout). + WithPolling(10 * time.Second). + Should(Succeed()) + + deleteNamespaceAndWaitForRecreation(ctx, namespaceName, k8sClient) + + var newSecrets map[string]*corev1.Secret + Eventually(func() error { + secrets, err := secretBackup.CollectSecrets(ctx, namespaceName) + if err != nil { + return err + } + newSecrets = secrets // pragma: allowlist secret + return nil + }).WithTimeout(1 * time.Minute).WithPolling(10).Should(Succeed()) + + assertEqualSecrets(newSecrets, oldSecrets) }) It("should transition central to deprovisioning state", func() { @@ -487,3 +510,34 @@ func printNotes(notes []string) { GinkgoWriter.Println(note) } } + +func deleteNamespaceAndWaitForRecreation(ctx context.Context, namespaceName string, k8sClient ctrlClient.Client) { + previousNamespace := corev1.Namespace{} + Expect(assertNamespaceExists(ctx, &previousNamespace, namespaceName)()). + To(Succeed()) + + previousCreationTime := previousNamespace.CreationTimestamp + Expect(k8sClient.Delete(ctx, &previousNamespace)). + NotTo(HaveOccurred()) + + Eventually(func() error { + newNamespace := corev1.Namespace{} + if err := k8sClient.Get(ctx, ctrlClient.ObjectKey{Name: namespaceName}, &newNamespace); err != nil { + return err + } + if previousCreationTime.Equal(&newNamespace.CreationTimestamp) { + return fmt.Errorf("namespace found but was not yet deleted") + } + return nil + }).WithTimeout(waitTimeout).WithPolling(defaultPolling).Should(Succeed()) +} + +func assertEqualSecrets(actualSecrets, expectedSecrets map[string]*corev1.Secret) { + Expect(actualSecrets).ToNot(BeEmpty()) + Expect(len(actualSecrets)).To(Equal(len(expectedSecrets))) + for secretName := range expectedSecrets { // pragma: allowlist secret + actualData := actualSecrets[secretName].Data + expectedData := expectedSecrets[secretName].Data + Expect(actualData).To(Equal(expectedData)) + } +} diff --git a/fleetshard/pkg/central/reconciler/reconciler.go b/fleetshard/pkg/central/reconciler/reconciler.go index 0c5e287e45..4b0872faed 100644 --- a/fleetshard/pkg/central/reconciler/reconciler.go +++ b/fleetshard/pkg/central/reconciler/reconciler.go @@ -155,7 +155,7 @@ func (r *CentralReconciler) Reconcile(ctx context.Context, remoteCentral private if err != nil { return nil, errors.Wrap(err, "checking if central changed") } - needsReconcile := r.needsReconcile(changed, central) + needsReconcile := r.needsReconcile(changed, central, remoteCentral.Metadata.SecretsStored) if !needsReconcile && r.shouldSkipReadyCentral(remoteCentral) { return nil, ErrCentralNotChanged @@ -1593,10 +1593,15 @@ func (r *CentralReconciler) shouldSkipReadyCentral(remoteCentral private.Managed isRemoteCentralReady(&remoteCentral) } -func (r *CentralReconciler) needsReconcile(changed bool, central *v1alpha1.Central) bool { +func (r *CentralReconciler) needsReconcile(changed bool, central *v1alpha1.Central, storedSecrets []string) bool { + if !r.areSecretsStored(storedSecrets) { + return true + } + if changed { return true } + forceReconcile, ok := central.Labels["rhacs.redhat.com/force-reconcile"] return ok && forceReconcile == "true" } diff --git a/fleetshard/pkg/central/reconciler/reconciler_test.go b/fleetshard/pkg/central/reconciler/reconciler_test.go index 7bcda8cf5c..ce56d59a80 100644 --- a/fleetshard/pkg/central/reconciler/reconciler_test.go +++ b/fleetshard/pkg/central/reconciler/reconciler_test.go @@ -177,6 +177,15 @@ func centralDBPasswordSecretObject() *v1.Secret { } } +func centralEncryptionKeySecretObject() *v1.Secret { + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: centralEncryptionKeySecretName, + Namespace: centralNamespace, + }, + } +} + func conditionForType(conditions []private.DataPlaneCentralStatusConditions, conditionType string) (*private.DataPlaneCentralStatusConditions, bool) { for _, c := range conditions { if c.Type == conditionType { @@ -346,6 +355,7 @@ func TestReconcileLastHashNotUpdatedOnError(t *testing.T) { central: private.ManagedCentral{}, resourcesChart: resourcesChart, encryptionKeyGenerator: cipher.AES256KeyGenerator{}, + secretBackup: k8s.NewSecretBackup(fakeClient, false), } _, err := r.Reconcile(context.TODO(), simpleManagedCentral) @@ -370,11 +380,12 @@ func TestReconcileLastHashSetOnSuccess(t *testing.T) { centralDeploymentObject(), centralTLSSecretObject(), centralDBPasswordSecretObject(), + centralEncryptionKeySecretObject(), ) managedCentral := simpleManagedCentral managedCentral.RequestStatus = centralConstants.CentralRequestStatusReady.String() - + managedCentral.Metadata.SecretsStored = r.secretBackup.GetWatchedSecrets() expectedHash, err := util.MD5SumFromJSONStruct(&managedCentral) require.NoError(t, err) diff --git a/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml b/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml index 644b9ae457..7baf3aff01 100644 --- a/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml +++ b/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml @@ -380,6 +380,7 @@ paths: summary: Update `expired_at` central property /api/rhacs/v1/admin/centrals/{id}/rotate-secrets: post: + operationId: centralRotateSecrets parameters: - description: The ID of record in: path @@ -396,7 +397,7 @@ paths: required: true responses: "200": - description: RHSSO client successfully rotated + description: Secret successfully rotated "401": content: application/json: @@ -422,7 +423,7 @@ paths: schema: $ref: '#/components/schemas/Error' description: Unexpected error occurred - summary: Rotate RHSSO client of a central tenant + summary: Rotate RHSSO client or Secret Backup of a central tenant /api/rhacs/v1/admin/centrals/{id}/restore: post: parameters: @@ -522,10 +523,13 @@ components: - $ref: '#/components/schemas/CentralList_allOf' CentralRotateSecretsRequest: example: + reset_secret_backup: true rotate_rhsso_client_credentials: true properties: rotate_rhsso_client_credentials: type: boolean + reset_secret_backup: + type: boolean type: object Error: allOf: diff --git a/internal/dinosaur/pkg/api/admin/private/api_default.go b/internal/dinosaur/pkg/api/admin/private/api_default.go index f5c7f53336..d90b0ba4e4 100644 --- a/internal/dinosaur/pkg/api/admin/private/api_default.go +++ b/internal/dinosaur/pkg/api/admin/private/api_default.go @@ -143,12 +143,12 @@ func (a *DefaultApiService) ApiRhacsV1AdminCentralsIdRestorePost(ctx _context.Co } /* -ApiRhacsV1AdminCentralsIdRotateSecretsPost Rotate RHSSO client of a central tenant +CentralRotateSecrets Rotate RHSSO client or Secret Backup of a central tenant - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @param id The ID of record - @param centralRotateSecretsRequest Options for secret rotation */ -func (a *DefaultApiService) ApiRhacsV1AdminCentralsIdRotateSecretsPost(ctx _context.Context, id string, centralRotateSecretsRequest CentralRotateSecretsRequest) (*_nethttp.Response, error) { +func (a *DefaultApiService) CentralRotateSecrets(ctx _context.Context, id string, centralRotateSecretsRequest CentralRotateSecretsRequest) (*_nethttp.Response, error) { var ( localVarHTTPMethod = _nethttp.MethodPost localVarPostBody interface{} diff --git a/internal/dinosaur/pkg/api/admin/private/model_central_rotate_secrets_request.go b/internal/dinosaur/pkg/api/admin/private/model_central_rotate_secrets_request.go index 923a99f7e5..34b6412ca0 100644 --- a/internal/dinosaur/pkg/api/admin/private/model_central_rotate_secrets_request.go +++ b/internal/dinosaur/pkg/api/admin/private/model_central_rotate_secrets_request.go @@ -13,4 +13,5 @@ package private // CentralRotateSecretsRequest struct for CentralRotateSecretsRequest type CentralRotateSecretsRequest struct { RotateRhssoClientCredentials bool `json:"rotate_rhsso_client_credentials,omitempty"` + ResetSecretBackup bool `json:"reset_secret_backup,omitempty"` } diff --git a/internal/dinosaur/pkg/handlers/admin_dinosaur.go b/internal/dinosaur/pkg/handlers/admin_dinosaur.go index 147ff87359..31e0f9c261 100644 --- a/internal/dinosaur/pkg/handlers/admin_dinosaur.go +++ b/internal/dinosaur/pkg/handlers/admin_dinosaur.go @@ -231,6 +231,14 @@ func (h adminCentralHandler) RotateSecrets(w http.ResponseWriter, r *http.Reques return nil, svcErr } } + + if rotateSecretsRequest.ResetSecretBackup { + svcErr = h.service.ResetCentralSecretBackup(ctx, centralRequest) + if svcErr != nil { + return nil, svcErr + } + } + return nil, nil }, } diff --git a/internal/dinosaur/pkg/services/dinosaur.go b/internal/dinosaur/pkg/services/dinosaur.go index 4f3e7f7d4a..6a9d418e00 100644 --- a/internal/dinosaur/pkg/services/dinosaur.go +++ b/internal/dinosaur/pkg/services/dinosaur.go @@ -107,6 +107,11 @@ type DinosaurService interface { VerifyAndUpdateDinosaurAdmin(ctx context.Context, dinosaurRequest *dbapi.CentralRequest) *errors.ServiceError Restore(ctx context.Context, id string) *errors.ServiceError RotateCentralRHSSOClient(ctx context.Context, centralRequest *dbapi.CentralRequest) *errors.ServiceError + // ResetCentralSecretBackup resets the Secret field of centralReqest, which are the backed up secrets + // of a tenant. By resetting the field the next update will store new secrets which enables manual rotation. + // This is currently the only way to update secret backups, an automatic approach should be implemented + // to accomated for regular processes like central TLS cert rotation. + ResetCentralSecretBackup(ctx context.Context, centralRequest *dbapi.CentralRequest) *errors.ServiceError } var _ DinosaurService = &dinosaurService{} @@ -170,6 +175,17 @@ func (k *dinosaurService) RotateCentralRHSSOClient(ctx context.Context, centralR return nil } +func (k *dinosaurService) ResetCentralSecretBackup(ctx context.Context, centralRequest *dbapi.CentralRequest) *errors.ServiceError { + centralRequest.Secrets = nil // pragma: allowlist secret + + dbConn := k.connectionFactory.New() + if err := dbConn.Model(centralRequest).Select("secrets").Updates(centralRequest).Error; err != nil { + return errors.NewWithCause(errors.ErrorGeneral, err, "Unable to reset secrets for central request") + } + + return nil +} + // HasAvailableCapacityInRegion ... func (k *dinosaurService) HasAvailableCapacityInRegion(dinosaurRequest *dbapi.CentralRequest) (bool, *errors.ServiceError) { regionCapacity := int64(k.dataplaneClusterConfig.ClusterConfig.GetCapacityForRegion(dinosaurRequest.Region)) diff --git a/internal/dinosaur/pkg/services/dinosaurservice_moq.go b/internal/dinosaur/pkg/services/dinosaurservice_moq.go index d33b51fac6..6fe4140cf3 100644 --- a/internal/dinosaur/pkg/services/dinosaurservice_moq.go +++ b/internal/dinosaur/pkg/services/dinosaurservice_moq.go @@ -82,6 +82,9 @@ var _ DinosaurService = &DinosaurServiceMock{} // RegisterDinosaurJobFunc: func(ctx context.Context, dinosaurRequest *dbapi.CentralRequest) *serviceError.ServiceError { // panic("mock out the RegisterDinosaurJob method") // }, +// ResetCentralSecretBackupFunc: func(ctx context.Context, centralRequest *dbapi.CentralRequest) *serviceError.ServiceError { +// panic("mock out the ResetCentralSecretBackup method") +// }, // RestoreFunc: func(ctx context.Context, id string) *serviceError.ServiceError { // panic("mock out the Restore method") // }, @@ -164,6 +167,9 @@ type DinosaurServiceMock struct { // RegisterDinosaurJobFunc mocks the RegisterDinosaurJob method. RegisterDinosaurJobFunc func(ctx context.Context, dinosaurRequest *dbapi.CentralRequest) *serviceError.ServiceError + // ResetCentralSecretBackupFunc mocks the ResetCentralSecretBackup method. + ResetCentralSecretBackupFunc func(ctx context.Context, centralRequest *dbapi.CentralRequest) *serviceError.ServiceError + // RestoreFunc mocks the Restore method. RestoreFunc func(ctx context.Context, id string) *serviceError.ServiceError @@ -283,6 +289,13 @@ type DinosaurServiceMock struct { // DinosaurRequest is the dinosaurRequest argument value. DinosaurRequest *dbapi.CentralRequest } + // ResetCentralSecretBackup holds details about calls to the ResetCentralSecretBackup method. + ResetCentralSecretBackup []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // CentralRequest is the centralRequest argument value. + CentralRequest *dbapi.CentralRequest + } // Restore holds details about calls to the Restore method. Restore []struct { // Ctx is the ctx argument value. @@ -343,6 +356,7 @@ type DinosaurServiceMock struct { lockPrepareDinosaurRequest sync.RWMutex lockRegisterDinosaurDeprovisionJob sync.RWMutex lockRegisterDinosaurJob sync.RWMutex + lockResetCentralSecretBackup sync.RWMutex lockRestore sync.RWMutex lockRotateCentralRHSSOClient sync.RWMutex lockUpdate sync.RWMutex @@ -963,6 +977,42 @@ func (mock *DinosaurServiceMock) RegisterDinosaurJobCalls() []struct { return calls } +// ResetCentralSecretBackup calls ResetCentralSecretBackupFunc. +func (mock *DinosaurServiceMock) ResetCentralSecretBackup(ctx context.Context, centralRequest *dbapi.CentralRequest) *serviceError.ServiceError { + if mock.ResetCentralSecretBackupFunc == nil { + panic("DinosaurServiceMock.ResetCentralSecretBackupFunc: method is nil but DinosaurService.ResetCentralSecretBackup was just called") + } + callInfo := struct { + Ctx context.Context + CentralRequest *dbapi.CentralRequest + }{ + Ctx: ctx, + CentralRequest: centralRequest, + } + mock.lockResetCentralSecretBackup.Lock() + mock.calls.ResetCentralSecretBackup = append(mock.calls.ResetCentralSecretBackup, callInfo) + mock.lockResetCentralSecretBackup.Unlock() + return mock.ResetCentralSecretBackupFunc(ctx, centralRequest) +} + +// ResetCentralSecretBackupCalls gets all the calls that were made to ResetCentralSecretBackup. +// Check the length with: +// +// len(mockedDinosaurService.ResetCentralSecretBackupCalls()) +func (mock *DinosaurServiceMock) ResetCentralSecretBackupCalls() []struct { + Ctx context.Context + CentralRequest *dbapi.CentralRequest +} { + var calls []struct { + Ctx context.Context + CentralRequest *dbapi.CentralRequest + } + mock.lockResetCentralSecretBackup.RLock() + calls = mock.calls.ResetCentralSecretBackup + mock.lockResetCentralSecretBackup.RUnlock() + return calls +} + // Restore calls RestoreFunc. func (mock *DinosaurServiceMock) Restore(ctx context.Context, id string) *serviceError.ServiceError { if mock.RestoreFunc == nil { diff --git a/openapi/fleet-manager-private-admin.yaml b/openapi/fleet-manager-private-admin.yaml index ec09862e7c..c8febe4ca6 100644 --- a/openapi/fleet-manager-private-admin.yaml +++ b/openapi/fleet-manager-private-admin.yaml @@ -259,7 +259,8 @@ paths: $ref: 'fleet-manager.yaml#/components/schemas/Error' '/api/rhacs/v1/admin/centrals/{id}/rotate-secrets': post: - summary: Rotate RHSSO client of a central tenant + operationId: centralRotateSecrets + summary: Rotate RHSSO client or Secret Backup of a central tenant parameters: - $ref: "fleet-manager.yaml#/components/parameters/id" requestBody: @@ -271,7 +272,7 @@ paths: required: true responses: "200": - description: RHSSO client successfully rotated + description: Secret successfully rotated "401": description: Auth token is invalid content: @@ -454,6 +455,8 @@ components: properties: rotate_rhsso_client_credentials: type: boolean + reset_secret_backup: + type: boolean securitySchemes: Bearer: diff --git a/pkg/client/fleetmanager/api_moq.go b/pkg/client/fleetmanager/api_moq.go index 29b17c1e9f..b7541cb57c 100644 --- a/pkg/client/fleetmanager/api_moq.go +++ b/pkg/client/fleetmanager/api_moq.go @@ -490,6 +490,9 @@ var _ AdminAPI = &AdminAPIMock{} // // // make and configure a mocked AdminAPI // mockedAdminAPI := &AdminAPIMock{ +// CentralRotateSecretsFunc: func(ctx context.Context, id string, centralRotateSecretsRequest admin.CentralRotateSecretsRequest) (*http.Response, error) { +// panic("mock out the CentralRotateSecrets method") +// }, // CreateCentralFunc: func(ctx context.Context, async bool, centralRequestPayload admin.CentralRequestPayload) (admin.CentralRequest, *http.Response, error) { // panic("mock out the CreateCentral method") // }, @@ -506,6 +509,9 @@ var _ AdminAPI = &AdminAPIMock{} // // } type AdminAPIMock struct { + // CentralRotateSecretsFunc mocks the CentralRotateSecrets method. + CentralRotateSecretsFunc func(ctx context.Context, id string, centralRotateSecretsRequest admin.CentralRotateSecretsRequest) (*http.Response, error) + // CreateCentralFunc mocks the CreateCentral method. CreateCentralFunc func(ctx context.Context, async bool, centralRequestPayload admin.CentralRequestPayload) (admin.CentralRequest, *http.Response, error) @@ -517,6 +523,15 @@ type AdminAPIMock struct { // calls tracks calls to the methods. calls struct { + // CentralRotateSecrets holds details about calls to the CentralRotateSecrets method. + CentralRotateSecrets []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // ID is the id argument value. + ID string + // CentralRotateSecretsRequest is the centralRotateSecretsRequest argument value. + CentralRotateSecretsRequest admin.CentralRotateSecretsRequest + } // CreateCentral holds details about calls to the CreateCentral method. CreateCentral []struct { // Ctx is the ctx argument value. @@ -541,9 +556,50 @@ type AdminAPIMock struct { LocalVarOptionals *admin.GetCentralsOpts } } - lockCreateCentral sync.RWMutex - lockDeleteDbCentralById sync.RWMutex - lockGetCentrals sync.RWMutex + lockCentralRotateSecrets sync.RWMutex + lockCreateCentral sync.RWMutex + lockDeleteDbCentralById sync.RWMutex + lockGetCentrals sync.RWMutex +} + +// CentralRotateSecrets calls CentralRotateSecretsFunc. +func (mock *AdminAPIMock) CentralRotateSecrets(ctx context.Context, id string, centralRotateSecretsRequest admin.CentralRotateSecretsRequest) (*http.Response, error) { + if mock.CentralRotateSecretsFunc == nil { + panic("AdminAPIMock.CentralRotateSecretsFunc: method is nil but AdminAPI.CentralRotateSecrets was just called") + } + callInfo := struct { + Ctx context.Context + ID string + CentralRotateSecretsRequest admin.CentralRotateSecretsRequest + }{ + Ctx: ctx, + ID: id, + CentralRotateSecretsRequest: centralRotateSecretsRequest, + } + mock.lockCentralRotateSecrets.Lock() + mock.calls.CentralRotateSecrets = append(mock.calls.CentralRotateSecrets, callInfo) + mock.lockCentralRotateSecrets.Unlock() + return mock.CentralRotateSecretsFunc(ctx, id, centralRotateSecretsRequest) +} + +// CentralRotateSecretsCalls gets all the calls that were made to CentralRotateSecrets. +// Check the length with: +// +// len(mockedAdminAPI.CentralRotateSecretsCalls()) +func (mock *AdminAPIMock) CentralRotateSecretsCalls() []struct { + Ctx context.Context + ID string + CentralRotateSecretsRequest admin.CentralRotateSecretsRequest +} { + var calls []struct { + Ctx context.Context + ID string + CentralRotateSecretsRequest admin.CentralRotateSecretsRequest + } + mock.lockCentralRotateSecrets.RLock() + calls = mock.calls.CentralRotateSecrets + mock.lockCentralRotateSecrets.RUnlock() + return calls } // CreateCentral calls CreateCentralFunc. diff --git a/pkg/client/fleetmanager/client.go b/pkg/client/fleetmanager/client.go index e7c7c9b293..c4a9833611 100644 --- a/pkg/client/fleetmanager/client.go +++ b/pkg/client/fleetmanager/client.go @@ -34,6 +34,7 @@ type AdminAPI interface { GetCentrals(ctx context.Context, localVarOptionals *admin.GetCentralsOpts) (admin.CentralList, *http.Response, error) CreateCentral(ctx context.Context, async bool, centralRequestPayload admin.CentralRequestPayload) (admin.CentralRequest, *http.Response, error) DeleteDbCentralById(ctx context.Context, id string) (*http.Response, error) + CentralRotateSecrets(ctx context.Context, id string, centralRotateSecretsRequest admin.CentralRotateSecretsRequest) (*http.Response, error) } var (