亚马逊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,则说明复制已成功完成。