亚马逊AWS官方博客

自动复制 AWS Secrets Manager 密码到备份 AWS 区域

通过使用AWS Secrets Manager,您可以使用AWS KMS客户主密钥安全地存储RDS数据库密碼。借助AWS Lambda的集成,您现在可以更轻松地定期轮换这些密碼并为灾难恢复情况复制它们。在本文中,我将向您展示如何使用AWS CloudFormation设置密碼复制和创建AWS Lambda函数。通过跨AWS区域复制密碼,它可以通过使用备份副本来帮助减少灾难恢复时间

解决方案概述

这篇文章中描述的解决方案结合使用了AWS Secrets Manager,AWS CloudTrail,Amazon CloudWatch Events和AWS Lambda。您在Secrets Manager中创建一个包含RDS数据库凭据的密碼。此密碼使用AWS KMS加密。 Lambda通过在与您的只读副本相同的AWS区域中的同名密碼上执行PUT操作,来自动在您的原始AWS区域中复制密碼值。 CloudWatch Events确保每次旋转包含您的AWS RDS数据库凭证的密碼时,它都会触发Lambda函数将密碼的值复制到您的只读副本区域。这样,您的RDS数据库凭据将始终保持同步以进行恢复。

此过程假设您已经在主AWS区域中创建了一个包含RDS数据库的密碼,并已将CloudTrail日志配置为发送到CloudWatch Logs。完成此操作后,在此复制以下步骤:

  • Secrets Manager会在您原始的AWS地区中輪換一个密碼。
  • CloudTrail收到带有“ eventName”:“ RotationSuceeded”的日志。CloudTrail将此日志传递给CloudWatch Events。
  • CloudWatch Events中对此事件名称的过滤器会触发Lambda函数。
  • Lambda函数从原始AWS区域检索秘密值。
  • 然后,Lambda函数对副本AWS区域中具有相同名称的密钥执行PutSecretValue。

Lambda函数由CloudTrail传递的CloudWatch Event触发。每当秘密成功輪換时,都会触发事件,这将创建CloudTrail日志,并将EventName属性设置为RotationSucceeded。

部署解决方案

该解决方案可以使用AWS cloudformation模板进行部署

以下一步步说明cloudformation模板

附加到SecretsManagerRegionReplicatorRole角色的策略是一个内联策略,该策略授予对原始AWS区域和副本AWS区域中的机密进行解密和加密的权限。此策略还授予从原始AWS区域检索密钥并将密钥存储在副本AWS区域中的权限。您可以在下面看到此策略授予对特定机密的访问权限的示例。如果您选择使用此策略,请记住将参数放入占位符值。

{

    "Version": "2012-10-17",

    "Statement": [

        {

            "Sid": "KMSPermissions",

            "Effect": "Allow",

            "Action": [

                "kms:Decrypt",

                "kms:Encrypt",

                "kms:GenerateDataKey"

            ],

            "Resource": [

                "arn:aws:kms:us-east-1:111122223333:key/Example_Key_ID_12345",

                "arn:aws:kms:eu-west-1:111122223333:key/Example_Key_ID_12345"

            ]

        },

        {

            "Sid": "SecretsManagerOriginRegion",

            "Effect": "Allow",

            "Action": [

                "secretsmanager:DescribeSecret",

                "secretsmanager:GetSecretValue"

            ],

            "Resource": "arn:aws:secretsmanager:us-east-1:111122223333:secret:replica/myexamplereplica*"

        },

        {

            "Sid": "SecretsManagerReplicaRegion",

            "Effect": "Allow",

            "Action": [

                "secretsmanager:UpdateSecretVersionStage",

                "secretsmanager:PutSecretValue",

                "secretsmanager:DescribeSecret"

            ],

            "Resource": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:replica/myexamplereplica*"

        },

        {

            "Sid": "SecretsManagerReplicaRegion",

            "Effect": "Allow",

            "Action": [

                "secretsmanager:CreateSecret"

            ],

            "Resource": "*",

            "Condition": {

                "StringLike": {

                    "secretsmanager:Name": "myexamplereplica*"

                }

            }

        }

    ]

}

创建的下一个资源是CloudWatch Events规则SecretsManagerCrossRegionReplicator。

{

    "detail-type": [

        "AWS Service Event via CloudTrail"

    ],

    "source": [

        "aws.secretsmanager"

    ],

    "detail": {

        "eventSource": [

            "secretsmanager.amazonaws.com"

        ],

        "eventName": [

            "RotationSucceeded"

        ]

    }

}

CloudFormation模板创建的最后一个资源是Lambda函数,它将完成复制的实际工作。

import boto3

from os import environ

 

targetRegion = environ.get('TargetRegion')

if targetRegion == None:

    raise Exception('Environment variable "TargetRegion" must be set')

 

smSource = boto3.client('secretsmanager')

smTarget = boto3.client('secretsmanager', region_name=targetRegion)

 

def lambda_handler(event, context):

    detail = event['detail']

 

    print('Retrieving SecretArn from event data')

    secretArn = detail['additionalEventData']['SecretId']

 

    print('Retrieving new version of Secret "{0}"'.format(secretArn))

    newSecret = smSource.get_secret_value(SecretId = secretArn)

 

    secretName = newSecret['Name']

    currentVersion = newSecret['VersionId']

 

    replicaSecretExists = True

    print('Replicating secret "{0}" (Version {1}) to region "{2}"'.format(secretName, currentVersion, targetRegion))

    try:

        smTarget.put_secret_value(

            SecretId = secretName,

            ClientRequestToken = currentVersion,

            SecretString = newSecret['SecretString']

        )

        pass

    except smTarget.exceptions.ResourceNotFoundException:

        print('Secret "{0}" does not exist in target region "{1}". Creating it now with default values'.format(secretName, targetRegion))

        replicaSecretExists = False

    except smTarget.exceptions.ResourceExistsException:

        print('Secret version "{0}" has already been created, this must be a duplicate invocation'.format(currentVersion))

        pass

 

    if replicaSecretExists == False:

        secretMeta = smSource.describe_secret(SecretId = secretArn)

        if 'KmsKeyId' in secretMeta:

            replicaKmsKeyArn = environ.get('ReplicaKmsKeyArn')

            if replicaKmsKeyArn == None:

                raise Exception('Cannot create replica of a secret that uses a custom KMS key unless the "ReplicaKmsKeyArn" environment variable is set. Alternatively, you can also create the key manually in the replica region with the same name')

 

            smTarget.create_secret(

                Name = secretName,

                ClientRequestToken = currentVersion,

                KmsKeyId = replicaKmsKeyArn,

                SecretString = newSecret['SecretString'],

                Description = secretMeta['Description']

            )

        else:

            smTarget.create_secret(

                Name = secretName,

                ClientRequestToken = currentVersion,

                SecretString = newSecret['SecretString'],

                Description = secretMeta['Description']

            )

    else:

        secretMeta = smTarget.describe_secret(SecretId = secretName)

        for previousVersion, labelList in secretMeta['VersionIdsToStages'].items():

            if 'AWSCURRENT' in labelList and previousVersion != currentVersion:

                print('Moving "AWSCURRENT" label from version "{0}" to new version "{1}"'.format(previousVersion, currentVersion))

                smTarget.update_secret_version_stage(

                    SecretId = secretName,

                    VersionStage = 'AWSCURRENT',

                    MoveToVersionId = currentVersion,

                   RemoveFromVersionId = previousVersion

                )

                break

 

    print('Secret {0} replicated successfully to region "{1}"'.format(secretName, targetRegion))

现在,您的CloudFormation已完成,并且所有必需的资源都已设置好,您就可以开始秘密复制了。

密码标签返回到AWSCURRENT会花费一些时间,然后大约需要数分钟才能使CloudTrail日志填充,然后将事件发送到CloudWatch Logs。收到事件后,事件将触发Lambda函数以完成复制过程。如果复制的机密中的值与原始机密中的值相同,并且两个标签都为AWSCURRENT,则说明复制已成功完成。

本篇作者

徐欣蕾

Amazon Web Services公司专业服务团队WorkSpaces顾问。