Comment éviter l'erreur « Unable to validate the following destination configurations » (Impossible de valider les configurations de destination suivantes) avec les notifications d'événement Lambda dans AWS CloudFormation ?

Date de la dernière mise à jour : 16/01/2020

Ma pile échoue lorsque je déploie un modèle AWS CloudFormation. Je reçois ensuite une erreur similaire à celle-ci : « Unable to validate the following destination configurations » (Impossible de valider les configurations de destination suivantes).

Brève description

Vous recevez cette erreur lorsque vous déployez un modèle AWS CloudFormation avec les ressources suivantes :

Amazon S3 doit valider la configuration des notifications lorsqu'il crée le compartiment. La validation est effectuée en vérifiant si le compartiment est autorisé à envoyer des événements à la fonction Lambda. La ressource d'autorisation (qui doit exister pour que ce contrôle réussisse) nécessite le nom du compartiment. Cela signifie que la ressource d'autorisation dépend du compartiment et que le compartiment dépend de la ressource d'autorisation.

Remarque : vous recevez l'erreur « Circular dependency between resources » (Dépendance circulaire entre les ressources) si vous essayez de résoudre ce problème en implémentant un attribut de ressource DependsOn similaire à l'exemple de code suivant.

L'exemple de code suivant montre une dépendance circulaire entre la ressource de compartiment S3 et la propriété SourceArn de la ressource d'autorisation Lambda.

"Resources": {
  "MyS3BucketPermission": {
    "Type": "AWS::Lambda::Permission",
    "Properties": {
      "Action": "lambda:InvokeFunction",
      ...
      ...
      "SourceArn": {
        "Ref": "MyS3Bucket"
      }
    }
  },
  "MyS3Bucket": {
    "DependsOn" : "MyS3BucketPermission",
    ...
    ...

Remarque : il est recommandé d'ajouter la propriété SourceAccount à la ressource d'autorisation Lambda pour les sources d'événements Amazon S3, car un Amazon Resource Name (ARN) pour Amazon S3 n'inclut pas d'ID de compte. La propriété SourceArn convient à la plupart des autres sources d'événements, mais envisagez d'ajouter la propriété SourceAccount pour les sources d'événements Amazon S3. Cela évite le scénario dans lequel vous supprimez le compartiment S3 et découvrez que quelqu'un d'autre l'a recréé, accordant au nouveau propriétaire du compartiment les autorisations complètes pour appeler votre fonction Lambda.

Solution

Vous pouvez éviter les dépendances circulaires en utilisant la fonction intrinsèque Fn::Join avec les paramètres de pile.

Dans l'exemple de modèle suivant, le nom de compartiment S3 BucketPrefix est fourni comme paramètre aux ressources AWS::S3::Bucket et AWS::Lambda::Permission.

Remarque : l'exemple suivant suppose que le nom du compartiment n'a pas été utilisé précédemment avec vos comptes AWS. Si vous souhaitez réutiliser un modèle avec cet extrait de code, vous devez fournir un préfixe de compartiment différent chaque fois.

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "BucketPrefix": {
      "Type": "String",
      "Default": "test-bucket-name"
    }
  },
  "Resources": {
    "EncryptionServiceBucket": {
      "DependsOn": "LambdaInvokePermission",
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": {
          "Fn::Sub": "${BucketPrefix}-encryption-service"
        },
        "NotificationConfiguration": {
          "LambdaConfigurations": [
            {
              "Function": {
                "Fn::GetAtt": [
                  "AppendItemToListFunction",
                  "Arn"
                ]
              },
              "Event": "s3:ObjectCreated:*",
              "Filter": {
                "S3Key": {
                  "Rules": [
                    {
                      "Name": "suffix",
                      "Value": "zip"
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    },
    "LambdaInvokePermission": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "AppendItemToListFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "s3.amazonaws.com",
        "SourceAccount": {
          "Ref": "AWS::AccountId"
        },
        "SourceArn": { 
            "Fn::Sub": "arn:aws:s3:::${BucketPrefix}-encryption-service"
        }
      }
    },
    "AppendItemToListFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "LambdaExecutionRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "",
              [
                "var response = require('cfn-response');",
                "exports.handler = function(event, context) {",
                " var responseData = {Value: event.ResourceProperties.List};",
                " responseData.Value.push(event.ResourceProperties.AppendedItem);",
                " response.send(event, context, response.SUCCESS, responseData);",
                "};"
              ]
            ]
          }
        },
        "Runtime": "nodejs8.10"
      }
    },
    "LambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "/",
        "Policies": [
          {
            "PolicyName": "root",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:*"
                  ],
                  "Resource": "arn:aws:logs:*:*:*"
                }
              ]
            }
          }
        ]
      }
    }
  }
}

Le modèle évite les dépendances circulaires, car il crée les ressources dans l'ordre suivant :

  1. Rôle AWS Identity and Access Management (IAM)
  2. Fonction Lambda
  3. Autorisations Lambda
  4. Compartiment S3

Désormais, Amazon S3 peut vérifier sa configuration de notification et créer le compartiment sans aucun problème.

Vous pouvez également essayer les résolutions suivantes :

  • Créez le compartiment S3 sans configuration de notification, puis ajoutez le compartiment dans la prochaine mise à jour de la pile.
  • Créez une autorisation Lambda moins limitée. Par exemple, autorisez les appels pour un compte AWS spécifique en omettant SourceArn.
  • Créez une ressource personnalisée à exécuter à la fin du flux de travail de la pile. Cette ressource ajoute la configuration des notifications au compartiment S3 une fois que toutes les autres ressources ont été créées.