Required IAM permissions for DynamoDB anti-slashing and DynamoDB+KMS key source features
AWS permissions are only needed if you’re using DynamoDB-backed features. Filesystem keystores with SQLite or PostgreSQL anti-slashing require no AWS access at all.
Three DynamoDB-backed paths need IAM permissions:
- DynamoDB anti-slashing — stores EIP-3076 slashing protection data in DynamoDB
- DynamoDB + KMS key source — stores encrypted BLS validator keys in DynamoDB under a chamber master key protected by AWS KMS-wrapped Shamir shares
- Signer state — stores the KMS-wrapped Shamir master-key shares, break-glass seal latch, root-token bootstrap ciphertext, auth policies, and auth tokens
Required Permissions
Section titled “Required Permissions”{ "Version": "2012-10-17", "Statement": [ { "Sid": "DynamoDBAntiSlashing", "Effect": "Allow", "Action": [ "dynamodb:DescribeTable", "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:TransactGetItems", "dynamodb:TransactWriteItems" ], "Resource": "arn:aws:dynamodb:REGION:ACCOUNT_ID:table/TABLE_NAME" } ]}The DynamoDB anti-slashing backend uses transactions for atomicity. It never uses BatchWriteItem or BatchGetItem.
| Action | Purpose |
|---|---|
dynamodb:DescribeTable |
Table existence check at startup |
dynamodb:GetItem |
Read watermarks and signing roots |
dynamodb:PutItem |
Write watermark updates |
dynamodb:TransactGetItems |
Atomic multi-item reads |
dynamodb:TransactWriteItems |
Atomic conditional writes (slashing protection) |
No GSI is needed, so the resource ARN is the table only.
Replace REGION, ACCOUNT_ID, and TABLE_NAME with your values.
The terraform/examples/ configurations create the table and wire up this policy to your signer IAM role.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "KMSAccess", "Effect": "Allow", "Action": [ "kms:Encrypt", "kms:Decrypt", "kms:DescribeKey" ], "Resource": [ "arn:aws:kms:REGION:ACCOUNT_ID:key/KEY_ID_1", "arn:aws:kms:REGION:ACCOUNT_ID:key/KEY_ID_2", "arn:aws:kms:REGION:ACCOUNT_ID:key/KEY_ID_3" ] }, { "Sid": "DynamoDBAccess", "Effect": "Allow", "Action": [ "dynamodb:DescribeTable", "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:Query" ], "Resource": [ "arn:aws:dynamodb:REGION:ACCOUNT_ID:table/TABLE_NAME", "arn:aws:dynamodb:REGION:ACCOUNT_ID:table/TABLE_NAME/index/*" ] } ]}The key source needs two sets of permissions: DynamoDB for key storage, and KMS for wrapping and unwrapping Shamir shares.
DynamoDB:
| Action | Purpose |
|---|---|
dynamodb:DescribeTable |
Table existence check at startup |
dynamodb:GetItem |
Read individual validator keys |
dynamodb:PutItem |
Store new validator keys |
dynamodb:UpdateItem |
Change validator key status |
dynamodb:Query |
Load keys via GSI (parallel shard scan) |
KMS:
| Action | Purpose |
|---|---|
kms:Encrypt |
Wrap Shamir shares during auto-init and key rotation (re-encryption to new custody keys) |
kms:Decrypt |
Unwrap Shamir shares to reconstruct the master key |
kms:DescribeKey |
Operational/read permission granted by Terraform examples |
The index/* resource is required for dynamodb:Query on the GSI. The table ARN alone is not sufficient.
The terraform/examples/ configurations create the DynamoDB table, KMS keys, IAM role, and wire up all the resource ARNs automatically.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "DynamoDBSignerState", "Effect": "Allow", "Action": [ "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:Scan", "dynamodb:TransactWriteItems" ], "Resource": "arn:aws:dynamodb:REGION:ACCOUNT_ID:table/STATE_TABLE_NAME" } ]}Signer state uses a separate DynamoDB table from validator keys and anti-slashing. It stores the KMS-wrapped Shamir master-key shares, auth policies, auth tokens, and the root-token bootstrap ciphertext.
| Action | Purpose |
|---|---|
dynamodb:GetItem |
Read state rows (MASTER_KEY, SEAL_OVERRIDE, auth records) |
dynamodb:PutItem |
Write state rows (master key on auto-init, break-glass SEAL_OVERRIDE latch, auth records) |
dynamodb:UpdateItem |
Update master-key generation during rotation reconcile |
dynamodb:DeleteItem |
Revoke auth tokens; remove ROOT_TOKEN_BOOTSTRAP on re-create |
dynamodb:Scan |
List prefixed state rows (auth policies, auth tokens) |
dynamodb:TransactWriteItems |
Atomically commit key-rotation reconcile |
Break-glass latch protection (recommended hardening):
The SEAL_OVERRIDE row is the break-glass latch that seals the entire cluster. To prevent a
compromised server process from forging a latch or clearing an existing one, add an explicit
Deny statement scoped to that row:
{ "Sid": "DenySealOverrideTampering", "Effect": "Deny", "Action": [ "dynamodb:PutItem", "dynamodb:DeleteItem" ], "Resource": "arn:aws:dynamodb:REGION:ACCOUNT_ID:table/STATE_TABLE_NAME", "Condition": { "ForAllValues:StringEquals": { "dynamodb:LeadingKeys": ["SEAL_OVERRIDE"] } }}With this Deny in place, the server’s own IAM role cannot write or remove the SEAL_OVERRIDE
row. Consequences:
- Sealing via
POST /api/v1/chamber/sealwill be blocked for the signer role. Break-glass sealing must be performed by a separate ops/break-glass IAM role that issues a rawaws dynamodb put-itemdirectly (HMAC verification is bypassed, but the cluster still seals on the next heartbeat cycle when each replica observes the row). - Recovery (clearing the latch) must similarly be performed by that same out-of-band role. The signer’s runtime role never has permission to unseal itself.
In environments where containment-chamber operator seal must work via the HTTP API, omit the
PutItem deny and keep only the DeleteItem deny. This still prevents a compromised server from
clearing its own latch, while preserving the break-glass seal API.
KMS Permissions
Section titled “KMS Permissions”The same signer role is used for KMS wrapping and unwrapping. There is no separate first-boot KMS key, runtime KMS key, or setup role in the application model.
The signer calls kms:Encrypt when it writes KMS-wrapped Shamir shares: auto-init and key rotation (when the ceremony generation is bumped and new custody keys are reconciled in). It calls kms:Decrypt when it reconstructs the master key from enough shares to meet the configured threshold.
Grant both actions to the signer role for every Shamir KMS key. The Terraform examples also grant kms:DescribeKey for operational compatibility. In cross-account deployments, both the caller’s IAM policy and the target key policy must allow the operation.
If your DynamoDB tables use customer-managed KMS keys for server-side encryption, the signer role also needs the DynamoDB SSE KMS actions on those table-encryption keys:
[ "kms:Encrypt", "kms:Decrypt", "kms:DescribeKey", "kms:GenerateDataKey*", "kms:ReEncrypt*", "kms:CreateGrant"]These are not Shamir-share permissions. They belong only to the DynamoDB table-encryption keys because DynamoDB calls KMS at the service layer on behalf of the caller.
KMS Key Policy
Section titled “KMS Key Policy”Each KMS key used for Shamir shares must have a key policy granting the signer role access. If you use the Terraform modules, this is configured automatically. For manual key creation:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "EnableIAMDelegation", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::ACCOUNT_ID:root" }, "Action": "kms:*", "Resource": "*" }, { "Sid": "AllowSignerRole", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::ACCOUNT_ID:role/SIGNER_ROLE_NAME" }, "Action": ["kms:Encrypt", "kms:Decrypt", "kms:DescribeKey"], "Resource": "*" } ]}{ "Version": "2012-10-17", "Statement": [ { "Sid": "EnableIAMDelegation", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::SECONDARY_ACCOUNT_ID:root" }, "Action": "kms:*", "Resource": "*" }, { "Sid": "AllowSignerRole", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::PRIMARY_ACCOUNT_ID:role/SIGNER_ROLE_NAME" }, "Action": ["kms:Encrypt", "kms:Decrypt", "kms:DescribeKey"], "Resource": "*" } ]}For KMS keys in a secondary account, the key policy grants both encrypt and decrypt to the primary signer role.
IRSA Setup (EKS)
Section titled “IRSA Setup (EKS)”On EKS, use IAM Roles for Service Accounts (IRSA) or EKS Pod Identity to bind the IAM role to your signer pod without static credentials.
ServiceAccount annotation (IRSA):
apiVersion: v1kind: ServiceAccountmetadata: name: containment-chamber namespace: validators annotations: eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/ROLE_NAMETrust policy for the IAM role:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.REGION.amazonaws.com/id/OIDC_ID" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks.REGION.amazonaws.com/id/OIDC_ID:sub": "system:serviceaccount:validators:containment-chamber", "oidc.eks.REGION.amazonaws.com/id/OIDC_ID:aud": "sts.amazonaws.com" } } } ]}The signer uses the standard AWS SDK credential chain, so IRSA works without any configuration changes. See the Kubernetes deployment guide for the full pod spec and ServiceAccount setup.
Cross-Account KMS
Section titled “Cross-Account KMS”For maximum custody separation, split the master key across KMS keys in separate AWS accounts. A 2-of-3 setup means any two accounts can reconstruct the master key, but no single account can do so alone.
The strongest pattern is N KMS keys in N AWS accounts, with each account administered by a different person or team. The signer stores one Shamir share per KMS key, so separating both the keys and the administrators makes unilateral recovery much harder: one compromised account, one malicious admin, or one accidental key-policy change is not enough to decrypt the master key.
Match the Shamir threshold to your governance model. For example, 2-of-3 tolerates one unavailable account while preventing any single account from recovering the master key. 3-of-5 raises the collusion bar but requires more accounts to be healthy during unseal and rotation.
Account A (primary, runs signer) KMS Key 1 + DynamoDB table + IAM signer role
Account B (secondary custody) KMS Key 2 — key policy grants kms:Encrypt and kms:Decrypt to Account A signer role
Account C (tertiary custody) KMS Key 3 — key policy grants kms:Encrypt and kms:Decrypt to Account A signer roleThe terraform/examples/multi-account/ example uses provider aliases to create KMS keys in secondary accounts with cross-account key policies, and adds all key ARNs to the signer IAM role policy.
See the KMS Key Management guide for the full multi-account deployment sequence.
Terraform Modules
Section titled “Terraform Modules”| Example | What it creates |
|---|---|
terraform/examples/single-account/ |
DynamoDB tables (keystore + antislashing), 3 KMS Shamir keys, table encryption CMKs, IAM role with instance profile — all in one account |
terraform/examples/multi-account/ |
Same resources, but KMS keys split across 3 AWS accounts using provider aliases with cross-account key policies |
Each example includes a README with usage instructions and the matching signer config.yaml.
Next Steps
Section titled “Next Steps”- DynamoDB Key Source — configure encrypted key storage and the matching IAM role
- Enclave Deployment — deploy the signer with the IAM policies you just defined
- AWS KMS Permissions — Shamir-share KMS actions, DynamoDB table-encryption KMS actions, and attestation-bound decryption

