AWS Secrets & Parameter 跨账号跨区域迁移指南

前言

在多账号架构或区域迁移场景中,Secrets Manager 和 Parameter Store 的数据迁移是常见需求。然而:

  • Secrets Manager 原生复制功能仅支持同账号跨区域,不支持跨账号
  • Parameter Store 不提供原生复制功能,跨账号共享需要 AWS RAM 且仅支持读取

本指南提供基于 AWS CLI 的一次性迁移方案,支持批量导出导入、并发执行、指数退避重试(避免 API 限流中断)以及 Tags 元数据同步。适用于以下场景:

  • 多账号架构下的环境迁移(如从开发账号迁移到生产账号)
  • 区域迁移或灾备部署
  • 账号整合或拆分

方案概览

 


迁移脚本

1. Secrets Manager 迁移脚本

注意:此脚本同步 Secret 值和 Tags,不同步 Resource Policy(资源策略, 如允许某个用户/角色访问)。跨账号后源账号的 IAM 实体在目标账号可能不存在,直接复制策略没有意义,需在目标账号根据实际情况重新配置。

#!/bin/bash
# sync-secrets-manager.sh
# 跨账号跨区域同步 Secrets Manager(含 Tags 和重试)

set -e

# ============ 配置 ============
SOURCE_PROFILE="source-account"      # 源账号 AWS profile
TARGET_PROFILE="target-account"      # 目标账号 AWS profile
SOURCE_REGION="ap-northeast-1"       # 东京
TARGET_REGION="eu-west-1"            # 爱尔兰
MAX_RETRIES=3                        # 最大重试次数

# ============ 带重试的 AWS 命令 ============
aws_retry() {
    local i
    for ((i=1; i<=MAX_RETRIES; i++)); do
        if "$@" 2>/dev/null; then return 0; fi
        [ $i -lt $MAX_RETRIES ] && sleep $((i * 2))
    done
    return 1
}

# ============ Secrets Manager 同步 ============
echo "=== 同步 Secrets Manager ==="
echo "源: $SOURCE_PROFILE ($SOURCE_REGION)"
echo "目标: $TARGET_PROFILE ($TARGET_REGION)"
echo "重试: $MAX_RETRIES 次"
echo ""

# 获取所有 secret 名称
aws secretsmanager list-secrets \
    --region $SOURCE_REGION \
    --profile $SOURCE_PROFILE \
    --query 'SecretList[*].Name' \
    --output json > /tmp/secret_names.json

count=$(jq length /tmp/secret_names.json)
echo "发现 $count 个 secrets"

# 逐个同步
jq -r '.[]' /tmp/secret_names.json | while read secret_name; do
    echo "同步 secret: $secret_name"
    
    # 获取 secret 值(带重试)
    secret_value=$(aws_retry aws secretsmanager get-secret-value \
        --secret-id "$secret_name" \
        --region $SOURCE_REGION \
        --profile $SOURCE_PROFILE \
        --query 'SecretString' \
        --output text) || continue
    
    # 获取 secret 描述和 Tags
    secret_info=$(aws_retry aws secretsmanager describe-secret \
        --secret-id "$secret_name" \
        --region $SOURCE_REGION \
        --profile $SOURCE_PROFILE \
        --output json)
    
    description=$(echo "$secret_info" | jq -r '.Description // empty')
    tags=$(echo "$secret_info" | jq -c '.Tags // []')
    
    # 在目标账号创建或更新(带重试)
    if aws secretsmanager describe-secret \
        --secret-id "$secret_name" \
        --region $TARGET_REGION \
        --profile $TARGET_PROFILE &>/dev/null; then
        aws_retry aws secretsmanager put-secret-value \
            --secret-id "$secret_name" \
            --secret-string "$secret_value" \
            --region $TARGET_REGION \
            --profile $TARGET_PROFILE
    else
        aws_retry aws secretsmanager create-secret \
            --name "$secret_name" \
            --secret-string "$secret_value" \
            --description "${description:-Migrated from $SOURCE_REGION}" \
            --region $TARGET_REGION \
            --profile $TARGET_PROFILE
    fi
    
    # 同步 Tags(带重试)
    if [ "$tags" != "[]" ] && [ -n "$tags" ]; then
        aws_retry aws secretsmanager tag-resource \
            --secret-id "$secret_name" \
            --tags "$tags" \
            --region $TARGET_REGION \
            --profile $TARGET_PROFILE || true
    fi
done

# 清理临时文件
rm -f /tmp/secret_names.json

echo ""
echo "=== Secrets Manager 同步完成 ==="

2. Parameter Store 迁移脚本

注意:此脚本同步参数值和 Tags。Parameter Store 不支持资源策略,访问控制通过 IAM Policy 管理。

#!/bin/bash
# sync-parameter-store.sh
# 跨账号跨区域同步 Parameter Store(含 Tags,支持并发和重试)

set -e

# ============ 配置 ============
SOURCE_PROFILE="source-account"      # 源账号 AWS profile
TARGET_PROFILE="target-account"      # 目标账号 AWS profile
SOURCE_REGION="ap-northeast-1"       # 东京
TARGET_REGION="eu-west-1"            # 爱尔兰
PARAM_PATH="/"                       # Parameter Store 路径,"/" 表示全部
PARALLEL=5                           # 并发数
MAX_RETRIES=3                        # 最大重试次数

# 导出变量供子进程使用
export SOURCE_PROFILE TARGET_PROFILE SOURCE_REGION TARGET_REGION MAX_RETRIES

# ============ 带重试的 AWS 命令 ============
aws_retry() {
    local i
    for ((i=1; i<=MAX_RETRIES; i++)); do
        if "$@" 2>/dev/null; then return 0; fi
        [ $i -lt $MAX_RETRIES ] && sleep $((i * 2))
    done
    return 1
}
export -f aws_retry

# ============ 同步单个参数 ============
sync_one_param() {
    local idx="$1"
    local param=$(jq -c ".[$idx]" /tmp/parameters.json)
    local name=$(echo "$param" | jq -r '.Name')
    local value=$(echo "$param" | jq -r '.Value')
    local type=$(echo "$param" | jq -r '.Type')
    
    echo "同步参数: $name"
    
    # 创建或更新参数(带重试)
    aws_retry aws ssm put-parameter \
        --name "$name" \
        --value "$value" \
        --type "$type" \
        --overwrite \
        --region $TARGET_REGION \
        --profile $TARGET_PROFILE || \
    aws_retry aws ssm put-parameter \
        --name "$name" \
        --value "$value" \
        --type "$type" \
        --region $TARGET_REGION \
        --profile $TARGET_PROFILE
    
    # 获取并同步 Tags(带重试)
    tags=$(aws_retry aws ssm list-tags-for-resource \
        --resource-type "Parameter" \
        --resource-id "$name" \
        --region $SOURCE_REGION \
        --profile $SOURCE_PROFILE \
        --query 'TagList' \
        --output json) || tags="[]"
    
    if [ "$tags" != "[]" ] && [ -n "$tags" ]; then
        aws_retry aws ssm add-tags-to-resource \
            --resource-type "Parameter" \
            --resource-id "$name" \
            --tags "$tags" \
            --region $TARGET_REGION \
            --profile $TARGET_PROFILE || true
    fi
}
export -f sync_one_param

# ============ Parameter Store 同步 ============
echo "=== 同步 Parameter Store ==="
echo "源: $SOURCE_PROFILE ($SOURCE_REGION)"
echo "目标: $TARGET_PROFILE ($TARGET_REGION)"
echo "并发数: $PARALLEL,重试: $MAX_RETRIES 次"
echo ""

# 导出参数
aws ssm get-parameters-by-path \
    --path "$PARAM_PATH" \
    --recursive \
    --with-decryption \
    --region $SOURCE_REGION \
    --profile $SOURCE_PROFILE \
    --query 'Parameters[*].{Name:Name,Value:Value,Type:Type}' \
    --output json > /tmp/parameters.json

count=$(jq length /tmp/parameters.json)
echo "导出 $count 个参数"

# 并行导入(使用索引方式避免特殊字符问题)
seq 0 $((count-1)) | xargs -P $PARALLEL -I {} bash -c 'sync_one_param "$@"' _ {}

# 清理临时文件
rm -f /tmp/parameters.json

echo ""
echo "=== Parameter Store 同步完成 ==="

3. 使用方式

环境要求

  • Linux/macOS(或 WSL)
  • AWS CLI v2
  • jq
# 1. 配置 AWS profiles
aws configure --profile source-account   # 源账号凭证
aws configure --profile target-account   # 目标账号凭证

# 2. 修改脚本中的配置变量
#    - SOURCE_PROFILE / TARGET_PROFILE
#    - SOURCE_REGION / TARGET_REGION
#    - PARAM_PATH(Parameter Store 路径过滤)
#    - PARALLEL(并发数,默认 5)

# 3. 执行 Secrets Manager 迁移
chmod +x sync-secrets-manager.sh
./sync-secrets-manager.sh

# 4. 执行 Parameter Store 迁移
chmod +x sync-parameter-store.sh
./sync-parameter-store.sh

同步参考时间(跨区域 ap-northeast-1 → eu-west-1,PARALLEL=5):

数据量 耗时
10 Secrets ~12 秒
50 Parameters ~30 秒

IAM 权限要求

注意:请将以下 Policy 中的 ap-northeast-1eu-west-1SOURCE_ACCOUNTTARGET_ACCOUNT 替换为实际值。

1. 源账号权限

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:GetParametersByPath",
                "ssm:GetParameters",
                "ssm:GetParameter",
                "ssm:ListTagsForResource"
            ],
            "Resource": "arn:aws:ssm:ap-northeast-1:SOURCE_ACCOUNT:parameter/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:ListSecrets",
                "secretsmanager:GetSecretValue",
                "secretsmanager:DescribeSecret"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": "arn:aws:kms:ap-northeast-1:SOURCE_ACCOUNT:key/*"
        }
    ]
}

2. 目标账号权限

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:PutParameter",
                "ssm:AddTagsToResource"
            ],
            "Resource": "arn:aws:ssm:eu-west-1:TARGET_ACCOUNT:parameter/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:CreateSecret",
                "secretsmanager:PutSecretValue",
                "secretsmanager:DescribeSecret",
                "secretsmanager:TagResource"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "kms:Encrypt",
                "kms:GenerateDataKey"
            ],
            "Resource": "arn:aws:kms:eu-west-1:TARGET_ACCOUNT:key/*"
        }
    ]
}

安全注意事项

事项 说明 建议
KMS 密钥 SecureString/Secrets 在目标账号会用目标账号的 KMS 密钥重新加密 可指定 CMK
敏感数据传输 脚本执行时敏感值会经过执行环境 在 EC2/CloudShell 执行
临时文件 Parameter Store 脚本的 /tmp/parameters.json 含解密后的值 脚本已自动清理,执行环境应受限
审计日志 操作会记录在 CloudTrail 确保两个账号都开启
网络安全 API 调用走公网 可配置 VPC Endpoint

参考资料

Previous Post
No Comment
Add Comment
comment url