Right, let’s talk about Key Policies. This is where the rubber meets the road for your CMKs. IAM policies are great, but they’re global. A Key Policy is a resource-based policy you attach directly to the CMK itself, and it’s the final, most powerful authority on who can do what with this specific key. Think of IAM as the bouncer at the club’s front door, but the Key Policy is the specific, unbreakable rule from the owner that says, “This VIP must be allowed into the backstage area, no matter what any other bouncer says.”

The first thing you need to wrap your head around is this: if you don’t explicitly define a key policy when you create a CMK, AWS will generate a default one for you. And while it’s well-intentioned, it’s a bit like your overprotective aunt—it means well but can seriously get in your way later. The default policy gives the root user of the AWS account (that’s you, wearing your God-mode hat) full admin permissions over the key. It also allows any IAM user or role in the account to use the key for cryptographic operations… but only if their IAM policies also allow it. This two-key system (IAM + Key Policy) is a security feature, but it’s a common source of head-scratching “why can’t I do this?!” moments.

The Anatomy of a Key Policy

A key policy is a JSON document, and its structure is non-negotiable. Let’s break down a robust, custom one you’d write yourself. We’ll make one that allows an IAM role to use the key and allows an admin to manage it.

{
    "Id": "my-custom-key-policy",
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "EnableIAMUserPermissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "AllowUseByMyAppRole",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:role/my-encryption-app-role"
            },
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey",
                "kms:DescribeKey"
            ],
            "Resource": "*"
        },
        {
            "Sid": "AllowAttachmentOfResources",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:role/my-encryption-app-role"
            },
            "Action": "kms:CreateGrant",
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "kms:GrantIsForAWSResource": "true"
                }
            }
        }
    ]
}

Notice the Principal field. This is crucial. In a key policy, you must specify the Principal. This is the fundamental difference from an identity-based IAM policy. You’re saying, “This specific entity (user, role, account, etc.) is allowed to perform these actions.” The kms:GrantIsForAWSResource condition is a best practice safety catch; it prevents the role from creating grants willy-nilly and only allows it when the grant is for a supported AWS service like EBS or S3.

The Superpower: Allowing Other AWS Accounts

Here’s where key policies become indispensable. Want to let another AWS account encrypt data with your key? You can’t do that with IAM. You must do it with a key policy. You add a statement where the Principal is the other account’s root or a specific role in that other account.

{
    "Sid": "AllowCrossAccountEncrypt",
    "Effect": "Allow",
    "Principal": {
        "AWS": "arn:aws:iam::111122223333:root"
    },
    "Action": [
        "kms:Encrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
    ],
    "Resource": "*"
}

Now, the account 111122223333 can use this key, but only if their own IAM policies also grant permission. Again, both policies must allow the action. It’s a two-man rule for cross-account security.

The #1 Pitfall: The Key Admin Paradox

AWS, in its infinite wisdom, decided that having kms:PutKeyPolicy permission is the ultimate key admin power. It’s a classic “who guards the guardians?” situation. Here’s the catch: if you write a key policy that accidentally revokes your own kms:PutKeyPolicy permission, you have effectively lobotomized the key. You can’t give the permission back to yourself via the key policy because you no longer have the permission to change the key policy. It’s a perfect, self-inflicted lock-out. The only way back from this is to use the root user credentials of the account, which is why that default policy giving root full access is both a curse and a safety net. My advice? Test your key policies in a dev account first. Always have a statement that explicitly grants a trusted principal full kms:* access. Never remove the root principal’s access unless you have a terrifyingly good reason and a documented break-glass procedure.

Best Practices: Your Policy on Policies

  1. Never Rely on the Default: Always define a custom key policy. The default is a trap for the unwary.
  2. Be Explicity, Not Implicit: Clearly list the actions and principals. Avoid wildcards in Action for production keys unless you truly mean “all KMS actions.”
  3. Use Conditions as Safeguards: The kms:GrantIsForAWSResource and kms:ViaService conditions are your friends. They limit permissions to specific, intended use cases.
  4. The Principle of Least Privilege: This isn’t just a buzzword. Your Lambda function doesn’t need kms:ScheduleKeyDeletion. Give it only the permissions it needs (Encrypt, Decrypt, GenerateDataKey).
  5. Version Control Your Policies: These are critical security documents. Store them in Git, review changes, and use infrastructure-as-code (like CloudFormation or Terraform) to apply them. Manually clicking in the console is a recipe for disaster.

Master key policies, and you move from just using KMS to actually controlling it. It’s the difference between renting an apartment and owning the building—you get to make the rules.