From c42ad2040b0705b78d951c80d0afe2b8b1626eaa Mon Sep 17 00:00:00 2001 From: Kelvin Liu Date: Tue, 27 Aug 2024 17:33:16 +1000 Subject: [PATCH 1/7] feat: change get mode cloud and credential state method reusable; --- .../facades/client/secretbackends/service.go | 2 - domain/secretbackend/service/interface.go | 2 +- domain/secretbackend/service/service.go | 23 +--- domain/secretbackend/state/state.go | 110 +++++++++++------- domain/secretbackend/state/state_test.go | 4 +- domain/secretbackend/state/types.go | 24 ++++ 6 files changed, 93 insertions(+), 72 deletions(-) diff --git a/apiserver/facades/client/secretbackends/service.go b/apiserver/facades/client/secretbackends/service.go index 28cc16715a3..05b9bbf4665 100644 --- a/apiserver/facades/client/secretbackends/service.go +++ b/apiserver/facades/client/secretbackends/service.go @@ -15,7 +15,5 @@ type SecretBackendService interface { CreateSecretBackend(context.Context, coresecrets.SecretBackend) error UpdateSecretBackend(context.Context, secretbackendservice.UpdateSecretBackendParams) error DeleteSecretBackend(context.Context, secretbackendservice.DeleteSecretBackendParams) error - GetSecretBackendByName(context.Context, string) (*coresecrets.SecretBackend, error) - BackendSummaryInfo(ctx context.Context, reveal bool, names ...string) ([]*secretbackendservice.SecretBackendInfo, error) } diff --git a/domain/secretbackend/service/interface.go b/domain/secretbackend/service/interface.go index cc1c7a3a511..1930baee53a 100644 --- a/domain/secretbackend/service/interface.go +++ b/domain/secretbackend/service/interface.go @@ -18,7 +18,7 @@ import ( // State provides methods for working with secret backends. type State interface { GetControllerModelCloudAndCredential(ctx context.Context) (cloud.Cloud, cloud.Credential, error) - GetModelCloudAndCredential(ctx context.Context, modelUUID coremodel.UUID) (cloud.Cloud, cloud.Credential, error) + GetModelCloudAndCredential(ctx context.Context, modelUUID coremodel.UUID, modelName string) (cloud.Cloud, cloud.Credential, error) CreateSecretBackend(ctx context.Context, params secretbackend.CreateSecretBackendParams) (string, error) UpdateSecretBackend(ctx context.Context, params secretbackend.UpdateSecretBackendParams) (string, error) diff --git a/domain/secretbackend/service/service.go b/domain/secretbackend/service/service.go index 15ebfbc7498..43a90ebc936 100644 --- a/domain/secretbackend/service/service.go +++ b/domain/secretbackend/service/service.go @@ -421,6 +421,7 @@ func (s *Service) BackendSummaryInfo(ctx context.Context, reveal bool, names ... ID: b.ID, Name: b.Name, BackendType: b.BackendType, + Config: convertConfigToAny(b.Config), // TODO: fetch the correct config for non controller model k8s backend. // https://warthogs.atlassian.net/browse/JUJU-6561 }, @@ -643,28 +644,6 @@ func (s *Service) DeleteSecretBackend(ctx context.Context, params DeleteSecretBa return s.st.DeleteSecretBackend(ctx, params.BackendIdentifier, params.DeleteInUse) } -// GetSecretBackendByName returns the secret backend for the given backend name. -func (s *Service) GetSecretBackendByName(ctx context.Context, name string) (*coresecrets.SecretBackend, error) { - sb, err := s.st.GetSecretBackend(ctx, secretbackend.BackendIdentifier{Name: name}) - if err != nil { - return nil, errors.Trace(err) - } - cfg := convertConfigToAny(sb.Config) - if name == kubernetes.BackendName { - var err error - if cfg, err = s.tryControllerModelK8sBackendConfig(ctx); err != nil { - return nil, errors.Trace(err) - } - } - return &coresecrets.SecretBackend{ - ID: sb.ID, - Name: sb.Name, - BackendType: sb.BackendType, - TokenRotateInterval: sb.TokenRotateInterval, - Config: cfg, - }, nil -} - // RotateBackendToken rotates the token for the given secret backend. func (s *Service) RotateBackendToken(ctx context.Context, backendID string) error { backendInfo, err := s.st.GetSecretBackend(ctx, diff --git a/domain/secretbackend/state/state.go b/domain/secretbackend/state/state.go index 713a9dac803..074fe3df73e 100644 --- a/domain/secretbackend/state/state.go +++ b/domain/secretbackend/state/state.go @@ -27,7 +27,9 @@ import ( "github.com/juju/juju/domain/secretbackend" domainsecretbackend "github.com/juju/juju/domain/secretbackend" secretbackenderrors "github.com/juju/juju/domain/secretbackend/errors" + "github.com/juju/juju/environs/cloudspec" "github.com/juju/juju/internal/database" + "github.com/juju/juju/internal/secrets/provider" "github.com/juju/juju/internal/secrets/provider/juju" "github.com/juju/juju/internal/secrets/provider/kubernetes" ) @@ -371,7 +373,7 @@ func (s *State) ListInUseKubernetesSecretBackends(ctx context.Context) ([]*secre if err != nil { return nil, errors.Trace(err) } - q := fmt.Sprintf(` + backendQuery := fmt.Sprintf(` SELECT sbr.secret_backend_uuid AS &SecretBackendForK8sModelRow.uuid, b.name AS &SecretBackendForK8sModelRow.name, @@ -384,14 +386,17 @@ FROM secret_backend_reference sbr JOIN model m ON sbr.model_uuid = m.uuid WHERE b.name = '%s' GROUP BY m.name`, kubernetes.BackendName) - stmt, err := s.Prepare(q, SecretBackendForK8sModelRow{}) + backendStmt, err := s.Prepare(backendQuery, SecretBackendForK8sModelRow{}) if err != nil { return nil, errors.Trace(err) } var rows []SecretBackendForK8sModelRow + clouds := map[string]cloud.Cloud{} + credentials := map[string]cloud.Credential{} + err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error { - err := tx.Query(ctx, stmt).GetAll(&rows) + err := tx.Query(ctx, backendStmt).GetAll(&rows) if errors.Is(err, sql.ErrNoRows) { // We do not want to return an error if there are no secret backends. // We just return an empty list. @@ -401,6 +406,14 @@ GROUP BY m.name`, kubernetes.BackendName) if err != nil { return fmt.Errorf("querying kubernetes secret backends: %w", err) } + for _, row := range rows { + cld, cred, err := s.getModelCloudAndCredential(ctx, tx, ModelIdentifier{ModelName: row.ModelName}) + if err != nil { + return fmt.Errorf("cannot get cloud and credential for model %q: %w", row.ModelName, err) + } + clouds[row.ModelName] = cld + credentials[row.ModelName] = cred + } return nil }) if err != nil { @@ -408,14 +421,41 @@ GROUP BY m.name`, kubernetes.BackendName) } var result []*secretbackend.SecretBackend for _, row := range rows { - result = append(result, &secretbackend.SecretBackend{ + k8sConfig, err := getK8sBackendConfig(clouds[row.ModelName], credentials[row.ModelName]) + if err != nil { + return nil, errors.Trace(err) + } + sb := &secretbackend.SecretBackend{ ID: row.ID, Name: kubernetes.BuiltInName(row.ModelName), BackendType: row.BackendType, NumSecrets: row.NumSecrets, - }) + Config: convertConfigToString(k8sConfig.Config), + } + result = append(result, sb) } - return result, errors.Trace(err) + return result, nil +} + +func convertConfigToString(config map[string]interface{}) map[string]string { + if len(config) == 0 { + return nil + } + return transform.Map(config, func(k string, v interface{}) (string, string) { + return k, fmt.Sprintf("%v", v) + }) +} + +func getK8sBackendConfig(cloud cloud.Cloud, cred cloud.Credential) (*provider.BackendConfig, error) { + spec, err := cloudspec.MakeCloudSpec(cloud, "", &cred) + if err != nil { + return nil, errors.Trace(err) + } + k8sConfig, err := kubernetes.BuiltInConfig(spec) + if err != nil { + return nil, errors.Trace(err) + } + return k8sConfig, nil } // ListSecretBackendsForModel returns a list of all secret backends @@ -680,39 +720,18 @@ WHERE model_uuid = $ModelSecretBackend.uuid` // GetControllerModelCloudAndCredential returns the cloud and cloud credential for the // controller model. -func (s *State) GetControllerModelCloudAndCredential( - ctx context.Context, -) (cloud.Cloud, cloud.Credential, error) { +func (s *State) GetControllerModelCloudAndCredential(ctx context.Context) (cloud.Cloud, cloud.Credential, error) { db, err := s.DB() if err != nil { return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) } - modelQuery := ` -SELECT uuid AS &M.uuid FROM MODEL -WHERE name = 'controller' -` - modelStmt, err := s.Prepare(modelQuery, sqlair.M{}) - if err != nil { - return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) - } - var ( cld cloud.Cloud cred cloud.Credential ) err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error { - result := sqlair.M{} - err := tx.Query(ctx, modelStmt).Get(&result) - if errors.Is(err, sql.ErrNoRows) { - // Should never happen. - return fmt.Errorf("controller model not found%w", errors.Hide(modelerrors.NotFound)) - } - if err != nil { - return fmt.Errorf("querying controller model: %w", err) - } - modelID, _ := result["uuid"].(string) - cld, cred, err = s.getModelCloudAndCredential(ctx, tx, coremodel.UUID(modelID)) + cld, cred, err = s.getModelCloudAndCredential(ctx, tx, ModelIdentifier{ModelName: "controller"}) return errors.Trace(err) }) if err != nil { @@ -725,8 +744,7 @@ WHERE name = 'controller' // given model id. If no model is found for the provided id an error of // [modelerrors.NotFound] is returned. func (s *State) GetModelCloudAndCredential( - ctx context.Context, - modelID coremodel.UUID, + ctx context.Context, modelIdentifier ModelIdentifier, ) (cloud.Cloud, cloud.Credential, error) { db, err := s.DB() if err != nil { @@ -738,7 +756,7 @@ func (s *State) GetModelCloudAndCredential( ) err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error { var err error - cld, cred, err = s.getModelCloudAndCredential(ctx, tx, modelID) + cld, cred, err = s.getModelCloudAndCredential(ctx, tx, modelIdentifier) return errors.Trace(err) }) if err != nil { @@ -750,44 +768,46 @@ func (s *State) GetModelCloudAndCredential( func (s *State) getModelCloudAndCredential( ctx context.Context, tx *sqlair.TX, - modelID coremodel.UUID, + modelIdentifier ModelIdentifier, ) (cloud.Cloud, cloud.Credential, error) { - + if err := modelIdentifier.Validate(); err != nil { + return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) + } q := ` - SELECT (cloud_uuid, cloud_credential_uuid) AS (&modelCloudAndCredentialID.*) - FROM v_model - WHERE uuid = $M.model_id - ` +SELECT (cloud_uuid, cloud_credential_uuid) AS (&modelCloudAndCredentialID.*) +FROM v_model` + if modelIdentifier.ModelID != "" { + q += "\nWHERE uuid = $ModelIdentifier.model_id" + } else { + q += "\nWHERE name = $ModelIdentifier.model_name" + } stmt, err := s.Prepare(q, sqlair.M{}, modelCloudAndCredentialID{}) if err != nil { return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) } - args := sqlair.M{ - "model_id": modelID, - } ids := modelCloudAndCredentialID{} var ( cld cloud.Cloud credResult credential.CloudCredentialResult ) - err = tx.Query(ctx, stmt, args).Get(&ids) + err = tx.Query(ctx, stmt, modelIdentifier).Get(&ids) if errors.Is(err, sql.ErrNoRows) { - return cloud.Cloud{}, cloud.Credential{}, fmt.Errorf("%w for id %q", modelerrors.NotFound, modelID) + return cloud.Cloud{}, cloud.Credential{}, fmt.Errorf("%w: model for %q", modelerrors.NotFound, modelIdentifier) } else if err != nil { return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) } cld, err = cloudstate.GetCloudForID(ctx, s, tx, ids.CloudID) if err != nil { - return cloud.Cloud{}, cloud.Credential{}, fmt.Errorf("getting model %q cloud for id %q: %w", modelID, ids.CloudID, err) + return cloud.Cloud{}, cloud.Credential{}, fmt.Errorf("cannot get model %q cloud for id %q: %w", modelIdentifier, ids.CloudID, err) } credResult, err = credentialstate.GetCloudCredential(ctx, s, tx, ids.CredentialID) if err != nil { - return cloud.Cloud{}, cloud.Credential{}, fmt.Errorf("getting model %q cloud credential for id %q: %w", modelID, ids.CredentialID, err) + return cloud.Cloud{}, cloud.Credential{}, fmt.Errorf("cannot get model %q cloud credential for id %q: %w", modelIdentifier, ids.CredentialID, err) } return cld, cloud.NewNamedCredential(credResult.Label, cloud.AuthType(credResult.AuthType), credResult.Attributes, credResult.Revoked), nil } diff --git a/domain/secretbackend/state/state_test.go b/domain/secretbackend/state/state_test.go index ebfd399c513..f1c935db445 100644 --- a/domain/secretbackend/state/state_test.go +++ b/domain/secretbackend/state/state_test.go @@ -1566,7 +1566,7 @@ func (s *stateSuite) TestGetModelSecretBackendDetails(c *gc.C) { func (s *stateSuite) TestGetModelCloudAndCredential(c *gc.C) { modelUUID := s.createModel(c, coremodel.IAAS) - cld, cred, err := s.state.GetModelCloudAndCredential(context.Background(), modelUUID) + cld, cred, err := s.state.GetModelCloudAndCredential(context.Background(), ModelIdentifier{ModelID: modelUUID}) c.Assert(err, jc.ErrorIsNil) c.Check(cld, jc.DeepEquals, cloud.Cloud{ Name: "my-cloud", @@ -1608,7 +1608,7 @@ func (s *stateSuite) TestGetControllerModelCloudAndCredential(c *gc.C) { func (s *stateSuite) TestGetModelCloudAndCredentialNotFound(c *gc.C) { modelUUID := modeltesting.GenModelUUID(c) - _, _, err := s.state.GetModelCloudAndCredential(context.Background(), modelUUID) + _, _, err := s.state.GetModelCloudAndCredential(context.Background(), ModelIdentifier{ModelID: modelUUID}) c.Check(err, jc.ErrorIs, modelerrors.NotFound) } diff --git a/domain/secretbackend/state/types.go b/domain/secretbackend/state/types.go index f80bd088a8b..2822ec1d49d 100644 --- a/domain/secretbackend/state/types.go +++ b/domain/secretbackend/state/types.go @@ -35,6 +35,30 @@ type ModelSecretBackend struct { SecretBackendName string `db:"secret_backend_name"` } +// ModelIdentifier represents a set of identifiers for a model. +type ModelIdentifier struct { + // ModelID is the unique identifier for the model. + ModelID coremodel.UUID `db:"uuid"` + // ModelName is the name of the model. + ModelName string `db:"name"` +} + +// Validate checks that the model identifier is valid. +func (m ModelIdentifier) Validate() error { + if m.ModelID == "" && m.ModelName == "" { + return fmt.Errorf("both model ID and name are missing") + } + return nil +} + +// String returns the model name if it is set, otherwise the model ID. +func (m ModelIdentifier) String() string { + if m.ModelName != "" { + return m.ModelName + } + return m.ModelID.String() +} + // modelDetails represents details about a model. type modelDetails struct { // Type is the type of the model. From ac18628d8b73302f763925f6fdf3839deaa9ad91 Mon Sep 17 00:00:00 2001 From: Kelvin Liu Date: Thu, 29 Aug 2024 19:06:21 +1000 Subject: [PATCH 2/7] feat: secret backend config fetching state methods return related k8s credentials; --- domain/secretbackend/state/state.go | 305 ++++++++++------------- domain/secretbackend/state/state_test.go | 220 +++++++--------- domain/secretbackend/state/types.go | 140 +++++++++-- domain/secretbackend/state/types_test.go | 8 +- 4 files changed, 342 insertions(+), 331 deletions(-) diff --git a/domain/secretbackend/state/state.go b/domain/secretbackend/state/state.go index 074fe3df73e..90fe2e783a5 100644 --- a/domain/secretbackend/state/state.go +++ b/domain/secretbackend/state/state.go @@ -20,9 +20,6 @@ import ( "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/watcher" "github.com/juju/juju/domain" - cloudstate "github.com/juju/juju/domain/cloud/state" - "github.com/juju/juju/domain/credential" - credentialstate "github.com/juju/juju/domain/credential/state" modelerrors "github.com/juju/juju/domain/model/errors" "github.com/juju/juju/domain/secretbackend" domainsecretbackend "github.com/juju/juju/domain/secretbackend" @@ -328,7 +325,7 @@ func (s *State) ListSecretBackends(ctx context.Context) ([]*secretbackend.Secret if err != nil { return nil, errors.Trace(err) } - q := ` + nonK8sQuery := fmt.Sprintf(` SELECT b.uuid AS &SecretBackendRow.uuid, b.name AS &SecretBackendRow.name, @@ -341,15 +338,21 @@ FROM secret_backend b JOIN secret_backend_type bt ON b.backend_type_id = bt.id LEFT JOIN secret_backend_config c ON b.uuid = c.backend_uuid LEFT JOIN secret_backend_reference sbr ON b.uuid = sbr.secret_backend_uuid -GROUP BY b.name, c.name` - stmt, err := s.Prepare(q, SecretBackendRow{}) +WHERE b.name <> '%s' +GROUP BY b.name, c.name`, kubernetes.BackendName) + nonK8sStmt, err := s.Prepare(nonK8sQuery, SecretBackendRow{}) if err != nil { return nil, errors.Trace(err) } - var rows SecretBackendRows + var result []*secretbackend.SecretBackend + var nonK8sRows SecretBackendRows err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error { - err := tx.Query(ctx, stmt).GetAll(&rows) + result, err = s.listInUseKubernetesSecretBackends(ctx, tx) + if err != nil { + return errors.Trace(err) + } + err = tx.Query(ctx, nonK8sStmt).GetAll(&nonK8sRows) if errors.Is(err, sql.ErrNoRows) { // We do not want to return an error if there are no secret backends. // We just return an empty list. @@ -364,86 +367,61 @@ GROUP BY b.name, c.name` if err != nil { return nil, fmt.Errorf("cannot list secret backends: %w", err) } - return rows.toSecretBackends(), nil + return append(result, nonK8sRows.toSecretBackends()...), nil } -// ListInUseKubernetesSecretBackends returns a list of all kubernetes secret backends which contain secrets. -func (s *State) ListInUseKubernetesSecretBackends(ctx context.Context) ([]*secretbackend.SecretBackend, error) { - db, err := s.DB() - if err != nil { - return nil, errors.Trace(err) - } +// listInUseKubernetesSecretBackends returns a list of all kubernetes secret backends which contain secrets. +func (s *State) listInUseKubernetesSecretBackends(ctx context.Context, tx *sqlair.TX) ([]*secretbackend.SecretBackend, error) { backendQuery := fmt.Sprintf(` SELECT sbr.secret_backend_uuid AS &SecretBackendForK8sModelRow.uuid, b.name AS &SecretBackendForK8sModelRow.name, - m.name AS &SecretBackendForK8sModelRow.model_name, + vm.name AS &SecretBackendForK8sModelRow.model_name, bt.type AS &SecretBackendForK8sModelRow.backend_type, - COUNT(DISTINCT sbr.secret_revision_uuid) AS &SecretBackendForK8sModelRow.num_secrets + vc.uuid AS &SecretBackendForK8sModelRow.cloud_uuid, + vcca.uuid AS &SecretBackendForK8sModelRow.cloud_credential_uuid, + COUNT(DISTINCT sbr.secret_revision_uuid) AS &SecretBackendForK8sModelRow.num_secrets, + (vc.uuid, + vc.name, + vc.endpoint, + vc.skip_tls_verify, + vc.is_controller_cloud, + ccc.ca_cert) AS (&cloudRow.*), + (vcca.uuid, + vcca.name, + vcca.auth_type, + vcca.revoked, + vcca.attribute_key, + vcca.attribute_value) AS (&cloudCredentialRow.*) FROM secret_backend_reference sbr JOIN secret_backend b ON sbr.secret_backend_uuid = b.uuid JOIN secret_backend_type bt ON b.backend_type_id = bt.id - JOIN model m ON sbr.model_uuid = m.uuid + JOIN v_model vm ON sbr.model_uuid = vm.uuid + JOIN v_cloud_auth vc ON vm.cloud_uuid = vc.uuid + JOIN cloud_ca_cert ccc ON vc.uuid = ccc.cloud_uuid + JOIN v_cloud_credential_attributes vcca ON vm.cloud_credential_uuid = vcca.uuid WHERE b.name = '%s' -GROUP BY m.name`, kubernetes.BackendName) - backendStmt, err := s.Prepare(backendQuery, SecretBackendForK8sModelRow{}) +GROUP BY vm.name, vcca.attribute_key`, kubernetes.BackendName) + backendStmt, err := s.Prepare(backendQuery, SecretBackendForK8sModelRow{}, cloudRow{}, cloudCredentialRow{}) if err != nil { return nil, errors.Trace(err) } - var rows []SecretBackendForK8sModelRow - clouds := map[string]cloud.Cloud{} - credentials := map[string]cloud.Credential{} + var sbData SecretBackendForK8sModelRows + var cloudData cloudRows + var credentialData cloudCredentialRows - err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error { - err := tx.Query(ctx, backendStmt).GetAll(&rows) - if errors.Is(err, sql.ErrNoRows) { - // We do not want to return an error if there are no secret backends. - // We just return an empty list. - s.logger.Debugf("no in-use kubernetes secret backends found") - return nil - } - if err != nil { - return fmt.Errorf("querying kubernetes secret backends: %w", err) - } - for _, row := range rows { - cld, cred, err := s.getModelCloudAndCredential(ctx, tx, ModelIdentifier{ModelName: row.ModelName}) - if err != nil { - return fmt.Errorf("cannot get cloud and credential for model %q: %w", row.ModelName, err) - } - clouds[row.ModelName] = cld - credentials[row.ModelName] = cred - } - return nil - }) - if err != nil { - return nil, fmt.Errorf("cannot list kubernetes secret backends: %w", err) - } - var result []*secretbackend.SecretBackend - for _, row := range rows { - k8sConfig, err := getK8sBackendConfig(clouds[row.ModelName], credentials[row.ModelName]) - if err != nil { - return nil, errors.Trace(err) - } - sb := &secretbackend.SecretBackend{ - ID: row.ID, - Name: kubernetes.BuiltInName(row.ModelName), - BackendType: row.BackendType, - NumSecrets: row.NumSecrets, - Config: convertConfigToString(k8sConfig.Config), - } - result = append(result, sb) + err = tx.Query(ctx, backendStmt).GetAll(&sbData, &cloudData, &credentialData) + if errors.Is(err, sql.ErrNoRows) { + // We do not want to return an error if there are no secret backends. + // We just return an empty list. + s.logger.Debugf("no in-use kubernetes secret backends found") + return nil, nil } - return result, nil -} - -func convertConfigToString(config map[string]interface{}) map[string]string { - if len(config) == 0 { - return nil + if err != nil { + return nil, fmt.Errorf("querying kubernetes secret backends: %w", err) } - return transform.Map(config, func(k string, v interface{}) (string, string) { - return k, fmt.Sprintf("%v", v) - }) + return sbData.toSecretBackend(cloudData, credentialData) } func getK8sBackendConfig(cloud cloud.Cloud, cred cloud.Credential) (*provider.BackendConfig, error) { @@ -468,15 +446,18 @@ func (s *State) ListSecretBackendsForModel(ctx context.Context, modelUUID coremo } inUseCondition := "" - var inputArgs []any + var nonK8sArgs []any if !includeEmpty { - inputArgs = append(inputArgs, SecretBackendReference{ModelID: modelUUID}) + nonK8sArgs = append(nonK8sArgs, SecretBackendReference{ModelID: modelUUID}) inUseCondition = fmt.Sprintf(` LEFT JOIN secret_backend_reference sbr ON b.uuid = sbr.secret_backend_uuid -WHERE sbr.model_uuid = $SecretBackendReference.model_uuid OR b.name = '%s' OR b.name = '%s'`[1:], kubernetes.BackendName, juju.BackendName) +WHERE (sbr.model_uuid = $SecretBackendReference.model_uuid OR b.name = '%s') AND b.name <> '%s'`[1:], juju.BackendName, kubernetes.BackendName) + } else { + inUseCondition = fmt.Sprintf(` +WHERE b.name <> '%s'`[1:], kubernetes.BackendName) } - q := fmt.Sprintf(` + nonK8sQuery := fmt.Sprintf(` SELECT b.uuid AS &SecretBackendRow.uuid, b.name AS &SecretBackendRow.name, @@ -489,7 +470,7 @@ FROM secret_backend b LEFT JOIN secret_backend_config c ON b.uuid = c.backend_uuid %s ORDER BY b.name`, inUseCondition) - stmt, err := s.Prepare(q, append(inputArgs, SecretBackendRow{})...) + nonK8sStmt, err := s.Prepare(nonK8sQuery, append(nonK8sArgs, SecretBackendRow{})...) if err != nil { return nil, errors.Trace(err) } @@ -505,8 +486,9 @@ WHERE m.uuid = $M.uuid } var ( - rows SecretBackendRows - modelType coremodel.ModelType + rows SecretBackendRows + modelType coremodel.ModelType + currentK8sBackend *secretbackend.SecretBackend ) err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error { var modelDetails modelDetails @@ -520,7 +502,7 @@ WHERE m.uuid = $M.uuid } modelType = modelDetails.Type - err = tx.Query(ctx, stmt, inputArgs...).GetAll(&rows) + err = tx.Query(ctx, nonK8sStmt, nonK8sArgs...).GetAll(&rows) if errors.Is(err, sql.ErrNoRows) { // We do not want to return an error if there are no secret backends. // We just return an empty list. @@ -530,23 +512,90 @@ WHERE m.uuid = $M.uuid if err != nil { return fmt.Errorf("querying secret backends: %w", err) } - return nil + if modelType != coremodel.CAAS { + return nil + } + currentK8sBackend, err = s.getK8sSecretBackendForModel(ctx, tx, modelUUID) + return errors.Trace(err) }) if err != nil { return nil, fmt.Errorf("cannot list secret backends: %w", err) } - backends := rows.toSecretBackends() var result []*secretbackend.SecretBackend - for _, b := range backends { + for _, b := range rows.toSecretBackends() { if modelType == coremodel.CAAS && b.Name == juju.BackendName { continue } result = append(result, b) } + if currentK8sBackend != nil { + result = append(result, currentK8sBackend) + } return result, errors.Trace(err) } +func (s *State) getK8sSecretBackendForModel(ctx context.Context, tx *sqlair.TX, modelUUID coremodel.UUID) (*secretbackend.SecretBackend, error) { + stmt, err := s.Prepare(` +SELECT + vc.uuid AS &SecretBackendForK8sModelRow.cloud_uuid, + vcca.uuid AS &SecretBackendForK8sModelRow.cloud_credential_uuid, + vm.model_type AS &modelDetails.model_type, + (vc.uuid, + vc.name, + vc.endpoint, + vc.skip_tls_verify, + vc.is_controller_cloud, + ccc.ca_cert) AS (&cloudRow.*), + (vcca.uuid, + vcca.name, + vcca.auth_type, + vcca.revoked, + vcca.attribute_key, + vcca.attribute_value) AS (&cloudCredentialRow.*) +FROM v_model vm + JOIN v_cloud_auth vc ON vm.cloud_uuid = vc.uuid + JOIN cloud_ca_cert ccc ON vc.uuid = ccc.cloud_uuid + JOIN v_cloud_credential_attributes vcca ON vm.cloud_credential_uuid = vcca.uuid +WHERE vm.uuid = $modelIdentifier.uuid +GROUP BY vm.name, vcca.attribute_key`, modelIdentifier{}, modelDetails{}, SecretBackendForK8sModelRow{}, cloudRow{}, cloudCredentialRow{}) + if err != nil { + return nil, errors.Trace(err) + } + var models []modelDetails + var sbCloudCredentialIDs SecretBackendForK8sModelRows + var clds cloudRows + var creds cloudCredentialRows + err = tx.Query(ctx, stmt, modelIdentifier{ModelID: modelUUID}).GetAll(&models, &sbCloudCredentialIDs, &clds, &creds) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("%w: %q", modelerrors.NotFound, modelUUID) + } + if err != nil { + return nil, fmt.Errorf("cannot fetch k8s secret backend credential for model %q: %w", modelUUID, err) + } + if len(models) == 0 || len(sbCloudCredentialIDs) == 0 { + return nil, fmt.Errorf("%w: %q", modelerrors.NotFound, modelUUID) + } + model := models[0] + if model.Type != coremodel.CAAS { + return nil, fmt.Errorf("%w: %q", modelerrors.NotFound, modelUUID) + } + sbCloudCredentialID := sbCloudCredentialIDs[0] + + cld := clds.toClouds()[sbCloudCredentialID.CloudID] + cred := creds.toCloudCredentials()[sbCloudCredentialID.CredentialID] + k8sConfig, err := getK8sBackendConfig(cld, cred) + if err != nil { + return nil, errors.Trace(err) + } + sb, err := s.getSecretBackend(ctx, tx, secretbackend.BackendIdentifier{Name: kubernetes.BackendName}) + if err != nil { + return nil, fmt.Errorf("cannot get k8s secret backend for model %q: %w", modelUUID, err) + } + sb.Config = k8sConfig.Config + return sb, nil +} + func (s *State) getSecretBackend(ctx context.Context, tx *sqlair.TX, identifier secretbackend.BackendIdentifier) (*secretbackend.SecretBackend, error) { if identifier.ID == "" && identifier.Name == "" { return nil, fmt.Errorf("%w: both ID and name are missing", secretbackenderrors.NotValid) @@ -718,100 +767,6 @@ WHERE model_uuid = $ModelSecretBackend.uuid` return err } -// GetControllerModelCloudAndCredential returns the cloud and cloud credential for the -// controller model. -func (s *State) GetControllerModelCloudAndCredential(ctx context.Context) (cloud.Cloud, cloud.Credential, error) { - db, err := s.DB() - if err != nil { - return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) - } - - var ( - cld cloud.Cloud - cred cloud.Credential - ) - err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error { - cld, cred, err = s.getModelCloudAndCredential(ctx, tx, ModelIdentifier{ModelName: "controller"}) - return errors.Trace(err) - }) - if err != nil { - return cloud.Cloud{}, cloud.Credential{}, err - } - return cld, cred, nil -} - -// GetModelCloudAndCredential returns the cloud and cloud credential for the -// given model id. If no model is found for the provided id an error of -// [modelerrors.NotFound] is returned. -func (s *State) GetModelCloudAndCredential( - ctx context.Context, modelIdentifier ModelIdentifier, -) (cloud.Cloud, cloud.Credential, error) { - db, err := s.DB() - if err != nil { - return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) - } - var ( - cld cloud.Cloud - cred cloud.Credential - ) - err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error { - var err error - cld, cred, err = s.getModelCloudAndCredential(ctx, tx, modelIdentifier) - return errors.Trace(err) - }) - if err != nil { - return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) - } - return cld, cred, nil -} - -func (s *State) getModelCloudAndCredential( - ctx context.Context, - tx *sqlair.TX, - modelIdentifier ModelIdentifier, -) (cloud.Cloud, cloud.Credential, error) { - if err := modelIdentifier.Validate(); err != nil { - return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) - } - q := ` -SELECT (cloud_uuid, cloud_credential_uuid) AS (&modelCloudAndCredentialID.*) -FROM v_model` - if modelIdentifier.ModelID != "" { - q += "\nWHERE uuid = $ModelIdentifier.model_id" - } else { - q += "\nWHERE name = $ModelIdentifier.model_name" - } - - stmt, err := s.Prepare(q, sqlair.M{}, modelCloudAndCredentialID{}) - if err != nil { - return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) - } - - ids := modelCloudAndCredentialID{} - - var ( - cld cloud.Cloud - credResult credential.CloudCredentialResult - ) - err = tx.Query(ctx, stmt, modelIdentifier).Get(&ids) - if errors.Is(err, sql.ErrNoRows) { - return cloud.Cloud{}, cloud.Credential{}, fmt.Errorf("%w: model for %q", modelerrors.NotFound, modelIdentifier) - } else if err != nil { - return cloud.Cloud{}, cloud.Credential{}, errors.Trace(err) - } - - cld, err = cloudstate.GetCloudForID(ctx, s, tx, ids.CloudID) - if err != nil { - return cloud.Cloud{}, cloud.Credential{}, fmt.Errorf("cannot get model %q cloud for id %q: %w", modelIdentifier, ids.CloudID, err) - } - - credResult, err = credentialstate.GetCloudCredential(ctx, s, tx, ids.CredentialID) - if err != nil { - return cloud.Cloud{}, cloud.Credential{}, fmt.Errorf("cannot get model %q cloud credential for id %q: %w", modelIdentifier, ids.CredentialID, err) - } - return cld, cloud.NewNamedCredential(credResult.Label, cloud.AuthType(credResult.AuthType), credResult.Attributes, credResult.Revoked), nil -} - // GetSecretBackendReferenceCount returns the number of references to the secret backend. // It returns 0 if there are no references for the provided secret backend ID. func (s *State) GetSecretBackendReferenceCount(ctx context.Context, backendID string) (int, error) { diff --git a/domain/secretbackend/state/state_test.go b/domain/secretbackend/state/state_test.go index f1c935db445..7c2dd3cb390 100644 --- a/domain/secretbackend/state/state_test.go +++ b/domain/secretbackend/state/state_test.go @@ -107,7 +107,7 @@ func (s *stateSuite) createModelWithName(c *gc.C, modelType coremodel.ModelType, ID: s.vaultBackendID, Name: "my-backend", BackendType: "vault", - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -144,6 +144,7 @@ func (s *stateSuite) createModelWithName(c *gc.C, modelType coremodel.ModelType, Name: "my-cloud", Type: "ec2", AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType}, + Endpoint: "https://my-cloud.com", CACertificates: []string{"my-ca-cert"}, Regions: []cloud.Region{ {Name: "my-region"}, @@ -242,7 +243,7 @@ WHERE backend_uuid = ?`[1:], expectedSecretBackend.ID) } if len(expectedSecretBackend.Config) > 0 { - actual.Config = map[string]string{} + actual.Config = map[string]any{} rows, err := db.Query(` SELECT name, content FROM secret_backend_config @@ -329,7 +330,7 @@ func (s *stateSuite) TestCreateSecretBackend(c *gc.C) { Name: "my-backend", BackendType: "vault", TokenRotateInterval: &rotateInternal, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -417,7 +418,7 @@ func (s *stateSuite) TestUpdateSecretBackend(c *gc.C) { Name: "my-backend", BackendType: "vault", TokenRotateInterval: &rotateInternal, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -438,7 +439,7 @@ func (s *stateSuite) TestUpdateSecretBackend(c *gc.C) { Name: "my-backend-updated", BackendType: "vault", TokenRotateInterval: &rotateInternal, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -465,7 +466,7 @@ func (s *stateSuite) TestUpdateSecretBackend(c *gc.C) { Name: "my-backend-updated", BackendType: "vault", TokenRotateInterval: &newRotateInternal, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1-updated", "key3": "value3", }, @@ -495,7 +496,7 @@ func (s *stateSuite) TestUpdateSecretBackendWithNoRotateNoConfig(c *gc.C) { Name: "my-backend", BackendType: "vault", TokenRotateInterval: &rotateInternal, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -514,7 +515,7 @@ func (s *stateSuite) TestUpdateSecretBackendWithNoRotateNoConfig(c *gc.C) { Name: "my-backend-updated", BackendType: "vault", TokenRotateInterval: &rotateInternal, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -840,7 +841,7 @@ WHERE model_uuid = ?`[1:], modelUUID) c.Assert(configuredBackend, gc.Equals, "internal") } -func (s *stateSuite) TestListSecretBackends(c *gc.C) { +func (s *stateSuite) TestListSecretBackendsIAAS(c *gc.C) { backendID1 := uuid.MustNewUUID().String() rotateInternal1 := 24 * time.Hour nextRotateTime1 := time.Now().Add(rotateInternal1) @@ -863,7 +864,7 @@ func (s *stateSuite) TestListSecretBackends(c *gc.C) { Name: "my-backend1", BackendType: "vault", TokenRotateInterval: &rotateInternal1, - Config: map[string]string{ + Config: map[string]any{ "key3": "value3", "key4": "value4", }, @@ -879,6 +880,47 @@ func (s *stateSuite) TestListSecretBackends(c *gc.C) { _, err = s.state.AddSecretBackendReference(context.Background(), &secrets.ValueRef{BackendID: backendID1}, modelUUID, secrectRevisionID2) c.Assert(err, gc.IsNil) + backends, err := s.state.ListSecretBackends(context.Background()) + c.Assert(err, gc.IsNil) + c.Assert(backends, gc.HasLen, 3) + c.Assert(backends, jc.DeepEquals, []*secretbackend.SecretBackend{ + { + ID: s.internalBackendID, + Name: "internal", + BackendType: "controller", + }, + { + ID: s.vaultBackendID, + Name: "my-backend", + BackendType: "vault", + Config: map[string]any{ + "key1": "value1", + "key2": "value2", + }, + }, + { + ID: backendID1, + Name: "my-backend1", + BackendType: "vault", + TokenRotateInterval: &rotateInternal1, + Config: map[string]any{ + "key3": "value3", + "key4": "value4", + }, + NumSecrets: 2, + }, + }) +} + +func (s *stateSuite) TestListSecretBackendsCAAS(c *gc.C) { + modelUUID := s.createModel(c, coremodel.CAAS) + secrectRevisionID1 := uuid.MustNewUUID().String() + _, err := s.state.AddSecretBackendReference(context.Background(), &secrets.ValueRef{BackendID: s.kubernetesBackendID}, modelUUID, secrectRevisionID1) + c.Assert(err, gc.IsNil) + secrectRevisionID2 := uuid.MustNewUUID().String() + _, err = s.state.AddSecretBackendReference(context.Background(), &secrets.ValueRef{BackendID: s.kubernetesBackendID}, modelUUID, secrectRevisionID2) + c.Assert(err, gc.IsNil) + backendID2 := uuid.MustNewUUID().String() rotateInternal2 := 48 * time.Hour nextRotateTime2 := time.Now().Add(rotateInternal2) @@ -901,7 +943,7 @@ func (s *stateSuite) TestListSecretBackends(c *gc.C) { Name: "my-backend2", BackendType: "kubernetes", TokenRotateInterval: &rotateInternal2, - Config: map[string]string{ + Config: map[string]any{ "key5": "value5", "key6": "value6", }, @@ -911,6 +953,18 @@ func (s *stateSuite) TestListSecretBackends(c *gc.C) { c.Assert(err, gc.IsNil) c.Assert(backends, gc.HasLen, 4) c.Assert(backends, jc.DeepEquals, []*secretbackend.SecretBackend{ + { + ID: s.kubernetesBackendID, + Name: "my-model-local", + BackendType: kubernetes.BackendType, + Config: map[string]any{ + "ca-certs": []string{"my-ca-cert"}, + "credential": `{"auth-type":"access-key","Attributes":{"bar":"bar val","foo":"foo val"}}`, + "endpoint": "https://my-cloud.com", + "is-controller-cloud": false, + }, + NumSecrets: 2, + }, { ID: s.internalBackendID, Name: "internal", @@ -920,28 +974,17 @@ func (s *stateSuite) TestListSecretBackends(c *gc.C) { ID: s.vaultBackendID, Name: "my-backend", BackendType: "vault", - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, }, - { - ID: backendID1, - Name: "my-backend1", - BackendType: "vault", - TokenRotateInterval: &rotateInternal1, - Config: map[string]string{ - "key3": "value3", - "key4": "value4", - }, - NumSecrets: 2, - }, { ID: backendID2, Name: "my-backend2", BackendType: "kubernetes", TokenRotateInterval: &rotateInternal2, - Config: map[string]string{ + Config: map[string]any{ "key5": "value5", "key6": "value6", }, @@ -1018,7 +1061,7 @@ func (s *stateSuite) assertListSecretBackendsForModelIAAS(c *gc.C, includeEmpty Name: "my-backend1", BackendType: "vault", TokenRotateInterval: &rotateInternal1, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -1046,7 +1089,7 @@ func (s *stateSuite) assertListSecretBackendsForModelIAAS(c *gc.C, includeEmpty Name: "my-backend2", BackendType: "kubernetes", TokenRotateInterval: &rotateInternal2, - Config: map[string]string{ + Config: map[string]any{ "key3": "value3", "key4": "value4", }, @@ -1064,7 +1107,7 @@ func (s *stateSuite) assertListSecretBackendsForModelIAAS(c *gc.C, includeEmpty ID: s.vaultBackendID, Name: "my-backend", BackendType: "vault", - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -1077,7 +1120,7 @@ func (s *stateSuite) assertListSecretBackendsForModelIAAS(c *gc.C, includeEmpty Name: "my-backend1", BackendType: "vault", TokenRotateInterval: &rotateInternal1, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -1087,7 +1130,7 @@ func (s *stateSuite) assertListSecretBackendsForModelIAAS(c *gc.C, includeEmpty Name: "my-backend2", BackendType: "kubernetes", TokenRotateInterval: &rotateInternal2, - Config: map[string]string{ + Config: map[string]any{ "key3": "value3", "key4": "value4", }, @@ -1135,7 +1178,7 @@ func (s *stateSuite) assertListSecretBackendsForModelCAAS(c *gc.C, includeEmpty Name: "my-backend1", BackendType: "vault", TokenRotateInterval: &rotateInternal1, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -1163,7 +1206,7 @@ func (s *stateSuite) assertListSecretBackendsForModelCAAS(c *gc.C, includeEmpty Name: "my-backend2", BackendType: "kubernetes", TokenRotateInterval: &rotateInternal2, - Config: map[string]string{ + Config: map[string]any{ "key3": "value3", "key4": "value4", }, @@ -1176,12 +1219,18 @@ func (s *stateSuite) assertListSecretBackendsForModelCAAS(c *gc.C, includeEmpty ID: s.kubernetesBackendID, Name: "kubernetes", BackendType: "kubernetes", + Config: map[string]any{ + "ca-certs": []string{"my-ca-cert"}, + "credential": `{"auth-type":"access-key","Attributes":{"bar":"bar val","foo":"foo val"}}`, + "endpoint": "https://my-cloud.com", + "is-controller-cloud": false, + }, }, { ID: s.vaultBackendID, Name: "my-backend", BackendType: "vault", - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -1194,7 +1243,7 @@ func (s *stateSuite) assertListSecretBackendsForModelCAAS(c *gc.C, includeEmpty Name: "my-backend1", BackendType: "vault", TokenRotateInterval: &rotateInternal1, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -1204,14 +1253,14 @@ func (s *stateSuite) assertListSecretBackendsForModelCAAS(c *gc.C, includeEmpty Name: "my-backend2", BackendType: "kubernetes", TokenRotateInterval: &rotateInternal2, - Config: map[string]string{ + Config: map[string]any{ "key3": "value3", "key4": "value4", }, }, ) } - c.Assert(backends, jc.DeepEquals, expected) + c.Assert(backends, jc.SameContents, expected) } func (s *stateSuite) TestListSecretBackendsForModelCAASIncludeEmpty(c *gc.C) { @@ -1228,55 +1277,6 @@ func (s *stateSuite) TestListSecretBackendsEmpty(c *gc.C) { c.Assert(backends, gc.IsNil) } -func (s *stateSuite) TestListKubernetesSecretBackends(c *gc.C) { - backendID1 := uuid.MustNewUUID().String() - rotateInternal1 := 24 * time.Hour - nextRotateTime1 := time.Now().Add(rotateInternal1) - _, err := s.state.CreateSecretBackend(context.Background(), secretbackend.CreateSecretBackendParams{ - BackendIdentifier: secretbackend.BackendIdentifier{ - ID: backendID1, - Name: "my-backend1", - }, - BackendType: "vault", - TokenRotateInterval: &rotateInternal1, - NextRotateTime: &nextRotateTime1, - Config: map[string]string{ - "key1": "value1", - "key2": "value2", - }, - }) - c.Assert(err, gc.IsNil) - s.assertSecretBackend(c, secretbackend.SecretBackend{ - ID: backendID1, - Name: "my-backend1", - BackendType: "vault", - TokenRotateInterval: &rotateInternal1, - Config: map[string]string{ - "key1": "value1", - "key2": "value2", - }, - }, &nextRotateTime1) - - modelUUID := s.createModel(c, coremodel.CAAS) - err = s.state.SetModelSecretBackend(context.Background(), modelUUID, "kubernetes") - c.Assert(err, gc.IsNil) - secrectRevisionID := uuid.MustNewUUID().String() - _, err = s.state.AddSecretBackendReference(context.Background(), &secrets.ValueRef{BackendID: s.kubernetesBackendID}, modelUUID, secrectRevisionID) - c.Assert(err, gc.IsNil) - - backends, err := s.state.ListInUseKubernetesSecretBackends(context.Background()) - c.Assert(err, gc.IsNil) - c.Assert(backends, gc.HasLen, 1) - c.Assert(backends, jc.DeepEquals, []*secretbackend.SecretBackend{ - { - ID: s.kubernetesBackendID, - Name: "my-model-local", - BackendType: "kubernetes", - NumSecrets: 1, - }, - }) -} - func (s *stateSuite) TestGetSecretBackendByName(c *gc.C) { backendID := uuid.MustNewUUID().String() rotateInternal := 24 * time.Hour @@ -1335,7 +1335,7 @@ func (s *stateSuite) TestGetSecretBackendByName(c *gc.C) { Name: "my-backend", BackendType: "vault", TokenRotateInterval: &rotateInternal, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -1408,7 +1408,7 @@ func (s *stateSuite) TestGetSecretBackend(c *gc.C) { Name: "my-backend", BackendType: "vault", TokenRotateInterval: &rotateInternal, - Config: map[string]string{ + Config: map[string]any{ "key1": "value1", "key2": "value2", }, @@ -1564,54 +1564,6 @@ func (s *stateSuite) TestGetModelSecretBackendDetails(c *gc.C) { }) } -func (s *stateSuite) TestGetModelCloudAndCredential(c *gc.C) { - modelUUID := s.createModel(c, coremodel.IAAS) - cld, cred, err := s.state.GetModelCloudAndCredential(context.Background(), ModelIdentifier{ModelID: modelUUID}) - c.Assert(err, jc.ErrorIsNil) - c.Check(cld, jc.DeepEquals, cloud.Cloud{ - Name: "my-cloud", - Type: "ec2", - AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType}, - CACertificates: []string{"my-ca-cert"}, - Regions: []cloud.Region{ - {Name: "my-region"}, - }, - }) - expectedCred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ - "foo": "foo val", - "bar": "bar val", - }) - expectedCred.Label = "foobar" - c.Check(cred, jc.DeepEquals, expectedCred) -} - -func (s *stateSuite) TestGetControllerModelCloudAndCredential(c *gc.C) { - s.createModelWithName(c, coremodel.IAAS, "controller") - cld, cred, err := s.state.GetControllerModelCloudAndCredential(context.Background()) - c.Assert(err, jc.ErrorIsNil) - c.Check(cld, jc.DeepEquals, cloud.Cloud{ - Name: "my-cloud", - Type: "ec2", - AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType}, - CACertificates: []string{"my-ca-cert"}, - Regions: []cloud.Region{ - {Name: "my-region"}, - }, - }) - expectedCred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ - "foo": "foo val", - "bar": "bar val", - }) - expectedCred.Label = "foobar" - c.Check(cred, jc.DeepEquals, expectedCred) -} - -func (s *stateSuite) TestGetModelCloudAndCredentialNotFound(c *gc.C) { - modelUUID := modeltesting.GenModelUUID(c) - _, _, err := s.state.GetModelCloudAndCredential(context.Background(), ModelIdentifier{ModelID: modelUUID}) - c.Check(err, jc.ErrorIs, modelerrors.NotFound) -} - func (s *stateSuite) TestGetSecretBackendReference(c *gc.C) { modelUUID := s.createModel(c, coremodel.IAAS) secretRevisionID := uuid.MustNewUUID().String() diff --git a/domain/secretbackend/state/types.go b/domain/secretbackend/state/types.go index 2822ec1d49d..b284bacf66a 100644 --- a/domain/secretbackend/state/types.go +++ b/domain/secretbackend/state/types.go @@ -9,14 +9,19 @@ import ( "sort" "time" + "github.com/juju/collections/set" + "github.com/juju/errors" + "github.com/juju/juju/cloud" corecloud "github.com/juju/juju/core/cloud" corecredential "github.com/juju/juju/core/credential" "github.com/juju/juju/core/logger" coremodel "github.com/juju/juju/core/model" "github.com/juju/juju/core/watcher" + "github.com/juju/juju/domain/credential" "github.com/juju/juju/domain/secretbackend" backenderrors "github.com/juju/juju/domain/secretbackend/errors" "github.com/juju/juju/internal/database" + "github.com/juju/juju/internal/secrets/provider/kubernetes" ) // ModelSecretBackend represents a set of data about a model and its current secret backend config. @@ -35,8 +40,8 @@ type ModelSecretBackend struct { SecretBackendName string `db:"secret_backend_name"` } -// ModelIdentifier represents a set of identifiers for a model. -type ModelIdentifier struct { +// modelIdentifier represents a set of identifiers for a model. +type modelIdentifier struct { // ModelID is the unique identifier for the model. ModelID coremodel.UUID `db:"uuid"` // ModelName is the name of the model. @@ -44,7 +49,7 @@ type ModelIdentifier struct { } // Validate checks that the model identifier is valid. -func (m ModelIdentifier) Validate() error { +func (m modelIdentifier) Validate() error { if m.ModelID == "" && m.ModelName == "" { return fmt.Errorf("both model ID and name are missing") } @@ -52,7 +57,7 @@ func (m ModelIdentifier) Validate() error { } // String returns the model name if it is set, otherwise the model ID. -func (m ModelIdentifier) String() string { +func (m modelIdentifier) String() string { if m.ModelName != "" { return m.ModelName } @@ -193,7 +198,7 @@ func (rows SecretBackendRows) toSecretBackends() []*secretbackend.SecretBackend } if currentBackend.Config == nil { - currentBackend.Config = make(map[string]string) + currentBackend.Config = make(map[string]any) } currentBackend.Config[row.ConfigName] = row.ConfigContent } @@ -205,6 +210,47 @@ type SecretBackendForK8sModelRow struct { SecretBackendRow // ModelName is the name of the model. ModelName string `db:"model_name"` + + // CloudID is the cloud UUID. + CloudID string `db:"cloud_uuid"` + // CredentialID is the cloud credential UUID. + CredentialID string `db:"cloud_credential_uuid"` +} + +type SecretBackendForK8sModelRows []SecretBackendForK8sModelRow + +func (rows SecretBackendForK8sModelRows) toSecretBackend(cldData cloudRows, credData cloudCredentialRows) ([]*secretbackend.SecretBackend, error) { + clds := cldData.toClouds() + creds := credData.toCloudCredentials() + + cloudIDs := set.NewStrings() + var result []*secretbackend.SecretBackend + for _, row := range rows { + // ????? each model's secret backend should not share the same k8s backend UUID????? + if cloudIDs.Contains(row.CloudID) { + continue + } + cloudIDs.Add(row.CloudID) + if _, ok := clds[row.CloudID]; !ok { + return nil, errors.Errorf("cloud %q not found", row.CloudID) + } + if _, ok := creds[row.CredentialID]; !ok { + return nil, errors.Errorf("cloud credential %q not found", row.CredentialID) + } + k8sConfig, err := getK8sBackendConfig(clds[row.CloudID], creds[row.CredentialID]) + if err != nil { + return nil, errors.Trace(err) + } + result = append(result, &secretbackend.SecretBackend{ + ID: row.ID, + Name: kubernetes.BuiltInName(row.ModelName), + BackendType: row.BackendType, + NumSecrets: row.NumSecrets, + Config: k8sConfig.Config, + }) + + } + return result, nil } // SecretBackendRotationRow represents a single joined result from @@ -252,31 +298,89 @@ type ModelCloudCredentialRow struct { OwnerName string `db:"owner_name"` } -// CloudRow represents a single row from the state database's cloud table. -type CloudRow struct { - // ID holds the cloud document key. +// cloudRow represents a single row from the state database's cloud table. +type cloudRow struct { + // ID holds the cloud UUID. ID string `db:"uuid"` // Name holds the cloud name. Name string `db:"name"` - // Type holds the cloud type reference. - CloudType string `db:"type"` - - // AuthType is the type of authentication used by the cloud. - AuthType string `db:"auth_type"` - // Endpoint holds the cloud's primary endpoint URL. Endpoint string `db:"endpoint"` // SkipTLSVerify indicates if the client should skip cert validation. SkipTLSVerify bool `db:"skip_tls_verify"` - // ModelName holds the name of the model of the cloud. - ModelName string `db:"model_name"` + // IsControllerCloud indicates if the cloud is hosting the controller model. + IsControllerCloud bool `db:"is_controller_cloud"` - // ModelOwnerUserName holds the name of the user who owns the model. - ModelOwnerUserName string `db:"model_owner_user_name"` + // CACert holds the ca cert. + CACert string `db:"ca_cert"` +} + +func (r cloudRow) toCloud() cloud.Cloud { + return cloud.Cloud{ + Name: r.Name, + Endpoint: r.Endpoint, + SkipTLSVerify: r.SkipTLSVerify, + IsControllerCloud: r.IsControllerCloud, + CACertificates: []string{r.CACert}, + } +} + +type cloudRows []cloudRow + +func (rows cloudRows) toClouds() map[string]cloud.Cloud { + clouds := make(map[string]cloud.Cloud, len(rows)) + for _, row := range rows { + clouds[row.ID] = row.toCloud() + } + return clouds +} + +type cloudCredentialRow struct { + // ID holds the cloud credential UUID. + ID string `db:"uuid"` + + // Name holds the cloud credential name. + Name string `db:"name"` + + // AuthType holds the cloud credential auth type. + AuthType string `db:"auth_type"` + + // Revoked is true if the credential has been revoked. + Revoked bool `db:"revoked"` + + // AttributeKey contains a single credential attribute key + AttributeKey string `db:"attribute_key"` + + // AttributeValue contains a single credential attribute value + AttributeValue string `db:"attribute_value"` +} + +type cloudCredentialRows []cloudCredentialRow + +func (rows cloudCredentialRows) toCloudCredentials() map[string]cloud.Credential { + credentials := make(map[string]cloud.Credential, len(rows)) + data := make(map[string]credential.CloudCredentialInfo, len(rows)) + for _, row := range rows { + if _, ok := data[row.ID]; !ok { + data[row.ID] = credential.CloudCredentialInfo{ + Label: row.Name, + AuthType: row.AuthType, + Revoked: row.Revoked, + Attributes: make(map[string]string), + } + } + if row.AttributeKey != "" { + data[row.ID].Attributes[row.AttributeKey] = row.AttributeValue + } + } + for id, info := range data { + credentials[id] = cloud.NewNamedCredential(info.Label, cloud.AuthType(info.AuthType), info.Attributes, info.Revoked) + } + return credentials } // SecretBackendReference represents a single row from the state database's secret_backend_reference table. diff --git a/domain/secretbackend/state/types_test.go b/domain/secretbackend/state/types_test.go index 09bb025685a..c97e82fdce4 100644 --- a/domain/secretbackend/state/types_test.go +++ b/domain/secretbackend/state/types_test.go @@ -109,7 +109,7 @@ func (s *typesSuite) TestToSecretBackends(c *gc.C) { Name: "name1", BackendType: "vault", TokenRotateInterval: ptr(10 * time.Second), - Config: map[string]string{ + Config: map[string]any{ "config11": "content11", "config12": "content12", "config13": "content13", @@ -119,7 +119,7 @@ func (s *typesSuite) TestToSecretBackends(c *gc.C) { ID: "uuid2", Name: "name2", BackendType: "vault", - Config: map[string]string{ + Config: map[string]any{ "config21": "content21", }, }, @@ -128,7 +128,7 @@ func (s *typesSuite) TestToSecretBackends(c *gc.C) { Name: "name3", BackendType: "vault", TokenRotateInterval: ptr(30 * time.Second), - Config: map[string]string{ + Config: map[string]any{ "config31": "content31", }, }, @@ -141,7 +141,7 @@ func (s *typesSuite) TestToSecretBackends(c *gc.C) { ID: "uuid5", Name: "name5", BackendType: "vault", - Config: map[string]string{ + Config: map[string]any{ "config51": "content51", "config52": "content52", }, From 23744ac147c41b77db904ad2031d365306e3069b Mon Sep 17 00:00:00 2001 From: Kelvin Liu Date: Thu, 29 Aug 2024 19:10:21 +1000 Subject: [PATCH 3/7] fix: secret backend service wrongly uses controller model k8s backend config for all other models; --- domain/secretbackend/params.go | 2 +- domain/secretbackend/service/interface.go | 5 - domain/secretbackend/service/service.go | 114 ++--------- domain/secretbackend/service/service_test.go | 188 +++++-------------- 4 files changed, 57 insertions(+), 252 deletions(-) diff --git a/domain/secretbackend/params.go b/domain/secretbackend/params.go index 41f98f89033..345ec056104 100644 --- a/domain/secretbackend/params.go +++ b/domain/secretbackend/params.go @@ -106,7 +106,7 @@ type SecretBackend struct { // NextRotateTime is the time at which the next token rotation should occur. NextRotateTime *time.Time // Config is the configuration of the secret backend. - Config map[string]string + Config map[string]any // NumSecrets is the number of secrets stored in the secret backend. NumSecrets int } diff --git a/domain/secretbackend/service/interface.go b/domain/secretbackend/service/interface.go index 1930baee53a..78b23260ab8 100644 --- a/domain/secretbackend/service/interface.go +++ b/domain/secretbackend/service/interface.go @@ -7,7 +7,6 @@ import ( "context" "time" - "github.com/juju/juju/cloud" "github.com/juju/juju/core/changestream" coremodel "github.com/juju/juju/core/model" "github.com/juju/juju/core/watcher" @@ -17,16 +16,12 @@ import ( // State provides methods for working with secret backends. type State interface { - GetControllerModelCloudAndCredential(ctx context.Context) (cloud.Cloud, cloud.Credential, error) - GetModelCloudAndCredential(ctx context.Context, modelUUID coremodel.UUID, modelName string) (cloud.Cloud, cloud.Credential, error) - CreateSecretBackend(ctx context.Context, params secretbackend.CreateSecretBackendParams) (string, error) UpdateSecretBackend(ctx context.Context, params secretbackend.UpdateSecretBackendParams) (string, error) DeleteSecretBackend(ctx context.Context, _ secretbackend.BackendIdentifier, deleteInUse bool) error ListSecretBackends(ctx context.Context) ([]*secretbackend.SecretBackend, error) ListSecretBackendIDs(ctx context.Context) ([]string, error) ListSecretBackendsForModel(ctx context.Context, modelUUID coremodel.UUID, includeEmpty bool) ([]*secretbackend.SecretBackend, error) - ListInUseKubernetesSecretBackends(ctx context.Context) ([]*secretbackend.SecretBackend, error) GetSecretBackend(context.Context, secretbackend.BackendIdentifier) (*secretbackend.SecretBackend, error) SecretBackendRotated(ctx context.Context, backendID string, next time.Time) error diff --git a/domain/secretbackend/service/service.go b/domain/secretbackend/service/service.go index 43a90ebc936..91d18ae8019 100644 --- a/domain/secretbackend/service/service.go +++ b/domain/secretbackend/service/service.go @@ -14,7 +14,6 @@ import ( "github.com/juju/errors" "github.com/juju/names/v5" - "github.com/juju/juju/cloud" "github.com/juju/juju/core/changestream" "github.com/juju/juju/core/leadership" "github.com/juju/juju/core/logger" @@ -26,7 +25,6 @@ import ( secretservice "github.com/juju/juju/domain/secret/service" "github.com/juju/juju/domain/secretbackend" secretbackenderrors "github.com/juju/juju/domain/secretbackend/errors" - "github.com/juju/juju/environs/cloudspec" internalsecrets "github.com/juju/juju/internal/secrets" "github.com/juju/juju/internal/secrets/provider" "github.com/juju/juju/internal/secrets/provider/juju" @@ -76,44 +74,32 @@ func (s *Service) GetSecretBackendConfigForAdmin(ctx context.Context, modelUUID if err != nil { return nil, errors.Trace(err) } - currentBackend, err := s.st.GetSecretBackend(ctx, secretbackend.BackendIdentifier{ID: m.SecretBackendID}) - if err != nil { - return nil, errors.Trace(err) - } + currentBackendName := m.SecretBackendName var info provider.ModelBackendConfigInfo info.Configs = make(map[string]provider.ModelBackendConfig) - // TODO(secrets) - only use those in use by model - // For now, we'll return all backends on the controller. - backends, err := s.st.ListSecretBackendsForModel(ctx, modelUUID, true) + backends, err := s.st.ListSecretBackendsForModel(ctx, modelUUID, false) if err != nil { return nil, errors.Trace(err) } for _, b := range backends { - if b.Name == currentBackend.Name { + s.logger.Criticalf("backend => \n%#v", b) + if b.Name == currentBackendName { info.ActiveID = b.ID } - - cfg := convertConfigToAny(b.Config) - if b.Name == kubernetes.BackendName { - var err error - if cfg, err = s.tryControllerModelK8sBackendConfig(ctx); err != nil { - return nil, errors.Trace(err) - } - } info.Configs[b.ID] = provider.ModelBackendConfig{ ControllerUUID: m.ControllerUUID, ModelUUID: m.ModelID.String(), ModelName: m.ModelName, BackendConfig: provider.BackendConfig{ BackendType: b.BackendType, - Config: cfg, + Config: b.Config, }, } } if info.ActiveID == "" { - return nil, fmt.Errorf("%w: %q", secretbackenderrors.NotFound, currentBackend.Name) + return nil, fmt.Errorf("%w: %q", secretbackenderrors.NotFound, currentBackendName) } return &info, nil } @@ -310,15 +296,6 @@ func (s *Service) backendConfigInfo( return info, nil } -func convertConfigToAny(config map[string]string) map[string]interface{} { - if len(config) == 0 { - return nil - } - return transform.Map(config, func(k string, v string) (string, any) { - return k, v - }) -} - func convertConfigToString(config map[string]interface{}) map[string]string { if len(config) == 0 { return nil @@ -328,31 +305,6 @@ func convertConfigToString(config map[string]interface{}) map[string]string { }) } -func getK8sBackendConfig(cloud cloud.Cloud, cred cloud.Credential) (*provider.BackendConfig, error) { - spec, err := cloudspec.MakeCloudSpec(cloud, "", &cred) - if err != nil { - return nil, errors.Trace(err) - } - k8sConfig, err := kubernetes.BuiltInConfig(spec) - if err != nil { - return nil, errors.Trace(err) - } - return k8sConfig, nil -} - -// tryControllerModelK8sBackendConfig returns the k8s backend info for the controller model UUID if it's possible. -func (s *Service) tryControllerModelK8sBackendConfig(ctx context.Context) (provider.ConfigAttrs, error) { - cloud, cred, err := s.st.GetControllerModelCloudAndCredential(ctx) - if err != nil { - return nil, errors.Trace(err) - } - k8sConfig, err := getK8sBackendConfig(cloud, cred) - if err != nil { - return nil, errors.Trace(err) - } - return k8sConfig.Config, nil -} - // BackendSummaryInfoForModel returns a summary of the secret backends // which contain secrets from the specified model. func (s *Service) BackendSummaryInfoForModel(ctx context.Context, modelUUID coremodel.UUID) ([]*SecretBackendInfo, error) { @@ -368,12 +320,12 @@ func (s *Service) BackendSummaryInfoForModel(ctx context.Context, modelUUID core Name: b.Name, BackendType: b.BackendType, TokenRotateInterval: b.TokenRotateInterval, - Config: convertConfigToAny(b.Config), + Config: b.Config, }, NumSecrets: b.NumSecrets, }) } - return s.composeBackendInfoResults(ctx, backendInfos, false) + return s.composeBackendInfoResults(backendInfos, false) } // ListBackendIDs returns the IDs of all the secret backends. @@ -394,49 +346,26 @@ func (s *Service) BackendSummaryInfo(ctx context.Context, reveal bool, names ... } backendInfos := make([]*SecretBackendInfo, 0, len(backends)) for _, b := range backends { - if b.Name == kubernetes.BackendName { - // We only care about non kubernetes backend here and - // the kubernetes backend info will be fetched later using the reference count data. - continue - } backendInfos = append(backendInfos, &SecretBackendInfo{ SecretBackend: coresecrets.SecretBackend{ ID: b.ID, Name: b.Name, BackendType: b.BackendType, TokenRotateInterval: b.TokenRotateInterval, - Config: convertConfigToAny(b.Config), + Config: b.Config, }, NumSecrets: b.NumSecrets, }) } - k8sBackends, err := s.st.ListInUseKubernetesSecretBackends(ctx) - if err != nil { - return nil, errors.Trace(err) - } - for _, b := range k8sBackends { - backendInfos = append(backendInfos, &SecretBackendInfo{ - SecretBackend: coresecrets.SecretBackend{ - ID: b.ID, - Name: b.Name, - BackendType: b.BackendType, - Config: convertConfigToAny(b.Config), - // TODO: fetch the correct config for non controller model k8s backend. - // https://warthogs.atlassian.net/browse/JUJU-6561 - }, - NumSecrets: b.NumSecrets, - }) - } - - results, err := s.composeBackendInfoResults(ctx, backendInfos, reveal, names...) + results, err := s.composeBackendInfoResults(backendInfos, reveal, names...) if err != nil { return nil, errors.Trace(err) } return results, nil } -func (s *Service) composeBackendInfoResults(ctx context.Context, backendInfos []*SecretBackendInfo, reveal bool, names ...string) ([]*SecretBackendInfo, error) { +func (s *Service) composeBackendInfoResults(backendInfos []*SecretBackendInfo, reveal bool, names ...string) ([]*SecretBackendInfo, error) { wanted := set.NewStrings(names...) for i := 0; i < len(backendInfos); { b := backendInfos[i] @@ -474,23 +403,6 @@ func (s *Service) composeBackendInfoResults(ctx context.Context, backendInfos [] return backendInfos, nil } -// PingSecretBackend checks the secret backend for the given backend name. -func (s *Service) PingSecretBackend(ctx context.Context, name string) error { - backend, err := s.st.GetSecretBackend(ctx, secretbackend.BackendIdentifier{Name: name}) - if err != nil { - return errors.Trace(err) - } - p, err := s.registry(backend.BackendType) - if err != nil { - return errors.Trace(err) - } - err = pingBackend(p, convertConfigToAny(backend.Config)) - if err != nil { - return fmt.Errorf("cannot ping secret backend %q: %w", name, err) - } - return nil -} - // pingBackend instantiates a backend and pings it. func pingBackend(p provider.SecretBackendProvider, cfg provider.ConfigAttrs) error { b, err := p.NewBackend(&provider.ModelBackendConfig{ @@ -614,7 +526,7 @@ func (s *Service) UpdateSecretBackend(ctx context.Context, params UpdateSecretBa cfgToApply[k] = defaultVal } } - err = configValidator.ValidateConfig(convertConfigToAny(existing.Config), cfgToApply) + err = configValidator.ValidateConfig(existing.Config, cfgToApply) if err != nil { return fmt.Errorf("%w: config for provider %q: %w", secretbackenderrors.NotValid, existing.BackendType, err) } @@ -668,7 +580,7 @@ func (s *Service) RotateBackendToken(ctx context.Context, backendID string) erro s.logger.Debugf("refresh token for backend %v", backendInfo.Name) cfg := provider.BackendConfig{ BackendType: backendInfo.BackendType, - Config: convertConfigToAny(backendInfo.Config), + Config: backendInfo.Config, } // Ideally, we should do this in a transaction, but it's not critical. // Because it's called by a single worker at a time. diff --git a/domain/secretbackend/service/service_test.go b/domain/secretbackend/service/service_test.go index fd4c5bea023..fff11869fba 100644 --- a/domain/secretbackend/service/service_test.go +++ b/domain/secretbackend/service/service_test.go @@ -175,7 +175,7 @@ func (s *serviceSuite) expectGetSecretBackendConfigForAdminDefault( ID: k8sBackendID, Name: kubernetes.BackendName, BackendType: kubernetes.BackendType, - Config: map[string]string{ + Config: map[string]any{ "ca-certs": "[cert-data]", "credential": `{"auth-type":"access-key","Attributes":{"foo":"bar"}}`, "endpoint": "http://nowhere", @@ -204,28 +204,7 @@ func (s *serviceSuite) expectGetSecretBackendConfigForAdminDefault( Return(&secretbackend.SecretBackend{Name: modelBackend.Name}, nil) } -func (s *serviceSuite) assertGetSecretBackendConfigForAdminDefault( - c *gc.C, svc *Service, modelType string, backendName string, expected *provider.ModelBackendConfigInfo, -) { - backend := secretbackend.BackendIdentifier{ - ID: vaultBackendID, - Name: backendName, - } - s.expectGetSecretBackendConfigForAdminDefault(c, modelType, backend, &secretbackend.SecretBackend{ - ID: vaultBackendID, - Name: "myvault", - BackendType: vault.BackendType, - Config: map[string]string{ - "endpoint": "http://vault", - }, - }) - modelUUID := coremodel.UUID(jujutesting.ModelTag.Id()) - info, err := svc.GetSecretBackendConfigForAdmin(context.Background(), modelUUID) - c.Assert(err, jc.ErrorIsNil) - c.Assert(info, jc.DeepEquals, expected) -} - -func (s *serviceSuite) TestGetSecretBackendConfigForAdminInternalIAAS(c *gc.C) { +func (s *serviceSuite) TestGetSecretBackendConfigForAdmin(c *gc.C) { defer s.setupMocks(c).Finish() svc := newService( s.mockState, s.logger, s.clock, @@ -234,75 +213,48 @@ func (s *serviceSuite) TestGetSecretBackendConfigForAdminInternalIAAS(c *gc.C) { return s.mockRegistry, nil }, ) - s.assertGetSecretBackendConfigForAdminDefault(c, svc, "iaas", "internal", - &provider.ModelBackendConfigInfo{ - ActiveID: jujuBackendID, - Configs: map[string]provider.ModelBackendConfig{ - jujuBackendID: jujuBackendConfig, - vaultBackendID: vaultBackendConfig, - }, - }, - ) -} -func (s *serviceSuite) TestGetSecretBackendConfigForAdminInternalCAAS(c *gc.C) { - defer s.setupMocks(c).Finish() - svc := newService( - s.mockState, s.logger, s.clock, - func(backendType string) (provider.SecretBackendProvider, error) { - c.Assert(backendType, gc.Equals, "vault") - return s.mockRegistry, nil - }, - ) - s.assertGetSecretBackendConfigForAdminDefault(c, svc, "caas", "kubernetes", - &provider.ModelBackendConfigInfo{ - ActiveID: k8sBackendID, - Configs: map[string]provider.ModelBackendConfig{ - k8sBackendID: k8sBackendConfig, - vaultBackendID: vaultBackendConfig, + modelUUID := coremodel.UUID(jujutesting.ModelTag.Id()) + s.mockState.EXPECT().ListSecretBackendsForModel(gomock.Any(), modelUUID, true).Return([]*secretbackend.SecretBackend{ + { + ID: vaultBackendID, + Name: "myvault", + BackendType: "vault", + Config: map[string]any{ + "endpoint": "http://vault", }, }, - ) -} - -func (s *serviceSuite) TestGetSecretBackendConfigForAdminExternalIAAS(c *gc.C) { - defer s.setupMocks(c).Finish() - svc := newService( - s.mockState, s.logger, s.clock, - func(backendType string) (provider.SecretBackendProvider, error) { - c.Assert(backendType, gc.Equals, "vault") - return s.mockRegistry, nil - }, - ) - s.assertGetSecretBackendConfigForAdminDefault(c, svc, "iaas", "myvault", - &provider.ModelBackendConfigInfo{ - ActiveID: vaultBackendID, - Configs: map[string]provider.ModelBackendConfig{ - jujuBackendID: jujuBackendConfig, - vaultBackendID: vaultBackendConfig, + { + ID: k8sBackendID, + Name: kubernetes.BackendName, + BackendType: kubernetes.BackendType, + Config: map[string]any{ + "ca-certs": []string{"cert-data"}, + "credential": `{"auth-type":"access-key","Attributes":{"foo":"bar"}}`, + "endpoint": "http://nowhere", + "is-controller-cloud": true, }, }, - ) -} + }, nil) + s.mockState.EXPECT().GetModelSecretBackendDetails(gomock.Any(), modelUUID). + Return(secretbackend.ModelSecretBackend{ + ControllerUUID: jujutesting.ControllerTag.Id(), + ModelID: modelUUID, + ModelName: "fred", + ModelType: coremodel.CAAS, + SecretBackendID: vaultBackendID, + SecretBackendName: "myvault", + }, nil) -func (s *serviceSuite) TestGetSecretBackendConfigForAdminExternalCAAS(c *gc.C) { - defer s.setupMocks(c).Finish() - svc := newService( - s.mockState, s.logger, s.clock, - func(backendType string) (provider.SecretBackendProvider, error) { - c.Assert(backendType, gc.Equals, "vault") - return s.mockRegistry, nil - }, - ) - s.assertGetSecretBackendConfigForAdminDefault(c, svc, "caas", "myvault", - &provider.ModelBackendConfigInfo{ - ActiveID: vaultBackendID, - Configs: map[string]provider.ModelBackendConfig{ - k8sBackendID: k8sBackendConfig, - vaultBackendID: vaultBackendConfig, - }, + info, err := svc.GetSecretBackendConfigForAdmin(context.Background(), modelUUID) + c.Assert(err, jc.ErrorIsNil) + c.Assert(info, jc.DeepEquals, &provider.ModelBackendConfigInfo{ + ActiveID: k8sBackendID, + Configs: map[string]provider.ModelBackendConfig{ + k8sBackendID: k8sBackendConfig, + vaultBackendID: vaultBackendConfig, }, - ) + }) } func (s *serviceSuite) TestGetSecretBackendConfig(c *gc.C) { @@ -319,7 +271,7 @@ func (s *serviceSuite) assertBackendSummaryInfo( ID: vaultBackendID, Name: "myvault", BackendType: vault.BackendType, - Config: map[string]string{ + Config: map[string]any{ "endpoint": "http://vault", "token": "deadbeef", }, @@ -329,7 +281,7 @@ func (s *serviceSuite) assertBackendSummaryInfo( ID: "another-vault-id", Name: "another-vault", BackendType: vault.BackendType, - Config: map[string]string{ + Config: map[string]any{ "endpoint": "http://another-vault", }, NumSecrets: 2, @@ -578,38 +530,6 @@ func (s *serviceSuite) TestBackendIDs(c *gc.C) { c.Assert(result, jc.DeepEquals, []string{vaultBackendID, "another-vault-id"}) } -func (s *serviceSuite) TestPingSecretBackend(c *gc.C) { - defer s.setupMocks(c).Finish() - svc := newService( - s.mockState, s.logger, s.clock, - func(backendType string) (provider.SecretBackendProvider, error) { - c.Assert(backendType, gc.Equals, "vault") - return s.mockRegistry, nil - }, - ) - s.mockState.EXPECT().GetSecretBackend(gomock.Any(), secretbackend.BackendIdentifier{Name: "myvault"}).Return(&secretbackend.SecretBackend{ - ID: "backend-uuid", - Name: "myvault", - BackendType: vault.BackendType, - Config: map[string]string{ - "endpoint": "http://vault", - }, - }, nil) - - s.mockRegistry.EXPECT().Type().Return(vault.BackendType) - s.mockRegistry.EXPECT().NewBackend(&provider.ModelBackendConfig{ - BackendConfig: provider.BackendConfig{ - BackendType: vault.BackendType, - Config: provider.ConfigAttrs{ - "endpoint": "http://vault", - }, - }, - }).Return(s.mockSecretProvider, nil) - s.mockSecretProvider.EXPECT().Ping().Return(nil) - err := svc.PingSecretBackend(context.Background(), "myvault") - c.Assert(err, jc.ErrorIsNil) -} - func (s *serviceSuite) TestCreateSecretBackendFailed(c *gc.C) { defer s.setupMocks(c).Finish() svc := newService( @@ -776,7 +696,7 @@ func (s *serviceSuite) assertUpdateSecretBackend(c *gc.C, byName, skipPing bool) ID: "backend-uuid", Name: "myvault", BackendType: "vault", - Config: map[string]string{ + Config: map[string]any{ "endpoint": "http://vault", }, }, nil) @@ -785,7 +705,7 @@ func (s *serviceSuite) assertUpdateSecretBackend(c *gc.C, byName, skipPing bool) ID: "backend-uuid", Name: "myvault", BackendType: "vault", - Config: map[string]string{ + Config: map[string]any{ "endpoint": "http://vault", }, }, nil) @@ -857,28 +777,6 @@ func (s *serviceSuite) TestDeleteSecretBackend(c *gc.C) { c.Assert(err, jc.ErrorIsNil) } -func (s *serviceSuite) TestGetSecretBackendByName(c *gc.C) { - defer s.setupMocks(c).Finish() - svc := newService( - s.mockState, s.logger, s.clock, - func(backendType string) (provider.SecretBackendProvider, error) { - return providerWithConfig{ - SecretBackendProvider: s.mockRegistry, - }, nil - }, - ) - s.mockState.EXPECT().GetSecretBackend(gomock.Any(), secretbackend.BackendIdentifier{Name: "myvault"}).Return(&secretbackend.SecretBackend{ - ID: "backend-uuid", - Name: "myvault", - }, nil) - result, err := svc.GetSecretBackendByName(context.Background(), "myvault") - c.Assert(err, jc.ErrorIsNil) - c.Assert(result, gc.DeepEquals, &coresecrets.SecretBackend{ - ID: "backend-uuid", - Name: "myvault", - }) -} - func (s *serviceSuite) TestRotateBackendToken(c *gc.C) { defer s.setupMocks(c).Finish() svc := newService( @@ -895,7 +793,7 @@ func (s *serviceSuite) TestRotateBackendToken(c *gc.C) { Name: "myvault", BackendType: vault.BackendType, TokenRotateInterval: ptr(200 * time.Minute), - Config: map[string]string{ + Config: map[string]any{ "endpoint": "http://vault", }, }, nil) @@ -933,7 +831,7 @@ func (s *serviceSuite) TestRotateBackendTokenRetry(c *gc.C) { Name: "myvault", BackendType: vault.BackendType, TokenRotateInterval: ptr(200 * time.Minute), - Config: map[string]string{ + Config: map[string]any{ "endpoint": "http://vault", }, }, nil) From 8036cf5d6d2d1e062630b84b2c92bbd1ea02eace Mon Sep 17 00:00:00 2001 From: Kelvin Liu Date: Thu, 29 Aug 2024 19:11:44 +1000 Subject: [PATCH 4/7] build: regenerate mocks for secret backend service; --- .../secretbackend/service/state_mock_test.go | 120 ------------------ 1 file changed, 120 deletions(-) diff --git a/domain/secretbackend/service/state_mock_test.go b/domain/secretbackend/service/state_mock_test.go index e88d6862d59..9ca35e8be65 100644 --- a/domain/secretbackend/service/state_mock_test.go +++ b/domain/secretbackend/service/state_mock_test.go @@ -14,7 +14,6 @@ import ( reflect "reflect" time "time" - cloud "github.com/juju/juju/cloud" model "github.com/juju/juju/core/model" watcher "github.com/juju/juju/core/watcher" secretbackend "github.com/juju/juju/domain/secretbackend" @@ -121,86 +120,6 @@ func (c *MockStateDeleteSecretBackendCall) DoAndReturn(f func(context.Context, s return c } -// GetControllerModelCloudAndCredential mocks base method. -func (m *MockState) GetControllerModelCloudAndCredential(arg0 context.Context) (cloud.Cloud, cloud.Credential, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetControllerModelCloudAndCredential", arg0) - ret0, _ := ret[0].(cloud.Cloud) - ret1, _ := ret[1].(cloud.Credential) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// GetControllerModelCloudAndCredential indicates an expected call of GetControllerModelCloudAndCredential. -func (mr *MockStateMockRecorder) GetControllerModelCloudAndCredential(arg0 any) *MockStateGetControllerModelCloudAndCredentialCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetControllerModelCloudAndCredential", reflect.TypeOf((*MockState)(nil).GetControllerModelCloudAndCredential), arg0) - return &MockStateGetControllerModelCloudAndCredentialCall{Call: call} -} - -// MockStateGetControllerModelCloudAndCredentialCall wrap *gomock.Call -type MockStateGetControllerModelCloudAndCredentialCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *MockStateGetControllerModelCloudAndCredentialCall) Return(arg0 cloud.Cloud, arg1 cloud.Credential, arg2 error) *MockStateGetControllerModelCloudAndCredentialCall { - c.Call = c.Call.Return(arg0, arg1, arg2) - return c -} - -// Do rewrite *gomock.Call.Do -func (c *MockStateGetControllerModelCloudAndCredentialCall) Do(f func(context.Context) (cloud.Cloud, cloud.Credential, error)) *MockStateGetControllerModelCloudAndCredentialCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockStateGetControllerModelCloudAndCredentialCall) DoAndReturn(f func(context.Context) (cloud.Cloud, cloud.Credential, error)) *MockStateGetControllerModelCloudAndCredentialCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - -// GetModelCloudAndCredential mocks base method. -func (m *MockState) GetModelCloudAndCredential(arg0 context.Context, arg1 model.UUID) (cloud.Cloud, cloud.Credential, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetModelCloudAndCredential", arg0, arg1) - ret0, _ := ret[0].(cloud.Cloud) - ret1, _ := ret[1].(cloud.Credential) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// GetModelCloudAndCredential indicates an expected call of GetModelCloudAndCredential. -func (mr *MockStateMockRecorder) GetModelCloudAndCredential(arg0, arg1 any) *MockStateGetModelCloudAndCredentialCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModelCloudAndCredential", reflect.TypeOf((*MockState)(nil).GetModelCloudAndCredential), arg0, arg1) - return &MockStateGetModelCloudAndCredentialCall{Call: call} -} - -// MockStateGetModelCloudAndCredentialCall wrap *gomock.Call -type MockStateGetModelCloudAndCredentialCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *MockStateGetModelCloudAndCredentialCall) Return(arg0 cloud.Cloud, arg1 cloud.Credential, arg2 error) *MockStateGetModelCloudAndCredentialCall { - c.Call = c.Call.Return(arg0, arg1, arg2) - return c -} - -// Do rewrite *gomock.Call.Do -func (c *MockStateGetModelCloudAndCredentialCall) Do(f func(context.Context, model.UUID) (cloud.Cloud, cloud.Credential, error)) *MockStateGetModelCloudAndCredentialCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockStateGetModelCloudAndCredentialCall) DoAndReturn(f func(context.Context, model.UUID) (cloud.Cloud, cloud.Credential, error)) *MockStateGetModelCloudAndCredentialCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - // GetModelSecretBackendDetails mocks base method. func (m *MockState) GetModelSecretBackendDetails(arg0 context.Context, arg1 model.UUID) (secretbackend.ModelSecretBackend, error) { m.ctrl.T.Helper() @@ -362,45 +281,6 @@ func (c *MockStateInitialWatchStatementForSecretBackendRotationChangesCall) DoAn return c } -// ListInUseKubernetesSecretBackends mocks base method. -func (m *MockState) ListInUseKubernetesSecretBackends(arg0 context.Context) ([]*secretbackend.SecretBackend, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListInUseKubernetesSecretBackends", arg0) - ret0, _ := ret[0].([]*secretbackend.SecretBackend) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListInUseKubernetesSecretBackends indicates an expected call of ListInUseKubernetesSecretBackends. -func (mr *MockStateMockRecorder) ListInUseKubernetesSecretBackends(arg0 any) *MockStateListInUseKubernetesSecretBackendsCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListInUseKubernetesSecretBackends", reflect.TypeOf((*MockState)(nil).ListInUseKubernetesSecretBackends), arg0) - return &MockStateListInUseKubernetesSecretBackendsCall{Call: call} -} - -// MockStateListInUseKubernetesSecretBackendsCall wrap *gomock.Call -type MockStateListInUseKubernetesSecretBackendsCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *MockStateListInUseKubernetesSecretBackendsCall) Return(arg0 []*secretbackend.SecretBackend, arg1 error) *MockStateListInUseKubernetesSecretBackendsCall { - c.Call = c.Call.Return(arg0, arg1) - return c -} - -// Do rewrite *gomock.Call.Do -func (c *MockStateListInUseKubernetesSecretBackendsCall) Do(f func(context.Context) ([]*secretbackend.SecretBackend, error)) *MockStateListInUseKubernetesSecretBackendsCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockStateListInUseKubernetesSecretBackendsCall) DoAndReturn(f func(context.Context) ([]*secretbackend.SecretBackend, error)) *MockStateListInUseKubernetesSecretBackendsCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - // ListSecretBackendIDs mocks base method. func (m *MockState) ListSecretBackendIDs(arg0 context.Context) ([]string, error) { m.ctrl.T.Helper() From d815c5e94b942bfdb1a13c99b6acac10746a9d3a Mon Sep 17 00:00:00 2001 From: Kelvin Liu Date: Fri, 30 Aug 2024 17:44:13 +1000 Subject: [PATCH 5/7] fix: secret backend service tests; --- domain/secretbackend/service/service.go | 16 ++- domain/secretbackend/service/service_test.go | 112 ++++++++++--------- domain/secretbackend/state/types.go | 1 - 3 files changed, 69 insertions(+), 60 deletions(-) diff --git a/domain/secretbackend/service/service.go b/domain/secretbackend/service/service.go index 91d18ae8019..ebc3a37570c 100644 --- a/domain/secretbackend/service/service.go +++ b/domain/secretbackend/service/service.go @@ -63,12 +63,8 @@ func newService( } } -// For testing. -var ( - GetProvider = provider.Provider -) - -// GetSecretBackendConfigForAdmin returns the secret backend configuration for the given backend ID for an admin user. +// GetSecretBackendConfigForAdmin returns the secret backend configuration for the given backend ID for an admin user, +// returning an error satisfying [secretbackenderrors.NotFound] if the backend is not found. func (s *Service) GetSecretBackendConfigForAdmin(ctx context.Context, modelUUID coremodel.UUID) (*provider.ModelBackendConfigInfo, error) { m, err := s.st.GetModelSecretBackendDetails(ctx, modelUUID) if err != nil { @@ -79,12 +75,14 @@ func (s *Service) GetSecretBackendConfigForAdmin(ctx context.Context, modelUUID var info provider.ModelBackendConfigInfo info.Configs = make(map[string]provider.ModelBackendConfig) - backends, err := s.st.ListSecretBackendsForModel(ctx, modelUUID, false) + // We need to include builtin backends for secret draining and accessing those secrets while drain is in progress. + // TODO(secrets) - only use those in use by model + // For now, we'll return all backends on the controller. + backends, err := s.st.ListSecretBackendsForModel(ctx, modelUUID, true) if err != nil { return nil, errors.Trace(err) } for _, b := range backends { - s.logger.Criticalf("backend => \n%#v", b) if b.Name == currentBackendName { info.ActiveID = b.ID } @@ -188,7 +186,7 @@ func (s *Service) backendConfigInfo( return nil, errors.Errorf("unexpected nil value for GrantedSecretsGetter") } - p, err := GetProvider(adminCfg.BackendType) + p, err := s.registry(adminCfg.BackendType) if err != nil { return nil, errors.Trace(err) } diff --git a/domain/secretbackend/service/service_test.go b/domain/secretbackend/service/service_test.go index fff11869fba..f3596df9214 100644 --- a/domain/secretbackend/service/service_test.go +++ b/domain/secretbackend/service/service_test.go @@ -22,7 +22,6 @@ import ( gc "gopkg.in/check.v1" "gopkg.in/juju/environschema.v1" - "github.com/juju/juju/cloud" "github.com/juju/juju/core/changestream" "github.com/juju/juju/core/leadership" "github.com/juju/juju/core/logger" @@ -154,23 +153,12 @@ func (s *serviceSuite) setupMocks(c *gc.C) *gomock.Controller { } func (s *serviceSuite) expectGetSecretBackendConfigForAdminDefault( - c *gc.C, modelType string, modelBackend secretbackend.BackendIdentifier, backends ...*secretbackend.SecretBackend, + modelType string, modelBackend secretbackend.BackendIdentifier, backends ...*secretbackend.SecretBackend, ) { - ctrl := gomock.NewController(c) - defer ctrl.Finish() - modelUUID := coremodel.UUID(jujutesting.ModelTag.Id()) var builtIn []*secretbackend.SecretBackend if modelType == "caas" { - cld := cloud.Cloud{ - Name: "test", - Type: "kubernetes", - Endpoint: "http://nowhere", - CACertificates: []string{"cert-data"}, - IsControllerCloud: true, - } - cred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{"foo": "bar"}) builtIn = []*secretbackend.SecretBackend{{ ID: k8sBackendID, Name: kubernetes.BackendName, @@ -182,7 +170,7 @@ func (s *serviceSuite) expectGetSecretBackendConfigForAdminDefault( "is-controller-cloud": "true", }, }} - s.mockState.EXPECT().GetControllerModelCloudAndCredential(gomock.Any()).Return(cld, cred, nil) + } else { builtIn = []*secretbackend.SecretBackend{{ ID: jujuBackendID, @@ -194,14 +182,13 @@ func (s *serviceSuite) expectGetSecretBackendConfigForAdminDefault( s.mockState.EXPECT().ListSecretBackendsForModel(gomock.Any(), modelUUID, true).Return(append(builtIn, backends...), nil) s.mockState.EXPECT().GetModelSecretBackendDetails(gomock.Any(), modelUUID). Return(secretbackend.ModelSecretBackend{ - ControllerUUID: jujutesting.ControllerTag.Id(), - ModelID: modelUUID, - ModelName: "fred", - ModelType: coremodel.ModelType(modelType), - SecretBackendID: modelBackend.ID, + ControllerUUID: jujutesting.ControllerTag.Id(), + ModelID: modelUUID, + ModelName: "fred", + ModelType: coremodel.ModelType(modelType), + SecretBackendID: modelBackend.ID, + SecretBackendName: modelBackend.Name, }, nil) - s.mockState.EXPECT().GetSecretBackend(gomock.Any(), secretbackend.BackendIdentifier{ID: modelBackend.ID}). - Return(&secretbackend.SecretBackend{Name: modelBackend.Name}, nil) } func (s *serviceSuite) TestGetSecretBackendConfigForAdmin(c *gc.C) { @@ -249,7 +236,7 @@ func (s *serviceSuite) TestGetSecretBackendConfigForAdmin(c *gc.C) { info, err := svc.GetSecretBackendConfigForAdmin(context.Background(), modelUUID) c.Assert(err, jc.ErrorIsNil) c.Assert(info, jc.DeepEquals, &provider.ModelBackendConfigInfo{ - ActiveID: k8sBackendID, + ActiveID: vaultBackendID, Configs: map[string]provider.ModelBackendConfig{ k8sBackendID: k8sBackendConfig, vaultBackendID: vaultBackendConfig, @@ -257,6 +244,44 @@ func (s *serviceSuite) TestGetSecretBackendConfigForAdmin(c *gc.C) { }) } +func (s *serviceSuite) TestGetSecretBackendConfigForAdminFailedNotFound(c *gc.C) { + defer s.setupMocks(c).Finish() + svc := newService( + s.mockState, s.logger, s.clock, + func(backendType string) (provider.SecretBackendProvider, error) { + c.Assert(backendType, gc.Equals, "vault") + return s.mockRegistry, nil + }, + ) + + modelUUID := coremodel.UUID(jujutesting.ModelTag.Id()) + s.mockState.EXPECT().ListSecretBackendsForModel(gomock.Any(), modelUUID, true).Return([]*secretbackend.SecretBackend{ + { + ID: k8sBackendID, + Name: kubernetes.BackendName, + BackendType: kubernetes.BackendType, + Config: map[string]any{ + "ca-certs": []string{"cert-data"}, + "credential": `{"auth-type":"access-key","Attributes":{"foo":"bar"}}`, + "endpoint": "http://nowhere", + "is-controller-cloud": true, + }, + }, + }, nil) + s.mockState.EXPECT().GetModelSecretBackendDetails(gomock.Any(), modelUUID). + Return(secretbackend.ModelSecretBackend{ + ControllerUUID: jujutesting.ControllerTag.Id(), + ModelID: modelUUID, + ModelName: "fred", + ModelType: coremodel.CAAS, + SecretBackendID: vaultBackendID, + SecretBackendName: "myvault", + }, nil) + + _, err := svc.GetSecretBackendConfigForAdmin(context.Background(), modelUUID) + c.Assert(err, jc.ErrorIs, secretbackenderrors.NotFound) +} + func (s *serviceSuite) TestGetSecretBackendConfig(c *gc.C) { c.Skip("TODO: wait for secret DqLite support") } @@ -287,16 +312,13 @@ func (s *serviceSuite) assertBackendSummaryInfo( NumSecrets: 2, }, } - var listK8sBackendResult []*secretbackend.SecretBackend if modelType == coremodel.CAAS { - listK8sBackendResult = []*secretbackend.SecretBackend{ - { - ID: k8sBackendID, - Name: "my-model-local", - BackendType: kubernetes.BackendType, - NumSecrets: 3, - }, - } + backends = append(backends, &secretbackend.SecretBackend{ + ID: k8sBackendID, + Name: "my-model-local", + BackendType: kubernetes.BackendType, + NumSecrets: 3, + }) } else { backends = append(backends, &secretbackend.SecretBackend{ ID: jujuBackendID, @@ -305,7 +327,6 @@ func (s *serviceSuite) assertBackendSummaryInfo( }) } s.mockState.EXPECT().ListSecretBackends(gomock.Any()).Return(backends, nil) - s.mockState.EXPECT().ListInUseKubernetesSecretBackends(gomock.Any()).Return(listK8sBackendResult, nil) s.mockRegistry.EXPECT().Type().Return(vault.BackendType).AnyTimes() if set.NewStrings(names...).Contains("myvault") || len(names) == 0 { s.mockRegistry.EXPECT().NewBackend(&provider.ModelBackendConfig{ @@ -1307,9 +1328,6 @@ func (s *serviceSuite) assertBackendConfigInfoLeaderUnit(c *gc.C, wanted []strin ID: "gitlab/0", } token := NewMockToken(ctrl) - p := NewMockSecretBackendProvider(ctrl) - - s.PatchValue(&GetProvider, func(string) (provider.SecretBackendProvider, error) { return p, nil }) owned := []*coresecrets.SecretRevisionRef{ {URI: &coresecrets.URI{ID: "owned-1"}, RevisionID: "owned-rev-1"}, @@ -1336,7 +1354,7 @@ func (s *serviceSuite) assertBackendConfigInfoLeaderUnit(c *gc.C, wanted []strin ID: "backend-id", Name: "backend1", } - s.expectGetSecretBackendConfigForAdminDefault(c, "iaas", backend, []*secretbackend.SecretBackend{{ + s.expectGetSecretBackendConfigForAdminDefault("iaas", backend, []*secretbackend.SecretBackend{{ ID: "backend-id", Name: "backend1", BackendType: "some-backend", @@ -1345,10 +1363,10 @@ func (s *serviceSuite) assertBackendConfigInfoLeaderUnit(c *gc.C, wanted []strin Name: "backend2", BackendType: "some-backend2", }}...) - p.EXPECT().Initialise(gomock.Any()).Return(nil) + s.mockRegistry.EXPECT().Initialise(gomock.Any()).Return(nil) token.EXPECT().Check().Return(nil) - p.EXPECT().RestrictedConfig(context.Background(), &adminCfg, false, false, accessor, ownedRevs, readRevs).Return(&adminCfg.BackendConfig, nil) + s.mockRegistry.EXPECT().RestrictedConfig(context.Background(), &adminCfg, false, false, accessor, ownedRevs, readRevs).Return(&adminCfg.BackendConfig, nil) listGranted := func( ctx context.Context, backendID string, role coresecrets.SecretRole, consumers ...secretservice.SecretAccessor, @@ -1421,9 +1439,6 @@ func (s *serviceSuite) TestBackendConfigInfoNonLeaderUnit(c *gc.C) { ID: "gitlab/0", } token := NewMockToken(ctrl) - p := NewMockSecretBackendProvider(ctrl) - - s.PatchValue(&GetProvider, func(string) (provider.SecretBackendProvider, error) { return p, nil }) unitOwned := []*coresecrets.SecretRevisionRef{ {URI: &coresecrets.URI{ID: "owned-1"}, RevisionID: "owned-rev-1"}, @@ -1456,7 +1471,7 @@ func (s *serviceSuite) TestBackendConfigInfoNonLeaderUnit(c *gc.C) { ID: "backend-id", Name: "backend1", } - s.expectGetSecretBackendConfigForAdminDefault(c, "iaas", backend, []*secretbackend.SecretBackend{{ + s.expectGetSecretBackendConfigForAdminDefault("iaas", backend, []*secretbackend.SecretBackend{{ ID: "backend-id", Name: "backend1", BackendType: "some-backend", @@ -1465,10 +1480,10 @@ func (s *serviceSuite) TestBackendConfigInfoNonLeaderUnit(c *gc.C) { Name: "backend2", BackendType: "some-backend2", }}...) - p.EXPECT().Initialise(gomock.Any()).Return(nil) + s.mockRegistry.EXPECT().Initialise(gomock.Any()).Return(nil) token.EXPECT().Check().Return(leadership.NewNotLeaderError("", "")) - p.EXPECT().RestrictedConfig(context.Background(), &adminCfg, true, false, accessor, ownedRevs, readRevs).Return(&adminCfg.BackendConfig, nil) + s.mockRegistry.EXPECT().RestrictedConfig(context.Background(), &adminCfg, true, false, accessor, ownedRevs, readRevs).Return(&adminCfg.BackendConfig, nil) listGranted := func( ctx context.Context, backendID string, role coresecrets.SecretRole, consumers ...secretservice.SecretAccessor, @@ -1541,9 +1556,6 @@ func (s *serviceSuite) TestDrainBackendConfigInfo(c *gc.C) { ID: "gitlab/0", } token := NewMockToken(ctrl) - p := NewMockSecretBackendProvider(ctrl) - - s.PatchValue(&GetProvider, func(string) (provider.SecretBackendProvider, error) { return p, nil }) unitOwned := []*coresecrets.SecretRevisionRef{ {URI: &coresecrets.URI{ID: "owned-1"}, RevisionID: "owned-rev-1"}, @@ -1576,7 +1588,7 @@ func (s *serviceSuite) TestDrainBackendConfigInfo(c *gc.C) { ID: "backend-id", Name: "backend1", } - s.expectGetSecretBackendConfigForAdminDefault(c, "iaas", backend, []*secretbackend.SecretBackend{{ + s.expectGetSecretBackendConfigForAdminDefault("iaas", backend, []*secretbackend.SecretBackend{{ ID: "backend-id", Name: "backend1", BackendType: "some-backend", @@ -1585,10 +1597,10 @@ func (s *serviceSuite) TestDrainBackendConfigInfo(c *gc.C) { Name: "backend2", BackendType: "some-backend2", }}...) - p.EXPECT().Initialise(gomock.Any()).Return(nil) + s.mockRegistry.EXPECT().Initialise(gomock.Any()).Return(nil) token.EXPECT().Check().Return(leadership.NewNotLeaderError("", "")) - p.EXPECT().RestrictedConfig(context.Background(), &adminCfg, true, true, accessor, ownedRevs, readRevs).Return(&adminCfg.BackendConfig, nil) + s.mockRegistry.EXPECT().RestrictedConfig(context.Background(), &adminCfg, true, true, accessor, ownedRevs, readRevs).Return(&adminCfg.BackendConfig, nil) listGranted := func( ctx context.Context, backendID string, role coresecrets.SecretRole, consumers ...secretservice.SecretAccessor, diff --git a/domain/secretbackend/state/types.go b/domain/secretbackend/state/types.go index b284bacf66a..209acfc796d 100644 --- a/domain/secretbackend/state/types.go +++ b/domain/secretbackend/state/types.go @@ -226,7 +226,6 @@ func (rows SecretBackendForK8sModelRows) toSecretBackend(cldData cloudRows, cred cloudIDs := set.NewStrings() var result []*secretbackend.SecretBackend for _, row := range rows { - // ????? each model's secret backend should not share the same k8s backend UUID????? if cloudIDs.Contains(row.CloudID) { continue } From 92c3b84d365938b2e400bb19f62a64cd36020e1c Mon Sep 17 00:00:00 2001 From: Kelvin Liu Date: Tue, 3 Sep 2024 16:04:27 +1000 Subject: [PATCH 6/7] feat: unexport secret backend db row types; --- domain/secretbackend/service/service_test.go | 6 ++++ domain/secretbackend/state/state.go | 34 ++++++++++---------- domain/secretbackend/state/types.go | 24 +++++--------- domain/secretbackend/state/types_test.go | 2 +- 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/domain/secretbackend/service/service_test.go b/domain/secretbackend/service/service_test.go index f3596df9214..b40049801d9 100644 --- a/domain/secretbackend/service/service_test.go +++ b/domain/secretbackend/service/service_test.go @@ -203,6 +203,11 @@ func (s *serviceSuite) TestGetSecretBackendConfigForAdmin(c *gc.C) { modelUUID := coremodel.UUID(jujutesting.ModelTag.Id()) s.mockState.EXPECT().ListSecretBackendsForModel(gomock.Any(), modelUUID, true).Return([]*secretbackend.SecretBackend{ + { + ID: jujuBackendID, + Name: juju.BackendName, + BackendType: juju.BackendType, + }, { ID: vaultBackendID, Name: "myvault", @@ -238,6 +243,7 @@ func (s *serviceSuite) TestGetSecretBackendConfigForAdmin(c *gc.C) { c.Assert(info, jc.DeepEquals, &provider.ModelBackendConfigInfo{ ActiveID: vaultBackendID, Configs: map[string]provider.ModelBackendConfig{ + jujuBackendID: jujuBackendConfig, k8sBackendID: k8sBackendConfig, vaultBackendID: vaultBackendConfig, }, diff --git a/domain/secretbackend/state/state.go b/domain/secretbackend/state/state.go index 90fe2e783a5..716b68b69a6 100644 --- a/domain/secretbackend/state/state.go +++ b/domain/secretbackend/state/state.go @@ -297,7 +297,7 @@ FROM secret_backend b`, SecretBackendRow{}) return nil, errors.Trace(err) } - var rows SecretBackendRows + var rows secretBackendRows err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error { err := tx.Query(ctx, stmt).GetAll(&rows) if errors.Is(err, sql.ErrNoRows) { @@ -346,7 +346,7 @@ GROUP BY b.name, c.name`, kubernetes.BackendName) } var result []*secretbackend.SecretBackend - var nonK8sRows SecretBackendRows + var nonK8sRows secretBackendRows err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error { result, err = s.listInUseKubernetesSecretBackends(ctx, tx) if err != nil { @@ -374,13 +374,13 @@ GROUP BY b.name, c.name`, kubernetes.BackendName) func (s *State) listInUseKubernetesSecretBackends(ctx context.Context, tx *sqlair.TX) ([]*secretbackend.SecretBackend, error) { backendQuery := fmt.Sprintf(` SELECT - sbr.secret_backend_uuid AS &SecretBackendForK8sModelRow.uuid, - b.name AS &SecretBackendForK8sModelRow.name, - vm.name AS &SecretBackendForK8sModelRow.model_name, - bt.type AS &SecretBackendForK8sModelRow.backend_type, - vc.uuid AS &SecretBackendForK8sModelRow.cloud_uuid, - vcca.uuid AS &SecretBackendForK8sModelRow.cloud_credential_uuid, - COUNT(DISTINCT sbr.secret_revision_uuid) AS &SecretBackendForK8sModelRow.num_secrets, + sbr.secret_backend_uuid AS &secretBackendForK8sModelRow.uuid, + b.name AS &secretBackendForK8sModelRow.name, + vm.name AS &secretBackendForK8sModelRow.model_name, + bt.type AS &secretBackendForK8sModelRow.backend_type, + vc.uuid AS &secretBackendForK8sModelRow.cloud_uuid, + vcca.uuid AS &secretBackendForK8sModelRow.cloud_credential_uuid, + COUNT(DISTINCT sbr.secret_revision_uuid) AS &secretBackendForK8sModelRow.num_secrets, (vc.uuid, vc.name, vc.endpoint, @@ -402,12 +402,12 @@ FROM secret_backend_reference sbr JOIN v_cloud_credential_attributes vcca ON vm.cloud_credential_uuid = vcca.uuid WHERE b.name = '%s' GROUP BY vm.name, vcca.attribute_key`, kubernetes.BackendName) - backendStmt, err := s.Prepare(backendQuery, SecretBackendForK8sModelRow{}, cloudRow{}, cloudCredentialRow{}) + backendStmt, err := s.Prepare(backendQuery, secretBackendForK8sModelRow{}, cloudRow{}, cloudCredentialRow{}) if err != nil { return nil, errors.Trace(err) } - var sbData SecretBackendForK8sModelRows + var sbData secretBackendForK8sModelRows var cloudData cloudRows var credentialData cloudCredentialRows @@ -486,7 +486,7 @@ WHERE m.uuid = $M.uuid } var ( - rows SecretBackendRows + rows secretBackendRows modelType coremodel.ModelType currentK8sBackend *secretbackend.SecretBackend ) @@ -538,8 +538,8 @@ WHERE m.uuid = $M.uuid func (s *State) getK8sSecretBackendForModel(ctx context.Context, tx *sqlair.TX, modelUUID coremodel.UUID) (*secretbackend.SecretBackend, error) { stmt, err := s.Prepare(` SELECT - vc.uuid AS &SecretBackendForK8sModelRow.cloud_uuid, - vcca.uuid AS &SecretBackendForK8sModelRow.cloud_credential_uuid, + vc.uuid AS &secretBackendForK8sModelRow.cloud_uuid, + vcca.uuid AS &secretBackendForK8sModelRow.cloud_credential_uuid, vm.model_type AS &modelDetails.model_type, (vc.uuid, vc.name, @@ -558,12 +558,12 @@ FROM v_model vm JOIN cloud_ca_cert ccc ON vc.uuid = ccc.cloud_uuid JOIN v_cloud_credential_attributes vcca ON vm.cloud_credential_uuid = vcca.uuid WHERE vm.uuid = $modelIdentifier.uuid -GROUP BY vm.name, vcca.attribute_key`, modelIdentifier{}, modelDetails{}, SecretBackendForK8sModelRow{}, cloudRow{}, cloudCredentialRow{}) +GROUP BY vm.name, vcca.attribute_key`, modelIdentifier{}, modelDetails{}, secretBackendForK8sModelRow{}, cloudRow{}, cloudCredentialRow{}) if err != nil { return nil, errors.Trace(err) } var models []modelDetails - var sbCloudCredentialIDs SecretBackendForK8sModelRows + var sbCloudCredentialIDs secretBackendForK8sModelRows var clds cloudRows var creds cloudCredentialRows err = tx.Query(ctx, stmt, modelIdentifier{ModelID: modelUUID}).GetAll(&models, &sbCloudCredentialIDs, &clds, &creds) @@ -627,7 +627,7 @@ WHERE b.%s = $M.identifier`, columName) return nil, errors.Trace(err) } - var rows SecretBackendRows + var rows secretBackendRows err = tx.Query(ctx, stmt, sqlair.M{"identifier": v}).GetAll(&rows) if errors.Is(err, sql.ErrNoRows) || len(rows) == 0 { return nil, fmt.Errorf("%w: %q", secretbackenderrors.NotFound, v) diff --git a/domain/secretbackend/state/types.go b/domain/secretbackend/state/types.go index 209acfc796d..ec373f07ef0 100644 --- a/domain/secretbackend/state/types.go +++ b/domain/secretbackend/state/types.go @@ -11,9 +11,8 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/cloud" - corecloud "github.com/juju/juju/core/cloud" - corecredential "github.com/juju/juju/core/credential" "github.com/juju/juju/core/logger" coremodel "github.com/juju/juju/core/model" "github.com/juju/juju/core/watcher" @@ -70,13 +69,6 @@ type modelDetails struct { Type coremodel.ModelType `db:"model_type"` } -// modelCloudAndCredentialID represents the IDs of a models cloud and cloud -// credential. -type modelCloudAndCredentialID struct { - CloudID corecloud.ID `db:"cloud_uuid"` - CredentialID corecredential.ID `db:"cloud_credential_uuid"` -} - // upsertSecretBackendParams are used to upsert a secret backend. type upsertSecretBackendParams struct { ID string @@ -165,10 +157,10 @@ type SecretBackendRow struct { NumSecrets int `db:"num_secrets"` } -// SecretBackendRows represents a slice of SecretBackendRow. -type SecretBackendRows []SecretBackendRow +// secretBackendRows represents a slice of SecretBackendRow. +type secretBackendRows []SecretBackendRow -func (rows SecretBackendRows) toSecretBackends() []*secretbackend.SecretBackend { +func (rows secretBackendRows) toSecretBackends() []*secretbackend.SecretBackend { // Sort the rows by backend name to ensure that we group the config. sort.Slice(rows, func(i, j int) bool { return rows[i].Name < rows[j].Name @@ -205,8 +197,8 @@ func (rows SecretBackendRows) toSecretBackends() []*secretbackend.SecretBackend return result } -// SecretBackendForK8sModelRow represents a single joined result from secret_backend, secret_backend_reference and model tables. -type SecretBackendForK8sModelRow struct { +// secretBackendForK8sModelRow represents a single joined result from secret_backend, secret_backend_reference and model tables. +type secretBackendForK8sModelRow struct { SecretBackendRow // ModelName is the name of the model. ModelName string `db:"model_name"` @@ -217,9 +209,9 @@ type SecretBackendForK8sModelRow struct { CredentialID string `db:"cloud_credential_uuid"` } -type SecretBackendForK8sModelRows []SecretBackendForK8sModelRow +type secretBackendForK8sModelRows []secretBackendForK8sModelRow -func (rows SecretBackendForK8sModelRows) toSecretBackend(cldData cloudRows, credData cloudCredentialRows) ([]*secretbackend.SecretBackend, error) { +func (rows secretBackendForK8sModelRows) toSecretBackend(cldData cloudRows, credData cloudCredentialRows) ([]*secretbackend.SecretBackend, error) { clds := cldData.toClouds() creds := credData.toCloudCredentials() diff --git a/domain/secretbackend/state/types_test.go b/domain/secretbackend/state/types_test.go index c97e82fdce4..90bfef7eb9f 100644 --- a/domain/secretbackend/state/types_test.go +++ b/domain/secretbackend/state/types_test.go @@ -27,7 +27,7 @@ func ptr[T any](x T) *T { } func (s *typesSuite) TestToSecretBackends(c *gc.C) { - rows := SecretBackendRows{ + rows := secretBackendRows{ { ID: "uuid1", Name: "name1", From b6279b863bd92e48b2c05ca369f0267cf3486bec Mon Sep 17 00:00:00 2001 From: Kelvin Liu Date: Tue, 3 Sep 2024 17:39:42 +1000 Subject: [PATCH 7/7] build: regenerat mocks; --- .../client/secretbackends/mock_service.go | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/apiserver/facades/client/secretbackends/mock_service.go b/apiserver/facades/client/secretbackends/mock_service.go index dc91afde9c4..c5f6b4318b6 100644 --- a/apiserver/facades/client/secretbackends/mock_service.go +++ b/apiserver/facades/client/secretbackends/mock_service.go @@ -161,45 +161,6 @@ func (c *MockSecretBackendServiceDeleteSecretBackendCall) DoAndReturn(f func(con return c } -// GetSecretBackendByName mocks base method. -func (m *MockSecretBackendService) GetSecretBackendByName(arg0 context.Context, arg1 string) (*secrets.SecretBackend, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSecretBackendByName", arg0, arg1) - ret0, _ := ret[0].(*secrets.SecretBackend) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetSecretBackendByName indicates an expected call of GetSecretBackendByName. -func (mr *MockSecretBackendServiceMockRecorder) GetSecretBackendByName(arg0, arg1 any) *MockSecretBackendServiceGetSecretBackendByNameCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSecretBackendByName", reflect.TypeOf((*MockSecretBackendService)(nil).GetSecretBackendByName), arg0, arg1) - return &MockSecretBackendServiceGetSecretBackendByNameCall{Call: call} -} - -// MockSecretBackendServiceGetSecretBackendByNameCall wrap *gomock.Call -type MockSecretBackendServiceGetSecretBackendByNameCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *MockSecretBackendServiceGetSecretBackendByNameCall) Return(arg0 *secrets.SecretBackend, arg1 error) *MockSecretBackendServiceGetSecretBackendByNameCall { - c.Call = c.Call.Return(arg0, arg1) - return c -} - -// Do rewrite *gomock.Call.Do -func (c *MockSecretBackendServiceGetSecretBackendByNameCall) Do(f func(context.Context, string) (*secrets.SecretBackend, error)) *MockSecretBackendServiceGetSecretBackendByNameCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockSecretBackendServiceGetSecretBackendByNameCall) DoAndReturn(f func(context.Context, string) (*secrets.SecretBackend, error)) *MockSecretBackendServiceGetSecretBackendByNameCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - // UpdateSecretBackend mocks base method. func (m *MockSecretBackendService) UpdateSecretBackend(arg0 context.Context, arg1 service.UpdateSecretBackendParams) error { m.ctrl.T.Helper()