Skip to content

Auth Policies & Tokens

Runtime access control for validator clients, operators, and management automation

Containment Chamber can run open for Web3Signer-compatible migrations, or it can enforce runtime access control with policies and bearer tokens. Use policies when one signer serves multiple validator clients, when operators need different privileges, or when signing must be restricted to specific keys or operations.

There are two mutually exclusive auth modes. Pick the one that matches your deployment state model:

Deployment modeConfigure auth withBest forCan use operator auth?
Stateful / state-backedsigner_state + containment-chamber operator authProduction, multi-tenant signers, runtime token rotationYes
Statelessstatic_auth in the config fileSimple Web3Signer-compatible migrations, fixed auth at startupNo

Both modes use the same policy rule model: allow or deny by route scope, validator key, and signing operation. The difference is where policies and tokens live.

For the canonical scope list, request lifecycle, and seal/unseal API surface, see API Concepts.

TokenModePurposeHow to get it
Management tokenStatefulFull management access. Used to create runtime policies and client tokens.containment-chamber operator generate-root-token
Client tokenStatefulRuntime token controlled by one or more policies.containment-chamber operator auth token create
Static client tokenStatelessConfig-declared token controlled by one or more static_auth policies.static_auth.tokens in the config file
Setup tokenStateful initializationOne-time initialization token.Logged on first boot while uninitialized
Registration tokenStateful quorumOne-time operator registration token.Returned by operator init or operator rotate quorum

Bearer tokens are the primary transport:

Authorization: Bearer <token>

HTTP Basic is also accepted for validator clients that put credentials in the signer URL. The username is ignored and the password is treated as the token.

A policy is a named list of rules. Rules can allow or deny by:

  • Scope: which route family the token can use.
  • Key: which validator public keys the token can sign with.
  • Operation: which Ethereum signing operation is allowed.

Example:

{
"name": "validator-client-alpha",
"rules": [
{
"effect": "allow",
"scopes": ["sign", "public_keys"],
"keys": ["0xabc123...", "0xdef456..."]
},
{
"effect": "deny",
"operations": ["VOLUNTARY_EXIT"]
}
]
}

The common production pattern is:

  1. Generate a management token after the chamber is unsealed.
  2. Create one policy per validator client or tenant.
  3. Create short-lived client tokens bound to those policies.
  4. Configure validator clients with either Bearer tokens or Basic-auth URLs.

These policy shapes work in both modes: as runtime policy rules in a stateful deployment, or under static_auth.policies in a stateless deployment.

Validator client

Allows normal signing and public-key discovery for a bounded key set.

{
"name": "validator-client-alpha",
"rules": [
{
"effect": "allow",
"scopes": ["sign", "public_keys"],
"keys": ["0xabc123...", "0xdef456..."]
},
{
"effect": "deny",
"operations": ["VOLUNTARY_EXIT"]
}
]
}

Monitoring

Allows status and key visibility without signing authority.

{
"name": "monitoring",
"rules": [
{
"effect": "allow",
"scopes": ["public_keys", "list_keys", "chamber_status"]
}
]
}

Exit operator

Allows a dedicated workflow to submit voluntary exits without granting broad operational access.

{
"name": "exit-operator",
"rules": [
{
"effect": "allow",
"scopes": ["sign"],
"operations": ["VOLUNTARY_EXIT"]
}
]
}

Any client token can be bound to source CIDRs. Use this when validator clients have stable egress IPs and you want stolen tokens to fail outside that network path.

The underlying field name is token_bound_cidrs in both auth modes:

  • Stateful / state-backed: pass --token-bound-cidrs (comma-separated) to operator auth token create, or set token_bound_cidrs in the Auth API token-create request body.
  • Stateless: set static_auth.tokens[].token_bound_cidrs in the config file.

State-backed CLI example:

Terminal window
containment-chamber operator auth token create \
--policies validator-client-alpha \
--ttl-seconds 86400 \
--token-bound-cidrs 10.0.10.0/24,192.168.50.0/24 \
--token env:ROOT_TOKEN

Equivalent Auth API request body:

{
"policies": ["validator-client-alpha"],
"ttl_seconds": 86400,
"token_bound_cidrs": ["10.0.10.0/24"]
}

Stateless config example:

static_auth:
policies:
- name: attester
rules:
- effect: allow
scopes: [sign, public_keys]
tokens:
- secret: env:CC_ATTESTER_TOKEN
policies: [attester]
token_bound_cidrs: ["10.0.0.0/8"]

Management tokens are never CIDR-bound. For state-backed tokens, invalid ranges are rejected when the token is minted. For static_auth tokens, invalid ranges are rejected at startup before the listener binds.

Use this mode when signer_state is configured. Policies and tokens are stored in the signer-state backend and can be created, listed, updated, and revoked while the signer is running.

Runtime auth is managed through the containment-chamber operator auth CLI. The CLI calls the Auth API for you; request and response schemas live in the interactive API Reference.

Create a policy:

Terminal window
containment-chamber operator auth policy create \
--name validator-client-alpha \
--rules '[{"effect":"allow","scopes":["sign","public_keys"],"keys":["0xabc123...","0xdef456..."]},{"effect":"deny","operations":["VOLUNTARY_EXIT"]}]' \
--token env:ROOT_TOKEN \
--signer-url http://localhost:9000 \
--allow-plaintext-signer

Create a client token bound to that policy:

Terminal window
containment-chamber operator auth token create \
--policies validator-client-alpha \
--ttl-seconds 86400 \
--token env:ROOT_TOKEN \
--signer-url http://localhost:9000 \
--allow-plaintext-signer

For validator clients with stable egress IPs, add --token-bound-cidrs 10.0.10.0/24 to restrict the token to that network path.

List and inspect runtime auth records:

Terminal window
containment-chamber operator auth policy list --token env:ROOT_TOKEN
containment-chamber operator auth token list --token env:ROOT_TOKEN
containment-chamber operator auth policy get --name validator-client-alpha --token env:ROOT_TOKEN

Update or remove records:

Terminal window
containment-chamber operator auth policy update \
--name validator-client-alpha \
--rules '[{"effect":"allow","scopes":["sign","public_keys"],"keys":["0xabc123..."]}]' \
--token env:ROOT_TOKEN
containment-chamber operator auth token revoke \
--accessor cc_accessor_... \
--token env:ROOT_TOKEN
containment-chamber operator auth policy delete \
--name validator-client-alpha \
--token env:ROOT_TOKEN

Add --signer-url, TLS flags, or --allow-plaintext-signer to these commands when your signer is not at the default operator endpoint.

Stateful deployments are deny-by-default: requests without a valid token are rejected with 401, and valid tokens are allowed only by their bound policies.

Use this mode when signer_state is omitted. Policies and tokens are declared in the config file under static_auth; runtime auth management routes are unavailable.

static_auth has three parts:

  • policies: named rule sets.
  • anonymous: optional policy bindings for requests without a token.
  • tokens: optional static bearer tokens bound to named policies, optionally restricted to caller IP ranges via token_bound_cidrs (enforced exactly like API-issued tokens).

Auth is still deny-by-default unless a policy explicitly allows the request.

Modestatic_auth.anonymousRequest without token
StatelessNot configuredRejected with 401
StatelessConfiguredChecked against the referenced named policies
State-backedNot configuredRejected with 401
State-backedstatic_auth setInvalid config; startup rejects it

Invalid or expired tokens are rejected. They are not silently downgraded to unauthenticated access.

For a single-tenant migration where validator clients do not send tokens, grant anonymous access by referencing a named policy:

static_auth:
policies:
- name: anonymous
rules:
- effect: allow
scopes: [sign, public_keys]
- effect: deny
operations: [VOLUNTARY_EXIT]
anonymous:
policies: [anonymous]

Stateless deployments can also issue static client tokens — each secret is either env:VAR_NAME (injected from the environment) or a clear-text value:

static_auth:
policies:
- name: attester
rules:
- effect: allow
scopes: [sign, public_keys]
tokens:
- secret: env:CC_ATTESTER_TOKEN
policies: [attester]
token_bound_cidrs: ["10.0.0.0/8"] # optional

Omit static_auth.anonymous (and tokens) when every caller must authenticate.

The Web3Signer public-key endpoint only returns keys the caller can actually sign with. This prevents a validator client from accepting duties for keys it cannot sign.

GET /api/v1/eth2/publicKeys requires:

  • public_keys scope.
  • Matching key rules, when a policy restricts keys.

The Chamber key-management listing endpoint is operational inventory. It is controlled by the chamber_keys_list scope and can show keys that are not signable by the caller.

The complete scope vocabulary and the seal/unseal API surface live in API Concepts. Request and response schemas are in the API Reference.

  • API Concepts — how requests flow through auth evaluation, the token lifecycle, and the scope model
  • API Reference — interactive OpenAPI spec with request/response schemas
  • Production Hardening — token security, CIDR binding, and audit logging