BYOK (AWS KMS) PAID
Bring your own key (BYOK) adds an extra encryption layer to supported stored vault records, using a key you control in Amazon Web Services Key Management Service (AWS KMS). Available under an Enterprise contract.
Your client encrypts the content first. BYOK then wraps those encrypted bytes on the server with a per-record AES-256-GCM data encryption key (DEK); your AWS KMS customer-managed key (CMK) protects the DEK. A copy of the database alone does not expose the unwrapped vault content. Revoking KMS access blocks future unwrapping, not copies already obtained.
Storage scope:
vault_items.encrypted_data: standard vault records, including writes through the API and dashboard. The separate Managed Vault API above does not apply this wrapping.
The encrypt/decrypt API surface (/api/encrypt, /api/decrypt) is unaffected because nothing on those paths is server-stored.
For your engineers: an ARN (Amazon Resource Name) identifies an AWS resource. IAM (Identity and Access Management) defines the role and permissions. STS (Security Token Service) lets deny.sh assume that role temporarily.
Setup walkthrough
Full step-by-step at /byok-walkthrough. Four steps in summary:
- Create a symmetric AES-256 CMK in AWS KMS in the region you want.
- Create an IAM role in your account with a trust policy naming deny.sh's account as principal, an
sts:ExternalIdcondition with the external ID shown in your BYOK dashboard, and permissionskms:GenerateDataKey+kms:Decryptscoped to your CMK ARN. Mirror the same allow in the CMK's key policy. - Paste both ARNs plus the region into /dashboard/byok.
- Click Register. We immediately STS AssumeRole and roundtrip a verify call. On success, your state flips to
active.
IAM trust policy template
Show policy template and notes
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowDenyShAssumeRole",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::<DENY_SH_AWS_ACCOUNT_ID>:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "<YOUR_EXTERNAL_ID>" }
}
}]
}
The ExternalId condition ties access to your customer workspace, so another customer cannot ask deny.sh to assume your role. Both values are shown in your BYOK dashboard.
IAM role inline permission policy
Show policy template and notes
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyShEnvelopeOps",
"Effect": "Allow",
"Action": [ "kms:GenerateDataKey", "kms:Decrypt" ],
"Resource": "<YOUR_CMK_ARN>"
}]
}
KMS key policy addition
Show policy template and notes
Configure the role permissions and the key policy so this role can use your key. This example grants the role access in the key policy:
{
"Sid": "AllowDenyShRoleToUseKey",
"Effect": "Allow",
"Principal": { "AWS": "<YOUR_ROLE_ARN>" },
"Action": [ "kms:GenerateDataKey", "kms:Decrypt", "kms:DescribeKey" ],
"Resource": "*"
}
Config states
| state | meaning |
|---|---|
pending_verification | Config registered, verify roundtrip in flight. |
active | Verify succeeded. New writes envelope-wrap. Reads unwrap on demand. |
failed | Verify failed. See failure_reason for the error code. Existing rows unaffected. |
revoked | Config disabled. New writes requiring BYOK are blocked. Reads of wrapped historical rows return BYOK_UNAVAILABLE until re-registered. |
Failure-mode reference
| code | meaning | retryable |
|---|---|---|
BYOK_ROLE_NOT_ASSUMED | STS AssumeRole denied. Trust policy missing or condition mismatch. | no |
BYOK_KEY_NOT_FOUND | CMK ARN doesn't resolve. Wrong account, wrong region, or deleted. | no |
BYOK_KEY_DISABLED | CMK exists but is disabled or pending deletion. | no |
BYOK_REGION_MISMATCH | CMK ARN's region differs from the registered region. | no |
BYOK_PERMISSION_DENIED | Role assumed but kms:GenerateDataKey or kms:Decrypt missing. | no |
BYOK_UNAVAILABLE | KMS unavailable or the required configuration is unavailable. Restore access for revoked configurations; retry transient service failures. | depends on cause |
BYOK_ENVELOPE_CORRUPT | Stored envelope failed integrity check. Should never happen. | no |
Routes
All BYOK routes require a signed-in consumer session (cs_*) from /login, sent in the Bearer header, and BYOK-enabled account access. Only the account owner can register, verify, rotate or revoke the configuration. Cookies are not accepted for this authentication.
POST Register BYOK config
POST /v1/byok/config
Authorization: Bearer cs_your_session_token
{
"cmkArn": "arn:aws:kms:us-east-1:<12-digit-account-id>:key/<uuid>",
"iamRoleArn": "arn:aws:iam::<12-digit-account-id>:role/deny-sh-byok-role",
"region": "us-east-1"
}
201
{
"config": {
"state": "active",
"cmk_arn": "<...>",
"iam_role_arn": "<...>",
"region": "us-east-1",
"verified_at": 1764000000
}
}
GET Read BYOK config
GET /v1/byok/config
200
{ "config": { "cmk_arn": "...", "state": "active", "verified_at": ..., ... } }
POST Re-verify BYOK config
Forces a fresh STS AssumeRole + KMS roundtrip. Useful after rotating the IAM role or updating the key policy. State updates to active or failed on completion.
POST /v1/byok/verify
200
{ "config": { "state": "active", "verified_at": ..., ... } }
DELETE Revoke BYOK config
Sets state to revoked. New writes requiring BYOK are blocked; reads of historical wrapped rows return BYOK_UNAVAILABLE. Restore the same key configuration and permissions to regain access. Copies already obtained cannot be recalled.
DELETE /v1/byok/config
200
{ "config": { "state": "revoked", ... } }
Responses above show selected fields. GET /v1/byok/config returns { "config": null } if no configuration exists. An active configuration cannot be overwritten through registration; use POST /v1/byok/rotate to change its key, role or region. See the BYOK walkthrough.
Audit-chain op vocabulary
Every BYOK lifecycle event lands in the hash-chained audit log. Filter /dashboard/audit by op_type prefix byok. to see just BYOK ops. ARN suffixes only (last 12 chars) appear in payloads; never the full ARN.
| op_type | fires when |
|---|---|
byok.config.created | Customer registers a CMK + role. |
byok.config.verified | Roundtrip succeeds; state moves to active. |
byok.config.rotated | Customer rotates to a new version of the key configuration. |
byok.config.revoked | Customer revokes; future unwrapping is blocked. |
byok.envelope.created | A vault write was wrapped. |
byok.envelope.unwrapped | A vault read was unwrapped. |
byok.kms.failure | Any STS or KMS error during the above ops. Payload carries the error code. |
You also see our calls on your side: every kms:GenerateDataKey and kms:Decrypt shows in your AWS CloudTrail under the IAM role you provisioned, with our STS session name as deny-sh-byok-<tenant-prefix>. Cross-reference our hash-chained audit and your CloudTrail any time.