{{item}}
{{item.title}}
{{items.productName}}
{{items.price}}/年
{{item.title}}
部警SSL证书可实现网站HTTPS加密保护及身份的可信认证,防止传输数据的泄露或算改,提高网站可信度和品牌形象,利于SEO排名,为企业带来更多访问量,这也是网络安全法及PCI合规性的必备要求
前往SSL证书随着企业IT基础设施的不断扩展,自动化管理变得越来越重要。Ansible作为一款强大的自动化工具,可以帮助我们轻松应对各种复杂的运维场景。将SSL证书管理纳入自动化流程,是保障网站安全和高可用性的重要一步。
在现代Web架构中,负载均衡是保障高可用性和可扩展性的核心组件。当使用多台服务器组成集群并通过负载均衡器分发流量时,SSL/TLS证书的一致性成为一个关键问题。如果不同服务器上的证书版本不一致,会导致用户在访问时出现间歇性的SSL错误,严重影响用户体验和网站可信度。
Ansible作为一款轻量级的配置管理和自动化工具,非常适合解决多服务器证书同步问题。它采用无代理架构,通过SSH协议与目标服务器通信,不需要在目标服务器上安装任何客户端软件。
在开始编写Ansible脚本之前,需要确保环境满足以下条件:
下面我们将编写一个完整的Ansible Playbook,实现SSL证书的批量同步、自动备份、服务重启和验证。
ansible-ssl-sync/
├── inventory.ini # 主机清单文件
├── ssl_sync.yml # 主Playbook
├── vars/
│ └── main.yml # 变量文件
├── files/
│ ├── fullchain.pem # 最新的完整证书链文件
│ └── privkey.pem # 最新的私钥文件
└── logs/ # 日志目录[web_servers]
web01.example.com ansible_host=192.168.1.101
web02.example.com ansible_host=192.168.1.102
web03.example.com ansible_host=192.168.1.103
[web_servers:vars]
ansible_user=ubuntu
ansible_become=yes
ansible_become_method=sudo
ansible_ssh_private_key_file=~/.ssh/id_rsa# SSL证书配置
ssl_cert_local_path: "./files/fullchain.pem"
ssl_key_local_path: "./files/privkey.pem"
ssl_cert_remote_path: "/etc/ssl/certs/example.com.crt"
ssl_key_remote_path: "/etc/ssl/private/example.com.key"
# 证书权限配置(严格遵循最小权限原则)
ssl_cert_owner: "root"
ssl_cert_group: "root"
ssl_cert_mode: "0644"
ssl_key_owner: "root"
ssl_key_group: "root"
ssl_key_mode: "0600" # 私钥必须只有root用户可读
# 服务配置
ssl_services_to_restart:
- nginx
# - apache2
# - tomcat9
# - haproxy
# 验证配置
ssl_verify_port: 443
ssl_verify_domain: "example.com"
ssl_verify_timeout: 10
ssl_verify_days_before_expiry: 30 # 提前30天提醒证书即将过期
# 备份配置
ssl_backup_retention_days: 30 # 保留30天的备份---
- name: 批量同步SSL证书到多台服务器
hosts: web_servers
gather_facts: yes
vars_files:
- vars/main.yml
tasks:
- name: 检查本地证书文件是否存在
stat:
path: "{{ item }}"
loop:
- "{{ ssl_cert_local_path }}"
- "{{ ssl_key_local_path }}"
delegate_to: localhost
run_once: yes
register: local_cert_check
failed_when: not item.stat.exists
tags:
- check
- sync
- name: 验证本地证书有效性
command: openssl x509 -in {{ ssl_cert_local_path }} -checkend 86400 -noout
delegate_to: localhost
run_once: yes
register: cert_validity_check
failed_when: cert_validity_check.rc != 0
changed_when: false
tags:
- check
- sync
- name: 检查本地证书和私钥是否匹配
shell: |
cert_modulus=$(openssl x509 -noout -modulus -in {{ ssl_cert_local_path }} | openssl md5)
key_modulus=$(openssl rsa -noout -modulus -in {{ ssl_key_local_path }} | openssl md5)
if [ "$cert_modulus" != "$key_modulus" ]; then
echo "证书和私钥不匹配"
exit 1
fi
delegate_to: localhost
run_once: yes
changed_when: false
tags:
- check
- sync
- name: 创建远程证书目录(如果不存在)
file:
path: "{{ item | dirname }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- "{{ ssl_cert_remote_path }}"
- "{{ ssl_key_remote_path }}"
tags:
- sync
- name: 备份旧证书
copy:
src: "{{ item }}"
dest: "{{ item }}.backup.{{ ansible_date_time.epoch }}"
remote_src: yes
owner: root
group: root
mode: "0600"
loop:
- "{{ ssl_cert_remote_path }}"
- "{{ ssl_key_remote_path }}"
when: ansible_facts['os_family'] == 'Debian' or ansible_facts['os_family'] == 'RedHat'
ignore_errors: yes
tags:
- backup
- sync
- name: 清理过期备份
find:
paths: "{{ item | dirname }}"
patterns: "{{ item | basename }}.backup.*"
age: "{{ ssl_backup_retention_days }}d"
state: absent
loop:
- "{{ ssl_cert_remote_path }}"
- "{{ ssl_key_remote_path }}"
tags:
- backup
- sync
- name: 同步证书文件
copy:
src: "{{ ssl_cert_local_path }}"
dest: "{{ ssl_cert_remote_path }}"
owner: "{{ ssl_cert_owner }}"
group: "{{ ssl_cert_group }}"
mode: "{{ ssl_cert_mode }}"
backup: no
register: cert_sync_result
tags:
- sync
- name: 同步私钥文件
copy:
src: "{{ ssl_key_local_path }}"
dest: "{{ ssl_key_remote_path }}"
owner: "{{ ssl_key_owner }}"
group: "{{ ssl_key_group }}"
mode: "{{ ssl_key_mode }}"
backup: no
register: key_sync_result
tags:
- sync
- name: 重启依赖证书的服务
service:
name: "{{ item }}"
state: restarted
enabled: yes
loop: "{{ ssl_services_to_restart }}"
when: cert_sync_result.changed or key_sync_result.changed
tags:
- restart
- sync
- name: 等待服务启动完成
wait_for:
port: "{{ ssl_verify_port }}"
delay: 5
timeout: 30
when: cert_sync_result.changed or key_sync_result.changed
tags:
- verify
- sync
- name: 验证SSL证书是否正确安装
command: >
openssl s_client -connect {{ inventory_hostname }}:{{ ssl_verify_port }}
-servername {{ ssl_verify_domain }}
< /dev/null 2>/dev/null
| openssl x509 -noout -dates -subject -issuer
register: ssl_verify_result
changed_when: false
failed_when: ssl_verify_result.rc != 0
tags:
- verify
- sync
- name: 检查证书是否即将过期
command: >
openssl x509 -in {{ ssl_cert_remote_path }}
-checkend {{ ssl_verify_days_before_expiry * 86400 }} -noout
register: cert_expiry_check
changed_when: false
ignore_errors: yes
tags:
- verify
- sync
- name: 显示证书信息
debug:
msg: "{{ ssl_verify_result.stdout_lines }}"
tags:
- verify
- sync
- name: 证书即将过期警告
debug:
msg: "警告:证书将在{{ ssl_verify_days_before_expiry }}天内过期,请及时更新"
when: cert_expiry_check.rc != 0
tags:
- verify
- sync
- name: 同步完成通知
debug:
msg: "SSL证书已成功同步到 {{ inventory_hostname }} 并验证通过"
tags:
- sync为了避免不必要的文件传输和服务重启,我们可以添加证书差异检查功能,只在证书内容发生变化时才进行同步。
- name: 获取远程证书的SHA256哈希值
stat:
path: "{{ ssl_cert_remote_path }}"
get_checksum: yes
checksum_algorithm: sha256
register: remote_cert_hash
tags:
- check
- sync
- name: 获取本地证书的SHA256哈希值
stat:
path: "{{ ssl_cert_local_path }}"
get_checksum: yes
checksum_algorithm: sha256
delegate_to: localhost
run_once: yes
register: local_cert_hash
tags:
- check
- sync
- name: 比较证书哈希值
debug:
msg: "证书已更新,需要同步"
when: remote_cert_hash.stat.checksum != local_cert_hash.stat.checksum
tags:
- check
- sync在同步失败时,我们需要能够快速回滚到之前的证书版本。
- name: 列出可用的证书备份文件
find:
paths: "{{ ssl_cert_remote_path | dirname }}"
patterns: "{{ ssl_cert_remote_path | basename }}.backup.*"
sort: mtime
reverse: yes
register: cert_backups
tags:
- rollback
- name: 列出可用的私钥备份文件
find:
paths: "{{ ssl_key_remote_path | dirname }}"
patterns: "{{ ssl_key_remote_path | basename }}.backup.*"
sort: mtime
reverse: yes
register: key_backups
tags:
- rollback
- name: 恢复最新的证书备份
copy:
src: "{{ cert_backups.files[0].path }}"
dest: "{{ ssl_cert_remote_path }}"
remote_src: yes
owner: "{{ ssl_cert_owner }}"
group: "{{ ssl_cert_group }}"
mode: "{{ ssl_cert_mode }}"
when: cert_backups.matched > 0
tags:
- rollback
- name: 恢复最新的私钥备份
copy:
src: "{{ key_backups.files[0].path }}"
dest: "{{ ssl_key_remote_path }}"
remote_src: yes
owner: "{{ ssl_key_owner }}"
group: "{{ ssl_key_group }}"
mode: "{{ ssl_key_mode }}"
when: key_backups.matched > 0
tags:
- rollback
- name: 回滚后重启服务
service:
name: "{{ item }}"
state: restarted
loop: "{{ ssl_services_to_restart }}"
tags:
- rollback如果使用Certbot自动续期证书,可以在续期成功后自动运行Ansible脚本。
创建Certbot钩子文件 /etc/letsencrypt/renewal-hooks/post/ansible_sync.sh :
#!/bin/bash
LOG_FILE="/var/log/ansible-ssl-sync/$(date +%Y%m%d_%H%M%S).log"
mkdir -p /var/log/ansible-ssl-sync
cd /path/to/ansible-ssl-sync
ansible-playbook -i inventory.ini ssl_sync.yml > "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
echo "SSL证书同步成功" | mail -s "SSL证书同步通知" admin@example.com
else
echo "SSL证书同步失败,请查看日志:$LOG_FILE" | mail -s "SSL证书同步失败" admin@example.com
fi赋予执行权限:
chmod +x /etc/letsencrypt/renewal-hooks/post/ansible_sync.sh ansible-playbook -i inventory.ini ssl_sync.yml可以使用标签只运行Playbook的特定部分:
本文详细介绍了如何使用Ansible解决多服务器负载均衡环境下的SSL证书不同步问题。通过编写一个功能完善的Ansible Playbook,我们实现了证书的批量同步、自动备份、服务重启和验证。这种方法不仅提高了工作效率,减少了人为错误,还确保了所有服务器上的证书始终保持一致。
Dogssl.com拥有20年网络安全服务经验,提供构涵盖国际CA机构Sectigo、Digicert、GeoTrust、GlobalSign,以及国内CA机构CFCA、沃通、vTrus、上海CA等数十个SSL证书品牌。全程技术支持及免费部署服务,如您有SSL证书需求,欢迎联系!