Skip to main content

Overview

CustomerManagedKey provisions an AWS KMS customer-managed key (CMK) for protecting data at rest. It creates the key and its alias together, turns on automatic rotation, and resolves the key’s removal policy from the required protects prop.

Resource class

CustomerManagedKey, KEY_PROTECTION_SCOPES and the KeyProtectionScope type are exported. The props interface is not, so pass an inline object literal, or type a helper parameter with KeyProtectionScope.

Basic usage

Configuration options

CDK prefixes an alias with alias/ when the value omits it, so the default alias reaches AWS as alias/cmk/<id>. That is the string aws kms describe-key --key-id expects.

Choosing protects

protects is required and has no default, because the two answers fail in opposite directions and only one of the failures is visible.
  • "stack-scoped" means everything the key encrypts is deleted along with the stack that created it: an SSM SecureString parameter, a Secrets Manager secret, RDS performance-insights data. The key then follows the same env-aware default as every other Fjall wrapper, RETAIN in production, DESTROY everywhere else.
  • "outlives-stack" means at least one encrypted resource survives the stack: an RDS final snapshot, a retained audit bucket, a Backup vault’s recovery points. The key is RETAIN in every environment.
A key destroyed while something it encrypted still exists makes that thing permanently unreadable, and nothing detects it. The survivor stays listed, sized and restorable-looking right up to the moment you try to restore it. When in doubt pass "outlives-stack": a key costs a few dollars a month, and the failure in the other direction cannot be undone.
An unrecognised ENVIRONMENT throws at synth rather than guessing, since guessing DESTROY deletes data and guessing RETAIN leaks it. An explicit removalPolicy overrides the table and skips that check entirely.

Default features

Every CustomerManagedKey includes:
  • Automatic rotation: enableKeyRotation is always on. AWS rotates the backing key material annually. The construct exposes no option to turn it off.
  • Removal policy: resolved from protects (see the table above), or taken verbatim from an explicit removalPolicy.
  • Pending deletion window: 30 days whenever the key resolves to DESTROY. The key is disabled immediately and stays recoverable until the window elapses, long enough to cover the 30-day recovery window of a deleted Secrets Manager secret.
  • Retention marker tag: fjall:kms:retention, valued with the protects scope, on every key that resolves to RETAIN. Tags outlive the stack, so a posture scan can tell a key retained on purpose from one that was forgotten.
  • Automatic alias: created with the supplied or default name.
  • CloudFormation outputs: key ARN and alias ARN, both exported under a stack-scoped name.

Key components

The KMS key

The key alias

Key rotation

Rotation is on for every key the construct creates. No configuration is needed, and there is no prop to disable it. AWS rotates the key material once a year and keeps every previous version, so ciphertext written under an older version stays decryptable. Rotation is billed. See Cost considerations before creating a key per resource.

Removal policy

protects decides the policy (see Choosing protects). Pass removalPolicy only to override that, either to keep a non-production key that would otherwise be destroyed, or to destroy one in an environment the library cannot resolve.
Whenever the key resolves to RemovalPolicy.DESTROY, from protects or from an explicit override, the construct sets a 30-day pending window. AWS disables the key immediately, then deletes it after the window elapses. You can cancel the deletion during that window to recover the key and any data it protects. Prefer binding removalPolicy to the policy of the thing being encrypted rather than choosing independently. The CloudTrail construct passes the same storagePolicy to its bucket and its key precisely so the two cannot drift apart.

Outputs

The construct creates two CloudFormation outputs. <Id> below is the construct id run through toPascalCase, which strips the hyphens and underscores CloudFormation rejects in an output key. Export names are unique per account per region, so a name built only from a construct id collides between two applications that happen to share the id. Prefixing with the emitting stack’s name makes each export globally unique and puts the application name in it. Build a cross-stack import from the full stack-scoped name:

Common patterns

Database encryption

RDS constructs create their own keys only when you ask for a CMK with the USE_CMK marker. The storage key is created "outlives-stack" (an RDS stack teardown leaves a final snapshot behind) and the performance-insights key "stack-scoped".
To share one key across resources, create it yourself and pass it in:

S3 bucket encryption

Secrets Manager integration

The Secret construct creates its own "stack-scoped" key and exposes it as secretsCustomerManagedKey. The property is undefined when the secret was imported with importExisting: true, since an imported secret keeps the key of the stack that owns it.

EBS volume encryption

Key policies

Default key policy

The default policy grants the account root full access, so IAM policies in the same account govern who can use and administer the key.

Granting access

Granting read on an encrypted resource usually carries the KMS permissions with it, so grant on the resource first and reach for key.key.grant* only for principals the resource grant does not cover:

Cross-account access

Service integration

With Lambda

With CloudWatch Logs

Cost considerations

Pricing

Because rotation is always on, a key that has rotated twice converges on about **3.00permonth.Budgetforthatfigure,notfor3.00 per month**. Budget for that figure, not for 1.00, when deciding how many keys an application needs.

Cost optimisation

Share one key across resources with the same lifetime and the same audience.
Turn on S3 bucket keys. bucketKeyEnabled: true collapses per-object KMS calls into one call per bucket key, cutting request charges by up to 99%. Cache decrypted values in Lambda outside the handler so a warm invocation skips the KMS call.

Complete example

Best practices

  1. Pick protects from the lifetime of the data, not the key. If anything the key encrypts can outlive the stack, pass "outlives-stack".
  2. Separate keys by purpose (data, secrets, logs) so a grant to one audience does not reach another.
  3. Share a key across resources with the same lifetime to keep the per-key charge down.
  4. Use aliases so a rotated or replaced key does not require a config change.
  5. Grant least privilege. grantDecrypt for readers, grantEncryptDecrypt for writers, grantAdmin only for operators.
  6. Monitor key usage with CloudTrail. Every KMS API call is logged.
  7. Tag keys for cost allocation and posture scanning.

Security considerations

Key deletion protection

Restricting a key to specific services

Deny every use that does not arrive through the services you expect, so a leaked credential cannot call KMS directly.

Troubleshooting

Inspecting a key

Run AWS commands against a connected account with Fjall-minted credentials. fjall target list shows the available targets.

Next Steps

Secrets Manager

Store and rotate secrets encrypted with a customer-managed key.

S3 Bucket

Encrypt object storage at rest with your KMS key.

RDS Aurora

Provision an Aurora cluster with KMS storage and backup encryption.

IAM Role

Grant roles least-privilege encrypt and decrypt permissions.