diff --git a/operator/apis/platform/v1alpha1/central_types.go b/operator/apis/platform/v1alpha1/central_types.go index 357de14d97d8b..92ccd66b6bce0 100644 --- a/operator/apis/platform/v1alpha1/central_types.go +++ b/operator/apis/platform/v1alpha1/central_types.go @@ -182,6 +182,21 @@ type CentralDBSpec struct { DeploymentSpec `json:",inline"` } +// GetPasswordSecret provides a way to retrieve the admin password that is safe to use on a nil receiver object. +func (c *CentralDBSpec) GetPasswordSecret() *LocalSecretReference { + if c == nil { + return nil + } + return c.PasswordSecret +} + +func (c *CentralDBSpec) GetPasswordGenerationDisabled() bool { + if c == nil { + return false + } + return pointer.BoolPtrDerefOr(c.PasswordGenerationDisabled, false) +} + // IsExternal specifies that the database should not be managed by the Operator func (c *CentralDBSpec) IsExternal() bool { if c == nil { diff --git a/operator/pkg/central/extensions/reconcile_central_db_password.go b/operator/pkg/central/extensions/reconcile_central_db_password.go index 596d7de2eefe7..f7a38ae21c07b 100644 --- a/operator/pkg/central/extensions/reconcile_central_db_password.go +++ b/operator/pkg/central/extensions/reconcile_central_db_password.go @@ -2,6 +2,7 @@ package extensions import ( "context" + "strings" "github.com/go-logr/logr" "github.com/operator-framework/helm-operator-plugins/pkg/extensions" @@ -10,57 +11,93 @@ import ( commonExtensions "github.com/stackrox/rox/operator/pkg/common/extensions" "github.com/stackrox/rox/operator/pkg/types" "github.com/stackrox/rox/pkg/renderer" + coreV1 "k8s.io/api/core/v1" ctrlClient "sigs.k8s.io/controller-runtime/pkg/client" ) const ( - centralDBPasswordKey = `password` - centralDBPasswordResourceName = "central-db-password" + centralDBPasswordKey = `password` + + defaultCentralDBPasswordSecretName = `central-db-password` ) -// ReconcileCentralDBPasswordExtension returns an extension that takes care of creating the central-db-password -// secret ahead of time. +// ReconcileCentralDBPasswordExtension returns an extension that takes care of reconciling the central-db-password secret. func ReconcileCentralDBPasswordExtension(client ctrlClient.Client) extensions.ReconcileExtension { return wrapExtension(reconcileCentralDBPassword, client) } -func reconcileCentralDBPassword(ctx context.Context, c *platform.Central, client ctrlClient.Client, _ func(updateStatusFunc), _ logr.Logger) error { - if !c.Spec.Central.CentralDBEnabled() || c.Spec.Central.DB.IsExternal() { - return nil - } +func reconcileCentralDBPassword(ctx context.Context, c *platform.Central, client ctrlClient.Client, statusUpdater func(updateStatusFunc), log logr.Logger) error { run := &reconcileCentralDBPasswordExtensionRun{ SecretReconciliator: commonExtensions.NewSecretReconciliator(client, c), - obj: c, + centralObj: c, } return run.Execute(ctx) } type reconcileCentralDBPasswordExtensionRun struct { *commonExtensions.SecretReconciliator - obj *platform.Central + centralObj *platform.Central + password string } -func (r *reconcileCentralDBPasswordExtensionRun) Execute(ctx context.Context) error { - // Delete any central-db password only if the CR is being deleted - shouldExist := r.obj.GetDeletionTimestamp() == nil +func (r *reconcileCentralDBPasswordExtensionRun) readPasswordFromReferencedSecret(ctx context.Context) error { + if r.centralObj.Spec.Central.DB.GetPasswordSecret() == nil { + return nil + } - if err := r.ReconcileSecret(ctx, centralDBPasswordResourceName, shouldExist, r.validateCentralDBPasswordData, r.generateCentralDBPasswordData, true); err != nil { - return errors.Wrapf(err, "reconciling %q secret", centralDBPasswordResourceName) + passwordSecretName := r.centralObj.Spec.Central.DB.PasswordSecret.Name + + passwordSecret := &coreV1.Secret{} + key := ctrlClient.ObjectKey{Namespace: r.centralObj.GetNamespace(), Name: passwordSecretName} + if err := r.Client().Get(ctx, key, passwordSecret); err != nil { + return errors.Wrapf(err, "failed to retrieve central db password secret %q", passwordSecretName) + } + + password := strings.TrimSpace(string(passwordSecret.Data[centralDBPasswordKey])) + if password == "" || strings.ContainsAny(password, "\r\n") { + return errors.Errorf("central db password secret %s must contain a non-empty, single-line %q entry", passwordSecretName, centralDBPasswordKey) } + r.password = password return nil } -func (r *reconcileCentralDBPasswordExtensionRun) validateCentralDBPasswordData(data types.SecretDataMap, _ bool) error { - if len(data[centralDBPasswordKey]) == 0 { - return errors.Errorf("%s secret must contain a non-empty %q entry", centralDBPasswordResourceName, centralDBPasswordKey) +func (r *reconcileCentralDBPasswordExtensionRun) Execute(ctx context.Context) error { + if r.centralObj.DeletionTimestamp != nil { + return r.ReconcileSecret(ctx, defaultCentralDBPasswordSecretName, false, nil, nil, false) + } + + if r.centralObj.Spec.Central.DB.GetPasswordGenerationDisabled() && r.centralObj.Spec.Central.DB.GetPasswordSecret() == nil { + err := r.ReconcileSecret(ctx, defaultCentralDBPasswordSecretName, false, nil, nil, false) + if err != nil { + return err + } + return nil + } + + if err := r.readPasswordFromReferencedSecret(ctx); err != nil { + return err + } + // If the user puts the password into central-db-password secret then don't reconcile + if secret := r.centralObj.Spec.Central.DB.GetPasswordSecret(); secret != nil && secret.Name == defaultCentralDBPasswordSecretName { + return nil + } + if err := r.ReconcileSecret(ctx, defaultCentralDBPasswordSecretName, true, r.validateSecretData, r.generateDBPassword, true); err != nil { + return errors.Wrap(err, "reconciling central-db-password secret") } return nil } -func (r *reconcileCentralDBPasswordExtensionRun) generateCentralDBPasswordData() (types.SecretDataMap, error) { - data := types.SecretDataMap{ - centralDBPasswordKey: []byte(renderer.CreatePassword()), +func (r *reconcileCentralDBPasswordExtensionRun) validateSecretData(data types.SecretDataMap, controllerOwned bool) error { + return nil +} + +func (r *reconcileCentralDBPasswordExtensionRun) generateDBPassword() (types.SecretDataMap, error) { + if r.password == "" { + r.password = renderer.CreatePassword() } - return data, nil + + return types.SecretDataMap{ + centralDBPasswordKey: []byte(r.password), + }, nil } diff --git a/operator/pkg/central/extensions/reconcile_pvc.go b/operator/pkg/central/extensions/reconcile_pvc.go index f154c53018cf5..f4fc44dfc0abd 100644 --- a/operator/pkg/central/extensions/reconcile_pvc.go +++ b/operator/pkg/central/extensions/reconcile_pvc.go @@ -72,6 +72,9 @@ func getPersistenceByClaimName(central *platform.Central, claim string) *platfor case DefaultCentralPVCName: return central.Spec.Central.GetPersistence() case DefaultCentralDBPVCName: + if central.Spec.Central.DB.IsExternal() { + return nil + } return convertDBPersistenceToPersistence(central.Spec.Central.DB.GetPersistence()) default: panic("unknown default claim name") @@ -82,6 +85,9 @@ func getPersistenceByClaimName(central *platform.Central, claim string) *platfor func ReconcilePVCExtension(client ctrlClient.Client, target PVCTarget, defaultClaimName string) extensions.ReconcileExtension { fn := func(ctx context.Context, central *platform.Central, client ctrlClient.Client, _ func(statusFunc updateStatusFunc), log logr.Logger) error { persistence := getPersistenceByClaimName(central, defaultClaimName) + if persistence == nil { + return nil + } return reconcilePVC(ctx, central, persistence, target, defaultClaimName, client, log) } return wrapExtension(fn, client) diff --git a/operator/pkg/central/values/translation/translation.go b/operator/pkg/central/values/translation/translation.go index e65041cf21909..12419933c187a 100644 --- a/operator/pkg/central/values/translation/translation.go +++ b/operator/pkg/central/values/translation/translation.go @@ -209,6 +209,8 @@ func getCentralDBComponentValues(c *platform.CentralDBSpec) *translation.ValuesB } if c.ConnectionStringOverride != nil { + cv.SetBoolValue("external", true) + source := translation.NewValuesBuilder() source.SetString("connectionString", c.ConnectionStringOverride) cv.AddChild("source", &source)