Agent Wranglers

Recommended

Keep encryption keys in a key service

Encrypt stored data with keys held in a key management service that never hands out the key itself. Rotate each key automatically, and keep the people who manage keys apart from the roles that use them.

How to do it

Use the cloud's key service, such as AWS KMS, Google Cloud KMS or Azure Key Vault, or a self-hosted one such as OpenBao. Create each key and its policy in code. Give the app's role encrypt and decrypt rights on its own key only. Give the key admin role rights to manage keys but not to decrypt. Turn on automatic rotation every 365 days at most. Rotation keeps the old key versions, so old data still decrypts. A deleted key makes its data unreadable for good. Set the longest waiting period the service allows, 30 days on AWS, and alert on every scheduled deletion. Encrypt single fields in the app with the same keys, as Encrypt the most sensitive fields describes. Disks, databases and backups take their keys from here, as Encrypt data on the wire and on disk requires.

Examples

Using these examples: they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

Terraform example

resource "aws_kms_key" "orders" {
  description             = "Encrypts the orders database"
  enable_key_rotation     = true
  rotation_period_in_days = 365
  deletion_window_in_days = 30
}

resource "aws_cloudwatch_event_rule" "key_deletion" {
  name = "kms-key-scheduled-for-deletion"
  event_pattern = jsonencode({
    source = ["aws.kms"]
    detail = { eventName = ["ScheduleKeyDeletion", "DisableKey"] }
  })
}

Python example

import boto3

kms = boto3.client("kms")

def keys_without_rotation():
    for page in kms.get_paginator("list_keys").paginate():
        for key in page["Keys"]:
            meta = kms.describe_key(KeyId=key["KeyId"])["KeyMetadata"]
            if meta["KeyManager"] != "CUSTOMER" or meta["KeyState"] != "Enabled":
                continue
            if meta.get("KeySpec") == "SYMMETRIC_DEFAULT" and not kms.get_key_rotation_status(KeyId=key["KeyId"])["KeyRotationEnabled"]:
                yield key["KeyId"]

for key_id in keys_without_rotation():
    print(key_id)

TypeScript example

import { KMSClient, paginateListKeys, DescribeKeyCommand, GetKeyRotationStatusCommand } from '@aws-sdk/client-kms';

const kms = new KMSClient({});

for await (const page of paginateListKeys({ client: kms }, {})) {
  for (const { KeyId } of page.Keys ?? []) {
    const { KeyMetadata: meta } = await kms.send(new DescribeKeyCommand({ KeyId }));
    const isRotatableCustomerKey = meta.KeyManager === 'CUSTOMER' && meta.KeyState === 'Enabled' && meta.KeySpec === 'SYMMETRIC_DEFAULT';
    if (!isRotatableCustomerKey) continue;
    const { KeyRotationEnabled } = await kms.send(new GetKeyRotationStatusCommand({ KeyId }));
    if (!KeyRotationEnabled) console.log(KeyId);
  }
}

How to check

The script below lists no customer-managed key without automatic rotation. No key policy grants every key action to a role other than the key admin role. Schedule the deletion of a test key in a sandbox account, and the alert arrives within 15 minutes.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/infrastructure/configuration-and-secrets/key-management">Agent Wranglers infrastructure playbook</a>. In code you write, add a comment next to it with the same link.