Skip to main content

Overview

KMS (Key Management Service) provides encryption keys to protect data at rest. While Fjall and AWS use default encryption automatically, you can create custom KMS keys when you need:
  • Customer-managed key rotation
  • Cross-account data access
  • Compliance requirements for key ownership
  • Granular access control

Default Encryption

Most AWS services use encryption by default:
// S3 - automatically encrypted with AWS managed keys
const bucket = new Bucket(this, "Data");

// RDS - automatically encrypted
const database = app.addDatabase(
  DatabaseFactory.build("DB", {
    type: "Instance",
    databaseName: "mydb",
  })
);

// Secrets Manager - automatically encrypted
// No KMS key needed for basic use

When to Create Custom Keys

Create explicit KMS keys for:
  • Compliance - Need customer-managed keys (CMK)
  • Key rotation control - Manage rotation schedule
  • Cross-account sharing - Share encrypted data
  • Audit requirements - Detailed CloudTrail logging
  • Cost allocation - Track encryption costs per key

Custom KMS Keys

Create a Key

import { Key } from "aws-cdk-lib/aws-kms";

const key = new Key(this, "DataKey", {
  description: "Application data encryption",
  enableKeyRotation: true,
  alias: "alias/myapp-data",
});

Use with S3

import { Bucket, BucketEncryption } from "aws-cdk-lib/aws-s3";

const bucket = new Bucket(this, "EncryptedBucket", {
  bucketName: "myapp-encrypted-data",
  encryption: BucketEncryption.KMS,
  encryptionKey: key,
  bucketKeyEnabled: true, // Reduce KMS costs
});

// Grant access to ECS
key.grantEncryptDecrypt(api.taskRole);
bucket.grantReadWrite(api.taskRole);

Use with Secrets Manager

import { Secret } from "aws-cdk-lib/aws-secretsmanager";

const secret = new Secret(this, "ApiKey", {
  secretName: "myapp/api-key",
  encryptionKey: key,
});

// Grant Lambda access
key.grantDecrypt(lambda.role);
secret.grantRead(lambda.role);

Use with RDS

// For manual RDS creation
import { DatabaseInstance } from "aws-cdk-lib/aws-rds";

const dbKey = new Key(this, "DbKey", {
  description: "Database encryption",
  enableKeyRotation: true,
});

const database = new DatabaseInstance(this, "Database", {
  // ... database config
  storageEncrypted: true,
  storageEncryptionKey: dbKey,
});

Granting Access

Lambda Access

const lambda = app.addCompute(
  ComputeFactory.build("Processor", {
    type: "lambda",
    handler: "index.handler",
    runtime: Runtime.NODEJS_20_X,
    code: Code.fromAsset("./lambda"),
    inlinePolicy: {},
  })
);

// Grant encrypt/decrypt
key.grantEncryptDecrypt(lambda.role);

// Or just decrypt
key.grantDecrypt(lambda.role);

ECS Access

const api = app.addCompute(
  ComputeFactory.build("API", {
    type: "ecs",
    ecsType: "fargate",
  })
);

// Grant to task role
key.grantEncryptDecrypt(api.taskRole);

Key Aliases

Aliases make keys easier to reference:
const key = new Key(this, "AppKey", {
  alias: "alias/myapp-key",
});

// Import by alias elsewhere
import { Key } from "aws-cdk-lib/aws-kms";

const existingKey = Key.fromLookup(this, "Key", {
  aliasName: "alias/myapp-key",
});

Common Patterns

Shared Encryption Key

One key for multiple resources:
const appKey = new Key(this, "AppKey", {
  description: "Application encryption key",
  enableKeyRotation: true,
  alias: "alias/myapp",
});

// Use for S3
const uploadBucket = new Bucket(this, "Uploads", {
  encryptionKey: appKey,
});

// Use for Secrets
const apiKey = new Secret(this, "ApiKey", {
  encryptionKey: appKey,
});

// Grant access once
appKey.grantEncryptDecrypt(api.taskRole);

Environment-Specific Keys

const env = process.env.ENVIRONMENT || "dev";

const key = new Key(this, "Key", {
  description: `${env} environment encryption`,
  alias: `alias/myapp-${env}`,
  enableKeyRotation: env === "prod",
});

Cross-Account Access

import { AccountPrincipal } from "aws-cdk-lib/aws-iam";

key.grantDecrypt(new AccountPrincipal("123456789012"));

Key Rotation

Automatic Rotation

const key = new Key(this, "RotatingKey", {
  enableKeyRotation: true, // Rotates annually
});
Automatic rotation:
  • Creates new cryptographic material
  • Keeps old versions for decryption
  • Transparent to applications
  • Recommended for production

Cost Optimization

KMS Pricing

  • Customer-managed keys: $1/month per key
  • API requests: $0.03 per 10,000 requests

Reduce Costs

1. Use S3 Bucket Keys - Reduces API calls by 99%:
const bucket = new Bucket(this, "Bucket", {
  encryption: BucketEncryption.KMS,
  encryptionKey: key,
  bucketKeyEnabled: true, // Save money
});
2. Share keys across resources:
// One key for all buckets instead of one per bucket
appKey.use(bucket1, bucket2, bucket3);
3. Use AWS managed keys for non-sensitive data:
// Free (no per-key charge)
const bucket = new Bucket(this, "Logs", {
  encryption: BucketEncryption.KMS_MANAGED, // aws/s3
});

Accessing Encrypted Data

From Lambda

// AWS SDK automatically decrypts with IAM permissions
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client();

export const handler = async (event) => {
  // Automatically decrypts if role has kms:Decrypt
  const object = await s3.send(
    new GetObjectCommand({
      Bucket: process.env.BUCKET_NAME,
      Key: "data.json",
    })
  );
  
  return { statusCode: 200 };
};

From ECS

// Same - automatic decryption with role permissions
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");

const client = new SecretsManagerClient();

async function getSecret() {
  const response = await client.send(
    new GetSecretValueCommand({
      SecretId: process.env.SECRET_ARN,
    })
  );
  return JSON.parse(response.SecretString);
}

Monitoring

Key usage is logged to CloudTrail:
  • Encrypt - data encryption operations
  • Decrypt - data decryption operations
  • GenerateDataKey - envelope encryption
  • ScheduleKeyDeletion - key deletion
// View logs in CloudTrail for key usage patterns

Best Practices

  • Use default encryption for most use cases (AWS managed keys)
  • Enable key rotation for customer-managed keys
  • Create one key per environment (dev, staging, prod)
  • Share keys across resources to reduce costs
  • Use S3 Bucket Keys to minimize API calls
  • Grant minimal permissions (decrypt-only when possible)
  • Use aliases for easier key management
  • Monitor CloudTrail for unexpected usage
  • Set RemovalPolicy.RETAIN for production keys

Troubleshooting

Access Denied on Encrypted Resources

  1. Check IAM role has kms:Decrypt permission
  2. Verify key policy allows the role
  3. Confirm resource uses the expected key
  4. Review CloudTrail for detailed error

High KMS Costs

  • Enable S3 Bucket Keys (bucketKeyEnabled: true)
  • Share keys across resources
  • Cache decrypted values in application
  • Consider AWS managed keys for non-sensitive data

Key Not Found

  • Verify key exists in the same region
  • Check key alias is correct
  • Ensure key hasn’t been scheduled for deletion

See Also