提交 93c5ab3a authored 作者: PGY's avatar PGY

新增自动化脚本 `generate_ssl.sh` ,支持一键替换ssl证书文件。

上级 e9d84f00
# SSL 证书生成(自签名,包含 SAN)
目标:生成一套可用于 Nginx 的自签名证书(`server.key` + `server.crt`),支持现代浏览器(包含 SAN),并提供自动化脚本以便快速部署与(可选)替换 Nginx 配置。
注意:OpenSSL 支持的最大有效期有限,常见做法是设置为 100 年(`-days 36500`),接近“长期有效”。自签名 CA 仅在受信任的环境(内网/测试)使用。
## 先决条件
- 服务器需安装 `openssl`
- 若希望脚本自动写入并重载 nginx,需具备相应的文件访问/重载权限(或 root 权限)。
## 要点
- 必须包含 `subjectAltName`(SAN),否则现代浏览器会拒绝或显示不安全警告。
- 脚本会尝试自动检测主机 IPv4 并填充到 SAN 的 IP 项中;也可通过 `--ip` 指定。
---
## 一、san.cnf 示例(带 SAN)
以下模板会被脚本生成;你也可以手动创建并修改:
```ini
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[dn]
CN = ubains.com
O = UBAINS
OU = DevOps
C = CN
ST = Guangdong
L = Shenzhen
emailAddress = admin@ubains.com
[v3_req]
basicConstraints = CA:TRUE
keyUsage = critical, digitalSignature, keyEncipherment, keyCertSign
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = ubains.com
DNS.2 = localhost
IP.1 = 192.168.1.100 ; 脚本会自动替换为服务器实际 IP
```
---
## 二、推荐方式:使用仓库中的脚本(自动化)
脚本路径:`辅助工具/脚本工具/服务器ssl证书生成/generate_ssl.sh`
示例:
```bash
# 在当前目录生成 cert/key(自动检测 IP)
bash generate_ssl.sh --domain ubains.com --out /etc/ssl/ubains
# 指定输出目录并直接修改 nginx 配置(会备份原文件并尝试重载)
bash generate_ssl.sh --domain ubains.com --out /etc/ssl/ubains --nginx-conf /etc/nginx/conf.d/unified443.conf --yes
```
脚本要点:
- 自动检测本机 IPv4 并填充到 SAN 中(可用 `--ip` 覆盖)。
- 默认证书有效期 100 年(`-days 36500`),可用 `--days` 修改。
- 会生成 `san.cnf``server.key``server.crt``--out` 指定目录。
- 若不指定 `--nginx-conf`,脚本会自动检测 PRD 常见配置路径并提示是否一键替换(支持 `--yes` 跳过提示)。
自动检测的候选 nginx 配置路径(来自 PRD):
- `/data/middleware/nginx/config/unified443.conf` (统一平台)
- `/var/www/java/nginx-conf.d/meeting443.conf` (预定系统)
- `/var/www/html/nginx-conf/moblie8081.conf` (运维系统)
- `/var/www/html/nginx-conf/rms8443.conf` (运维系统)
脚本在替换配置前会备份原文件(`*.bak.TIMESTAMP`)。替换后尝试 `nginx -t` 与重载,若失败会提示手动操作或使用 systemctl/docker 重启。
---
## 三、Nginx 配置示例
在你的 `server {}` 块中,确保有:
```nginx
ssl_certificate /path/to/server.crt;
ssl_certificate_key /path/to/server.key;
```
PRD 中常见的重载命令示例:
- 统一平台(示例):
- 配置文件:`/data/middleware/nginx/config/unified443.conf`
- 重启:`docker restart nginx`
- 预定系统(示例):
- 配置文件:`/var/www/java/nginx-conf.d/meeting443.conf`
- 重载:`docker exec -it ujava2 bash; /usr/local/nginx/sbin/nginx -s reload`
- 运维系统(示例):
- 配置文件:`/var/www/html/nginx-conf/moblie8081.conf``rms8443.conf`
- 重载:`docker exec -it upython bash; /usr/local/nginx/sbin/nginx -s reload`
如果在系统上运行 nginx,可使用:
```bash
nginx -t && nginx -s reload
# 或
systemctl reload nginx
```
---
## 四、在操作系统 / 浏览器 中导入证书(建立信任)
- Windows
1. 双击 `server.crt` → 点击 “安装证书” → 选择 “本地计算机”。
2. 放入 “受信任的根证书颁发机构” 存储。
3. 完成后重启浏览器。
- macOS
1. 双击 `server.crt` 打开“钥匙串访问”。
2.`System` 钥匙串中找到证书,设置为 `Always Trust`(始终信任)。
- Linux (Ubuntu/Debian)
```bash
sudo cp server.crt /usr/local/share/ca-certificates/ubains-ca.crt
sudo update-ca-certificates
```
- Firefox(独立证书库)
- 设置 → 隐私与安全 → 证书 → “查看证书” → “证书机构” → “导入” → 选择 `server.crt`,勾选 “信任此 CA 以标识网站”。
---
## 五、测试步骤
1. 确认域名解析:若域名不在 DNS 中,请在浏览器所在机器的 `/etc/hosts`(Windows: `C:\Windows\System32\drivers\etc\hosts`)中添加 `IP domain` 映射。
2. 关闭并完全重启浏览器(确保信任链刷新)。
3. 访问 `https://<domain>``https://<ip>`,应看到锁图标且无证书错误。
---
## 六、注意与故障排查
- 自签名 CA 仅对受信任的客户端有效;生产环境请使用受信任的公有 CA(如 Let’s Encrypt、商业 CA)。
- 若浏览器仍报错:
- 检查证书是否包含 SAN(`openssl x509 -in server.crt -text -noout`)。
- 确认证书已导入到“受信任的根证书颁发机构”。
- 检查 nginx 是否使用了正确的证书路径并重载成功(`nginx -t`)。
- 常见权限问题:私钥权限应为 600(`chmod 600 server.key`)。
---
## 七、变更记录
- 2025-12-26:整合并优化文档,新增自动化脚本 `generate_ssl.sh` 说明与 nginx 自动检测/替换说明。
---
## 八、配置文件 → 重载/重启 命令
说明:为了确保替换配置后能按各系统要求正确重载 nginx,建议在文档或脚本中维护显式的“配置文件路径 → 重载命令”映射。脚本实现自动检测并尝试多种重载方式。
默认建议映射(可直接放入脚本变量或单独配置文件):
- 统一平台
- 配置路径:`/data/middleware/nginx/config/unified443.conf`
- 推荐命令:`docker restart nginx`
- 预定系统
- 配置路径:`/var/www/java/nginx-conf.d/meeting443.conf`
- 推荐命令:`docker exec -it ujava2 /usr/local/nginx/sbin/nginx -s reload`
- 运维系统
- 配置路径:`/var/www/html/nginx-conf/moblie8081.conf``/var/www/html/nginx-conf/rms8443.conf`
- 推荐命令:`docker exec -it upython /usr/local/nginx/sbin/nginx -s reload`
-
\ No newline at end of file
#!/usr/bin/env bash
set -euo pipefail
# 生成自签名 SSL 证书(含 SAN)脚本
# 用法示例:
# ./generate_ssl.sh --domain ubains.com --out /etc/ssl/ubains --nginx-conf /etc/nginx/conf.d/unified443.conf --yes
DOMAIN_DEFAULT="ubains.com"
DAYS_DEFAULT=36500
OUT_DIR="."
DOMAIN=""
IP=""
NGINX_CONF=""
FORCE_NO_PROMPT=0
print_usage(){
cat <<EOF
Usage: $0 [--domain DOMAIN] [--ip IP] [--out DIR] [--days N] [--nginx-conf FILE] [--yes]
Options:
--domain DOMAIN 主机名 (默认: ${DOMAIN_DEFAULT})
--ip IP 指定 SAN 中的 IP(默认自动检测)
--out DIR 输出目录,默认当前目录
--days N 证书有效天数 (默认 ${DAYS_DEFAULT})
--nginx-conf FILE 可选:将证书路径写入指定的 nginx 配置并重载(脚本会备份原文件)
--yes 不交互确认,直接执行替换 nginx 配置
-h, --help 显示本帮助
EOF
}
# 解析参数
while [[ $# -gt 0 ]]; do
case "$1" in
--domain) DOMAIN="$2"; shift 2;;
--ip) IP="$2"; shift 2;;
--out) OUT_DIR="$2"; shift 2;;
--days) DAYS="$2"; shift 2;;
--nginx-conf) NGINX_CONF="$2"; shift 2;;
--yes) FORCE_NO_PROMPT=1; shift 1;;
-h|--help) print_usage; exit 0;;
*) echo "Unknown arg: $1"; print_usage; exit 1;;
esac
done
DOMAIN=${DOMAIN:-$DOMAIN_DEFAULT}
DAYS=${DAYS:-$DAYS_DEFAULT}
# 检测主机 IPv4(非回环)
detect_ip(){
local ip
ip=$(hostname -I 2>/dev/null | awk '{print $1}' || true)
if [ -z "$ip" ]; then
ip=$(ip route get 1.1.1.1 2>/dev/null | awk '/src/ {for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -n1 || true)
fi
if [ -z "$ip" ]; then
ip=$(ip addr show scope global 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)
fi
echo "${ip:-127.0.0.1}"
}
if [ -z "$IP" ]; then
IP=$(detect_ip)
fi
mkdir -p "$OUT_DIR"
cd "$OUT_DIR"
# 日志文件
LOG_FILE="$(pwd)/generate_ssl.log"
touch "$LOG_FILE"
# 颜色与日志函数
COL_RESET="\033[0m"
COL_RED="\033[31m"
COL_GREEN="\033[32m"
COL_YELLOW="\033[33m"
COL_BLUE="\033[34m"
log_to_file(){
echo "$(date +'%F %T') $*" >> "$LOG_FILE"
}
info(){
echo -e "${COL_BLUE}[INFO]${COL_RESET} $*"
log_to_file "[INFO] $*"
}
success(){
echo -e "${COL_GREEN}[OK]${COL_RESET} $*"
log_to_file "[OK] $*"
}
warn(){
echo -e "${COL_YELLOW}[WARN]${COL_RESET} $*" >&2
log_to_file "[WARN] $*"
}
err(){
echo -e "${COL_RED}[ERROR]${COL_RESET} $*" >&2
log_to_file "[ERROR] $*"
}
SAN_CONF="san.cnf"
KEY_FILE="server.key"
CRT_FILE="server.crt"
cat > "$SAN_CONF" <<EOF
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[dn]
CN = ${DOMAIN}
O = UBAINS
OU = DevOps
C = CN
ST = Guangdong
L = Shenzhen
emailAddress = admin@ubains.com
[v3_req]
basicConstraints = CA:TRUE
keyUsage = critical, digitalSignature, keyEncipherment, keyCertSign
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${DOMAIN}
DNS.2 = localhost
IP.1 = ${IP}
EOF
info "生成配置文件: $(pwd)/${SAN_CONF} (CN=${DOMAIN}, IP=${IP})"
# 生成私钥与自签名证书
if ! command -v openssl >/dev/null 2>&1; then
err "未找到 openssl,请先安装 OpenSSL。"
exit 2
fi
info "开始生成私钥与自签名证书(这可能需要几秒)..."
if openssl req -x509 \
-newkey rsa:2048 \
-nodes \
-keyout "$KEY_FILE" \
-out "$CRT_FILE" \
-days "$DAYS" \
-config "$SAN_CONF" \
-extensions v3_req; then
chmod 600 "$KEY_FILE"
success "已生成: 私钥: $(pwd)/${KEY_FILE} 证书: $(pwd)/${CRT_FILE}"
else
err "OpenSSL 生成证书失败。请检查 OpenSSL 输出与 $SAN_CONF 的配置。详细日志见 $LOG_FILE"
exit 3
fi
# 可选:更新 nginx 配置,将证书路径写入并重载
inject_into_nginx_conf(){
local conf="$1"
if [ ! -f "$conf" ]; then
warn "跳过:配置文件不存在: $conf"
return 1
fi
local backup="${conf}.bak.$(date +%s)"
cp "$conf" "$backup"
info "已备份原配置到: $backup"
sed -E -i \
-e "s|^\s*ssl_certificate\s+.+;| ssl_certificate $(pwd)/${CRT_FILE};|g" \
-e "s|^\s*ssl_certificate_key\s+.+;| ssl_certificate_key $(pwd)/${KEY_FILE};|g" \
"$conf"
if ! grep -q "ssl_certificate" "$conf"; then
awk -v crt="$(pwd)/${CRT_FILE}" -v key="$(pwd)/${KEY_FILE}" '
/server[[:space:]]*\{/ {print; in_server=1; next}
in_server && /\}/ {print " ssl_certificate " crt ";"; print " ssl_certificate_key " key ";"; in_server=0}
{print}
' "$conf" > "${conf}.tmp" && mv "${conf}.tmp" "$conf"
fi
info "已将证书路径写入: $conf"
# 在配置文件目录下创建证书存放目录,并把证书移动/复制到该目录
local conf_dir
conf_dir=$(dirname "$conf")
local cert_store_dir="$conf_dir/certs"
mkdir -p "$cert_store_dir"
local src_key="$(pwd)/${KEY_FILE}"
local src_crt="$(pwd)/${CRT_FILE}"
local dest_key="$cert_store_dir/server.key"
local dest_crt="$cert_store_dir/server.crt"
if [ -f "$src_key" ]; then
cp -f "$src_key" "$dest_key"
chmod 600 "$dest_key" || true
else
warn "未找到源私钥 $src_key,跳过复制私钥。"
fi
if [ -f "$src_crt" ]; then
cp -f "$src_crt" "$dest_crt"
chmod 644 "$dest_crt" || true
else
warn "未找到源证书 $src_crt,跳过复制证书。"
fi
info "已将证书复制到: $cert_store_dir"
# 替换 nginx 配置,使用证书目标路径(而非脚本所在路径)
sed -E -i \
-e "s|^\s*ssl_certificate\s+.+;| ssl_certificate ${dest_crt};|g" \
-e "s|^\s*ssl_certificate_key\s+.+;| ssl_certificate_key ${dest_key};|g" \
"$conf"
if ! grep -q "ssl_certificate" "$conf"; then
awk -v crt="${dest_crt}" -v key="${dest_key}" '
/server[[:space:]]*\{/ {print; in_server=1; next}
in_server && /\}/ {print " ssl_certificate " crt ";"; print " ssl_certificate_key " key ";"; in_server=0}
{print}
' "$conf" > "${conf}.tmp" && mv "${conf}.tmp" "$conf"
fi
info "已将证书路径写入: $conf (指向 $cert_store_dir)"
local RELOAD_OK=0
# 1) 优先尝试本地 nginx 可用时的 reload
if command -v nginx >/dev/null 2>&1; then
info "检测到本地 nginx 可执行文件,尝试 nginx -t && nginx -s reload"
if nginx -t >/dev/null 2>&1; then
if nginx -s reload >/dev/null 2>&1; then
RELOAD_OK=1
else
warn "nginx -s reload 返回非零,继续尝试其他重载方式。"
fi
else
warn "nginx -t 检测失败,跳过本地 reload。"
fi
fi
# 2) 尝试 systemctl reload
if [ $RELOAD_OK -eq 0 ] && command -v systemctl >/dev/null 2>&1; then
info "尝试使用 systemctl reload nginx"
if systemctl reload nginx >/dev/null 2>&1; then
RELOAD_OK=1
else
warn "systemctl reload nginx 失败。"
fi
fi
# 3) 尝试 Docker 环境中的常见容器名或 nginx 镜像
if [ $RELOAD_OK -eq 0 ] && command -v docker >/dev/null 2>&1; then
info "尝试在 Docker 容器中重载 nginx(寻找常见容器名或 nginx 镜像)"
# 首先检查常见容器名
DOCKER_TARGETS=()
for name in nginx ujava2 upython; do
if docker ps --format '{{.Names}}' | grep -wq "$name"; then
DOCKER_TARGETS+=("$name")
fi
done
# 再查找镜像名包含 nginx 的容器
while read -r line; do
c_name=$(echo "$line" | awk '{print $1}')
c_image=$(echo "$line" | awk '{print $2}')
if echo "$c_image" | grep -qi nginx; then
DOCKER_TARGETS+=("$c_name")
fi
done < <(docker ps --format '{{.Names}} {{.Image}}')
# 去重
if [ ${#DOCKER_TARGETS[@]} -gt 0 ]; then
unique_targets=($(printf "%s\n" "${DOCKER_TARGETS[@]}" | awk '!x[$0]++'))
for ct in "${unique_targets[@]}"; do
info "尝试在容器 $ct 内重载 nginx(优先尝试 nginx -t && nginx -s reload)"
if docker exec "$ct" /usr/local/nginx/sbin/nginx -t >/dev/null 2>&1; then
if docker exec "$ct" /usr/local/nginx/sbin/nginx -s reload >/dev/null 2>&1; then
RELOAD_OK=1
info "容器 $ct 内 nginx 重载成功。"
break
else
warn "容器 $ct 内 nginx -s reload 失败,尝试 docker restart $ct"
if docker restart "$ct" >/dev/null 2>&1; then
RELOAD_OK=1
info "已重启容器 $ct。"
break
fi
fi
else
warn "容器 $ct 中未找到 /usr/local/nginx/sbin/nginx 或检测失败,尝试 docker restart $ct"
if docker restart "$ct" >/dev/null 2>&1; then
RELOAD_OK=1
info "已重启容器 $ct。"
break
fi
fi
done
else
warn "未发现疑似 nginx 的 Docker 容器。"
fi
fi
if [ $RELOAD_OK -eq 1 ]; then
success "nginx 重载成功。"
else
err "未能自动重载 nginx。请根据实际运行环境手动重载或重启相应容器/服务。"
# 提示建议命令
if command -v docker >/dev/null 2>&1; then
err "如果使用 Docker,请运行: docker ps --format '{{.Names}} {{.Image}}' 查看容器,然后对相应容器执行 docker restart <name> 或 docker exec <name> /usr/local/nginx/sbin/nginx -s reload"
else
err "可尝试: nginx -t && nginx -s reload 或 systemctl reload nginx 或 docker restart <container>"
fi
fi
return 0
}
# 预定义候选 nginx 配置路径(根据 PRD)
CANDIDATES=(
"/data/middleware/nginx/config/unified443.conf"
"/var/www/java/nginx-conf.d/meeting443.conf"
"/var/www/html/nginx-conf/moblie8081.conf"
"/var/www/html/nginx-conf/rms8443.conf"
)
FOUND=()
for p in "${CANDIDATES[@]}"; do
if [ -f "$p" ]; then
FOUND+=("$p")
fi
done
if [ -n "$NGINX_CONF" ]; then
# 用户指定单个配置文件,优先处理它
inject_into_nginx_conf "$NGINX_CONF" || true
else
if [ ${#FOUND[@]} -gt 0 ]; then
info "检测到以下可能的 nginx 配置文件:"
for f in "${FOUND[@]}"; do
echo -e " - ${COL_YELLOW}$f${COL_RESET}"
done
if [ $FORCE_NO_PROMPT -eq 0 ]; then
read -r -p "是否一键替换以上所有配置并重载 nginx? [y/N]: " ans
else
ans="y"
fi
case "$ans" in
y|Y|yes|YES)
for f in "${FOUND[@]}"; do
inject_into_nginx_conf "$f" || true
done
;;
*) info "已取消自动替换。你仍然可以手动运行脚本并指定 --nginx-conf 来替换。";;
esac
else
warn "未检测到预定义的 nginx 配置文件。若要手动指定,请使用 --nginx-conf PATH。"
fi
fi
info "下一步:将 $(pwd)/${CRT_FILE} 导入到受信任的根证书颁发机构,以在浏览器中建立信任。"
echo "Windows: 双击证书并安装到 '受信任的根证书颁发机构'。"
echo "macOS: 双击并在钥匙串访问中设为始终信任。"
echo "Linux(Ubuntu/Debian): sudo cp ${CRT_FILE} /usr/local/share/ca-certificates/ubains-ca.crt && sudo update-ca-certificates"
echo "Firefox: 在设置 → 隐私与安全 → 证书 → 证书机构 中导入并信任此证书。"
success "完成。详细操作与错误请查看日志: $LOG_FILE"
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论