mgmt/scripts/utilities/tofu-secrets-uploader.sh

495 lines
17 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# OpenTofu 密钥上传脚本
# 用于将 terraform.tfvars 中的敏感配置批量上传到 Consul
set -euo pipefail
# 配置
CONSUL_ADDR="${CONSUL_ADDR:-http://master:8500}"
CONSUL_TOKEN="${CONSUL_TOKEN:-}"
ENVIRONMENT="${ENVIRONMENT:-dev}"
TOFU_DIR="${TOFU_DIR:-tofu/environments/${ENVIRONMENT}}"
TFVARS_FILE="${TFVARS_FILE:-${TOFU_DIR}/terraform.tfvars}"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查依赖
check_dependencies() {
local deps=("curl" "jq")
for dep in "${deps[@]}"; do
if ! command -v "$dep" &> /dev/null; then
log_error "缺少依赖: $dep"
exit 1
fi
done
}
# 检查 Consul 连接
check_consul() {
log_info "检查 Consul 连接..."
if ! curl -s "${CONSUL_ADDR}/v1/status/leader" > /dev/null; then
log_error "无法连接到 Consul: ${CONSUL_ADDR}"
exit 1
fi
log_success "Consul 连接正常"
}
# 检查 tfvars 文件
check_tfvars_file() {
if [[ ! -f "$TFVARS_FILE" ]]; then
log_error "找不到 terraform.tfvars 文件: $TFVARS_FILE"
exit 1
fi
log_info "找到配置文件: $TFVARS_FILE"
}
# 解析 HCL 配置并转换为 JSON
parse_hcl_to_json() {
local tfvars_file="$1"
local temp_tf_file="/tmp/temp_config.tf"
local temp_json_file="/tmp/temp_config.json"
# 创建临时 .tf 文件,将变量赋值转换为输出
log_info "解析 HCL 配置..."
# 读取 tfvars 文件并转换为 output 格式
cat > "$temp_tf_file" << 'EOF'
# 临时配置文件,用于解析 tfvars
EOF
# 解析每个配置块
while IFS= read -r line; do
# 跳过注释和空行
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "${line// }" ]]; then
continue
fi
# 提取变量名和值
if [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=[[:space:]]*(.+)$ ]]; then
local var_name="${BASH_REMATCH[1]}"
local var_value="${BASH_REMATCH[2]}"
echo "output \"$var_name\" {" >> "$temp_tf_file"
echo " value = $var_value" >> "$temp_tf_file"
echo "}" >> "$temp_tf_file"
fi
done < "$tfvars_file"
# 使用 terraform 解析配置
if command -v terraform &> /dev/null; then
cd "$(dirname "$temp_tf_file")"
terraform init -backend=false > /dev/null 2>&1 || true
terraform output -json > "$temp_json_file" 2>/dev/null || {
log_warning "无法使用 terraform 解析,尝试手动解析..."
manual_parse_tfvars "$tfvars_file" "$temp_json_file"
}
else
log_warning "未找到 terraform使用手动解析..."
manual_parse_tfvars "$tfvars_file" "$temp_json_file"
fi
echo "$temp_json_file"
}
# 手动解析 tfvars 文件
manual_parse_tfvars() {
local tfvars_file="$1"
local output_file="$2"
log_info "手动解析 tfvars 文件..."
# 创建基础 JSON 结构
echo "{" > "$output_file"
local first_item=true
local in_block=false
local block_name=""
local block_content=""
while IFS= read -r line; do
# 跳过注释和空行
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "${line// }" ]]; then
continue
fi
# 检测配置块开始
if [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=[[:space:]]*\{[[:space:]]*$ ]]; then
block_name="${BASH_REMATCH[1]}"
in_block=true
block_content=""
continue
fi
# 检测配置块结束
if [[ "$in_block" == true && "$line" =~ ^[[:space:]]*\}[[:space:]]*$ ]]; then
if [[ "$first_item" == false ]]; then
echo "," >> "$output_file"
fi
echo " \"$block_name\": {" >> "$output_file"
echo "$block_content" >> "$output_file"
echo " }" >> "$output_file"
first_item=false
in_block=false
continue
fi
# 处理块内容
if [[ "$in_block" == true ]]; then
if [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=[[:space:]]*\"([^\"]*)\"|^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=[[:space:]]*([^[:space:]]+) ]]; then
local key="${BASH_REMATCH[1]:-${BASH_REMATCH[3]}}"
local value="${BASH_REMATCH[2]:-${BASH_REMATCH[4]}}"
if [[ -n "$block_content" ]]; then
block_content+=","
fi
block_content+="\n \"$key\": \"$value\""
fi
continue
fi
# 处理简单变量
if [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=[[:space:]]*\"([^\"]*)\"|^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=[[:space:]]*([^[:space:]]+) ]]; then
local var_name="${BASH_REMATCH[1]:-${BASH_REMATCH[3]}}"
local var_value="${BASH_REMATCH[2]:-${BASH_REMATCH[4]}}"
if [[ "$first_item" == false ]]; then
echo "," >> "$output_file"
fi
echo " \"$var_name\": \"$var_value\"" >> "$output_file"
first_item=false
fi
done < "$tfvars_file"
echo "}" >> "$output_file"
}
# 上传配置到 Consul
upload_config_to_consul() {
local config_file="$1"
local uploaded_count=0
log_info "开始上传配置到 Consul..."
# 读取 JSON 配置
if [[ ! -f "$config_file" ]]; then
log_error "配置文件不存在: $config_file"
return 1
fi
# 上传 Oracle Cloud 配置
local oci_tenancy=$(jq -r '.oci_tenancy_ocid // empty' "$config_file")
local oci_user=$(jq -r '.oci_user_ocid // empty' "$config_file")
local oci_fingerprint=$(jq -r '.oci_fingerprint // empty' "$config_file")
local oci_private_key_path=$(jq -r '.oci_private_key_path // empty' "$config_file")
local oci_compartment=$(jq -r '.oci_compartment_ocid // empty' "$config_file")
local oci_region=$(jq -r '.oci_region // empty' "$config_file")
if [[ -n "$oci_tenancy" && "$oci_tenancy" != "null" && "$oci_tenancy" != "" ]]; then
=======
# 上传配置到 Consul
upload_config_to_consul() {
local config_file="$1"
local uploaded_count=0
log_info "开始上传配置到 Consul..."
# 读取 JSON 配置
if [[ ! -f "$config_file" ]]; then
log_error "配置文件不存在: $config_file"
return 1
fi
# 上传 Oracle Cloud 配置
local oci_tenancy=$(jq -r '.oci_tenancy_ocid // empty' "$config_file")
local oci_user=$(jq -r '.oci_user_ocid // empty' "$config_file")
local oci_fingerprint=$(jq -r '.oci_fingerprint // empty' "$config_file")
local oci_private_key_path=$(jq -r '.oci_private_key_path // empty' "$config_file")
local oci_compartment=$(jq -r '.oci_compartment_ocid // empty' "$config_file")
local oci_region=$(jq -r '.oci_region // empty' "$config_file")
if [[ -n "$oci_tenancy" && "$oci_tenancy" != "null" && "$oci_tenancy" != "" ]]; then
log_info "上传 Oracle Cloud 配置..."
local base_path="config/${ENVIRONMENT}/oracle"
local tenancy_ocid=$(jq -r '.oci_config.tenancy_ocid // empty' "$config_file")
local user_ocid=$(jq -r '.oci_config.user_ocid // empty' "$config_file")
local fingerprint=$(jq -r '.oci_config.fingerprint // empty' "$config_file")
local private_key_path=$(jq -r '.oci_config.private_key_path // empty' "$config_file")
local compartment_ocid=$(jq -r '.oci_config.compartment_ocid // empty' "$config_file")
local region=$(jq -r '.oci_config.region // "ap-seoul-1"' "$config_file")
# 上传非空配置
[[ -n "$tenancy_ocid" && "$tenancy_ocid" != "null" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/tenancy_ocid" -d "$tenancy_ocid" > /dev/null
((uploaded_count++))
}
[[ -n "$user_ocid" && "$user_ocid" != "null" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/user_ocid" -d "$user_ocid" > /dev/null
((uploaded_count++))
}
[[ -n "$fingerprint" && "$fingerprint" != "null" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/fingerprint" -d "$fingerprint" > /dev/null
((uploaded_count++))
}
[[ -n "$compartment_ocid" && "$compartment_ocid" != "null" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/compartment_ocid" -d "$compartment_ocid" > /dev/null
((uploaded_count++))
}
[[ -n "$region" && "$region" != "null" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/region" -d "$region" > /dev/null
((uploaded_count++))
}
# 上传私钥文件内容
if [[ -n "$private_key_path" && "$private_key_path" != "null" && -f "$private_key_path" ]]; then
local private_key_content=$(cat "$private_key_path")
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/private_key" -d "$private_key_content" > /dev/null
((uploaded_count++))
fi
log_success "Oracle Cloud 配置已上传"
fi
# 上传华为云配置
if jq -e '.huawei_config' "$config_file" > /dev/null 2>&1; then
log_info "上传华为云配置..."
local base_path="config/${ENVIRONMENT}/huawei"
local access_key=$(jq -r '.huawei_config.access_key // empty' "$config_file")
local secret_key=$(jq -r '.huawei_config.secret_key // empty' "$config_file")
local region=$(jq -r '.huawei_config.region // "cn-north-4"' "$config_file")
local project_id=$(jq -r '.huawei_config.project_id // empty' "$config_file")
[[ -n "$access_key" && "$access_key" != "null" && "$access_key" != "" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/access_key" -d "$access_key" > /dev/null
((uploaded_count++))
}
[[ -n "$secret_key" && "$secret_key" != "null" && "$secret_key" != "" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/secret_key" -d "$secret_key" > /dev/null
((uploaded_count++))
}
[[ -n "$region" && "$region" != "null" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/region" -d "$region" > /dev/null
((uploaded_count++))
}
[[ -n "$project_id" && "$project_id" != "null" && "$project_id" != "" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/project_id" -d "$project_id" > /dev/null
((uploaded_count++))
}
log_success "华为云配置已上传"
fi
# 上传 AWS 配置
if jq -e '.aws_config' "$config_file" > /dev/null 2>&1; then
log_info "上传 AWS 配置..."
local base_path="config/${ENVIRONMENT}/aws"
local access_key=$(jq -r '.aws_config.access_key // empty' "$config_file")
local secret_key=$(jq -r '.aws_config.secret_key // empty' "$config_file")
local region=$(jq -r '.aws_config.region // "ap-northeast-2"' "$config_file")
[[ -n "$access_key" && "$access_key" != "null" && "$access_key" != "" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/access_key" -d "$access_key" > /dev/null
((uploaded_count++))
}
[[ -n "$secret_key" && "$secret_key" != "null" && "$secret_key" != "" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/secret_key" -d "$secret_key" > /dev/null
((uploaded_count++))
}
[[ -n "$region" && "$region" != "null" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/region" -d "$region" > /dev/null
((uploaded_count++))
}
log_success "AWS 配置已上传"
fi
# 上传 DigitalOcean 配置
if jq -e '.do_config' "$config_file" > /dev/null 2>&1; then
log_info "上传 DigitalOcean 配置..."
local base_path="config/${ENVIRONMENT}/digitalocean"
local token=$(jq -r '.do_config.token // empty' "$config_file")
local region=$(jq -r '.do_config.region // "sgp1"' "$config_file")
[[ -n "$token" && "$token" != "null" && "$token" != "" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/token" -d "$token" > /dev/null
((uploaded_count++))
}
[[ -n "$region" && "$region" != "null" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/region" -d "$region" > /dev/null
((uploaded_count++))
}
log_success "DigitalOcean 配置已上传"
fi
# 上传 Google Cloud 配置
if jq -e '.gcp_config' "$config_file" > /dev/null 2>&1; then
log_info "上传 Google Cloud 配置..."
local base_path="config/${ENVIRONMENT}/gcp"
local project_id=$(jq -r '.gcp_config.project_id // empty' "$config_file")
local region=$(jq -r '.gcp_config.region // "asia-northeast3"' "$config_file")
local zone=$(jq -r '.gcp_config.zone // "asia-northeast3-a"' "$config_file")
local credentials_file=$(jq -r '.gcp_config.credentials_file // empty' "$config_file")
[[ -n "$project_id" && "$project_id" != "null" && "$project_id" != "" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/project_id" -d "$project_id" > /dev/null
((uploaded_count++))
}
[[ -n "$region" && "$region" != "null" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/region" -d "$region" > /dev/null
((uploaded_count++))
}
[[ -n "$zone" && "$zone" != "null" ]] && {
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/zone" -d "$zone" > /dev/null
((uploaded_count++))
}
# 上传凭证文件内容
if [[ -n "$credentials_file" && "$credentials_file" != "null" && -f "$credentials_file" ]]; then
local credentials_content=$(cat "$credentials_file")
curl -s -X PUT "${CONSUL_ADDR}/v1/kv/${base_path}/credentials" -d "$credentials_content" > /dev/null
((uploaded_count++))
fi
log_success "Google Cloud 配置已上传"
fi
log_success "总共上传了 $uploaded_count 个配置项到 Consul"
}
# 列出 Consul 中的配置
list_consul_configs() {
log_info "列出 Consul 中的配置..."
local base_path="config/${ENVIRONMENT}"
echo "=== Consul 中的配置 ==="
# 获取所有配置键
local keys=$(curl -s "${CONSUL_ADDR}/v1/kv/${base_path}/?keys" | jq -r '.[]' 2>/dev/null || echo "")
if [[ -z "$keys" ]]; then
log_warning "Consul 中没有找到配置"
return
fi
echo "$keys" | while read -r key; do
local value=$(curl -s "${CONSUL_ADDR}/v1/kv/${key}?raw" 2>/dev/null || echo "无法读取")
# 隐藏敏感信息
if [[ "$key" =~ (secret|key|token|password) ]]; then
echo "$key: [已隐藏]"
else
echo "$key: $value"
fi
done
}
# 清理 Consul 配置
cleanup_consul_configs() {
log_warning "清理 Consul 配置..."
read -p "确定要删除环境 '$ENVIRONMENT' 的所有配置吗?(y/N): " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
log_info "操作已取消"
return
fi
local base_path="config/${ENVIRONMENT}"
curl -s -X DELETE "${CONSUL_ADDR}/v1/kv/${base_path}?recurse" > /dev/null
log_success "环境 '$ENVIRONMENT' 的配置已清理"
}
# 显示帮助信息
show_help() {
cat << EOF
OpenTofu 密钥上传脚本
用法: $0 [选项]
选项:
upload 上传 terraform.tfvars 中的配置到 Consul
list 列出 Consul 中的配置
cleanup 清理 Consul 中的配置
help 显示此帮助信息
环境变量:
CONSUL_ADDR Consul 地址 (默认: http://localhost:8500)
CONSUL_TOKEN Consul ACL Token (可选)
ENVIRONMENT 环境名称 (默认: dev)
TOFU_DIR OpenTofu 目录 (默认: tofu/environments/\${ENVIRONMENT})
TFVARS_FILE 变量文件路径 (默认: \${TOFU_DIR}/terraform.tfvars)
示例:
# 上传配置到 Consul
$0 upload
# 列出 Consul 中的配置
$0 list
# 清理配置
$0 cleanup
# 指定不同环境
ENVIRONMENT=production $0 upload
EOF
}
# 主函数
main() {
check_dependencies
case "${1:-help}" in
"upload")
check_consul
check_tfvars_file
log_info "解析配置文件: $TFVARS_FILE"
local config_json=$(manual_parse_tfvars "$TFVARS_FILE" "/tmp/parsed_config.json")
upload_config_to_consul "/tmp/parsed_config.json"
# 清理临时文件
rm -f /tmp/parsed_config.json /tmp/temp_config.tf
;;
"list")
check_consul
list_consul_configs
;;
"cleanup")
check_consul
cleanup_consul_configs
;;
"help"|*)
show_help
;;
esac
}
main "$@"