# KMS keys for PHI protection: # - field key: app-level AES-256-GCM envelope encryption (PHI_KMS_KEY_ID) # and SSE-KMS at rest for RDS/S3/SQS. The ECS task role is granted via the # key policy (app_principal_arns); the RDS service principal needs its own # grant because storage encryption runs server-side in AWS. # - pseudonymization key: case_id HMAC for the offshore review boundary # Both have automatic rotation enabled; rotation is free for the envelope # format because each ciphertext records its own key version. data "aws_caller_identity" "current" {} data "aws_partition" "current" {} data "aws_region" "current" {} locals { # Default admin is the account root; operators may pin a specific IAM principal. admin_arn = var.admin_arn != "" ? var.admin_arn : "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root" field_alias_arn = "arn:${data.aws_partition.current.partition}:kms:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:alias/${var.name}-phi-field" pseudo_alias_arn = "arn:${data.aws_partition.current.partition}:kms:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:alias/${var.name}-pseudonymization" # RDS uses the field key for SSE-KMS; its service principal must be in the key policy. rds_principal_arn = "arn:${data.aws_partition.current.partition}:rds::${data.aws_caller_identity.current.account_id}:sourceregion:${data.aws_region.current.name}" } resource "aws_kms_key" "phi_field" { description = "PHI field encryption (enc:v1 envelopes) - ${var.name}" deletion_window_in_days = var.deletion_window_days enable_key_rotation = true policy = data.aws_iam_policy_document.key.json tags = merge(var.tags, { Name = "${var.name}-kms-phi-field" }) } resource "aws_kms_alias" "phi_field" { name = "alias/${var.name}-phi-field" target_key_id = aws_kms_key.phi_field.key_id } resource "aws_kms_key" "pseudonymization" { description = "PHI pseudonymization (case_id HMAC) - ${var.name}" deletion_window_in_days = var.deletion_window_days enable_key_rotation = true policy = data.aws_iam_policy_document.key.json tags = merge(var.tags, { Name = "${var.name}-kms-pseudonymization" }) } resource "aws_kms_alias" "pseudonymization" { name = "alias/${var.name}-pseudonymization" target_key_id = aws_kms_key.pseudonymization.key_id } data "aws_iam_policy_document" "key" { statement { sid = "AllowAccountAdmin" actions = ["kms:*"] resources = [local.field_alias_arn, local.pseudo_alias_arn] principals { type = "AWS" identifiers = [local.admin_arn] } } statement { sid = "AllowAppPrincipals" actions = ["kms:Encrypt", "kms:Decrypt", "kms:DescribeKey"] resources = [local.field_alias_arn, local.pseudo_alias_arn] dynamic "principals" { for_each = var.app_principal_arns content { type = "AWS" identifiers = [principals.value] } } } # Field key only: the app never needs to decrypt RDS data keys on its behalf. statement { sid = "AllowRDSServicePrincipal" actions = ["kms:CreateGrant", "kms:DescribeKey", "kms:GenerateDataKey*", "kms:Decrypt"] resources = [local.field_alias_arn] principals { type = "AWS" identifiers = [local.rds_principal_arn] } } }