-
v0.12.9 Stable
released this
2026-07-01 10:36:06 +00:00 | 94 commits to main since this releaseAdded
internal/secretsync/openbao: unit test forJoinPath(previously untested).
Changed
- CI: route Go module fetches through the in-cluster Athens proxy (
GOPROXY
build-arg,directfallback) in both the ci-test validate and the multi-arch
image build — cold builds no longer re-download the module graph from
proxy.golang.org.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v0.12.8 Stable
released this
2026-06-28 16:32:35 +00:00 | 97 commits to main since this releaseChanged
- First release shipped through the consolidated, ArgoCD-delivered pipeline — the
go-operator-image taxonomy now rides the shared templates (pipeline-build-buildkit
validate + multi-arch OCI,pipeline-promote-imageskopeo + cosign-EC by digest)
via the registry ApplicationSet, no per-projectscripts/set.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- First release shipped through the consolidated, ArgoCD-delivered pipeline — the
-
v0.12.7 Stable
released this
2026-06-28 13:41:16 +00:00 | 98 commits to main since this releaseAdded
- In-cluster CI/CD pipeline (the seanfarm release path), completing the
previously build-only pipeline: amake test(envtest) gate + the image-promote
— skopeo-push the multi-arch image toregistry.sean.farm/seanfarm/openbao-operator,
cosign-EC sign it by digest,make build-installer, and an in-cluster release
(ff main first, lightweight tag, Forgejo Release with these notes + the
install.yamlasset). Addsci-test.Dockerfile; curated notes switch to the
Keep-a-Changelog## [x.y.z]format (the## vX.Y.Zentries below predate it).
Unreleased
No unreleased changes.
v0.7.9
OpenBaoIntermediateCAnow imports the full chain into the intermediate
backend. v0.7.x stopped at uploading the leaf intermediate cert via
set-signed, sopki_internalonly knew about itself — its
/<backend>/ca_chainlookups returned[intermediate], and every leaf
issued frompki_internal(cert-manager Certificates,OpenBaoPKICertificate)
carried only the intermediate inca.crt(typically[intermediate, intermediate]
because OpenBao duplicates the issuing CA intoca_chain).Downstream consumers that walk the cert chain to a self-signed anchor —
postgresssl_ca_filein particular — then reject every client cert
at depth 1 withunable to get issuer certificate. The failure mode
surfaced on the first seanfarmmake rebuildafter the internal-ca
wiring landed: postgres-2-join's pg_basebackup looped on
tls: unknown certificate authorityand replication never started.The fix concatenates
signedCert.Certificatewith every entry in
signedCert.CAChain(the parent chain returned bysign-intermediate)
into a single PEM bundle and passes that toSetSignedIntermediate. OpenBao
imports each cert as a separate issuer entry on the backend, so subsequent
/<backend>/issueand/<backend>/signresponses come back with the full
chain.Existing intermediates imported by pre-v0.7.9 operators do NOT self-heal
because the reconciler skipsset-signedon theexists==truebranch.
For those backends either re-bootstrap (delete theOpenBaoIntermediateCA
withdeletionPolicy: Deleteand let the operator regenerate) or invoke
openbao write <backend>/intermediate/set-signed certificate=@bundle.pem
manually with the concatenated chain.v0.7.8
OpenBaoPKIIssuernow gatesReady=Trueon the backend having a usable
default issuer. v0.7.5 added the auto-promote-after-set-signed fix but
theOpenBaoPKIIssuerreconciler still flipped Ready as soon as the named
PKI role existed in OpenBao. On a cold cluster boot,OpenBaoPKIRole
reconciles can land before the parallelOpenBaoIntermediateCAreconcile
finishesset-signed+ensureDefaultIssuer, so cert-manager sees a
Ready issuer, dispatches aCertificateRequest,SignCSRhitscould not fetch the CA certificate (was one set?): no default issuer currently configuredand cert-manager marks the
CertificateRequestas terminally Failed.
The only recovery in pre-v0.7.8 deploys waskubectl delete certificaterequest.The reconciler now calls
vc.GetIntermediateCA(backend)(which reads
<backend>/cert/ca— exactly the endpoint OpenBao rejects when there is no
default issuer) and refuses to flip Ready until that probe succeeds. New
behaviour:- Role exists + default issuer set →
Ready=True,Reason=Verified
(unchanged). - Role exists + no default issuer →
Ready=False,
Reason=DependencyNotReady, requeue afterTransientErrorRetryInterval. - Role missing → unchanged (
Ready=False,Reason=ConfigError).
cert-manager backoffs through the requeue window and re-issues a fresh
CertificateRequestonce the issuer flips Ready, so the cold-boot path
self-converges with no manual intervention. Surfaced during the first
seanfarmmake rebuildafter the internal-ca wiring landed — the dev
cluster neededkubectl delete certificaterequest postgres-{server,replication}-tls-1
once before CNPG started.New regression test
TestOpenBaoPKIIssuerReconcile_NoDefaultIssuerreproduces the cold-boot
race against the fake OpenBao client and asserts the issuer requeues with
DependencyNotReadyinstead of flipping Ready.v0.7.7
OpenBaoPKICertificateno longer pre-creates an emptykubernetes.io/tls
Secret. The previous flowGet → Create empty → issue → Updatelost
every fresh reconcile to the apiserver's Secret-type validator:Secret "<name>" is invalid: [data[tls.crt]: Required value, data[tls.key]: Required value]kubernetes.io/tlsSecrets must carry valid PEM cert + key bytes at
Create time; the controller never reached the issuance step, so the
Secret was never produced. Surfaced via the bundle-source
OpenBaoPKICertificateincert-managerfor seanfarm'sinternal-ca
distribution — every reconcile failed with the validation error and the
trust-manager Bundle had no source.- Reordered the flow to
Get → issue → Create-with-data(orUpdateif
the Secret already existed and is owned by this CR). The in-memory
secretobject still carries the OwnerReference +SecretTypeTLSfrom
the moment we know the Secret needs to exist, but the apiserver only
sees the populated object. needsIssuancenow also triggers when the Secret does not yet exist,
even ifStatus.SerialNumbersomehow has a value — the on-disk Secret
is what consumers actually read, so its absence forces a fresh leaf.- No interface or CRD changes; pure controller-flow refactor.
v0.7.6
OpenBaoPKIIssuer(cert-manager external issuer) now signs the submitted
CSR instead of asking OpenBao to mint a fresh key pair. v0.7.5 still routed
CertificateRequests tovc.IssueCertificate, which posts to
/<backend>/issue/<role>and has OpenBao generate the private key. The cert's
public key then mismatched cert-manager's localtls.key(cert-manager
keeps its own key client-side and only stitches the signed certificate
into the Secret), so cert-manager triggered an immediate re-issue with
reasonInvalidKeyPair— a tight loop that minted dozens of
CertificateRequests per second per Certificate and filled OpenBao storage
with unused leaves.- New
openbaoclient.ClientmethodSignCSR(ctx, backend, role, csrPEM, request) (CertificateInfo, error)
posts to/<backend>/sign/<role>with the CSR body verbatim plus the
same SAN/TTL/format parameters asIssueCertificate. The returned
CertificateInfohasPrivateKeyempty (the caller already has it). openbaopkiissuer_controller.reconcileCertificateRequestnow calls
SignCSRwithcertReq.Spec.Request(the raw PEM CSR cert-manager
put on the wire).IssueCertificateis reserved forOpenBaoPKICertificate,
where there is no caller-side key.fakeOpenBao.SignCSRrecords the call count + the last CSR PEM body; the
PKI issuer test asserts the issuer hit/sign/<role>(not/issue/<role>)
and that the CSR landed in OpenBao.
v0.7.5
OpenBaoIntermediateCAreconciler now promotes the imported issuer to be
the backend's default. Without this, the first/<backend>/issue/<role>
call afterset-signedfails withno default issuer currently configured
because OpenBao PKI v2 does not auto-promote unless
default_follows_latest_issuer: truewas written to
/<backend>/config/issuersbeforehand.- The fix runs on two paths: right after
SetSignedIntermediatesucceeds on
the generate path, and on every reconcile that takes the
intermediate CA existsbranch — so legacy backends imported by older
operator builds self-heal on the next sync (≤DefaultResyncInterval)
without a manualopenbao write .../config/issuersintervention. - New helper
ensureDefaultIssuerin
internal/controller/openbaointermediateca_controller.gocalls two new
openbaoclient.Clientmethods:ListPKIIssuers(ctx, backend) ([]string, error)—LIST /<backend>/issuers.SetDefaultPKIIssuer(ctx, backend, issuerRef string) error— writes
/<backend>/config/issuerswithdefault=<ref>. Idempotent.
- Both methods are wired through
Real,deferred, and the controller test
fakeOpenBao.SetSignedIntermediatein the fake now appends to
pkiIssuers[backend]so the existing
TestOpenBaoIntermediateCAReconcile_GeneratesAndSignsIntermediateCAtest
can assert the default was set. - Surfaced while bootstrapping the per-stack
internal-cafor seanfarm CNPG;
everyCertificateRequestagainst aOpenBaoPKIIssuerwas returning
IssuanceError: no default issuer currently configuredeven though the
intermediate hadReady=True.
v0.7.2
Security hardening. Two findings from a directed audit of the operator's
OpenBao-client and CRD surface, plus a small classifier addition that callers
can use to drive smarter retries.Cross-namespace
BackendRefrejected (confused-deputy)OpenBaoGCPRoleset.spec.backendRefaccepted an arbitrary.namespaceand the
reconciler honored it verbatim when probingOpenBaoGCPSecretsBackend.Ready.
A CR in namespace A could observe Ready transitions of a backend in
namespace B, bypassing the cross-namespace RBAC boundary as a timing
side-channel and confusing the operator into completing a roleset write
whose readiness gate had been validated against an out-of-namespace object.- The
Namespacefield onNamespacedNameis now deprecated. CEL admission
rejects any non-empty value
(!has(self.namespace) || size(self.namespace) == 0); the controller's
in-process check is kept as belt-and-suspenders so the failure mode is
surfaced asReason=InvalidSpecrather than silently widening trust scope. - The field is retained on the API for compatibility with stored CRs that may
carry it; users must clear it on next edit.
OpenBao-client cache eviction on auth failure
Factory.GetClientcached an authenticated*Realfor up to 12 h. If the
OpenBao-side token was revoked (operator-role rotation, manual revoke, policy
change), every reconciler kept reusing the dead-token client until the TTL
elapsed, surfacing as a cascade ofConfigurationErrorinstead of a
self-healing re-auth.- A new
OnErrorhook onRealis invoked from each metric-instrumented
method's error path. The Factory wires the hook to callFactory.Evictif
the error classifies as 401/403, so the nextGetClientre-authenticates. - New public helpers:
openbaoclient.IsOpenBaoAuthError(err)(HTTP 401/403
classifier) andFactory.Evict(address)(manual cache drop).
Includes the v0.7.1 events RBAC fix
The previously un-released
b864c32 fix(rbac): grant events:create;patch
ships in this release.v0.7.1
Bug fix. v0.7.0 emitted Kubernetes Events but the operator SA lacked
events: create;patchRBAC, so every emit was rejected by the API
server (events is forbidden). v0.7.1 adds the kubebuilder rbac marker
on the EmitNormal/EmitWarning helpers ininternal/controller/common.go
so the manager-role grants the verb. No code-path or image change.v0.7.0
Operational hardening — events, RBAC split, image signing, lint zero.
Four workstreams; backwards-compatible upgrade.Kubernetes events on every reconciler
record.EventRecorderis now threaded through all 21 reconcilers via a
newRecorderfield on each Reconciler struct.mgr.GetEventRecorderFor
is wired inSetupWithManagerso events flow without manager-config
changes.EmitNormal/EmitWarninghelpers ininternal/controller/common.go
pair with everyMarkDegradedsite (Warning) and every primary write
path (Normal). Drift-skip / idle-reconcile paths intentionally skip
Normal emission to avoid event chatter.kubectl describeon any openbao.sean.farm CR now surfaces the same
reason and message as the status condition, so operators don't have to
pivot between condition history and event log.
RBAC: split secrets into reader + writer
manager-role(auto-generated) keeps onlyget;list;watchon
secrets. The two controllers that mutate Secrets
(openbaoapprolesecretid,openbaopkicertificate) document the split in
their kubebuilder rbac markers.- New hand-maintained
manager-secrets-writerClusterRole +
ClusterRoleBinding (config/rbac/secrets_writer_role*.yaml) carries
thecreate;delete;patch;updateverbs and binds to the same SA. The
auto-generatedrole.yamlis now the canonical read-only audit
surface; secret-write capability is reviewed at one explicit place
and a future per-controller-SA refactor only has to rebind.
Cosign image signing
make sign-image IMG=...runs cosign keyless OIDC signing; refuses
:latesttags as a guard.make verify-image IMG=... CERT_IDENTITY=... CERT_OIDC_ISSUER=...
verifies provenance for downstream consumers (the seanfarm cluster
pull pipeline can pin a verified identity before rolling out).
Lint debt to zero
- Auto-fixes from
make lint-fix(modernize, QF1008/QF1003/S1009). - Manual fixes:
TuneMountAllowNilWithContextmigration in
internal/openbaoclient/real.go;cmmeta.ObjectReference -> IssuerReference
rename in pki issuer tests; lower-case error strings; line-wrap of
long flag-help strings incmd/main.go; rename of a shadowed
reconcileimport in metrics middleware; SA9003 empty branches. - New
defaultAppRoleMountanddefaultKubernetesAuthMountconstants
replace string literals. - Lint config raises
gocycloto 45 (reconcile loops are inherently
branchy), restrictsgoconstto 5+ chars and 4+ occurrences, and
excludes test files fromgoconst/unparam. Result:make lint
reports 0 issues, down from 47 at the start of the v0.7.0 cycle.
Migration
- No CR or flag changes. Existing manifests apply unchanged.
- RBAC: re-applying
dist/install.yamladds the new
manager-secrets-writerClusterRole and ClusterRoleBinding alongside
manager-role. The old role'ssecretsrule narrows toget;list;watch;
the SA retains writes via the new role. - Events: no opt-in; events emit immediately on first reconcile.
v0.6.0
Security hardening — tenant isolation + outbound OpenBao. Driven by the
v0.5.x security audit. Seven workstreams; in-place upgrade with permissive
defaults so seanfarm doesn't need to flip every flag at once.New flags
--openbao-address-allowlist— comma-separated regexes; a CR's resolved
OpenBao address must match at least one. Empty default keeps v0.5
permissive behavior; in production, set to e.g.
^https?://openbao\.(openbao-[a-z0-9-]+\.)?svc\.cluster\.local:8200$
so a malicious CR cannot redirect operator writes to an external OpenBao.--openbao-allow-plaintext—never|cluster-local|always.
Defaultcluster-localaccepts plaintext only for*.svc.cluster.local,
localhost,127.0.0.1.neverenforces HTTPS everywhere.--openbao-ca-cert— path to PEM bundle that pins the OpenBao server's
TLS issuer; system roots ignored when set.
Manifest changes
config/manager/manager.yamlmounts an audience-bound projected
ServiceAccount token at/var/run/secrets/openbao.sean.farm/token
(audience: openbao,expirationSeconds: 3600). The
--openbao-sa-token-pathflag's default flips to the new mount; legacy
unbounded-token deployments must override.config/rbac/leader_election_role.yamlpinsget/update/patch/delete
onconfigmapsandleasestoresourceNames: [343a0da7.sean.farm].
Hardening
- OpenBaoRef CEL admission rule restored. Submitting a CR with both
openbaoRef.nameandopenbaoRef.connectionset is now rejected at
admission. The original rule (commit5c61915) was removed because the
marker comment had Unicode curly double-quotes (U+201D) instead of
ASCII"; reinstated with plain quotes. - TLS posture unified across bootstrap and per-OpenBaoRef paths. A new
openbaoclient.ConfigureClienthelper applies CA pin and plaintext gate
to bothcmd/bootstrap.buildOpenBaoClientOnceand
factory.createAuthenticatedClient. - Field index on OpenBaoGCPRoleset.backendRef. Replaces the unbounded
cluster-wide List in openbaogcpsecretsbackend's deletion path with an
indexed lookup so spam-creation of unrelated rolesets cannot slow
backend deletion. - Production-default zap logger.
cmd/main.goflips
Development: true → false. Verbose console output and stack traces
at WarnLevel return only when--zap-develis passed explicitly.
Tests
TestConfigureClient_PlaintextGate(16 subtests) covers all three
modes against representative addresses.TestFactory_GetClient_Allowlist{Rejects,Accepts}covers the new
address allowlist gate.- 4 new envtest specs in
marker_validation_test.gopin the OpenBaoRef
mutual-exclusion CEL rule (both / name-only / conn-only / neither). TestBuildOpenBaoClientOnce_ValidationErrorsextended with a
plaintext-rejection case.
v0.5.1
Hot-fix on the v0.5.0 deploy. Caught immediately on cluster rollout.
Reliability fix
-
IsOpenBaoServerNotReadyno longer misclassifies a OpenBao 4xx whose body
echoes a network keyword. When OpenBao performs an inline downstream
check (e.g.verify_connection=trueon
database/config/<name>doing a TCP ping to the configured postgres
host), a downstream DNS or socket failure is reported in the 400
response body. The pre-fix raw-string fallback in
IsOpenBaoServerNotReadymatched onerr.Error()and saw the keyword,
routing what is actually aReasonConfigurationError(OpenBao said
"your config is bad") toReasonDependencyNotReady(OpenBao is sealed
or unreachable). The user would then chase a non-existent OpenBao
outage while postgres connectivity was the real issue.Fix: when
errors.Asmatches*openbaoapi.ResponseError, return the
result of the API-response classification directly (without falling
through to the raw-string fallback). The fallback now only applies
to errors that never reached OpenBao — i.e. real operator-side
network failures.New
TestIsOpenBaoServerNotReady_4xxBodyKeywordIgnoredregression-pins
the exact 400-with-no such hostshape that triggered this in the
seanfarm cluster on the v0.5.0 rollout. -
openbaodatabaseconnection_controlleradds the
IsOpenBaoConfigErrorbranch that v0.5.0 added to the other
reconcilers. A 4xx from OpenBao now classifies as
ReasonConfigurationError with no requeue, instead of falling into
the catch-all ReasonOpenBaoError that returns the error and triggers
controller-runtime backoff. Missed in v0.5.0's W2 rollout.
v0.5.0
Correctness sweep — bug fixes, drift-detection gaps closed, JSON tag
canonicalization. Includes one breaking v1alpha1 change (see "Breaking
changes" below); since the operator is pre-1.0, in-place upgrades require
re-applying CRs that use the renamed fields.Breaking changes
-
JSON tags standardized on lowercase initialisms. Inconsistent
uppercase initialisms (tokenTTLSeconds,tokenBoundCIDRs,
oidcDiscoveryURL,kubernetesCACert,connectionURL,
defaultLeaseTTL,usePSS,extKeyUsageOIDs, etc.) renamed to their
lowercase form (tokenTtlSeconds,tokenBoundCidrs,
oidcDiscoveryUrl,kubernetesCaCert,connectionUrl,
defaultLeaseTtl,usePss,extKeyUsageOids, etc.). 24 fields
affected across 11 type files. Existing CRs using the old names will
fail strict YAML validation; re-apply with the new field names. Go
field names unchanged. A newapi/v1alpha1/json_canonical_test.go
meta-test prevents regressions. -
OpenBaoPKIRootCert.spec.deletionPolicyenum tightened toRetain
only. The operator does not implement root-cert revocation (would
invalidate every issued cert), so theDeletevalue previously
resulted in a silent no-op with a log warning. Admission now rejects
Deleteoutright. CRs that explicitly setdeletionPolicy: Delete
must drop the field (default isRetain) or set it toRetain.
Reliability fixes
-
StatusUpdateWithRetryhelper added tointernal/controller/common.go
and applied to every success-path status update. Previously a 409 conflict
onr.Status().Updateafter a successful OpenBao write would bubble up
through the reconciler and trigger a redundant OpenBao write on the next
pass. The helper retries the status update in place with a re-fetch +
re-mutate loop so the OpenBao write stays single-shot. 15 call sites
migrated. -
Factory cache is now time-bounded (default 12h). Previously
cached non-default-OpenBao clients stayed in the cache forever and
silently held expired tokens long after the underlying OpenBao token
TTL elapsed (default 24h). The cache now refreshes any entry older
thanCacheTTL. Configurable per-Factory; default 12h is safely
below typical OpenBao token TTL. -
Bootstrap type assertion fails loudly. If the future OpenBao
client constructor returns anything other than*openbaoclient.Real,
the build path now returns an explicit error rather than silently
skipping the token-expiry watcher (which would have let the operator
run with an expiring token). -
OpenBaoRef rejects both
nameandconnectionset. The doc
comment said mutually exclusive but enforcement was lax; reconciler
silently used Connection and ignored Name. Now returns a clear
ReasonInvalidSpec error.
Drift-detection fixes
-
equalPKIRoleextended with the v0.4.0 fields. Pre-fix, drift
onissuerRef,noStore,generateLease,signatureBits,
usePSS,keyUsage,extKeyUsage,extKeyUsageOIDswas invisible
to the reconciler — OpenBao could be wildly out of sync with spec
while the CR reported Ready=True. -
appRoleConfigEqualswitched to unordered comparison for
secretIdBoundCidrs,tokenBoundCidrs,tokenPolicies. OpenBao
treats these as sets; the previous order-sensitive comparison
caused spurious OpenBao Puts every time the user re-ordered policies
in spec.
Failure-mode classification
- Five-reason taxonomy documented and enforced.
shared_types.go
now codifies the rule:InvalidSpec— caught before talking to OpenBao.ConfigurationError— OpenBao rejected our request (4xx, won't
self-heal).OpenBaoError— unexpected server-side OpenBao error (5xx, parse
fail).TransientError— retriable client/network issue.DependencyNotReady— precondition will become true on its own.
IsOpenBaoConfigErrorhelper added tointernal/openbaoclientfor
4xx-but-not-404 detection. 13 reconciler call sites re-classified to
route OpenBao 4xx responses to ConfigurationError instead of
OpenBaoError or TransientError.MarkDegradeddefault branch logs loudly. Previously
logf.Log.V(1).Info(verbose only); a controller passing an
unsupported CRD type would silently no-op the status update. Now
logs at error level so misconfiguration is visible in default
operator logs.
CRD validation markers
- Added
MinLength=1to required string fields the reconcilers
treated as required at runtime:OpenBaoPKIBackend.spec.path,
OpenBaoPKIRootCert.spec.{backend, commonName},
OpenBaoIntermediateCA.spec.{backend, parentBackend, commonName, ttl},OpenBaoPKIRole.spec.backend,
OpenBaoPKICertificate.spec.{backend, role, commonName},
OpenBaoKVBackend.spec.path,OpenBaoAudit.spec.path,
OpenBaoTransitBackend.spec.path,
OpenBaoTransitBackend.spec.keys[].name. Empty-string admission is
now rejected with a precise field reference rather than a generic
reconciler-runtime error.
Status correctness
- OpenBaoPKIRootCert clears stale serial/expiration when the cert
vanishes from OpenBao. A narrow race between the initial GetRootCert
and the post-rotation re-Get could land in a state where the
reconciler reported Ready=True with a stale serial while the cert
was actually gone. Status closure now overwrites
Status.SerialNumberandStatus.Expirationunconditionally and
marks Ready=False/DependencyNotReady whenexists=false.
Tests
- New envtest specs validate every
config/samples/*.yamlagainst
the live admission schema (internal/controller/samples_validation_test.go). - New per-CRD admission tests for the MinLength=1 markers
(internal/controller/marker_validation_test.go, 16 specs). - New
TestStatusUpdateWithRetry_*suite covers the conflict-retry
helper. - New
TestOpenBaoPKIRoleDriftDetection_v04Fieldsexercises drift on
each v0.4.0 PKI role field. - New per-controller integration tests for OpenBaoGCPSecretsBackend
(7 tests) and OpenBaoGCPRoleset (8 tests) — these controllers had
zero tests before this slice. - New
TestStatusTimestamp_RapidReconcilesreplaces the three
time.Sleep(1.1s)flakes instatus_timestamp_test.go. Total
controller test wall-clock down from ~10s to ~6s. - New factory tests for the time-bounded cache, mutual-exclusion
rejection, and default-client no-cache behavior.
Cleanup
- Removed
log.Error(nil, ...)anti-pattern at 9 sites; demoted to
log.V(1).Infowhere the kv pairs aided debugging or dropped
outright where the post-fixMarkDegraded(InvalidSpec)carried the
user-visible signal. - Removed unused
ObjectRefFromOwnerstub ininternal/openbaoclient. - Removed 9 dead per-controller
markDegradedmethods left over from
beforecommon.go's helper. - Removed duplicate
stringMapEqual(useequalStringMap). - Removed unreachable empty-name guard in
openbaotransitbackend_controller.goafterMinLength=1on
TransitKeySpec.Name.
v0.4.0
Sprint 2: Medium-Priority Field Implementation - Added 33 new configuration fields across Database, PKI, Transit, KV, and AppRole engines. Improved error handling and retry logic for faster recovery from transient failures.
New Fields Added (33 total)
Database Engine (9 fields)
- OpenBaoDatabaseConnection: verifyConnection, rootRotationStatements, passwordPolicy, allowedRoles, rootCredentialsRotateStatements
- OpenBaoDatabaseDynamicRole: renewStatements, rollbackStatements, credentialType
PKI Engine (15 fields)
- OpenBaoPKIBackend: ocspServers, issuerName, enabledIssuers, manualChain, aiaPathEnableGlobal
- OpenBaoPKIRole: issuerRef, noStore, generateLease, signatureBits, usePSS, keyUsage, extKeyUsage, extKeyUsageOIDs, clientFlag, codeSigningFlag, emailProtectionFlag
- OpenBaoPKICertificate: format (pem/der/pem_bundle)
Transit Engine (4 fields)
- OpenBaoTransitBackend: forceNoCache, cacheSize, minDecryptionVersion, minEncryptionVersion
KV Engine (3 fields)
- OpenBaoKVBackend: casRequired, deleteVersionAfter, customMetadata (KV v2 only)
AppRole Auth (2 fields)
- OpenBaoAppRole: localSecretIDs, roleID (custom role-id support)
Bug Fixes
- Faster recovery from transient failures: Changed all
GetOpenBaoClientOrDegradefailure paths to returnTransientErrorRetryInterval(30s) instead ofDefaultResyncInterval(5m). Speeds up recovery when OpenBao is temporarily sealed, in standby, or unreachable. - Improved error classification:
GetOpenBaoClientOrDegradenow distinguishesOpenBaoServerNotReadyerrors and marks them asReasonDependencyNotReadyinstead of genericReasonTransientError. - Fixed database connection retry: When password Secret is missing, now uses 30s retry interval (was 5m).
Test Improvements
- Removed obsolete validation tests (
TestOpenBaoDatabaseDynamicRoleReconcile_InvalidTTL,TestOpenBaoPKIRoleReconcile_InvalidMaxTTL) - validation now handled by CRD admission with+kubebuilder:validationmarkers. - All controller tests passing.
Technical Details
- Added
ConfigurePKIIssuersandUpdatePKIIssuermethods to openbaoclient for PKI issuer management - Added
ConfigureKVV2method for KV v2-specific configuration - Extended
CreateTransitKeysignature to support new cache and version control parameters - Updated
AppRoleConfigstruct with new fields and drift detection
v0.3.0
Quick Wins: Operational Improvements - Five operational improvements for faster recovery, reduced API calls, and better user experience. No new OpenBao API features - focused on reliability and performance.
Quick Win 1: Secret Watches (Auto-Reconcile on Secret Changes)
- OpenBaoJWTAuthBackend: Watch 4 optional SecretRefs (OIDC client ID/secret, discovery CA, JWKS CA)
- OpenBaoDatabaseConnection: Watch password SecretRef
- Secret updates trigger immediate reconciliation without manual CR annotation
- Eliminates operational friction for password rotation and certificate renewal
- Existing generation+hash drift detection prevents infinite loops
Quick Win 2: Faster Transient Error Retry
- Add
TransientErrorRetryInterval(30 seconds) constant for OpenBao unavailability - Replace 5-minute retry with 30-second retry when OpenBao sealed/network errors/not ready
- Updated 49 occurrences across 21 controllers
- Maintains 5-minute interval for normal drift detection and dependency resolution
- Recovery time: 30s instead of 5min for transient issues
Quick Win 3: Remove Redundant GET After PUT (JWT Auth Backend)
PutJWTAuthBackendnow returnsJWTAuthBackenddirectly instead of just error- Internal implementation performs GET after successful PUT and returns result
- Controller no longer needs separate GET call to populate
status.accessor - Reduces OpenBao API calls by 33% for JWT auth backend writes (1 call instead of 2)
- Simplifies controller logic (32 lines → 10 lines)
Quick Win 4: Password Drift Detection (Database Connections)
- Track SHA256 hash of password in
status.appliedPasswordHash - Skip OpenBao write when generation/name/hash unchanged and OpenBao state matches spec
- Password change (Secret update) triggers write via Secret watch
- Non-password spec change triggers write
- Touch CR (
kubectl annotate) with no changes skips write (drift check passes) - Password security preserved (one-way SHA256 hash in status, not reversible)
Quick Win 5: Dry-Run Mode
- Enable simulation via
openbao.sean.farm/dry-run="true"annotation - Controllers skip OpenBao client creation and write operations
- Immediately mark
status.Ready=Truewithreason=DryRun - All 21 controllers support dry-run mode
- Useful for testing CR syntax, validating manifests, CI pipelines
- Zero OpenBao API calls in dry-run mode
- Explicit opt-in via annotation (no default behavior change)
Impact Summary
- Faster recovery: 30s retry for transient errors (was 5min)
- Automatic propagation: Secret changes reconcile immediately (was manual)
- Reduced API calls: JWT auth backend writes 33% fewer calls
- Better drift detection: Database connections skip unnecessary password writes
- Safe testing: Dry-run mode for previewing OpenBao operations
Files Modified
- Added CRD field:
OpenBaoDatabaseConnection.status.appliedPasswordHash - Updated 21 controllers with Secret watches, faster retry, and dry-run support
- Updated openbaoclient interface:
PutJWTAuthBackendsignature change - Updated 6 test files for TransientErrorRetryInterval expectations
v0.2.0
Sprint 1: Token Security Hardening - Add 18 high-priority token lifecycle and identity fields across Kubernetes, JWT/OIDC, and AppRole auth backends.
Kubernetes Auth Role (7 fields)
tokenExplicitMaxTTLSeconds: Hard cap on token lifetime including renewalstokenNoDefaultPolicy: Exclude default policy (least privilege)tokenNumUses: Maximum token usage counttokenPeriodSeconds: Periodic token renewal window for indefinite renewalsboundServiceAccountNamespaceSelector: Label selector for dynamic namespace matchingaliasNameSource: Entity alias generation method (serviceaccount_uid/serviceaccount_name)aliasMetadata: Custom entity alias metadata
JWT/OIDC Auth Role (11 fields)
userClaimJsonPointer: Enable JSON pointer syntax for nested claims (e.g.,data/user/email)expirationLeewaySeconds: Tolerance for expiration claim checksnotBeforeLeewaySeconds: Tolerance for not-before claim checksboundClaimsType: Claim matching mode (string vs glob)verboseOIDCLogging: Enable detailed OIDC logging for debuggingmaxAgeSeconds: Authentication recency requirementtokenExplicitMaxTTLSeconds: Hard token expiration captokenNoDefaultPolicy: Exclude default policytokenNumUses: Maximum token usestokenPeriodSeconds: Periodic token renewal windowaliasMetadata: Custom entity alias metadata
AppRole Auth Role (5 fields)
tokenExplicitMaxTTLSeconds: Hard expiration captokenPeriodSeconds: Periodic token durationtokenType: Token category (default/service/batch)tokenNoDefaultPolicy: Exclude default policyaliasMetadata: Pre-populated entity alias data
Impact
- Enables comprehensive token lifecycle management (explicit max TTL, periodic tokens, usage limits)
- Supports principle of least privilege (no default policy option)
- Adds identity enrichment (alias metadata across all auth methods)
- Implements dynamic namespace discovery (Kubernetes label selectors)
- Supports nested JWT claims (JSON pointer syntax)
v0.1.0
Feature completeness for supported engines - Add 13 high-value fields to reach 60-65% coverage of OpenBao API capabilities for existing auth backends and secrets engines (no new backends added).
Kubernetes Auth (2 fields)
tokenMaxTTL: Maximum token renewal TTL (security hardening)tokenBoundCIDRs: CIDR-based source IP restrictions (already in API, now fully wired)
JWT/OIDC Auth (2 fields)
boundClaims: Claim-based authentication restrictions for fine-grained access controltokenType: Service/batch/default token types for performance optimization
Database Secrets (2 fields)
rootRotationStatements: Automatic root credential rotation (OpenBao 1.8+)passwordPolicy: Password generation policy references (OpenBao 1.13+)
PKI Secrets (7 fields)
- Distinguished Name fields for certificate compliance:
country,organization,organizationalUnit,locality,province,streetAddress,postalCode - Added to both
OpenBaoPKIRootCertandOpenBaoPKIIntermediateCAfor enterprise certificate requirements
Transit Secrets (2 fields)
derived: Key derivation support for context-based subkeysconvergentEncryption: Deterministic encryption for deduplication use cases
Bug Fixes
- Fixed 109 bugs across 40 rounds of systematic bug hunting:
- Migrated all 21 controllers to generic helpers (GetOpenBaoClientOrDegrade, EnsureFinalizerOrRequeue)
- Added spec validation to 6 controllers (OpenBaoDatabaseConnection, OpenBaoDatabaseDynamicRole, OpenBaoDatabaseStaticRole, OpenBaoJWTAuthBackend, OpenBaoJWTAuthRole, OpenBaoKubernetesAuthRole)
- Sanitized error messages to prevent credential leakage (removed 20+ instances of err.Error() exposure)
- Standardized error handling across all reconcileDelete functions
- Added nil check preconditions for optional ref fields
Internal
- All CRDs regenerated with kubebuilder markers
- Test coverage: 57.4% (2 pre-existing TTL validation test failures remain, documented as out of scope)
- Code quality: Reduced duplication by ~1,000 lines through generic helper adoption
v0.0.3
Add generic JWT/OIDC auth backend and role support so OpenBao UI / OIDC login can
be managed declaratively without the Terraform OpenBao provider.api/v1alpha1: addOpenBaoJWTAuthBackendandOpenBaoJWTAuthRoleunder
openbao.sean.farm/v1alpha1.OpenBaoJWTAuthBackendmanages both the auth mount
(sys/auth/<mount>) and config (auth/<mount>/config) with same-namespace
secret refs for OIDC client secret and CA material.OpenBaoJWTAuthRole
managesauth/<mount>/role/<name>with optionalbackendRefand the same
policyRefspattern used by Kubernetes-auth roles.internal/openbaoclient: add typed JWT/OIDC backend and role operations,
including adoption-safe backend writes that enable the mount only when absent
and otherwise reconcile config in place.internal/controller: add JWT/OIDC backend and role reconcilers, drift
detection helpers, and unit coverage for backend adoption, dependency-not-
ready gating, role rewrite, andRetainfinalization.cmd/main.go: register the new controllers with the shared deferred OpenBao
client bootstrap path.dist/install.yaml: publish the two new CRDs and updated RBAC/controller
deployment surface.
v0.0.1
Initial scaffold and first-pass Terraform-replacement surface.
api/v1alpha1: 4 CRDs (OpenBaoPolicy,OpenBaoKubernetesAuthRole,
OpenBaoDatabaseStaticRole,OpenBaoDatabaseDynamicRole) under
openbao.sean.farm/v1alpha1. SharedDeletionPolicy(Delete/Retain),
LocalObjectReference, unified condition reason taxonomy
(Ready,Reconciling,DependencyNotReady,OpenBaoError,
TransientError,InvalidSpec), andopenbao.sean.farm/finalizer.internal/openbaoclient: typed client interface, real implementation
overhashicorp/openbao/api, deferred wrapper surfacing
ErrOpenBaoClientNotReadyuntil bootstrap installs a live client, and
IsOpenBaoServerNotReady/IsOpenBaoNotFoundclassifiers consumed by
reconcilers.internal/controller: 4 reconcilers. Drift-skip upsert,Delete/
Retainfinalize,observedGenerationtracking,policyRefs
resolution onOpenBaoKubernetesAuthRole(not-Ready refs keep the role
atDependencyNotReadyso OpenBao never sees a dangling policy name).cmd/main.go: Kubernetes-auth bootstrap goroutine with exponential
backoff (cap 30s), token-expiry watcher via
auth/token/lookup-selfcancelling the root context on low TTL, and
a split/readyz/ping+/readyz/openbao-bootstrap. Seven new manager
flags:--openbao-address,--openbao-auth-path,--openbao-kubernetes-role,
--openbao-sa-token-path,--openbao-token-poll-interval,
--openbao-token-renew-threshold,--openbao-bootstrap-base-backoff.Makefile: non-mutatingbuild-installer(traps and restores
config/manager/kustomization.yaml),verify-installerdrift guard,
smoke-install(Kind +dist/install.yaml; asserts aOpenBaoPolicy
lands atReady=False/DependencyNotReadywhen no OpenBao is configured,
which is the correct transient state), cleanerdocker-buildxwithout
aDockerfile.crosssidecar.test/e2e: minimal happy-path + failure-path reconcile suite.
BeforeSuitebuilds + loads the image, installs CRDs, deploys the
manager.fixtures_test.gostands up an in-cluster OpenBao dev server,
runs a bootstrap Job that enables Kubernetes auth and writes an
operator policy covering every controller's OpenBao surface, and
patches the operator Deployment with the OpenBao flags.
openbao_reconcile_test.go: happy path applies aOpenBaoPolicyand
expectsReady=True; failure path scales OpenBao to zero, touches the
CR, and expectsReady=False/DependencyNotReady. Scaffold
metrics/curl specs dropped (no webhooks, no cert-manager).dist/install.yaml: single flat manifest pinned to
codeberg.org/someara/openbao-operator:v0.0.1.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- In-cluster CI/CD pipeline (the seanfarm release path), completing the