亚马逊AWS官方博客

满足 PCI-DSS 合规要求的 Aurora 全球数据库密码自动轮转解决方案

在需要遵守支付卡行业数据安全标准(PCI-DSS)的企业中,数据库凭证的定期轮转是满足合规要求的关键措施。PCI-DSS 8.2.4 要求至少每 90 天更改一次用户密码,这对于使用 Amazon Aurora 全球数据库的企业来说是一个挑战,因为 Aurora 全球数据库目前不支持与 AWS Secrets Manager 集成的原生密码自动轮转功能。本文将介绍一个基于 AWS CDK 的解决方案,通过自定义集成 AWS Secrets Manager 和 Lambda 函数,实现 Aurora 全球数据库主用户密码的自动轮转,从而满足 PCI-DSS 合规要求。

背景与挑战

PCI-DSS 合规要求

支付卡行业数据安全标准(PCI-DSS)是由主要信用卡公司建立的安全标准,旨在保护持卡人数据。其中,PCI-DSS 8.2.4 要求:

至少每 90 天更改一次用户密码/密码短语。

对于处理信用卡数据的企业,满足这一要求是获得 PCI-DSS 认证的必要条件。自动化密码轮转不仅可以满足合规要求,还能减少人为错误和安全风险。

Aurora 全球数据库的特殊性

Amazon Aurora 是一个兼容 MySQL 和 PostgreSQL 的关系型数据库,提供了高性能、高可用性和全球分布式部署能力。Aurora 全球数据库允许单个 Aurora 数据库跨多个 AWS 区域,提供低延迟的全球读取和灾难恢复能力。

在安全管理方面,Aurora 全球数据库目前不支持原生密码轮转,因此本方案就是对这一特性的补充。

解决方案架构

我们设计了一个基于 AWS CDK 的解决方案,通过 AWS Secrets Manager 和 Lambda 函数实现 Aurora 全球数据库主用户密码的自动轮转。该解决方案遵循 AWS 安全最佳实践,并考虑了 Aurora 全球数据库的特殊性,同时满足 PCI-DSS 合规要求。

Aurora 全球数据库密码自动轮转架构图

架构组件

该解决方案包含以下核心组件:

  1. AWS Secrets Manager Secret:存储 Aurora 全球数据库的凭证,包括用户名、密码、主机名、端口和数据库名称。
  2. Lambda 轮转函数:执行密码轮转逻辑,包括创建新密码、更新数据库密码、测试新密码和完成轮转过程。
  3. Lambda :包含 PyMySQL 库,用于从 Lambda 函数连接到 Aurora 数据库。
  4. 轮转计划:按照 PCI-DSS 要求的周期(默认 90 天)自动触发密码轮转。
  5. VPC 配置:确保 Lambda 函数能够安全地访问 Aurora 数据库。
  6. IAM 角色和策略:提供必要的权限,遵循最小权限原则。
  7. CloudWatch 告警:监控密码轮转失败情况。
  8. SNS 通知:在密码轮转失败时发送邮件通知给管理员。

密码轮转流程

AWS Secrets Manager 的密码轮转遵循标准的四步流程,我们的解决方案针对 Aurora 全球数据库进行了优化:

密码轮转四步流程

1. createSecret 阶段

在这个阶段,Lambda 函数执行以下操作:

  • 获取当前密钥内容(AWSCURRENT 版本)
  • 生成新的强密码,确保符合 PCI-DSS 密码复杂性要求
  • 创建新版本的密钥(AWSPENDING 阶段)
def generate_password():
    lowercase = string.ascii_lowercase
    uppercase = string.ascii_uppercase
    digits = string.digits
    special = "!@#$%^&*()_+-=[]{}|;:,.<>?"
    
    pwd = [
        random.choice(lowercase),
        random.choice(uppercase),
        random.choice(digits),
        random.choice(special)
    ]
    
    remaining_length = PASSWORD_LENGTH - len(pwd)
    all_chars = lowercase + uppercase + digits + special
    pwd.extend(random.choice(all_chars) for _ in range(remaining_length))
    
    random.shuffle(pwd)
    
    return ''.join(pwd)

2. setSecret 阶段

在这个阶段,Lambda 函数执行以下操作:

  • 获取当前密钥(AWSCURRENT)和待设置的新密钥(AWSPENDING)
  • 连接到 Aurora 主集群
  • 执行 ALTER USER 命令更新数据库密码

等待 10 ,允许密码更改复制到全球数据库的只读集群

def update_db_password(current_secret, new_secret):
    conn = None
    try:
        conn = pymysql.connect(
            host=current_secret['host'],
            user=current_secret['username'],
            password=current_secret['password'],
            port=int(current_secret.get('port', 3306)),
            connect_timeout=10
        )
        
        with conn.cursor() as cur:
            escaped_password = new_secret['password'].replace("'", "''")
            
            sql = f"ALTER USER '{current_secret['username']}'@'%' IDENTIFIED BY '{escaped_password}'"
            cur.execute(sql)
            conn.commit()
            
            logger.info("等待密码更改复制到全球数据库的只读集群...")
            time.sleep(10)
            
    except pymysql.MySQLError as e:
        logger.error(f"更新数据库密码失败: {str(e)}")
        raise
    finally:
        if conn:
            conn.close()

3. testSecret 阶段

在这个阶段,Lambda 函数执行以下操作:

  • 使用新密码测试连接到数据库
  • 执行简单查询验证连接
def test_db_connection(secret):
    conn = None
    try:
        conn = pymysql.connect(
            host=secret['host'],
            user=secret['username'],
            password=secret['password'],
            port=int(secret.get('port', 3306)),
            connect_timeout=10
        )
        
        with conn.cursor() as cur:
            cur.execute("SELECT 1")
            result = cur.fetchone()
            if result[0] != 1:
                raise Exception("数据库连接测试失败")
                
    except pymysql.MySQLError as e:
        logger.error(f"测试数据库连接失败: {str(e)}")
        raise
    finally:
        if conn:
            conn.close()

4. finishSecret 阶段

在这个阶段,Lambda 函数执行以下操作:

  • 再次测试新密码连接
  • 完成轮转,标记新密钥为当前密钥(AWSCURRENT 阶段)

解决方案的关键特性

1. 错误处理和重试机制

为了提高轮转过程的可靠性,我们实现了全面的错误处理和重试机制:

def update_db_password_with_retry(current_secret, new_secret):
    retry_count = 0
    last_exception = None
    
    while retry_count < MAX_RETRIES:
        try:
            update_db_password(current_secret, new_secret)
            return
        except Exception as e:
            last_exception = e
            retry_count += 1
            logger.warning(f"更新数据库密码失败, 尝试重试 ({retry_count}/{MAX_RETRIES}): {str(e)}")
            if retry_count < MAX_RETRIES:
                time.sleep(RETRY_DELAY_SECONDS)
    
    logger.error(f"更新数据库密码失败, 已达到最大重试次数: {str(last_exception)}")
    raise last_exception

2. 全球复制延迟处理

为了处理 Aurora 全球数据库的复制延迟,我们在更新密码后添加了等待时间:

logger.info("等待密码更改复制到全球数据库的只读集群...")
time.sleep(10)

这个等待时间可以根据实际环境中的复制延迟进行调整。

3. 安全性考虑

解决方案遵循 AWS 安全最佳实践和 PCI-DSS 要求:

  • 使用强密码生成算法,确保密码复杂性符合 PCI-DSS 8.2.3 要求
  • 遵循最小权限原则配置 IAM 角色
  • 将 Lambda 函数部署在 VPC 中,增强网络安全性
  • 使用 CloudWatch 监控和告警,及时发现问题
  • 实现自动化轮转,满足 PCI-DSS 8.2.4 的 90 天密码更改要求

4. 可定制性

解决方案通过 CDK 参数提供高度可定制性:

  • 密码轮转周期可配置(默认 90 天,符合 PCI-DSS 要求)
  • VPC 和子网配置可自定义
  • 通知邮箱可配置
  • 密码复杂性和长度可调整

部署指南

1. 前提条件

  • AWS 账户和适当的权限
  • 已安装 AWS CLI 并配置凭证
  • 已安装 js (≥ 14.x) 和 npm
  • 已安装 Python (≥ 3.9)
  • 已有 Aurora 全球数据库实例
  • VPC 和子网配置,使 Lambda 函数能够访问数据库

2. 部署步骤

2.1 Git 克隆解决方案代码并安装依赖

可选:如果出现 pip install 失败,可先创建虚拟环境,然后再执行以上命令。

git clone https://github.com/yourlin/cdk-aurora-password-rotation

#配置环境
pip install -r requirements.txt
cd layer
pip install -r requirements.txt -t python
cd ..

可选:如果出现 pip install 失败,可先创建虚拟环境,然后再执行以上命令。

pip install virtualenv
virtualenv --version
virtualenv venv
source venv/bin/activate

2.2 修改 cdk.json

{
  "context": {
    "vpc_id": "vpc-xxxxxxxx",  // 替换为您的实际 VPC ID
    "subnet_ids": ["subnet-xxxxxxxx", "subnet-yyyyyyyy"]  // 替换为您的实际子网 ID
  }
}

2.3 执行 CDK 部署命令

cdk bootstrap
cdk deploy \
  --parameters VpcId=vpc-xxxxxxxx \
  --parameters SubnetIds=subnet-xxxxxxxx,subnet-yyyyyyyy \
  --parameters SecretName=aurora-global-db-credentials \
  --parameters RotationDays=90 \
  --parameters NotificationEmail=your-email@example.com
  • VpcId: Lambda 函数将部署在此 VPC 中(必须能够访问 Aurora 数据库)
  • SubnetIds: Lambda 函数将部署在这些子网中(必须能够访问 Aurora 数据库)
  • SecretName: Aurora 全球数据库凭证的 Secret 名称
  • RotationDays: 密码轮转周期(天数),默认为 90 天
  • NotificationEmail: 密码轮转失败时的通知邮箱(可选)

等待部署完成后,进行下一步操作。完成状态可在 Cloudformation 中查看状态。

3. 配置线上环境

3.1 获取 Aurora 全局写入器的地址

3.2 更新 Secrets Manager 配置

在 Secrets Manager 中找到 aurora-global-db-credentials,点击“检索密钥值”

更新 host 的值为 Aurora 的全局写入器端点地址,复制 password

3.3 修改 Aurora Global Database 主凭证

选中主集群,点“修改”

在凭证管理中的主密码确认密码中粘贴从 Secrets Manager 中获取的 password,然后点继续修改集群

等待集群主凭证更新完成,用新密码测试数据库连接。

4. 测试方案

在 Secrets Manager 中点击立即轮转密钥,然后在概览中检索密钥值可获取新密码,然后用客户端测试用新密码登录到集群。

测试连接命令
mysql -p -h your-aurora-global-database-global-endpoint

另外,在 CloudWatch Logs 中,也可以查看到密钥更新的状态。

5. 应用获取 Secrets Manager 密钥方式参考

// Use this code snippet in your app.
// If you need more information about configurations or implementing the sample
// code, visit the AWS docs:
// https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/home.html

// Make sure to import the following packages in your code
// import software.amazon.awssdk.regions.Region;
// import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
// import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
// import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;        

public static void getSecret() {

    String secretName = "aurora-global-db-credentials";
    Region region = Region.of("us-east-1");

    // Create a Secrets Manager client
    SecretsManagerClient client = SecretsManagerClient.builder()
            .region(region)
            .build();

    GetSecretValueRequest getSecretValueRequest = GetSecretValueRequest.builder()
            .secretId(secretName)
            .build();

    GetSecretValueResponse getSecretValueResponse;

    try {
        getSecretValueResponse = client.getSecretValue(getSecretValueRequest);
    } catch (Exception e) {
        // For a list of exceptions thrown, see
        // https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
        throw e;
    }

    String secret = getSecretValueResponse.secretString();

    // Your code goes here.
}

下载适用于 Java 的 AWS 开发工具包

6. 邮件通知配置

密码自动轮转失败,会通过 SNS 发送 email。如果要添加或修改 email 地址,可在 SNS 中创建编辑订阅。

最佳实践与注意事项

1. 复制延迟监控

在高负载情况下,Aurora 全球数据库的复制延迟可能会增加。建议监控复制延迟指标,并根据需要调整 Lambda 函数中的等待时间。

2. 复制密钥

出于容灾的考虑,建议后期开启复制密钥,复制密钥到另一个 Region(推荐和 Aurora Global Database 的数据库辅助区域同一个 Region)。以确保极端情况下,数据库密码可以从另一个 Region 的 Secrets Manager中获得。

3. 应用程序集成

应用程序应从 Secrets Manager 获取数据库凭证,而不是硬编码:

import boto3

def get_db_credentials():
    client = boto3.client('secretsmanager')
    response = client.get_secret_value(
        SecretId='aurora-global-db-credentials'
    )
    return json.loads(response['SecretString'])

4. 故障排除

如果密码轮转失败,可以通过以下步骤进行故障排除:

  • 检查 Lambda 函数的 CloudWatch 日志
  • 验证 VPC 和安全组配置
  • 确认 Secret 中的数据库连接信息正确
  • 检查数据库用户权限

5. PCI-DSS 合规审计

为了满足 PCI-DSS 审计要求,建议配置以下内容:

  • 启用 CloudTrail 跟踪 Secrets Manager 和数据库相关操作
  • 配置 CloudWatch 日志保留期限,确保符合 PCI-DSS 要求
  • 定期审查轮转日志,确保密码轮转按计划执行
  • 保留密码轮转记录,作为合规证明

结论

本文介绍的 Aurora 全球数据库密码自动轮转解决方案,通过 AWS CDK、Secrets Manager 和 Lambda 函数,实现了安全、可靠的密码轮转流程。该解决方案解决了 Aurora 全球数据库不支持原生密码轮转的挑战,同时考虑了全球复制延迟的问题,有效满足了 PCI-DSS 8.2.4 的密码定期轮转要求。

通过自动化密码轮转,企业可以增强数据库安全性,满足 PCI-DSS 合规要求,同时减少手动操作和人为错误。该解决方案可以根据企业需求进行定制,适用于各种规模的 Aurora 全球数据库部署。

参考资源

本篇作者

林业

亚马逊云科技资深解决方案架构师,负责基于 AWS 的云计算方案的咨询与架构设计。拥有超过 18 年研发经验,曾打造千万级用户 APP,持续开发 Github 开源项目获 3000+ 星。在零售、游戏、IoT、智慧城市、汽车、电商等多个领域都拥有丰富的实践经验。现专注企业云原生架构和 GenAI 发展,致力将前沿技术应用于企业业务场景,推动数字化转型。热爱技术,追求卓越,乐于分享交流。

詹叶

亚马逊云科技技术客户经理,主要支持互联网金融,汽车行业客户的架构优化、成本管理、技术咨询与交付工作。拥有多年企业级产品研发和管理经验,曾经贡献过多项开源项目。加入亚马逊云科技后,热衷于 serverless 领域。

曹琪

亚马逊云科技的资深客户解决方案经理,在亚马逊云科技主要支持制造业,游戏和 OTA 等行业的用户。专注于在亚马逊云科技用户上云期间运用云相关解决方案帮助亚马逊云科技用户实现自身的业务价值。他始终坚持运用亚马逊云科技已有的数据分析,机器学习和 AIGC 的能力帮助用户在业务上做出更多的创新。