Biny

利用 acme.sh 自动更新 HAProxy 的证书

Biny 2024-09-19 15:01:34    259 次浏览

1. 首先,查看 acme.sh 的配置信息

root@VM-24-16-debian:/etc/profile.d# acme.sh --info
LE_WORKING_DIR=/root/.acme.sh
LE_CONFIG_HOME=/root/.acme.sh


#LOG_FILE="/root/.acme.sh/acme.sh.log"
#LOG_LEVEL=1

AUTO_UPGRADE='1'

#NO_TIMESTAMP=1

2. 接着,查看 acme.sh 已配置的任务

root@VM-24-16-debian:~/.acme.sh# acme.sh --list
Main_Domain  KeyLength  SAN_Domains  CA           Created               Renew
kzwr.com     "ec-256"   *.kzwr.com   ZeroSSL.com  2024-07-11T09:15:22Z  2024-09-08T09:15:22Z

可以看见我们为 kzwr.com 配置了一个证书任务

3. 根据以上信息,我们定位到 acme.sh 的 LE_WORKING_DIR 工作目录

root@VM-24-16-debian:~/.acme.sh# cd /root/.acme.sh
root@VM-24-16-debian:~/.acme.sh# ls
account.conf  acme.sh  acme.sh.env  ca	deploy	dnsapi	http.header  kzwr.com_ecc  notify

root@VM-24-16-debian:~/.acme.sh# cd kzwr.com_ecc/
root@VM-24-16-debian:~/.acme.sh/kzwr.com_ecc# ls
backup	ca.cer	fullchain.cer  kzwr.com.cer  kzwr.com.conf  kzwr.com.csr  kzwr.com.csr.conf  kzwr.com.key

kzwr.com_ecc 是证书保存的位置

  • kzwr.com.cer:服务器证书
  • ca.cer:中间证书链(CA 证书)
  • kzwr.com.key:私钥
  • fullchain.cer:完整证书链(包括服务器证书和中间证书)

4. 查看 HAProxy 的配置信息

root@VM-24-16-debian:~/.acme.sh/kzwr.com_ecc# cat /etc/haproxy/
block/        errors/       haproxy.cfg   kzwr.com.pem  
root@VM-24-16-debian:~/.acme.sh/kzwr.com_ecc# cat /etc/haproxy/haproxy.cfg 
global
	log /dev/log	local0
	log /dev/log	local1 notice
	chroot /var/lib/haproxy
	stats socket /run/haproxy/admin.sock mode 660 level admin
	stats timeout 30s
	user haproxy
	group haproxy
	daemon

	# Default SSL material locations
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private

	# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
        ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
	log	global
	mode	http
	option	httplog
	option	dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http

# https://discourse.haproxy.org/t/how-to-redirect-non-www-or-www-without-ssl-to-www-ssl-prefix-in-haproxy/2439	
# Frontend for HTTP (port 80)
frontend http_in
    bind *:80
    # Redirect http://kzwr.com to https://www.kzwr.com
    redirect scheme https code 301 if { hdr(host) -i kzwr.com }
    # Redirect http://www.kzwr.com to https://www.kzwr.com
    redirect location https://www.kzwr.com%[capture.req.uri] code 301 if { hdr(host) -i www.kzwr.com }

frontend https_in
    bind *:443 ssl crt /etc/haproxy/kzwr.com.pem  alpn h2,http/1.1

由上可见,/etc/haproxy/kzwr.com.pem 是 HAProxy 读取的证书的位置

5. 在 /etc/haproxy/ 路径创建更新脚本

root@VM-24-16-debian:~/.acme.sh# nano renew_haproxy_cert.sh

#!/bin/bash

# Paths
ACME_CERT_DIR="/root/.acme.sh/kzwr.com_ecc"
HAPROXY_CERT_FILE="/etc/haproxy/kzwr.com.pem"
TEMP_CERT_FILE="/tmp/haproxy_new_cert.pem"

# acme.sh certificate and key files
FULLCHAIN="$ACME_CERT_DIR/fullchain.cer"
PRIVATE_KEY="$ACME_CERT_DIR/kzwr.com.key"

# Check if certificate and private key files exist
if [[ -f "$FULLCHAIN" && -f "$PRIVATE_KEY" ]]; then
    echo "Merging new certificate and private key into a temporary file..."
    cat "$FULLCHAIN" "$PRIVATE_KEY" > "$TEMP_CERT_FILE"

    # Compare the new certificate with the current one
    if ! diff "$TEMP_CERT_FILE" "$HAPROXY_CERT_FILE" > /dev/null; then
        echo "New certificate detected, updating and reloading HAProxy..."

        # Copy the new certificate to HAProxy directory
        cp "$TEMP_CERT_FILE" "$HAPROXY_CERT_FILE"

        # Check if the copy operation was successful
        if [[ $? -eq 0 ]]; then
            echo "Certificate successfully updated, reloading HAProxy..."
            systemctl reload haproxy

            if [[ $? -eq 0 ]]; then
                echo "HAProxy successfully reloaded."
            else
                echo "Failed to reload HAProxy."
            fi
        else
            echo "Failed to update the certificate."
        fi
    else
        echo "No changes in the certificate, no update needed."
    fi

    # Clean up the temporary file
    rm -f "$TEMP_CERT_FILE"

else
    echo "Certificate or private key file not found, please check $ACME_CERT_DIR"
fi

关键步骤解释:

  1. 临时合并文件:将新的证书和私钥合并到一个临时文件 $TEMP_CERT_FILE。
  2. 将新的证书和私钥合并到一个临时文件 $TEMP_CERT_FILE。
  3. 使用 diff 比较证书文件:通过 diff "$TEMP_CERT_FILE" "$HAPROXY_CERT_FILE" 来检查当前使用的证书和新生成的证书是否相同。如果有差异,表示证书已更新。
  4. 通过 diff "$TEMP_CERT_FILE" "$HAPROXY_CERT_FILE" 来检查当前使用的证书和新生成的证书是否相同。
  5. 如果有差异,表示证书已更新。
  6. 证书变更时的处理:当检测到新证书时,复制新的证书文件到 HAProxy 目录,覆盖旧证书。之后执行 systemctl reload haproxy 以重新加载 HAProxy 配置。
  7. 当检测到新证书时,复制新的证书文件到 HAProxy 目录,覆盖旧证书。
  8. 之后执行 systemctl reload haproxy 以重新加载 HAProxy 配置。
  9. 临时文件清理:最后删除临时文件 $TEMP_CERT_FILE。
  10. 最后删除临时文件 $TEMP_CERT_FILE。

6. 赋予执行权限

chmod +x renew_haproxy_cert.sh

7. 配置定时任务 (cronjob)

编辑 crontab:
crontab -e

然后添加以下条目来每天凌晨 3 点执行一次脚本:
0 3 * * * /root/.acme.sh/renew_haproxy_cert.sh >> /var/log/haproxy_cert_renew.log 2>&1
内容加载中