diff --git a/backend/.env.prod.example b/backend/.env.prod.example index b5db876..a35306f 100644 --- a/backend/.env.prod.example +++ b/backend/.env.prod.example @@ -111,8 +111,8 @@ BACKUP_OSS_URI=oss://hfb-backup # 客户端加密密码短语(独立保管,与业务密钥一同纳入 KMS/密码管理)。 # 生成方式:openssl rand -hex 32 BACKUP_PASSPHRASE=change-to-random-backup-passphrase -# OSS 服务端 KMS 加密 Key ID,备份脚本强制使用,不可留空。 -BACKUP_KMS_KEY_ID=change-kms-key-id +# 可选:自有 KMS Key ID。留空时 Bucket 必须开启“OSS 完全托管”服务器端加密。 +BACKUP_KMS_KEY_ID= # XtraBackup 专用 MySQL 账号,部署脚本会幂等创建并授予最小备份权限。 BACKUP_MYSQL_USER=hfb_backup # 生成方式:openssl rand -hex 24。不要和 MySQL 应用账号或 root 密码复用。 diff --git a/deploy/README.md b/deploy/README.md index 97221c2..6aabea9 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -40,7 +40,6 @@ BACKUP_DIR BACKUP_RESTORE_DIR BACKUP_OSS_URI BACKUP_PASSPHRASE -BACKUP_KMS_KEY_ID BACKUP_MYSQL_PASSWORD ``` @@ -72,11 +71,11 @@ RAM 凭证只授予该 Bucket 的列举、读取、写入对象权限,不要 ### 数据库在线备份与时间点恢复 -生产 MySQL 开启了 `ROW` 格式 binlog 和崩溃安全刷盘。备份期间业务持续读写:每日用 XtraBackup 做物理全量备份,每分钟轮转并归档已关闭的 binlog。备份对象先做客户端加密,再强制以 OSS KMS 加密上传;远端仅在 payload、checksum 和 manifest 都上传成功后写入 `complete.env`。 +生产 MySQL 开启了 `ROW` 格式 binlog 和崩溃安全刷盘。备份期间业务持续读写:每日用 XtraBackup 做物理全量备份,每分钟轮转并归档已关闭的 binlog。备份对象先做客户端加密,再由 Bucket 的服务器端加密保护;远端仅在 payload、checksum 和 manifest 都上传成功后写入 `complete.env`。 部署脚本会创建或更新 `BACKUP_MYSQL_USER`,该用户只有 XtraBackup 所需的全局备份权限和业务库只读权限,不开放远程 root。 -备份 Bucket 必须独立于业务 Bucket,备份 RAM 身份仅授予该 Bucket 的列举、读写对象及指定 KMS Key 的加解密权限。推荐 Bucket 开启版本控制和不可变保留策略。 +备份 Bucket 必须独立于业务 Bucket,开启“OSS 完全托管”服务器端加密和版本控制。备份 RAM 身份仅授予该 Bucket 的列举、读写对象权限;若填写 `BACKUP_KMS_KEY_ID`,再授予该 Key 的加解密权限。推荐配置不可变保留策略。 部署机需安装 `ossutil`、`openssl` 和 `flock`,并使用与 cron 相同的业务用户完成 `ossutil` 凭证配置;部署脚本会在启动服务前检查这些依赖。 diff --git a/scripts/archive-binlog.sh b/scripts/archive-binlog.sh index 2283ded..80d7c1d 100755 --- a/scripts/archive-binlog.sh +++ b/scripts/archive-binlog.sh @@ -22,7 +22,6 @@ require_archive_config() { backup_require_env BACKUP_DIR >/dev/null backup_require_env BACKUP_OSS_URI >/dev/null backup_require_env BACKUP_PASSPHRASE >/dev/null - backup_require_env BACKUP_KMS_KEY_ID >/dev/null } closed_binlogs() { @@ -91,10 +90,10 @@ archive_one() { printf 'created_at=%s\n' "$(date -Iseconds)" } > "${work_dir}/${object_id}.complete.env" - backup_oss_upload_kms "${cipher_file}" "${remote_dir}/binlog.enc" - backup_oss_upload_kms "${work_dir}/${object_id}.enc.sha256" "${remote_dir}/binlog.enc.sha256" + backup_oss_upload "${cipher_file}" "${remote_dir}/binlog.enc" + backup_oss_upload "${work_dir}/${object_id}.enc.sha256" "${remote_dir}/binlog.enc.sha256" # complete.env 最后上传,恢复程序只使用有该文件的 binlog。 - backup_oss_upload_kms "${work_dir}/${object_id}.complete.env" "${remote_dir}/complete.env" + backup_oss_upload "${work_dir}/${object_id}.complete.env" "${remote_dir}/complete.env" write_state "${state_dir}" "${filename}" "${binlog_index}" "${raw_sha}" "${cipher_sha}" "${remote_dir}" rm -f -- "${raw_file}" "${cipher_file}" "${work_dir}/${object_id}.enc.sha256" "${work_dir}/${object_id}.complete.env" } diff --git a/scripts/backup-common.sh b/scripts/backup-common.sh index 5f47e63..0e40eee 100755 --- a/scripts/backup-common.sh +++ b/scripts/backup-common.sh @@ -153,13 +153,18 @@ backup_decrypt_file() { unset passphrase } -backup_oss_upload_kms() { +backup_oss_upload() { local source="$1" local destination="$2" local kms_key_id - kms_key_id="$(backup_require_env BACKUP_KMS_KEY_ID)" - ossutil cp "${source}" "${destination}" \ - --encryption KMS --kms-master-key-id "${kms_key_id}" + kms_key_id="$(backup_env_value BACKUP_KMS_KEY_ID)" + if [[ -n "${kms_key_id}" ]]; then + ossutil cp "${source}" "${destination}" \ + --encryption KMS --kms-master-key-id "${kms_key_id}" + return + fi + # 未配置 KMS 时依赖 Bucket 已启用的 OSS 托管服务器端加密;备份载荷仍会先经过客户端加密。 + ossutil cp "${source}" "${destination}" } backup_validate_oss_uri() { diff --git a/scripts/backup-online.sh b/scripts/backup-online.sh index f57d277..4ff4714 100755 --- a/scripts/backup-online.sh +++ b/scripts/backup-online.sh @@ -22,7 +22,6 @@ require_backup_config() { backup_require_env BACKUP_DIR >/dev/null backup_require_env BACKUP_OSS_URI >/dev/null backup_require_env BACKUP_PASSPHRASE >/dev/null - backup_require_env BACKUP_KMS_KEY_ID >/dev/null backup_require_env BACKUP_MYSQL_USER >/dev/null backup_require_env BACKUP_MYSQL_PASSWORD >/dev/null } @@ -36,11 +35,11 @@ upload_backup_set() { local remote_dir="${oss_uri}/mysql/full/${level}/${server_uuid}/${ts}" backup_log "上传 ${level} 全量备份到 ${remote_dir}" - backup_oss_upload_kms "${local_dir}/mysql-full.tgz.enc" "${remote_dir}/mysql-full.tgz.enc" - backup_oss_upload_kms "${local_dir}/manifest.env" "${remote_dir}/manifest.env" - backup_oss_upload_kms "${local_dir}/mysql-full.tgz.enc.sha256" "${remote_dir}/mysql-full.tgz.enc.sha256" + backup_oss_upload "${local_dir}/mysql-full.tgz.enc" "${remote_dir}/mysql-full.tgz.enc" + backup_oss_upload "${local_dir}/manifest.env" "${remote_dir}/manifest.env" + backup_oss_upload "${local_dir}/mysql-full.tgz.enc.sha256" "${remote_dir}/mysql-full.tgz.enc.sha256" # complete.env 必须最后上传,作为远端原子完成标识。 - backup_oss_upload_kms "${local_dir}/complete.env" "${remote_dir}/complete.env" + backup_oss_upload "${local_dir}/complete.env" "${remote_dir}/complete.env" } full_backup() { diff --git a/scripts/deploy-prod.sh b/scripts/deploy-prod.sh index cec8155..2349cca 100755 --- a/scripts/deploy-prod.sh +++ b/scripts/deploy-prod.sh @@ -165,7 +165,6 @@ validate_env() { backup_restore_dir="$(require_env BACKUP_RESTORE_DIR)" backup_oss_uri="$(require_env BACKUP_OSS_URI)" require_env BACKUP_PASSPHRASE >/dev/null - require_env BACKUP_KMS_KEY_ID >/dev/null backup_user="$(require_env BACKUP_MYSQL_USER)" require_env BACKUP_MYSQL_PASSWORD >/dev/null backend_uid="$(require_env BACKEND_UID)"