How to restrict default access to KMS via key policy
Tags: aws, kms, security
Published: 2024-03-31 23:49:31

## About AWS Key Management Service

AWS Key Management Service (AWS KMS) is a managed service that makes it easy for you to create and control the cryptographic keys that are used to protect your data.

The KMS key policy allows IAM identities in the account to access the KMS key with IAM permissions.

The objective of this article is to implement secure of KMS key from access by AWS Identity and Access Management (IAM) identities.

**Note**: This article demonstrates the AWS account ID `123456789012` with existing role named `TERRAFORM`, `ADMIN` and `ANALYST`. These values must be replaced for your environment.

## The default KMS Authorization behaviour

The default KMS key policy contains the following statement:
```json
{
    "Sid": "Enable IAM User Permissions",
    "Effect": "Allow",
    "Principal": {
        "AWS": "arn:aws:iam::123456789012:root"
    },
    "Action": "kms:*",
    "Resource": "*"
}
```

By default KMS policy allow caller's account to use IAM policy to control key access.

The Effect and Principal elements do not refer to the AWS root user account. Instead, it allows any principal in AWS account `123456789012` to have root access to the KMS key as long as you have attached the required permissions to the IAM entity.

## Implement secure KMS policy

Example of restricted key policy:

```json
{
    "Sid": "Enable root access and prevent permission delegation",
    "Effect": "Allow",
    "Principal": {
        "AWS": "arn:aws:iam::123456789012:root"
    },
    "Action": "kms:*",
    "Resource": "*",
    "Condition": {
        "StringEquals": {
            "aws:PrincipalType": "Account"
        }
    }
},
{
    "Sid": "Allow access for key administrators",
    "Effect": "Allow",
    "Principal": {
        "AWS": [
            "arn:aws:iam::123456789012:role/TERRAFORM",
            "arn:aws:iam::123456789012:role/ADMIN"
        ]
    },
    "Action": [
        "kms:Create*",
        "kms:Describe*",
        "kms:Enable*",
        "kms:List*",
        "kms:Put*",
        "kms:Update*",
        "kms:Revoke*",
        "kms:Disable*",
        "kms:Get*",
        "kms:Delete*",
        "kms:TagResource",
        "kms:UntagResource",
        "kms:ScheduleKeyDeletion",
        "kms:CancelKeyDeletion"
    ],
    "Resource": "*"
},
{
    "Sid": "Enable read access to all identities",
    "Effect": "Allow",
    "Principal": {
        "AWS": "arn:aws:iam::123456789012:root"
    },
    "Action": [
        "kms:List*",
        "kms:Get*",
        "kms:Describe*"
    ],
    "Resource": "*"
}
```

The key policy allows the following permissions:

- **First statement**: The AWS root user account has full access to the key.
- **Second statement**: The principals role `ADMIN` and `TERRAFORM` has access to perform management operations on the key.
- **Third statement** All account principals are able to read the key.

This way we can ensure that the root user account manages the key and prevents IAM entities from accessing the KMS key.

You can also allow IAM users or roles to use the key for cryptographic operations and with other AWS services by appending other statements like this:

```json
{
    "Sid": "Allow analyst role use the key",
    "Effect": "Allow",
    "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/ANALYST"
    },
    "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
    ],
    "Resource": "*"
},
{
    "Sid": "Allow sms service use the key",
    "Effect": "Allow",
    "Principal": {
        "Service": "sns.amazonaws.com"
    },
    "Action": [
        "kms:GenerateDataKey*",
        "kms:Decrypt"
    ],
    "Resource": "*"
}
```

So if the KMS is configured in this way, its access will be restricted and only those identities directly specified in the key policy will have access.

Check out the completed code example:

```hcl
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Enable root access and prevent permission delegation",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:root"
            },
            "Action": "kms:*",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:PrincipalType": "Account"
                }
            }
        },
        {
            "Sid": "Allow access for key administrators",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::123456789012:role/TERRAFORM",
                    "arn:aws:iam::123456789012:role/ADMIN"
                ]
            },
            "Action": [
                "kms:Create*",
                "kms:Describe*",
                "kms:Enable*",
                "kms:List*",
                "kms:Put*",
                "kms:Update*",
                "kms:Revoke*",
                "kms:Disable*",
                "kms:Get*",
                "kms:Delete*",
                "kms:TagResource",
                "kms:UntagResource",
                "kms:ScheduleKeyDeletion",
                "kms:CancelKeyDeletion"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Enable read access to all identities",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:root"
            },
            "Action": [
                "kms:List*",
                "kms:Get*",
                "kms:Describe*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow use of the key",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:role/ANALYST"
            },
            "Action": [
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:Encrypt",
                "kms:DescribeKey",
                "kms:Decrypt"
            ],
            "Resource": "*"
        }
    ]
}
```