Cuando intentamos configurar una alarma de CloudWatch para que envíe notificaciones a un topic SNS cifrado, podemos encontrarnos con un error como este, indicando que nos falta una autorización para poder comunicar los dos recursos:
Failed to execute action arn:aws:sns:eu-west-1:0123456789:alarms-topic.
Received error: "CloudWatch Alarms does not have authorization to access the SNS topic encryption key."
A primera vista, todo parece correcto: tenemos el tópico SNS creado, cifrado con la clave por defecto alias/aws/sns
, y la alarma configurada para usarlo. Sin embargo, las alarmas no llegan a ejecutarse. Vamos a ver como arreglar este error.
La causa
El problema está en la clave KMS.
- Cuando usas la clave gestionada por AWS (
alias/aws/sns
), no es posible editar su key policy. - Esa policy por defecto no concede permisos a CloudWatch Alarms para hacer llamadas como
kms:Decrypt
okms:GenerateDataKey*
. - Como resultado, la acción de la alarma falla en el momento de publicar en el tópico cifrado.
AWS lo documenta en re:Post: para que CloudWatch publique en un tópico SNS cifrado, es obligatorio usar una Customer Managed Key (CMK) con una policy explícita que otorgue acceso.
La solución
1. Crear una CMK para SNS
Creamos una clave simétrica en KMS y definimos una key policy que permita:
- A SNS usar la clave para el tópico concreto.
- A CloudWatch Alarms generar data keys y descifrar.
Ejemplo en Terraform:
resource "aws_kms_key" "sns_alarms" {
description = "CMK for SNS alarms"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Sid = "AllowRoot",
Effect = "Allow",
Principal = { AWS = "arn:aws:iam::0123456789:root" },
Action = "kms:*",
Resource = "*"
},
{
Sid = "AllowSNS",
Effect = "Allow",
Principal = { Service = "sns.amazonaws.com" },
Action = [
"kms:Encrypt","kms:Decrypt","kms:ReEncrypt*",
"kms:GenerateDataKey*","kms:DescribeKey"
],
Resource = "*",
Condition = {
StringEquals = { "AWS:SourceAccount": "0123456789" },
ArnLike = { "aws:SourceArn": "arn:aws:sns:eu-west-1:0123456789:alarms-topic" }
}
},
{
Sid = "AllowCloudWatchAlarms",
Effect = "Allow",
Principal = { Service = "cloudwatch.amazonaws.com" },
Action = ["kms:Decrypt","kms:GenerateDataKey*"],
Resource = "*",
Condition = {
StringEquals = { "AWS:SourceAccount": "0123456789" },
ArnLike = { "aws:SourceArn": "arn:aws:cloudwatch:eu-west-1:0123456789:alarm:*" }
}
}
]
})
}
2. Apuntar el tópico SNS a la CMK
En el recurso aws_sns_topic
cambiamos:
resource "aws_sns_topic" "alarms" {
name = "alarms-topic"
kms_master_key_id = aws_kms_key.sns_alarms.arn
}
3. Ajustar la policy del tópico SNS
Añadimos un statement para permitir que CloudWatch publique:
data "aws_iam_policy_document" "sns_topic_policy" {
statement {
sid = "AllowCloudWatchToPublish"
effect = "Allow"
actions = ["sns:Publish"]
resources = [aws_sns_topic.alarms.arn]
principals {
type = "Service"
identifiers = ["cloudwatch.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "AWS:SourceAccount"
values = ["0123456789"]
}
condition {
test = "ArnLike"
variable = "aws:SourceArn"
values = ["arn:aws:cloudwatch:eu-west-1:0123456789:alarm:*"]
}
}
}
4. Probar
Publicación manual al topic
aws sns publish \ --region eu-west-1 \ --topic-arn arn:aws:sns:eu-west-1:0123456789:alarms-topic \ --message "Test"
Forzar la alarma a estado ALARM
aws cloudwatch set-alarm-state \ --region eu-west-1 \ --alarm-name "eb-health-degraded-red" \ --state-value ALARM \ --state-reason "Test after CMK"
En ambos casos el mensaje debe entregarse con éxito.
Conclusión
El error "CloudWatch Alarms does not have authorization to access the SNS topic encryption key"
ocurre porque las AWS-managed keys (alias/aws/sns
) no permiten a CloudWatch usar la clave.
La solución definitiva es usar una CMK propia, dar permisos explícitos a SNS y CloudWatch en la key policy, y actualizar el tópico SNS para usar esa clave.
De esta forma, podemos mantener el tópico cifrado y compatible con las alarmas de CloudWatch. La verdad es que esta no me la sabía pero ha estado bien arreglar que CloudWatch tenga la autorización necesaria para enviar con SNS a un topic cifrado. Una cosa menos.