Documentation
Request wrapping example with VaultClientV2
This example demonstrates an AppRole flow where the admin wraps the generated role_id and secret_id, then the app unwraps them before logging in.
This example demonstrates an AppRole flow where the admin wraps the generated
role_id and secret_id, then the app unwraps them before logging in.
What the workflow demonstrates
- Prepare Vault by enabling a KV v2 mount at
secret. - Write an application secret at
secret/mysql/webapp. - Enable the
approleauth method. - Create a read-only
jenkinspolicy for that one secret path. - Register an AppRole with short-lived tokens.
- Generate
role_idandsecret_idcredentials. - Wrap those credentials with
vault.sys.wrapping.wrapand a60sTTL. - Clear the app token before unwrapping to model an unauthenticated receiver.
- Unwrap the credentials with
vault.sys.wrapping.unwrap. - Log in as the app and read the protected secret.
This example uses the shared decorator-based runner and personas described in
examples/README.md.
Local Vault
From the repository root, start only the plain Vault service:
docker compose up -d vault
One Vault instance is enough. You do not need vault_tls or vault_mtls unless
you are specifically testing TLS.
For a fresh Vault state:
docker compose down --volumes --remove-orphans
docker compose up -d vault
Run
Install dependencies from the repository root:
npm install
Then run the example:
NANVC_VAULT_CLUSTER_ADDRESS=http://127.0.0.1:8200 npx tsx examples/request-wrapping/main.ts
The helper defaults to http://vault.local:8200. Use the environment variable
above when vault.local is not mapped on your machine.
Environment
For an existing Vault server, set:
export NANVC_VAULT_CLUSTER_ADDRESS=http://127.0.0.1:8200
export TEST_NANVC_VAULT_AUTH_TOKEN=<root-or-admin-token>
export TEST_NANVC_VAULT_UNSEAL_KEY=<unseal-key>
If the local Vault server is initialized by any example or integration helper, the helper writes a shared cache file under your OS temp directory with:
TEST_NANVC_VAULT_AUTH_TOKENTEST_NANVC_VAULT_UNSEAL_KEY
Shell-exported TEST_NANVC_* variables take precedence over cached values. If
Vault reports invalid token, the cached credentials probably belong to another
Vault instance or an older Docker volume. Export valid TEST_NANVC_* values, or
reset local Vault with the fresh-state commands above.
import assert from 'node:assert';
import type { AdminPersona } from '../common/personas/admin.js';
import type { AppPersona } from '../common/personas/app.js';
import type { OperatorPersona } from '../common/personas/operator.js';
import { example, runAs, runExample, workflow } from '../common/workflow/decorators.js';
const secretData = {
db_name: 'users',
username: 'admin',
password: 'passw0rd',
};
@example('Request wrapping example')
class RequestWrappingExample {
private wrappingToken!: string;
@workflow('operator', 'Prerequisites: prepare Vault with a secret')
@runAs({ persona: 'operator' })
public async prepareVault(operator: OperatorPersona<'v2'>): Promise<void> {
await operator.ensureKvMountAvailable('secret');
}
@workflow('admin', 'Configure AppRole and create wrapped credentials')
@runAs({ persona: 'admin' })
public async adminWorkflow(admin: AdminPersona<'v2'>): Promise<void> {
await admin.vault.secret.kv.v2.write('secret', 'mysql/webapp', secretData).unwrap();
await admin.enableAppRoleAuth();
const jenkinsPolicy = [
"# Read-only permission on secrets stored at 'secret/data/mysql/webapp'",
'path "secret/data/mysql/webapp" {',
' capabilities = ["read"]',
'}',
].join('\n');
await admin.createPolicy('jenkins', jenkinsPolicy);
await admin.registerAppRole('jenkins', {
token_policies: ['jenkins'],
token_ttl: '20m',
token_max_ttl: '30m',
});
const credentials = await admin.createAppRoleCredentials('jenkins');
const wrappedResponse = await admin.vault.sys.wrapping
.wrap(
{
role_id: credentials.roleId,
secret_id: credentials.secretId,
},
'60s',
)
.unwrap();
const wrappingToken = wrappedResponse.wrap_info?.token;
if (!wrappingToken) {
throw new Error('Failed to create wrapping token');
}
this.wrappingToken = wrappingToken;
}
@workflow('app', 'Unwrap credentials and access secret')
@runAs({ persona: 'app' })
public async appWorkflow(app: AppPersona<'v2'>): Promise<void> {
app.vault.setToken(null);
const unwrapResponse = await app.vault.sys.wrapping.unwrap(this.wrappingToken).unwrap();
const roleId = unwrapResponse.data?.role_id as string | undefined;
const secretId = unwrapResponse.data?.secret_id as string | undefined;
if (!roleId || !secretId) {
throw new Error('Failed to unwrap wrapping token and retrieve credentials');
}
await app.loginWithAppRole({ roleId, secretId });
const secretResponse = await app.vault.secret.kv.v2.read('secret', 'mysql/webapp').unwrap();
assert.deepStrictEqual(
secretResponse.data,
secretData,
'Retrieved secret data does not match the expected value',
);
}
}
runExample(RequestWrappingExample).catch((error) => {
console.error(error);
process.exitCode = 1;
});Source Files
- README source:
examples/request-wrapping/README.md - Runnable source:
examples/request-wrapping/main.ts
This page is generated from the example README. Edit the source README and run
npm run generate:docsto update it.