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 mode | Configure auth with | Best for | Can use operator auth? |
|---|---|---|---|
| Stateful / state-backed | signer_state + containment-chamber operator auth | Production, multi-tenant signers, runtime token rotation | Yes |
| Stateless | static_auth in the config file | Simple Web3Signer-compatible migrations, fixed auth at startup | No |
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.
Token Types
Section titled “Token Types”| Token | Mode | Purpose | How to get it |
|---|---|---|---|
| Management token | Stateful | Full management access. Used to create runtime policies and client tokens. | containment-chamber operator generate-root-token |
| Client token | Stateful | Runtime token controlled by one or more policies. | containment-chamber operator auth token create |
| Static client token | Stateless | Config-declared token controlled by one or more static_auth policies. | static_auth.tokens in the config file |
| Setup token | Stateful initialization | One-time initialization token. | Logged on first boot while uninitialized |
| Registration token | Stateful quorum | One-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.
Policy Model
Section titled “Policy Model”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:
- Generate a management token after the chamber is unsealed.
- Create one policy per validator client or tenant.
- Create short-lived client tokens bound to those policies.
- Configure validator clients with either
Bearertokens or Basic-auth URLs.
Common Policies
Section titled “Common Policies”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"] } ]}Token CIDR Binding
Section titled “Token CIDR Binding”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) tooperator auth token create, or settoken_bound_cidrsin the Auth API token-create request body. - Stateless: set
static_auth.tokens[].token_bound_cidrsin the config file.
State-backed CLI example:
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_TOKENEquivalent 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.
Stateful Deployments: Runtime Auth
Section titled “Stateful Deployments: Runtime Auth”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:
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-signerCreate a client token bound to that policy:
containment-chamber operator auth token create \ --policies validator-client-alpha \ --ttl-seconds 86400 \ --token env:ROOT_TOKEN \ --signer-url http://localhost:9000 \ --allow-plaintext-signerFor 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:
containment-chamber operator auth policy list --token env:ROOT_TOKENcontainment-chamber operator auth token list --token env:ROOT_TOKENcontainment-chamber operator auth policy get --name validator-client-alpha --token env:ROOT_TOKENUpdate or remove records:
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_TOKENAdd --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.
Stateless Deployments: Config File Auth
Section titled “Stateless Deployments: Config File Auth”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 viatoken_bound_cidrs(enforced exactly like API-issued tokens).
Auth is still deny-by-default unless a policy explicitly allows the request.
| Mode | static_auth.anonymous | Request without token |
|---|---|---|
| Stateless | Not configured | Rejected with 401 |
| Stateless | Configured | Checked against the referenced named policies |
| State-backed | Not configured | Rejected with 401 |
| State-backed | static_auth set | Invalid 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"] # optionalOmit static_auth.anonymous (and tokens) when every caller must authenticate.
Key Visibility
Section titled “Key Visibility”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_keysscope.- 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.
Scope Reference
Section titled “Scope Reference”The complete scope vocabulary and the seal/unseal API surface live in API Concepts. Request and response schemas are in the API Reference.
Next Steps
Section titled “Next Steps”- 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