RFC: How should we manage database credentials for M1? #166
Replies: 5 comments 2 replies
-
Self-Managed Credentials StoryDefinitionSelf-managed credentials means the user owns the password lifecycle. DBaaS may help generate, apply, reset, or temporarily reveal a password, but DBaaS does not act as the long-term password manager. Core RulesRecommended API Shapes1. Self-Managed BYO Password — Retain SecretapiVersion: dbaas.opencloud.wso2.com/v1alpha1
kind: DBInstance
metadata:
name: orders-db
namespace: tenant-a
spec:
masterUsername: dbadmin
credentials:
managementPolicy: SelfManaged
passwordSource:
secretRef:
name: orders-db-password
key: password
consumePolicy: Retain2. Self-Managed BYO Password — Delete After Successful ApplyapiVersion: dbaas.opencloud.wso2.com/v1alpha1
kind: DBInstance
metadata:
name: orders-db
namespace: tenant-a
spec:
masterUsername: dbadmin
credentials:
managementPolicy: SelfManaged
passwordSource:
secretRef:
name: orders-db-password
key: password
consumePolicy: DeleteAfterSuccessfulApply3. Self-Managed DBaaS-Generated Initial PasswordapiVersion: dbaas.opencloud.wso2.com/v1alpha1
kind: DBInstance
metadata:
name: orders-db
namespace: tenant-a
spec:
masterUsername: dbadmin
credentials:
managementPolicy: SelfManaged
passwordSource:
generate: true
reveal:
ttlSeconds: 6004. Self-Managed Password Reset — Generated New PasswordapiVersion: dbaas.opencloud.wso2.com/v1alpha1
kind: DBPasswordReset
metadata:
name: orders-db-reset-1
namespace: tenant-a
spec:
dbInstanceRef:
name: orders-db
passwordSource:
generate: true
reveal:
ttlSeconds: 6005. Self-Managed Password Reset — BYO New PasswordapiVersion: dbaas.opencloud.wso2.com/v1alpha1
kind: DBPasswordReset
metadata:
name: orders-db-reset-2
namespace: tenant-a
spec:
dbInstanceRef:
name: orders-db
passwordSource:
secretRef:
name: new-orders-db-password
key: password
consumePolicy: RetainStatus ShapeBYO Passwordstatus:
credentials:
managementPolicy: SelfManaged
source: UserProvidedSecret
lifecycleOwner: User
secretRef:
name: orders-db-password
consumePolicy: Retain
recovery:
method: ResetOnlyGenerated Initial Passwordstatus:
credentials:
managementPolicy: SelfManaged
source: DBaaSGeneratedInitialPassword
lifecycleOwner: User
initialReveal:
secretRef:
name: orders-db-initial-credentials
expiresAt: "2026-06-17T10:30:00Z"
recovery:
method: ResetOnlyFlow 1: BYO Password — Retain SecretsequenceDiagram
actor User
participant K8s as Kubernetes API
participant Secret as User Password Secret
participant DBI as DBInstance
participant Ctrl as DBaaS Controller
participant PG as PostgreSQL
User->>K8s: Create Secret with password
K8s->>Secret: Store password Secret
User->>K8s: Create DBInstance<br/>SelfManaged + secretRef<br/>consumePolicy=Retain
K8s->>DBI: Store DBInstance
Ctrl->>K8s: Watch DBInstance
Ctrl->>K8s: Read password Secret
K8s-->>Ctrl: Return password
Ctrl->>PG: CREATE ROLE / ALTER ROLE WITH PASSWORD
PG-->>Ctrl: Success
Ctrl->>PG: Verify login with provided password
PG-->>Ctrl: Login successful
Ctrl->>K8s: Update DBInstance status
Ctrl-->>User: DB ready
Note over Secret: Secret is retained.<br/>User owns lifecycle.
Flow 2: BYO Password — Delete After Successful ApplysequenceDiagram
actor User
participant K8s as Kubernetes API
participant Secret as User Password Secret
participant DBI as DBInstance
participant Ctrl as DBaaS Controller
participant PG as PostgreSQL
User->>K8s: Create Secret with password
K8s->>Secret: Store password Secret
User->>K8s: Create DBInstance<br/>SelfManaged + secretRef<br/>consumePolicy=DeleteAfterSuccessfulApply
K8s->>DBI: Store DBInstance
Ctrl->>K8s: Watch DBInstance
Ctrl->>K8s: Read password Secret
K8s-->>Ctrl: Return password
Ctrl->>PG: CREATE ROLE / ALTER ROLE WITH PASSWORD
PG-->>Ctrl: Success
Ctrl->>PG: Verify login with provided password
PG-->>Ctrl: Login successful
Ctrl->>K8s: Delete user-provided Secret
K8s-->>Ctrl: Secret deleted
Ctrl->>K8s: Update DBInstance status<br/>secretConsumed=true
Ctrl-->>User: DB ready
Note over Secret: Secret is deleted only after<br/>successful apply and verification.
Flow 3: BYO Password — Failure CasesequenceDiagram
actor User
participant K8s as Kubernetes API
participant Secret as User Password Secret
participant DBI as DBInstance
participant Ctrl as DBaaS Controller
participant PG as PostgreSQL
User->>K8s: Create Secret with password
User->>K8s: Create DBInstance<br/>SelfManaged + secretRef
Ctrl->>K8s: Read password Secret
K8s-->>Ctrl: Return password
Ctrl->>PG: CREATE ROLE / ALTER ROLE WITH PASSWORD
PG-->>Ctrl: Error
Ctrl->>K8s: Update DBInstance status<br/>Ready=False<br/>Reason=PasswordApplyFailed
Note over Secret: Secret is not deleted,<br/>even if consumePolicy=DeleteAfterSuccessfulApply.
Ctrl-->>User: Failure visible in status/events
Flow 4: DBaaS-Generated Initial Password — Reveal WindowsequenceDiagram
actor User
participant K8s as Kubernetes API
participant DBI as DBInstance
participant Ctrl as DBaaS Controller
participant PG as PostgreSQL
participant Reveal as Temporary Reveal Secret
User->>K8s: Create DBInstance<br/>SelfManaged + generate=true
K8s->>DBI: Store DBInstance
Ctrl->>K8s: Watch DBInstance
Ctrl->>Ctrl: Generate strong random password
Ctrl->>PG: CREATE ROLE / ALTER ROLE WITH PASSWORD
PG-->>Ctrl: Success
Ctrl->>PG: Verify login with generated password
PG-->>Ctrl: Login successful
Ctrl->>K8s: Create temporary reveal Secret
K8s->>Reveal: Store temporary credentials
Ctrl->>K8s: Update DBInstance status<br/>initialReveal.secretRef + expiresAt
Ctrl-->>User: DB ready + reveal Secret reference
User->>K8s: kubectl get secret initial credentials
K8s-->>User: Return generated password
Ctrl->>K8s: Delete reveal Secret after TTL
K8s-->>Ctrl: Secret deleted
Ctrl->>K8s: Update status<br/>initialReveal.expired=true
Note over Reveal: After expiry, password cannot be retrieved.<br/>Recovery is reset-only.
Flow 5: User Misses Reveal WindowsequenceDiagram
actor User
participant K8s as Kubernetes API
participant DBI as DBInstance
participant Ctrl as DBaaS Controller
participant PG as PostgreSQL
participant Reveal as Temporary Reveal Secret
User->>K8s: Create DBInstance<br/>SelfManaged + generate=true
Ctrl->>Ctrl: Generate password
Ctrl->>PG: Configure PostgreSQL password
PG-->>Ctrl: Success
Ctrl->>K8s: Create temporary reveal Secret
Ctrl->>K8s: Update status with secretRef + expiresAt
Note over User,Reveal: User does not read Secret before TTL expires.
Ctrl->>K8s: Delete temporary reveal Secret
K8s-->>Ctrl: Secret deleted
User->>K8s: kubectl get secret orders-db-initial-credentials
K8s-->>User: NotFound
User->>K8s: Create DBPasswordReset
Note over User,Ctrl: Forgotten password recovery is reset-only.
Flow 6: Reset with DBaaS-Generated PasswordsequenceDiagram
actor User
participant K8s as Kubernetes API
participant Reset as DBPasswordReset
participant Ctrl as DBaaS Controller
participant PG as PostgreSQL
participant Reveal as Temporary Reset Secret
User->>K8s: Create DBPasswordReset<br/>generate=true
K8s->>Reset: Store reset request
Ctrl->>K8s: Watch DBPasswordReset
Ctrl->>Ctrl: Generate new password
Ctrl->>PG: ALTER ROLE master_user WITH PASSWORD
PG-->>Ctrl: Success
Ctrl->>PG: Verify login with new password
PG-->>Ctrl: Login successful
Ctrl->>K8s: Create temporary reset reveal Secret
K8s->>Reveal: Store new password temporarily
Ctrl->>K8s: Update DBPasswordReset status<br/>secretRef + expiresAt
Ctrl-->>User: Reset completed
User->>K8s: kubectl get secret reset credentials
K8s-->>User: Return new password
Ctrl->>K8s: Delete reset reveal Secret after TTL
K8s-->>Ctrl: Secret deleted
Ctrl->>K8s: Update DBPasswordReset status<br/>revealExpired=true
Flow 7: Reset with BYO New PasswordsequenceDiagram
actor User
participant K8s as Kubernetes API
participant Secret as New Password Secret
participant Reset as DBPasswordReset
participant Ctrl as DBaaS Controller
participant PG as PostgreSQL
User->>K8s: Create Secret with new password
K8s->>Secret: Store new password
User->>K8s: Create DBPasswordReset<br/>secretRef + consumePolicy=Retain
K8s->>Reset: Store reset request
Ctrl->>K8s: Watch DBPasswordReset
Ctrl->>K8s: Read new password Secret
K8s-->>Ctrl: Return new password
Ctrl->>PG: ALTER ROLE master_user WITH PASSWORD
PG-->>Ctrl: Success
Ctrl->>PG: Verify login with new password
PG-->>Ctrl: Login successful
Ctrl->>K8s: Update DBPasswordReset status<br/>Completed=True
Note over Secret: Secret is retained by default.<br/>User owns lifecycle.
Ctrl-->>User: Reset completed
Flow 8: Reset with BYO New Password — Delete After Successful ApplysequenceDiagram
actor User
participant K8s as Kubernetes API
participant Secret as New Password Secret
participant Reset as DBPasswordReset
participant Ctrl as DBaaS Controller
participant PG as PostgreSQL
User->>K8s: Create Secret with new password
User->>K8s: Create DBPasswordReset<br/>secretRef + DeleteAfterSuccessfulApply
Ctrl->>K8s: Read new password Secret
K8s-->>Ctrl: Return new password
Ctrl->>PG: ALTER ROLE master_user WITH PASSWORD
PG-->>Ctrl: Success
Ctrl->>PG: Verify login with new password
PG-->>Ctrl: Login successful
Ctrl->>K8s: Delete new password Secret
K8s-->>Ctrl: Secret deleted
Ctrl->>K8s: Update DBPasswordReset status<br/>Completed=True<br/>secretConsumed=true
Note over Secret: Deleted only after DB update<br/>and login verification succeed.
State Machine SummarystateDiagram-v2
[*] --> SelfManagedRequested
SelfManagedRequested --> BYOSecretProvided: passwordSource.secretRef
SelfManagedRequested --> GenerateInitialPassword: passwordSource.generate=true
BYOSecretProvided --> ReadUserSecret
ReadUserSecret --> ApplyPasswordToPostgres
ApplyPasswordToPostgres --> VerifyLogin
VerifyLogin --> ReadyRetainSecret: consumePolicy=Retain
VerifyLogin --> DeleteSecret: consumePolicy=DeleteAfterSuccessfulApply
DeleteSecret --> ReadySecretConsumed
GenerateInitialPassword --> ApplyGeneratedPasswordToPostgres
ApplyGeneratedPasswordToPostgres --> VerifyGeneratedLogin
VerifyGeneratedLogin --> CreateRevealSecret
CreateRevealSecret --> WaitingForRevealTTL
WaitingForRevealTTL --> DeleteRevealSecret
DeleteRevealSecret --> ReadyResetOnly
ApplyPasswordToPostgres --> Failed: apply failed
ApplyGeneratedPasswordToPostgres --> Failed: apply failed
VerifyLogin --> Failed: verification failed
VerifyGeneratedLogin --> Failed: verification failed
ReadyRetainSecret --> PasswordResetRequested
ReadySecretConsumed --> PasswordResetRequested
ReadyResetOnly --> PasswordResetRequested
PasswordResetRequested --> ResetWithBYOSecret
PasswordResetRequested --> ResetWithGeneratedPassword
ResetWithBYOSecret --> ApplyPasswordToPostgres
ResetWithGeneratedPassword --> ApplyGeneratedPasswordToPostgres
Failed --> SelfManagedRequested: retry after fix
kubectl UXRetrieve Generated Initial Passwordkubectl get secret orders-db-initial-credentials \
-n tenant-a \
-o jsonpath='{.data.password}' | base64 -dRetrieve Generated Reset Passwordkubectl get secret orders-db-reset-1-credentials \
-n tenant-a \
-o jsonpath='{.data.password}' | base64 -dCheck Credential Statuskubectl get dbinstance orders-db -n tenant-a -o yamlkubectl get dbpasswordreset orders-db-reset-1 -n tenant-a -o yamlExtension Point: Key-Vault Managed CredentialsThis API can be extended to support key-vault managed credential lifecycles. Example future shape: apiVersion: dbaas.opencloud.wso2.com/v1alpha1
kind: DBInstance
metadata:
name: orders-db
namespace: tenant-a
spec:
masterUsername: dbadmin
credentials:
managementPolicy: Managed
externalSecretStoreRef:
name: tenant-a-secret-store
rotation:
enabled: true
interval: 30dIn managed mode: This keeps the API extensible: |
Beta Was this translation helpful? Give feedback.
-
|
Your proposed solution for M1 is great. Please find below the input regarding your questions. Should M1 support only consumePolicy: Retain, or also DeleteAfterSuccessfulApply? Should reset be a dedicated DBPasswordReset CRD or an operation on DBInstance? What should the default reveal TTL be? Should the reveal Secret include only username/password, or also endpoint, port, database name, and CA certificate? Should TLS material follow the same lifecycle as the password, or be split into separate Secrets from M1? Should generated-password reveal be exposed through status, events, or both? |
Beta Was this translation helpful? Give feedback.
-
|
One related point we should make explicit is the lifecycle for DBaaS internal credentials. The tenant master password lifecycle is self-managed in M1, but the database also has DBaaS-owned operational credentials, such as:
These should not be part of the tenant password lifecycle. Suggested model:
This keeps three lifecycles separate:
For storage, I’m leaning toward a controller-private Secret, named deterministically by DBInstance namespace/name/UID, for example:
with keys like:
This avoids exposing operational DBaaS credentials through tenant-facing Secrets while still giving the controller stable material for reconcile/retry flows. |
Beta Was this translation helpful? Give feedback.
-
DBaaS M1 Secret Inventory SummaryWe can Summarize the DB Credential secrets , TLS secrets and Operational secrets as follows. The goal is to make the ownership and lifecycle boundaries easy to see for the proposed design for M1:
Secret Classesflowchart TB
DBI[DBInstance]
subgraph TenantNS[Tenant namespace]
BYO[User BYO password Secret<br/>create or reset, user-owned]
InitialReveal[Temporary initial password reveal Secret<br/>generated create only]
ResetReveal[Temporary reset password reveal Secret<br/>generated reset only]
Connect[Connection Secret<br/>pg-<db>-connect]
CloudInit[Cloud-init bootstrap Secret<br/>ephemeral]
end
subgraph ControllerNS[Controller namespace, e.g. dbaas-system]
Internal[Private internal credentials Secret<br/>dbi-<hash>-internal]
TLS[Private TLS Secret<br/>dbi-<hash>-tls]
end
DBI --> BYO
DBI --> InitialReveal
DBI --> ResetReveal
DBI --> Connect
DBI --> CloudInit
DBI --> Internal
DBI --> TLS
BYO -.tenant owns.-> User[(Tenant/User)]
InitialReveal -.tenant reads during TTL.-> User
ResetReveal -.tenant reads during TTL.-> User
Connect -.tenant/app reads.-> App[(Application)]
CloudInit -.VM consumes during bootstrap.-> VM[(PostgreSQL VM)]
Internal -.controller only.-> Ctrl[(DBaaS Controller)]
TLS -.controller only.-> Ctrl
Inventory
Lifecycle Boundariesflowchart LR
subgraph PasswordLifecycle[Tenant master password lifecycle]
BYOCreate[BYO create password Secret]
GenCreate[Generated create password]
InitialReveal2[Temporary initial reveal Secret]
Reset[DBPasswordReset]
BYOReset[BYO reset password Secret]
GenReset[Generated reset password]
ResetReveal2[Temporary reset reveal Secret]
GenCreate --> InitialReveal2
Reset --> BYOReset
Reset --> GenReset
GenReset --> ResetReveal2
end
subgraph ConnectionLifecycle[Connection metadata lifecycle]
Connect2[Connection Secret]
Status[DBInstance status endpoint]
end
subgraph InternalLifecycle[DBaaS internal credential lifecycle]
Internal2[Private internal credentials Secret]
end
subgraph TLSLifecycle[TLS lifecycle]
TLS2[Private TLS Secret]
CA[Public ca.crt]
end
BYOCreate -.password source.-> DB[(PostgreSQL)]
BYOReset -.reset password source.-> DB
InitialReveal2 -.initial password delivery.-> User[(Tenant/User)]
ResetReveal2 -.reset password delivery.-> User
Connect2 -.non-password connection details.-> App[(Application)]
Status -.endpoint visibility.-> User
Internal2 -.repl/exporter users.-> DB
TLS2 -.server identity.-> DB
CA -.trust bundle.-> Connect2
Key rule: Configuration Matrix
Reset paths are separate from this create-time matrix: Proposed Secret ShapesBYO Create Password SecretCreated and owned by the user when provisioning with a user-provided master password. apiVersion: v1
kind: Secret
metadata:
name: orders-db-password
namespace: tenant-a
type: Opaque
data:
password: <base64>Connection SecretCreated for every DBInstance. apiVersion: v1
kind: Secret
metadata:
name: pg-orders-db-connect
namespace: tenant-a
type: Opaque
data:
host: <base64>
port: <base64>
dbname: <base64>
jdbcUrl: <base64>
sslmode: <base64>
ca.crt: <base64> # only when TLS is enabledTemporary Reveal SecretCreated only for generated-password provisioning. apiVersion: v1
kind: Secret
metadata:
name: pg-orders-db-initial-credentials
namespace: tenant-a
type: Opaque
data:
username: <base64>
password: <base64>Temporary Generated Reset Reveal SecretCreated only when a reset asks DBaaS to generate the new password. apiVersion: v1
kind: Secret
metadata:
name: pg-orders-db-reset-credentials
namespace: tenant-a
type: Opaque
data:
username: <base64>
password: <base64>BYO Reset Password SecretCreated and owned by the user when reset uses a user-provided password. apiVersion: v1
kind: Secret
metadata:
name: orders-db-reset-password
namespace: tenant-a
type: Opaque
data:
password: <base64>Private Internal Credentials SecretStored in the controller namespace. apiVersion: v1
kind: Secret
metadata:
name: dbi-7f3a91c2-internal
namespace: dbaas-system
type: Opaque
data:
repl_password: <base64>
exporter_password: <base64>Private TLS SecretStored in the controller namespace when TLS is enabled. apiVersion: v1
kind: Secret
metadata:
name: dbi-7f3a91c2-tls
namespace: dbaas-system
type: kubernetes.io/tls
data:
ca.crt: <base64>
ca.key: <base64>
tls.crt: <base64>
tls.key: <base64>Cleanup Summaryflowchart TB
Delete[DBInstance deletion]
Delete --> Finalizer[DBaaS finalizer]
Finalizer --> DelConnect[Delete connection Secret]
Finalizer --> DelInitialReveal[Delete initial reveal Secret if present]
Finalizer --> DelResetReveal[Delete generated reset reveal Secret if present]
Finalizer --> DelCloudInit[Delete cloud-init Secret if present]
Finalizer --> DelInternal[Delete private internal credentials Secret]
Finalizer --> DelTLS{TLS enabled?}
DelTLS -->|yes| DelTLSSecret[Delete private TLS Secret]
DelTLS -->|no| NoTLS[No private TLS Secret expected]
DelConnect --> Done[Remove finalizer]
DelInitialReveal --> Done
DelResetReveal --> Done
DelCloudInit --> Done
DelInternal --> Done
DelTLSSecret --> Done
NoTLS --> Done
Do not rely on cross-namespace owner references for controller-private Secrets. Use finalizer cleanup by status reference, deterministic name, or DBInstance UID labels. |
Beta Was this translation helpful? Give feedback.
-
|
@Yohansenanayake Before starting implementing, there are few edge cases to clarify on how your credential design meets the other two workstreams — backup and os-baked images. 1. Passwords after restore
2. BYO password delete + restore 3. Who deletes the internal secret? 4. TLS after a repave 5. DBPasswordReset cleanup 6. Reveal expiry notification |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
RFC: How should we manage database credential lifecycles for M1?
What we're asking
For each managed database, we need a clear lifecycle for the master password:
The main M1 question:
Credential lifecycle models
We should separate credential lifecycle by who owns the credential lifecycle, not only by where the password is stored.
Self-managed credentials
In the self-managed model, the user owns the password lifecycle.
DBaaS may help create or apply the password, but DBaaS does not become the long-term password manager.
This means:
Managed credentials
In the managed model, DBaaS and/or an external key vault owns the credential lifecycle.
This is a stronger long-term model for centralized secret management, auditability, and managed rotation, but it introduces new infrastructure and operational complexity. Unless M1 explicitly requires key-vault integration, this seems better suited for a later milestone.
Proposed M1 direction
For M1, implement self-managed credentials.
M1 should support:
Why self-managed credentials fit M1
1. Clear ownership boundary
This avoids making DBaaS act like a full secret manager in M1.
2. Supports both BYO and generated passwords
3. Clean reset story
If the password is forgotten, the recovery path is reset.
This avoids relying on recovering an old password from PostgreSQL or from a SCRAM verifier.
4. Avoids key-vault dependency in M1
Key vault integration is valuable, but it raises additional design questions:
These should not block a useful M1 credential lifecycle.
5. Keeps the API extensible
The same API can later grow into managed credentials:
What M1 intentionally does not include
These belong to the managed credential lifecycle.
Future direction: key-vault-managed credentials
A future milestone can add managed credentials using an external key vault and ESO:
This is different from self-managed generated credentials, where the password is revealed only temporarily and then recovery is reset-only.
Open questions for M1
consumePolicy: Retain, or alsoDeleteAfterSuccessfulApply?DBPasswordResetCRD or an operation onDBInstance?status, events, or both?Proposed conclusion
For M1, adopt the self-managed credential lifecycle:
This gives M1 a clear, shippable credential story while keeping the API open for key-vault-managed credentials later.
Beta Was this translation helpful? Give feedback.
All reactions