Skip to content

Tutorial: temporary access

Goal

At the end you will see that a wrapped token works once, that a session ends when its token expires, that the server fails predictably without a token, and that when a token expires, OpenBao revokes everything it created.

Every command and output below comes from a real run, recorded in demo/tutorials/access/expected/ and checked by run.sh --check.

Run it

bash demo/tutorials/access/run.sh all
bash demo/tutorials/access/run.sh --check

Script: demo/tutorials/access/run.sh.

1. A wrapped token opens one session

The first server unwraps the token. The second start with the same value fails: the vault refuses to unwrap it again. A wrapped token that leaks after use is worthless, and a token that was intercepted before use shows up as this failure.

# A wrapped token opens one session; reusing it fails

$ BAO_WRAPPING_TOKEN=<wrapped token> khcall keyholder_status
{
  "addr": "http://127.0.0.1:18200",
  "initialized": true,
  "kv_mount": "secret",
  "scope": "my-project",
  "sealed": false,
  "server_version": "2.7.1",
  "token_expire_time": "<time>",
  "token_policies": [
    "default",
    "my-project-read"
  ],
  "token_renewable": true,
  "token_ttl_seconds": <ttl>
}
exit_code=0

$ BAO_WRAPPING_TOKEN=<same wrapped token> khcall keyholder_status
{
  "addr": "http://127.0.0.1:18200",
  "initialized": true,
  "kv_mount": "secret",
  "scope": "my-project",
  "sealed": false,
  "server_version": "2.7.1",
  "session_error": "unwrap token: vault returned HTTP 400 (already used or expired?)",
  "token_policies": null,
  "token_renewable": false,
  "token_ttl_seconds": 0
}
--- stderr
<timestamp> session not opened: unwrap token: vault returned HTTP 400 (already used or expired?)
exit_code=0

2. A 5-second token expires mid-session

One session: the first call works, +wait=7s pauses, then keyholder_status reports the expiry and the next call is refused with a message that tells the agent what to do.

# A 5-second token: the call works, then the same session is refused

$ BAO_WRAPPING_TOKEN=<wrapped token> khcall keyholder_run '{"command":"curl","args":["-sS","-H","Authorization: Bearer {{vault://my-project/dev/API_TOKEN}}","http://127.0.0.1:18080/"]}' +wait=7s keyholder_status keyholder_run '{"command":"curl","args":["-sS","-H","Authorization: Bearer {{vault://my-project/dev/API_TOKEN}}","http://127.0.0.1:18080/"]}'
### keyholder_run
{
  "exit_code": 0,
  "refs_used": [
    "vault://my-project/dev/API_TOKEN"
  ],
  "stderr": "",
  "stdout": "api says: hello, caller with header Authorization: Bearer [hidden:vault://my-project/dev/API_TOKEN]\n"
}
### keyholder_status
{
  "addr": "http://127.0.0.1:18200",
  "initialized": true,
  "kv_mount": "secret",
  "scope": "my-project",
  "sealed": false,
  "server_version": "2.7.1",
  "session_error": "token expired; renew access outside the chat",
  "token_policies": null,
  "token_renewable": false,
  "token_ttl_seconds": 0
}
### keyholder_run
error: vault://my-project/dev/API_TOKEN: read my-project/dev: access denied by the vault: token expired or revoked, or the policy does not allow this path; renew access outside the chat
--- stderr
<timestamp> audit cmd="curl" refs=[vault://my-project/dev/API_TOKEN] exit=0 dur=<duration> status=ok
<timestamp> audit cmd="curl" refs=[] exit=0 dur=<duration> status=error: vault://my-project/dev/API_TOKEN: read my-project/dev: access denied by the vault: token expired or revoked, or the policy does not allow this path; renew access outside the chat
exit_code=1

3. No token

The server still starts, so the agent can call keyholder_status and learn why nothing works. The other tools refuse.

# Without a token the server starts, explains, and refuses

$ env -u BAO_TOKEN -u BAO_WRAPPING_TOKEN khcall keyholder_status keyholder_list
### keyholder_status
{
  "addr": "http://127.0.0.1:18200",
  "initialized": true,
  "kv_mount": "secret",
  "scope": "my-project",
  "sealed": false,
  "server_version": "2.7.1",
  "session_error": "no token: set BAO_WRAPPING_TOKEN (preferred) or BAO_TOKEN",
  "token_policies": null,
  "token_renewable": false,
  "token_ttl_seconds": 0
}
### keyholder_list
error: no session with the vault; a human must renew access outside the chat
--- stderr
<timestamp> session not opened: no token: set BAO_WRAPPING_TOKEN (preferred) or BAO_TOKEN
exit_code=1

4. Expiry revokes what the token created

Keyholder revokes the leases of a run when the command ends. If that cleanup never happens, because the server was killed or the machine went down, the token's own expiry still takes them away. Here a 5-second token issues a certificate whose lease would last 1 hour; when the token expires, the lease is gone with it. The certificate comes from OpenBao's PKI engine, so the case needs no external service.

# A 5-second token creates a 1-hour lease; when the token expires, the lease goes with it

$ BAO_TOKEN=<5-second token> bao write -field=lease_id pki/issue/agent common_name=agent.example.test ttl=1h
pki/issue/agent/<id>
exit_code=0

Leases under pki/issue/agent before the token expires: 1

$ BAO_TOKEN=<5-second token> bao token lookup
--- stderr
Error looking up token: Error making API request.

URL: GET http://127.0.0.1:18200/v1/auth/token/lookup-self
Code: 403. Errors:

* permission denied
exit_code=2

Leases under pki/issue/agent after the token expired: 0