Skip to content

Renew AIWB OpenBao token instead of rotating it - #816

Merged
johnl-amd merged 2 commits into
mainfrom
openbao-apikey-token-renewal
Aug 26, 2026
Merged

Renew AIWB OpenBao token instead of rotating it#816
johnl-amd merged 2 commits into
mainfrom
openbao-apikey-token-renewal

Conversation

@johnl-amd

Copy link
Copy Markdown
Contributor

Summary

  • Mint the AIWB scoped OpenBao token periodic (-period=768h instead of -ttl=768h) and renew it on every secret-manager run, so the stored token value stays constant instead of rotating.
  • Grant auth/token/renew-self in apikeys-write-policy, matching what read-policy already grants the external-secrets identity.
  • Re-mint remains the fallback when the token is absent or no longer renewable. That path also migrates the existing non-periodic token at its next expiry.

Why: creating an API key in AI Workbench returns 500. Observed on app-dev.

Root cause: aiwb-api reads OPENBAO_TOKEN from the environment once at import (app/openbao/config.py) and builds its OpenBao client at startup, so the value is snapshotted when the pod starts. The scoped token was minted with a 32-day TTL; on expiry the secret-manager re-mints and revokes the old one. ESO updates the Secret, but the running pod keeps presenting the revoked token, so every write to secrets/data/apikeys/* returns 403, surfacing as a 500 from the create endpoint. It stays broken until someone restarts the deployment. Nothing was misconfigured; this is missing lifecycle handling around a credential that was deliberately scoped rather than reusing the root token (EAI-7277).

Why periodic rather than just renewing: a non-periodic token cannot be renewed past the system max TTL, so adding renewal alone would not have prevented the expiry. Verified against the running OpenBao: a periodic token reports period 768h / explicit_max_ttl 0s and renewal resets its lease, while -ttl=768h produces no period and stays capped from creation.

Note on the CLI: there is no bao token renew-self subcommand. bao token renew with no argument calls auth/token/renew-self, which is what the policy grant covers.

Risk: low. Confined to the secret-manager script. The policy addition mirrors an existing grant, and the mint change only affects tokens created from here on.

Impact

One rotation is still pending. The token currently deployed is non-periodic and expires 2026-09-23. Even with this merged it will fail renewal then, be re-minted as periodic, and strand aiwb-api once more. Either accept a restart around that date, or force the rotation at a controlled time and restart the pod.

Non-goals

This does not stop a manual re-mint from stranding the pod, because the token is still read once at startup. The durable fix is for AIWB to authenticate rather than hold a token, the way external-secrets already does via userpass against read-policy. Worth a separate ticket.

Test plan

  • Secret-manager logs SKIP: secrets/aiwb-openbao-token (scoped token renewed) instead of (scoped token still valid).
  • bao token lookup on the stored token reports period 768h.
  • Creating an API key in AI Workbench succeeds past the token's original expiry without restarting aiwb-api.

AIWB reads OPENBAO_TOKEN once at import and builds its OpenBao client at
startup, so the value is snapshotted when the pod starts. The scoped token was
minted with -ttl=768h and re-minted (and the old one revoked) once it expired,
which stranded the running aiwb-api pod on a dead credential: every API-key
create returned 500 from a 403 on secrets/data/apikeys/*, until someone
restarted the deployment. Observed on app-dev.

Mint the token periodic and renew it on each run so the stored value stays
constant and nothing has to notice a rotation. A non-periodic token cannot be
renewed past the system max TTL, so renewal alone would not have helped.
Re-mint remains the fallback when the token is absent or no longer renewable,
which also migrates the existing non-periodic token on its next expiry.

The policy gains auth/token/renew-self, matching read-policy which already
grants it to the external-secrets identity.
@johnl-amd
johnl-amd requested a review from a team as a code owner August 24, 2026 09:30
@brownzebra

Copy link
Copy Markdown
Contributor

@johnl-amd Can you guys give any testing evidence here?

@johnl-amd

Copy link
Copy Markdown
Contributor Author

Testing evidence from app-dev, all against the live OpenBao.

The current token, showing the problem is real:

creation_ttl      768h
period            (absent)
explicit_max_ttl  0s
issue_time        2026-08-22T13:12:04Z
expire_time       2026-09-23T13:12:04Z
policies          [apikeys-write-policy default]

No period field, so it is non-periodic. Worth noting the issue_time is 2026-08-22, meaning this token is itself a re-mint from a few days ago, which lines up with the 500 that started this.

Why renewal alone would not have been enough:

$ bao read sys/auth/token/tune
default_lease_ttl    768h
max_lease_ttl        768h

System max equals the creation TTL, so a non-periodic token can never be renewed past its original 32 days.

What the mint change produces. Minted both ways against the live policy:

-ttl=768h (current) -period=768h (this PR)
period absent 768h
explicit_max_ttl 0s 0s
renewable true true

Both test tokens revoked afterwards.

Compressed lifecycle test. Same mechanism at tiny scale so it finishes in under three minutes. Token A is non-periodic with a 90s hard cap standing in for the system max, token B is periodic with a 60s period. Both renewed every 25s.

t=0    A(non-periodic, cap 90s) and B(periodic 60s) created
t=25s  A: renew=OK  ttl=59s        B: renew=OK  ttl=59s
t=50s  A: renew=OK  ttl=39s        B: renew=OK  ttl=1m
t=75s  A: renew=OK  ttl=14s        B: renew=OK  ttl=59s
t=100s A: renew=FAIL  TOKEN DEAD   B: renew=OK  ttl=59s
t=125s A: renew=FAIL  TOKEN DEAD   B: renew=OK  ttl=1m
t=150s A: renew=FAIL  TOKEN DEAD   B: renew=OK  ttl=59s
--- final ---
A: DEAD (non-periodic hit its cap)
B: alive (periodic renewed indefinitely)

The thing to look at is A's ttl coming back smaller every time, 59s then 39s then 14s. Renewal keeps succeeding but only ever hands back what is left before the cap, then the token dies. That is exactly what the deployed token will do on 2026-09-23. B resets to a full period every time and never approaches a cap.

One correction to the PR body. The auth/token/renew-self grant turns out to be redundant. Token B renewed six times using the current policy, which does not have that grant, because the built-in default policy already provides it and tokens are minted with [apikeys-write-policy default]. Happy to keep the hunk as belt and braces in case anyone ever mints with -no-default-policy, but it is not load bearing and the body overstates it.

The pending rotation on 2026-09-23 still stands either way.

@brownzebra brownzebra left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, nice!

@johnl-amd
johnl-amd merged commit 8252c92 into main Aug 26, 2026
7 checks passed
@johnl-amd
johnl-amd deleted the openbao-apikey-token-renewal branch August 26, 2026 09:32
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.

2 participants