Skip to content

design-proposal: tenant-supplied secrets by reference through External Secrets Operator - #82

Open
Timofei Larkin (lllamnyp) wants to merge 2 commits into
mainfrom
design/user-managed-secrets
Open

Timofei Larkin (lllamnyp) wants to merge 2 commits into
mainfrom
design/user-managed-secrets

Conversation

@lllamnyp

Copy link
Copy Markdown
Member

Adds design-proposals/tenant-supplied-secrets/: how a tenant supplies a secret to a managed application without a plaintext field in the spec and without any grant on core/v1 Secrets.

Tenants get a secret store of their own; application specs reference an entry in it by name; a chart-rendered ExternalSecret materialises the value for the operator. External Secrets Operator becomes a required component. The default store is a single platform OpenBao with one OpenBao namespace per tenant, but the only coupling to any store is one per-tenant SecretStore object, overridable cluster-wide by the operator and per tenant by the tenant, so an external OpenBao, a cloud secrets manager, or the tenant's own managed OpenBao all fit behind the same seam. Tenant Kubernetes clusters consume from the same store through an externalSecrets addon, with a per-cluster auth method the platform provisions.

The store topology is the main open question: the text recommends the in-cluster instance as the installed default, an external OpenBao as the documented production posture, and per-tenant instances as a tenant-level choice. Reviewers who would set a different default should argue it there. The concrete reference shape is deliberately left to the first conversions.

Relationship to existing work

Opening non-draft for direction review. Extends the platform's required components and the Tenant and Kubernetes application schemas, so it sits behind the API-owner gate.

Before review

  • If this revises a merged design proposal: it adds a decision record under that proposal's decisions/ directory, or says below why none is needed. New proposal; no decision record needed.

DCO

  • Commits are signed off (git commit --signoff).

…l Secrets Operator

Tenants hold no verbs on core/v1 Secrets, so every tenant-supplied credential today is a plaintext field in an application spec. This proposal gives tenants a secret store, references entries from application specs by name, and materialises them for operators through External Secrets Operator, with a per-tenant SecretStore as the seam that keeps the store pluggable. It absorbs the SecretRef proposal (#37), inherits cozystack/cozystack#1942, and sits beside the user secrets API proposal (#74), which keeps platform-generated credentials.

Assisted-By: LLM
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
@coderabbitai

coderabbitai Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 12b76c38-38ba-42d7-a3a6-36ce2de53d01


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…ies and reorder the rollout

External Secrets Operator and the central OpenBao become default-installed optional packages with the cost of disabling each stated, the way database autoscaling degrades without KEDA. The rollout no longer waits on the central OpenBao: the seam takes any store, so the first consumers are validated against a managed OpenBao application and an external store first. Platform service-account credentials are named as a non-goal belonging to the user secrets API proposal, the write-only TenantSecret alternative is argued concretely, and the phase-2 withdrawal is no longer recorded as settled.

Assisted-By: LLM
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
@lllamnyp

Copy link
Copy Markdown
Member Author

Revised in response to the discussion on #74, where this proposal was read as making External Secrets Operator and a central OpenBao required components. It never should have said so, and it did. What changed:

  • Capability model instead of required components. Both are default-installed optional packages. Disabling External Secrets Operator makes a chart that receives a reference fail the render naming the capability, the way database autoscaling already fails without KEDA; an application whose only credential field is reference-only is not installable there. Disabling the default store leaves the seam waiting for a provider override. The tenant chart gates its SecretStore on the operator's API being present, so disabling the package does not fail tenant releases on an unknown kind. The expected trajectory, that conversions narrow what an installation without the capability can run, is stated as intended rather than left implied.
  • Rollout reordered so nothing waits on the central OpenBao. The seam takes any store, so the first consumers are validated against a managed OpenBao application and an external store before feat(openbao): central platform instance and per-tenant transit provisioning cozystack#4177 lands. The central instance becomes step 5, not step 2.
  • Platform service-account credentials named as a non-goal. MariaDB root, ClickHouse's backup user, OpenSearch admin and Harbor's redis password are neither tenant-supplied nor tenant-facing; separating them from tenant users belongs to design-proposal: user secrets API, credentials minted on request and shown once #74's closure of the raw-grant route.
  • The write-only TenantSecret alternative argued concretely rather than dismissed: what create, update and delete on tenantsecrets each let a tenant do in a namespace shared with operator secrets, and why fencing that in is building a secrets engine inside an aggregated API. The stopgap of granting those verbs is dropped; the interim for strict installations is design-proposal: user secrets API, credentials minted on request and shown once #74 admitting the inline form as Legacy for reference-only classes until the capability exists.
  • The no-Keycloak write path stated plainly: one ServiceAccount identity per tenant, audit names the tenant.

The phase-2 withdrawal is no longer recorded as done; the scope section says the comparison is what this review settles.

myasnikovdaniil added a commit that referenced this pull request Sep 28, 2026
…r-only storage

Rewrites both documents against the line agreed in the review thread. The
platform now generates a password on an explicit mint call, stores only the
engine's verifier where the engine accepts one, and returns the plaintext once
in the response body.

Removed: the five API kinds and the v1alpha2 move, one-time collection with its
entitlement, window and commitment protocol, the durable journal with its
transactional outbox, exporter and fail-closed threshold, the candidate state
machine with fencing and advisory locks, credential delivery with bindings, and
phase 2 for tenant-supplied secrets, which moves to #82.

Added: a verifier column per coverage row, a service-account split that takes
the platform's own credentials out of the tenant-facing Secret, one small record
per account carrying identity, issuance, initiating identity and applied state,
revocation as three distinct end states with an allowLogin reverse, and the lost
pre-publication check written down as a trade rather than argued away.

Per-engine mechanics now follow what the operators actually do. PostgreSQL moves
the password to CNPG managed.roles, because a Secret change starts no Helm
render and the init Job is a post-upgrade hook; deny-login lives in application
values because a patch on the live Cluster is overwritten and a manual NOLOGIN
is reverted. ClickHouse takes the verifier through a values fragment in
valuesFrom, because its operator watches no Secrets and patching the live CHI
loses a race that hands an account the operator's default password. MariaDB
keeps its native reference and gains the watch label, with its lock behaviour
and its missing applied state named as acceptance requirements. OpenSearch
leaves phase 1 entirely: a tenant-declared user reaches no engine today, which
is a defect to repair before the class can be converted.

Assisted-By: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Andrey Kolkov (androndo) added a commit that referenced this pull request Oct 1, 2026
…erence

Folds the review consensus into the proposal: the destination becomes a
storageRef to a typed storage object (apps.cozystack.io/Bucket or a new
backups.cozystack.io/S3Storage) rather than an inline S3 struct in the core
types, keeping core out of storage semantics and giving restore and cleanup a
live anchor. Credentials move to the community#82 model instead of a tenant
write grant on TenantSecret. Records the settled calls in Decisions —
reference-over-inline, #82 credentials, per-strategy integrity opt-in with
single-artifact checksum verification and Velero kept off tenant-writable
storage, and failure-domain separation rather than a platform durability SLA —
and narrows Open questions to storage-object mutability, the durability story
the default relies on, the per-strategy declaration mechanism, enforcement, and
off-platform privileged-apply as a separate track.

Assisted-by: LLM
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant