Search code examples
amazon-web-servicesterraformterraform-provider-awsamazon-kms

Granting cloudtrails access to kms keys in terraform


My first attempt at doing this resulted in a circular reference:

resource "aws_kms_key" "cloudtrails-key" {
  description = "KMS Master Key for trails logs"
  key_usage = "ENCRYPT_DECRYPT"
  customer_master_key_spec = "SYMMETRIC_DEFAULT"
  deletion_window_in_days = 30
  is_enabled = true
  enable_key_rotation = false
  policy = data.aws_iam_policy_document.cloudtrails-key-policy.json
  multi_region = true
  
  tags = {
    ManagedBy                     = "terraform"
    Name                          = "${var.project_name}-${var.environment}-${var.aws_account_id}-cloudtrails-key"
    Environment                   = var.environment
    Usage                         = "monitoring"
  }
}

data "aws_iam_policy_document" "cloudtrails-key-policy" {
  statement {
    principals {
      type        = "Service"
      identifiers = [ "cloudtrail.amazonaws.com" ]
    }

    actions = [
      "kms:CallerAccount",
      "kms:EncryptionAlgorithm",
      "kms:EncryptionContext:*",
      "kms:EncryptionContextKeys",
      "kms:RequestAlias",
      "kms:ViaService"
    ]

    resources = [ aws_kms_key.cloudtrails-key.arn ]

    condition {
     test     = "ForAnyValue:StringLike"
     variable = "aws:SourceArn"
     values   = toset([for k, v in var.all_account_ids : "arn:aws:cloudtrail:*:${v}:trail/full-cloudtrails"])
    }
  }
}

As I couldn't determine how to remove the circular reference, I decided to try using grants instead:

resource "aws_kms_grant" "cloudtrails-key-grant" {
  depends_on = [ aws_kms_key.cloudtrails-key ]
  for_each = var.all_account_ids
  name = "${each.key}-cloudtrails-key-grant"
  key_id = aws_kms_key.cloudtrails-key.key_id
  grantee_principal = "arn:aws:cloudtrail::${each.value}:trail/full-cloudtrails"
  operations = [ "Encrypt" ]
}

var.all_account_ids refers to a map of account names and account IDs.

I then updated the aws_kms_key block to remove the policy argument. However, this resulted instead in the following output on apply:

Terraform v1.3.8
on linux_amd64
aws_kms_grant.cloudtrails-key-grant["users"]: Creating...
aws_kms_grant.cloudtrails-key-grant["root"]: Creating...
aws_kms_grant.cloudtrails-key-grant["dev"]: Creating...
aws_kms_grant.cloudtrails-key-grant["shared"]: Creating...
aws_kms_grant.cloudtrails-key-grant["monitoring"]: Creating...
aws_cloudtrail.full-cloudtrails: Modifying... [id=full-cloudtrails]

...

aws_kms_grant.cloudtrails-key-grant["users"]: Still creating... [2m50s elapsed]
aws_kms_grant.cloudtrails-key-grant["monitoring"]: Still creating... [2m50s elapsed]
╷
│ Error: updating CloudTrail Trail (full-cloudtrails): InsufficientEncryptionPolicyException: Insufficient permissions to access S3 bucket full-cloudtrails or KMS key arn:aws:kms:us-east-1:703617067304:key/mrk-c0a4616ba4f74ae9b5ac4ecd1f96e95b.
│ 
│   with aws_cloudtrail.full-cloudtrails,
│   on monitoring.tf line 1, in resource "aws_cloudtrail" "full-cloudtrails":
│    1: resource "aws_cloudtrail" "full-cloudtrails" {
│ 
╵
╷
│ Error: creating KMS Grant for Key (mrk-c0a4616ba4f74ae9b5ac4ecd1f96e95b): InvalidArnException: ARN is not valid: arn:aws:cloudtrail:us-east-1:323924548037:trail/full-cloudtrails
│ 
│   with aws_kms_grant.cloudtrails-key-grant["root"],
│   on security.tf line 19, in resource "aws_kms_grant" "cloudtrails-key-grant":
│   19: resource "aws_kms_grant" "cloudtrails-key-grant" {
│ 
╵
╷
│ Error: creating KMS Grant for Key (mrk-c0a4616ba4f74ae9b5ac4ecd1f96e95b): InvalidArnException: ARN is not valid: arn:aws:cloudtrail:us-east-1:739800733271:trail/full-cloudtrails
│ 
│   with aws_kms_grant.cloudtrails-key-grant["shared"],
│   on security.tf line 19, in resource "aws_kms_grant" "cloudtrails-key-grant":
│   19: resource "aws_kms_grant" "cloudtrails-key-grant" {
│ 
╵
╷
│ Error: creating KMS Grant for Key (mrk-c0a4616ba4f74ae9b5ac4ecd1f96e95b): InvalidArnException: ARN is not valid: arn:aws:cloudtrail:us-east-1:703617067304:trail/full-cloudtrails
│ 
│   with aws_kms_grant.cloudtrails-key-grant["monitoring"],
│   on security.tf line 19, in resource "aws_kms_grant" "cloudtrails-key-grant":
│   19: resource "aws_kms_grant" "cloudtrails-key-grant" {
│ 
╵
╷
│ Error: creating KMS Grant for Key (mrk-c0a4616ba4f74ae9b5ac4ecd1f96e95b): InvalidArnException: ARN is not valid: arn:aws:cloudtrail:us-east-1:457407591303:trail/full-cloudtrails
│ 
│   with aws_kms_grant.cloudtrails-key-grant["dev"],
│   on security.tf line 19, in resource "aws_kms_grant" "cloudtrails-key-grant":
│   19: resource "aws_kms_grant" "cloudtrails-key-grant" {
│ 
╵
╷
│ Error: creating KMS Grant for Key (mrk-c0a4616ba4f74ae9b5ac4ecd1f96e95b): InvalidArnException: ARN is not valid: arn:aws:cloudtrail:us-east-1:158889104371:trail/full-cloudtrails
│ 
│   with aws_kms_grant.cloudtrails-key-grant["users"],
│   on security.tf line 19, in resource "aws_kms_grant" "cloudtrails-key-grant":
│   19: resource "aws_kms_grant" "cloudtrails-key-grant" {
│ 
╵

I tried adding the region (though I would prefer multi-region), taking off the trail name after the slash, using wildcards for region, but everything resulted in the above invalid ARN exception.

I was curious to know if anyone has any working recipes for granting cloudtrails encryption access to a KMS key, as I'm unsure what I'm doing wrong with either method (policy or grant)


Solution

  • The solution for the cyclical reference was to broaden the resource scope to all keys, and not just the one key. This was done on all examples in AWS documentation. I also had to add the default key policy offered by AWS otherwise I got a warning that I would be locking myself out.

    The Default Key Policy can be found documented here. The specific key policy required for CloudTrail is found here. Using these two resources, the final working configuration is below:

    data "aws_iam_policy_document" "cloudtrail-key-policy" {
      statement {
        principals {
          type        = "Service"
          identifiers = [ "cloudtrail.amazonaws.com" ]
        }
    
        actions = [
          "kms:DescribeKey",
          "kms:GenerateDataKey*"
        ]
    
        resources = [ "*" ]
    
        condition {
          test      = "ForAnyValue:StringLike"
          variable  = "kms:EncryptionContext:aws:cloudtrail:arn"
          values    = toset([for k, v in var.all_account_ids : "arn:aws:cloudtrail:*:${v}:trail/${k}-full-cloudtrail"])
        }
    
        condition {
          test      = "ForAnyValue:StringLike"
          variable  = "aws:SourceArn"
          values    = toset([for k, v in var.all_account_ids : "arn:aws:cloudtrail:*:${v}:trail/${k}-full-cloudtrail"])
        }
      }
    
      statement {
        principals {
          type        = "AWS"
          identifiers = [ "arn:aws:iam::${var.aws_account_id}:role/terraform-${var.environment}" ]
        }
    
        actions = [
          "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"
        ]
    
        resources = [ "*" ]
      }
    
      statement {
        principals {
          type        = "AWS"
          identifiers = [ "arn:aws:iam::${var.aws_account_id}:root" ]
        }
    
        actions = [ "kms:*" ]
        resources = [ "*" ]
      }
    }
    

    Additionally, no grants were needed as we're doing this for a service. According to the documentation, service ARNs are excluded:

    The grantee principal can be any AWS principal, including an AWS account (root), an IAM user, an IAM role, a federated role or user, or an assumed role user. The grantee principal can be in the same account as the KMS key or a different account. However, the grantee principal cannot be a service principal, an IAM group, or an AWS organization

    The above text can be found here

    Hopefully this helps future readers with the same issue.

    EDIT: Updated with final working config