Skip to content

Tutorial: guardrails

Goal

At the end you will watch the project scope, the command allow list and the environment filter refuse what they should, even when the vault policy is broad.

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

Run it

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

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

1. Only the project scope

The token here has a broad policy on purpose (secret/*). keyholder_list still shows only my-project, and a reference to other-project is refused before any read.

# Only the project scope is listed and usable, even with a broad policy

$ BAO_WRAPPING_TOKEN=<wrapped token> khcall keyholder_list keyholder_run '{"command":"curl","args":["-sS","-H","X-Pass: {{vault://other-project/prod/DB_PASSWORD}}","http://127.0.0.1:9/"]}'
### keyholder_list
{
  "entries": [
    {
      "ref": "vault://my-project/dev/API_TOKEN",
      "version": 1
    }
  ],
  "note": "Use {{vault://path/KEY}} in the args, env or files of keyholder_run.",
  "scope": "my-project"
}
### keyholder_run
error: vault://other-project/prod/DB_PASSWORD: reference outside the project scope (my-project)
--- stderr
<timestamp> audit cmd="curl" refs=[] exit=0 dur=<duration> status=error: vault://other-project/prod/DB_PASSWORD: reference outside the project scope (my-project)
exit_code=1

2. The allow list

A command outside the list, a path instead of a name, and a placeholder as the command are all refused.

# Commands outside the list, with a path or with a placeholder are refused

$ BAO_WRAPPING_TOKEN=<wrapped token> khcall keyholder_run '{"command":"sh","args":["-c","echo hi"]}' keyholder_run '{"command":"/usr/bin/curl","args":["--version"]}' keyholder_run '{"command":"{{vault://my-project/dev/API_TOKEN}}"}'
### keyholder_run
error: command not in the allow list: "sh" (allowed: curl, printenv)
### keyholder_run
error: command not in the allow list: give only the command name
### keyholder_run
error: command not in the allow list: give only the command name
--- stderr
<timestamp> audit cmd="sh" refs=[] exit=0 dur=<duration> status=error: command not in the allow list: "sh" (allowed: curl, printenv)
<timestamp> audit cmd="/usr/bin/curl" refs=[] exit=0 dur=<duration> status=error: command not in the allow list: give only the command name
<timestamp> audit cmd="{{vault://my-project/dev/API_TOKEN}}" refs=[] exit=0 dur=<duration> status=error: command not in the allow list: give only the command name
exit_code=1

3. Reserved variables

The agent cannot set BAO_*, VAULT_* or KEYHOLDER_* for the child, for example to point it at another vault.

# The agent cannot set BAO_*, VAULT_* or KEYHOLDER_* variables

$ BAO_WRAPPING_TOKEN=<wrapped token> khcall keyholder_run '{"command":"curl","args":["--version"],"env":{"BAO_ADDR":"http://attacker.example.com"}}'
error: variable name not allowed: "BAO_ADDR"
--- stderr
<timestamp> audit cmd="curl" refs=[] exit=0 dur=<duration> status=error: variable name not allowed: "BAO_ADDR"
exit_code=1

4. The vault token never reaches the child

printenv is allowed in this tutorial only to prove the point; never allow it in real use. The server was started with BAO_TOKEN set, and the child does not have it.

# The vault token never reaches the child process

The server itself was started with BAO_TOKEN set below; the child must not see it.

$ BAO_TOKEN=<root token> BAO_WRAPPING_TOKEN=<wrapped token> khcall keyholder_run '{"command":"printenv","args":["BAO_TOKEN"]}'
{
  "exit_code": 1,
  "refs_used": [],
  "stderr": "",
  "stdout": ""
}
--- stderr
<timestamp> audit cmd="printenv" refs=[] exit=1 dur=<duration> status=ok
exit_code=0

printenv exits 1 and prints nothing: BAO_TOKEN is not set in the child process.