release v0.12.31 (7c4ea5045c) #95

Merged
pipeline-bot merged 4 commits from _promote/v0.12.31 into main 2026-07-28 14:59:26 +00:00
Contributor
No description provided.
chore(hygiene): sanitize two Events, drop two dead exports
All checks were successful
pipeline/ci CI green v7c32d6456cfe
7c32d6456c
Eventf was given err.Error() as its FORMAT STRING in the
lease-ID-changed path, so a lease ID carrying a % verb would corrupt the
rendered event. Passed as an argument now; vet flags the old shape.

The revoke-failure Event interpolated a raw client error. Events are
readable by anyone with get on the namespace while the Secret holding
the credential is not, so that error now goes through
secretSyncFailureStatus — the same sanitizer the status conditions
already use — instead of potentially carrying an OpenBao response body.

ErrEncryptionRequired and RemoveAllFinalizers both had zero callers: the
07-24 static-analysis pass renamed one and changed the signature of the
other without a consumer, leaving declarations nothing referenced.
defaultClient.Restore() assigned c.authSecret and the client token but never
assigned c.lastRenewal, leaving it at its zero value. lastRenewal has only two
other writers -- Login() and the renewal watcher -- and neither runs on the
restore path.

checkExpiry() consumes lastRenewal as an ABSOLUTE timestamp:

    ts := time.Unix(c.lastRenewal, 0).Add(horizon)
    return time.Now().After(ts), nil

With lastRenewal == 0 the horizon is computed from the Unix epoch, i.e.
1970-01-01 + TTL, so time.Now().After(ts) is unconditionally true for any
realistic TTL. Every client restored from storage was reported expired.

The sentinel that would have caught this is bypassed: the exported CheckExpiry()
explicitly rejects lastRenewal == 0 with "never logged in", but Validate() calls
the unexported checkExpiry() directly and so never sees the guard.

Consequence: with Persist enabled, an operator restart runs RestoreAll ->
restoreClient -> NewClientFromStorageEntry -> Restore() and caches the client.
The next Get() calls Validate(), which reports "client token expired", evicts
the entry from the cache and performs a full re-login. The client-persistence
feature silently never took effect across restarts.

Anchor lastRenewal to time.Now() at restore, matching Login(). The storage entry
carries no renewal timestamp of its own, so the restore instant is the only
available anchor and is the conservative choice (it can only under-estimate
remaining TTL, never over-estimate it).

Adds client_test.go, the first test in this package beyond utils_test.go. The
tests are offline: Restore() and checkExpiry() are local state transitions, and
a non-renewable auth secret starts no lifetime watcher, so no fake server is
needed. Coverage: lastRenewal is set, a freshly restored 24h token is not
expired via both checkExpiry() and CheckExpiry(), Validate() succeeds on a
restored client, and an elapsed TTL is still correctly reported as expired.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sean/openbao-operator!95
No description provided.