타사 ESP를 통해 AWS Cognito 이메일 보내기

AWS Cognito에서 이메일과 문자 메시지를 보내는 기본 방법은 AWS 자체 서비스인 SES와 SNS입니다.
보통 이것은 일리가 있는 것이다. 어쨌든 너는 이미 AWS 생태계에 있다.그러나 만약에 제3자 ESP(이메일 서비스 제공자), 예를 들어 Twilio Sendgrid나Mailgun/Sendinblue/Mailchimp를 사용하려면 어떻게 해야 합니까?이러한 이유 중 일부는 다음과 같습니다.
  • 설계자는 ESP 사용자 인터페이스
  • 의 이메일 템플릿에 쉽게 액세스할 수 있어야 합니다.
  • 사용자는 자신이 받은 전자메일의 구독을 취소할 수 있는 방법이 필요합니다(ESP 추적 구독 상태)
  • 귀사는 ESPs가 제공하는 분석 도구에 의존하고 SES
  • 는 이러한 도구가 없습니다.
  • 전자 우편 템플릿이 매우 크고(html과 css) Cognito의 20k 문자 제한에 부합되지 않는다
  • SES 소스(SPF, DKIM, DMARC) 검증을 원하지 않음
  • ESP 관리를 위한 신뢰할 수 있는 IP 주소
  • 를 재사용하고자 함
    사용자 정의 메시지 Lambda 트리거 (예: CustomMessage_ForgotPassword 가 있지만, 전자 우편 테마와 본문만 사용자 정의할 수 있고, 기본 전송을 변경할 수 없습니다.
    2020년 말 어느 때 AWS는 Cognito에 새로운 유형의 Lambda 트리거를 추가했다. 맞춤형 발송자인 Lambda 트리거이다.
    Cognito 문서가 좀 부족합니다...코드를 복사할 수 없습니다. 명령 중의 일부 절차가 부족합니다.
    또한 사용자 정의 이메일 및 SMS 발송자를 코드로 구성하는 방법도 표시되지 않습니다.
    다음은 이러한 새 Cognito Lambda 트리거를 배포하고 사용하는 지침입니다.
    우리는 구름 편대와 지형을 어떻게 사용해서 그것들을 배치하는지 보게 될 것이다.

    사용자 지정 발송자 Lambda 트리거


    더 구체적으로 말하면 두 개의 새로운 트리거가 있다.
  • CustomEmailSender 이메일을 보내는 기본(SES) 방법 덮어쓰기
  • CustomSMSSender SNS 대신 외부 서비스로 문자 메시지를 보내야 할 경우
  • 본 문서에서 사용자 정의 이메일 발송자인 람바다를 중점적으로 소개하지만 사용자 정의 SMS 발송자의 과정은 같다.
    이러한 Lambda 트리거가 Cognito에서 받은 매개변수는 사용자 정의 메시지 트리거에서 가져온 매개변수와 약간 다릅니다.CustomMessage_* 트리거를 사용할 때 코드에 기밀을 전달하지 않습니다.반대로, 보낼 전자메일이나 문자의 정확한 위치에 자리 표시자 문자열을 받을 수 있다.알림을 보내기 전에 이 자리 표시자는 Cognito에서 코드로 바뀝니다.
    다른 한편, 사용자 정의 송신자 트리거는 암호화된 알림 코드를 수신한다.따라서 함수 코드에서 코드를 복호화할 수 있도록 KMS 키를 설정해야 합니다.
    도구는 어떻게 새 트리거를 지원합니까?
  • Cognito 콘솔에서 트리거를 구성할 수 없음
  • Cognito 설명서는 AWS CLI 구성 트리거
  • 를 사용하는 것이 좋습니다.
  • CloudFormation 설명서에 따르면 이 기능은 아직 지원되지 않습니다.내 테스트 결과를 보면 이 문서들은 아직 업데이트되지 않았을 수도 있다
  • Terraform은 아직 지원되지 않지만 솔루션
  • 이 있습니다.

    Twilio Sendgrid를 사용하여 Cognito e-메일 보내기


    프레젠테이션의 경우 Cognito에 새 사용자를 등록하고 Sendgrid와 함께 이메일을 보내도록 하겠습니다.
    준수 선결 조건:
  • 미국 용접학회 계좌
  • Sendgrid 계정(또는 사용 중인 ESP)
  • Docker그렇습니다. AWS CLI는 없고 노드는 없습니다.js, Docker는 유일한 개발 의존항입니다.개발자로서 얼마나 아름다운 순간인가!
    코그니토를 어떻게 배치하는지에 관한 튜토리얼이 아니기 때문에 맞춤형 송신기 트리거와 관련된 부분만 살펴보겠습니다.
    CloudFormation과 Terraform에 정의된 모든 자원에 대한 전체 코드를 demo repository 에서 찾을 수 있습니다.

    KMS 키 만들기


    위에서 말한 바와 같이, 우리는 알림 코드를 암호화하고 복호화할 키가 필요합니다.키 정책은 AWS 콘솔을 사용하여 새 KMS 키를 만들 때의 기본 권한과 일치합니다.
    구름층 형성
    Parameters:
      ...
      CallingUserArn:
        Description: Calling user ARN
        Type: String
    
    Resources:
      ...
      KmsKey:
        Type: AWS::KMS::Key
        Properties:
          Enabled: true
          KeyPolicy:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Principal:
                AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
              Action: 'kms:*'
              Resource: '*'
            - Effect: Allow
              Principal:
                AWS: !Ref CallingUserArn
              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: '*'
    
    CallingUserArn 매개변수는 IAM 사용자를 호출한 ARN을 CloudFormation으로 전달하는 팁입니다.
    aws cloudformation deploy ... --parameter-overrides CallingUserArn="$(aws sts get-caller-identity --query Arn --output text)"
    
    지형.
    data "aws_caller_identity" "current" {}
    
    resource "aws_kms_key" "kms_key" {
      description             = "KMS key for Cognito Lambda trigger"
      policy = <<EOF
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
                },
                "Action": "kms:*",
                "Resource": "*"
            },
            {
                "Effect": "Allow",
                "Principal": {
                    "AWS": "${data.aws_caller_identity.current.arn}"
                },
                "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": "*"
            }
        ]
    }
    EOF
    }
    

    Lambda IAM 역할 만들기


    표준 AWSLambdaBasicExecutionRole 관리 정책 외에, 우리는 KMS 키를 복호화하기 위해 Lambda에 접근 권한을 부여해야 한다.
    주의: 최소한의 권한을 가지기 위해 세립도 정책 AWSLambdaBasicExecutionRole 을 사용하길 원할 수도 있습니다.
    CF와 TF 스크립트의 결과는 똑같다. Lambda 트리거의 한 역할에 두 가지 전략을 추가했다.
    구름층 형성
    Resources:
      ...
      LambdaTriggerRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: "sts:AssumeRole"
                Principal:
                  Service: "lambda.amazonaws.com"
          ManagedPolicyArns:
            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    
      LambdaTriggerRoleKmsPolicy:
        Type: AWS::IAM::Policy
        Properties:
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "kms:Decrypt"
                Resource: !GetAtt KmsKey.Arn
          PolicyName: "LambdaKmsPolicy"
          Roles:
            - !Ref LambdaTriggerRole
    
    지형.
    data "aws_iam_policy_document" "AWSLambdaTrustPolicy" {
      version = "2012-10-17"
      statement {
        actions    = ["sts:AssumeRole"]
        effect     = "Allow"
        principals {
          type        = "Service"
          identifiers = ["lambda.amazonaws.com"]
        }
      }
    }
    
    resource "aws_iam_role" "iam_role" {
      assume_role_policy = data.aws_iam_policy_document.AWSLambdaTrustPolicy.json
      name = "${var.project}-iam-role-lambda-trigger"
    }
    
    resource "aws_iam_role_policy_attachment" "iam_role_policy_attachment_lambda_basic_execution" {
      role       = aws_iam_role.iam_role.name
      policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
    }
    
    data "aws_iam_policy_document" "iam_policy_document_lambda_kms" {
      version = "2012-10-17"
      statement {
        actions    = ["kms:Decrypt"]
        effect     = "Allow"
        resources = [
            aws_kms_key.kms_key.arn
        ]
      }
    }
    
    resource "aws_iam_role_policy" "iam_role_policy_lambda_kms" {
      name   = "${var.project}-iam-role-policy-lambda-kms"
      role   = aws_iam_role.iam_role.name
      policy = data.aws_iam_policy_document.iam_policy_document_lambda_kms.json
    }
    

    Lambda 함수 만들기(Node.js 코드)


    AWS SDK 자체와 달리@aws-crypto/client-node 암호화 라이브러리는 코드와 함께 패키지화하고 배포해야 합니다.노드가 없으면js는 로컬에 설치되어 있으며, Docker를 사용하여 의존항을 설치할 수 있습니다.클론 재구매 중:
    cd lambda/
    
    docker run -it --rm -v $(pwd):/var/app node:12 bash
    
    npm i
    
    기능 코드:
    const AWS = require('aws-sdk')
    const b64 = require('base64-js')
    const encryptionSdk = require('@aws-crypto/client-node')
    const sgMail = require("@sendgrid/mail")
    
    const { decrypt } = encryptionSdk.buildClient(encryptionSdk.CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT)
    const keyIds = [process.env.KEY_ID];
    const keyring = new encryptionSdk.KmsKeyringNode({ keyIds })
    
    sgMail.setApiKey(process.env.SENDGRID_API_KEY)
    
    exports.handler = async(event) => {
      let plainTextCode
      if (event.request.code) {
        const { plaintext, messageHeader } = await decrypt(keyring, b64.toByteArray(event.request.code))
        plainTextCode = plaintext
      }
    
      const msg = {
        to: event.request.userAttributes.email,
        from: "[email protected]",
        subject: "Your Cognito code",
        text: `Your code: ${plainTextCode.toString()}`,
      }
    
      await sgMail.send(msg)      
    }
    
    KMS 키 ARN 및 ESP API 키를 환경 변수로 전달합니다.알림 코드는 암호화되어 전자 메일 공급업체 API에 보내는 메시지 본문에서 사용할 수 있습니다.
    함수에 전달된 객체 예제event:
    {
        "version": "1",
        "triggerSource": "CustomEmailSender_ForgotPassword",
        "region": "us-east-1",
        "userPoolId": "us-east-1_LnS...",
        "userName": "54cf7eb7-0b96-4304-...",
        "callerContext": {
            "awsSdkVersion": "aws-sdk-nodejs-2.856.0",
            "clientId": "6u7c9vr3pkstoog..."
        },
        "request": {
            "type": "customEmailSenderRequestV1",
            "code": "AYADeILxywKhhaq8Ys4mh0aHutYAgQACABVhd3MtY3J5c...",
            "clientMetadata": null,
            "userAttributes": {
                "sub": "54cf7eb7-0b96-4304-8d6b-...",
                "email_verified": "true",
                "cognito:user_status": "CONFIRMED",
                "cognito:email_alias": "[email protected]",
                "phone_number_verified": "false",
                "phone_number": "...",
                "given_name": "Max",
                "family_name": "Ivanov",
                "email": "[email protected]"
            }
        }
    }
    

    Lambda 함수 생성(infra)


    우리는 노드를 정의할 것이다.Cognito는 이메일을 보낼 때마다 js Lambda를 트리거합니다.ZIP 파일로 배치됩니다.사용자 정의 발송자 트리거의 유일한 부분은 환경 변수입니다.
    KMS 키 ID에 사용할 변수와 e-메일 공급업체 API 키가 필요합니다.
    구름층 형성
    Parameters:
      ...
      SendgridApiKey:
        Description: Sendgrid API key
        Type: String
    
    Resources:
      ...
      LambdaTrigger:
        Type: AWS::Lambda::Function
        Properties:
          Code: "../lambda"
          Environment:
            Variables:
              KEY_ID: !GetAtt KmsKey.Arn
              SENDGRID_API_KEY: !Ref SendgridApiKey
          FunctionName: !Sub ${ProjectName}-lambda-custom-email-sender-trigger
          PackageType: Zip
          Role: !GetAtt LambdaTriggerRole.Arn
          Runtime: nodejs12.x
          Handler: index.handler
    
    만약 네가 구름의 형성에 익숙하다면 어떤 의외의 일도 없을 것이다.
    지형.
    data "archive_file" "lambda" {
      type        = "zip"
      source_dir  = "../lambda"
      output_path = "lambda.zip"
    }
    
    resource "aws_lambda_function" "lambda_function_trigger" {
      environment {
        variables = {
          KEY_ID = aws_kms_key.kms_key.arn
          SENDGRID_API_KEY = var.sendgrid_api_key
        }
      }
      code_signing_config_arn = ""
      description = ""
      filename         = data.archive_file.lambda.output_path
      function_name    = "${var.project}-lambda-function-trigger"
      role             = aws_iam_role.iam_role.arn
      handler          = "index.handler"
      runtime          = "nodejs12.x"
      source_code_hash = filebase64sha256(data.archive_file.lambda.output_path)
    }
    
    source_code_hash 기능 코드를 수정할 때마다 자원은 변경된 것으로 표시되고 코드는 재배치됩니다.

    Cognito 사용자 풀 만들기


    이것은 가장 사람을 곤혹스럽게 하는 부분이다.사용자 풀의 LambdaConfig 설정이 필요합니다.Cognito 호출된 Lambda 트리거 구성을 저장하는 객체입니다.우리가 관심 있는 두 가지 새로운 옵션은 다음과 같습니다.
  • CustomEmailSender: { LambdaArn: "...", LambdaVersion: "..." }
  • KMSKeyID: "..."
  • 구름이 형성되는 과정은 매우 간단하지만 지형에서 변통해야 한다.상세한 상황은 아래와 같다.
    구름층 형성
    공식 문서에 따르면 사용자 정의 발송자 설정 옵션은 Not currently supported by AWS CloudFormation이라고 한다.그러나 다행히도 사실은 그렇지 않았다.그것은 내가 운행하는 여러 테스트에서 매우 효과적이다.
    Resources:
      UserPool:
        Type: AWS::Cognito::UserPool
        Properties:
          AccountRecoverySetting:
            RecoveryMechanisms:
              - Name: verified_email
                Priority: 1
          AutoVerifiedAttributes:
            - email
          LambdaConfig:
            CustomEmailSender:
              LambdaArn: !GetAtt LambdaTrigger.Arn
              LambdaVersion: "V1_0"
            KMSKeyID: !GetAtt KmsKey.Arn
          UsernameConfiguration: 
            CaseSensitive: false
          UserPoolName: !Sub ${ProjectName}-user-pool
          UsernameAttributes:
            - email
          Policies:
            PasswordPolicy: 
              MinimumLength: 10
          Schema:
            - Name: name
              AttributeDataType: String
              Mutable: true
              Required: true
            - Name: email
              AttributeDataType: String
              Mutable: true
              Required: true
    
    LambdaConfig:는 위에서 가장 흥미를 느끼는 부분이다.
    지형.
    지형의 특징 상태를 추적하기 위해 open issue가 있다.그러나 그것이 사용되기 전에 우리가 지금 무엇을 할 수 있을까?null_resource 이 목표를 실현하는 데 도움이 되지만 여전히 문제가 존재한다.
    CloudFormation 정의에 대한 링크를 해제하면 리소스 정의에 CustomEmailSender와 관련된 내용이 추가되지 않으므로 Terraform의 좋은 이전 Cognito 사용자 풀입니다.
    resource "aws_cognito_user_pool" "cognito_user_pool" {
      name = "${var.project}-cognito-user-pool"
    
      account_recovery_setting {
        recovery_mechanism {
          name     = "verified_email"
          priority = 1
        }
      }
    
      auto_verified_attributes = ["email"]
    
      password_policy {
        minimum_length                   = 10
        temporary_password_validity_days = 7
        require_lowercase                = false
        require_numbers                  = false
        require_symbols                  = false
        require_uppercase                = false
      }
    
      schema {
        attribute_data_type      = "String"
        developer_only_attribute = false
        mutable                  = true
        name                     = "email"
        required                 = true
    
        string_attribute_constraints {
          max_length = "2048"
          min_length = "0"
        }
      }
    
      schema {
        attribute_data_type      = "String"
        developer_only_attribute = false
        mutable                  = true
        name                     = "name"
        required                 = true
    
        string_attribute_constraints {
          max_length = "2048"
          min_length = "0"
        }
      }
    
      username_attributes = ["email"]
      username_configuration {
        case_sensitive = false
      }
    }
    
    사용자 풀에서 Lambda 구성을 설정하려면 aws cognito-idp update-user-pool --lambda-config "CustomEmailSender={LambdaVersion=V1_0,LambdaArn=... AWS CLI 명령을 사용합니다.
    문제는 이 명령에 다른 모든 관련 풀 옵션을 전달하지 않으면 기본값으로 재설정된다는 것이다.제안된 솔루션:
    1. lambda 구성을 설정하지 않고 Terraform 스택을 배포합니다.
    2. update-user-pool 명령에 예상되는 입력 변수의 프레임을 생성합니다.
    aws cognito-idp update-user-pool --user-pool-id us-east-1_evzTb... --generate-cli-skeleton input
    
    3. 사용자 풀의 현재 구성을 가져오려면:
    aws cognito-idp describe-user-pool --user-pool-id us-east-1_evzTb... --query UserPool > input.json
    
    4. 가져온 구성에서 뼈대에 나열되지 않은 키를 제거합니다.승인된 구성 옵션은 그대로 유지update-user-pool할 수 있습니다.누군가가 이 일을 자동으로 완성하기 위해 스크립트를 생각해 낼 수도 있다...하지만 나는 수작업으로 편집했다.
    5. Terraform을 사용하여 새 null_resource를 추가하고 배치합니다.
    locals {
        update_user_pool_command = "aws cognito-idp update-user-pool --user-pool-id ${aws_cognito_user_pool.cognito_user_pool.id} --cli-input-json file://${var.update_user_pool_config_file} --lambda-config \"CustomEmailSender={LambdaVersion=V1_0,LambdaArn=${aws_lambda_function.lambda_function_trigger.arn}},KMSKeyID=${aws_kms_key.kms_key.arn}\""
    }
    
    resource "null_resource" "cognito_user_pool_lambda_config" {
      provisioner "local-exec" {
        command = local.update_user_pool_command
      }
      depends_on = [local.update_user_pool_command]
      triggers = {
        input_json = filemd5(var.update_user_pool_config_file)
        update_user_pool_command = local.update_user_pool_command
      }
    }
    
    여기서 무슨 일이 일어났는지 빠르게 평론해 보세요.우리는 update-user-pool 명령을 사용하여 국부 값을 정의합니다.이것은 사용자 풀 ID, 단계 4. 에서 준비한 현재 사용자 풀 설정이 있는 JSON 파일과 lambda 설정을 받아들인다.Terraformnull 리소스는 처음 실행할 때apply와 명령이나 프로필을 업데이트할 때마다 이 명령을 실행합니다.
    오류 해석 매개 변수'cli input json'을 받은 경우 잘못된 json을 받았습니다.오류, 인자 json을 입력한 경로가 정확하고 접두사가 file:// 인지 확인하십시오.즉--cli-input-json file://${var.update_user_pool_config_file}.
    매개변수 유효성 검사 실패: 입력에서 알 수 없는 매개변수: "Id",..."오류, 파라미터 파일에서 update-user-pool 지원하지 않는 모든 키가 삭제되었는지 확인하십시오.
    UpdateUserPool 작업을 호출할 때 오류(Invalid Parameter Exception)가 발생하면 PasswordPolicy에서 UnusesaccountValidity Days 대신 임시 PasswordValidity Days 오류를 사용하십시오 AdminCreateUserConfig.UnusedAccountValidityDays 설정을 삭제하십시오.로 대체Policies.PasswordPolicy.TemporaryPasswordValidityDays.

    유효성 보장


    모든 리소스가 배포되면 ESP에서 코드가 있는 이메일을 보낼 수 있도록 새 사용자를 등록할 수 있습니다.
    CloudFormation을 사용하면
    aws cloudformation describe-stacks --stack-name cognito-custom-email-sender-cf-stack --query "Stacks[0].Outputs"
    
    Terraform의 경우 출력에 나열됩니다.
    새 사용자를 등록하려면 다음과 같이 하십시오.
    aws cognito-idp sign-up --client-id <CLIENT_ID> --username [email protected] --password <PASSOWORD> --user-attributes Name="name",Value="Max Ivanov"
    {
        "UserConfirmed": false,
        "CodeDeliveryDetails": {
            "Destination": "h***@m***.io",
            "DeliveryMedium": "EMAIL",
            "AttributeName": "email"
        },
        "UserSub": "51c9045e-2f3e-4..."
    }
    
    성공했어!

    색인을 얻으면프로세서가 CloudWatch에서 오류를 정의하거나 내보내지 않았습니다. 포함된 폴더를 압축하는 대신 함수 파일만 압축해야 합니다.
    KMS 키를 얻으려면 arn이 문자열이어야 합니다.또는 데이터 키를 복호화할 수 없으며 하나 이상의 KMS CMK 오류가 발생했습니다.CloudWatch에서 오류가 발생했습니다. 환경 변수의 KMS 키를 Lambda에 전달하고, 이 값은 KMS 키의 ARN이지 ID가 아닙니다.

    깨끗이 정리하다


    적어도 자원을 없애는 것은 훨씬 쉽다!
    구름층 형성
    aws cloudformation delete-stack --stack-name cognito-custom-email-sender-cf-stack
    
    지형.
    terraform destroy
    

    도구책

  • https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-sender-triggers.html
  • https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cognito-userpool-lambdaconfig.html#cfn-cognito-userpool-lambdaconfig-customemailsender
  • https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html
  • https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cognito-idp/update-user-pool.html
  • https://github.com/hashicorp/terraform-provider-aws/issues/16760
  • ...


    이제 Cognito에서 이메일 및 문자 알림을 보내는 방법은 제한되지 않습니다.프로젝트에 더 적합한 알림 서비스/공급자를 사용하십시오.
    만약 당신이 이런 내용을 좋아한다면 트위터에서 최신 업데이트를 얻을 수 있습니다.

    좋은 웹페이지 즐겨찾기