scripts-ssl证书
- TAGS: Script
Let's Encrypt 免费证书
有效期 3 个月 支持不用的客户端命令申请证书,官方推荐使用 Certbot
certbot
安装
MacOS
# Install Certbot brew install certbot certbot --version # 查看版本
申请证书
# 使用DNS认证的方式来获取证书 sudo certbot certonly -d "*.aiull.com" --manual --preferred-challenges dns
参数说明
certonly 安装模式 -d 申请证书的域名,如果是通配符域名输入 *.example.com –manual 手动安装插件 –preferred-challenges dns 使用 DNS 方式校验域名所有权。certbot 将要求您将包含特定内容的 TXT DNS 记录放置在域名下,该域名由要为其颁发证书的主机名组成,前缀为_acme-challenge –server,Let’s Encrypt ACME v2 版本,默认为 https://acme-staging-v02.api.letsencrypt.org/directory # 命令行选项 certbot --help all 手动安装插件 --manual-auth-hook 和 --manual-cleanup-hook 标志指定脚本来准备进行验证,执行身份验证过程和 / 或在身份验证之后进行清理。 # 锁定文件 --work-dir--logs-dir 和 --config-dir。默认情况下,这些分别都是 /var/lib/letsencrypt,/var/log/letsencrypt 和 /etc/letsencrypt 如: mkdir zhengzhu ; cd zhengzhu sudo certbot certonly -d "*.aiull.com" --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory --config-dir ./config --work-dir ./work --logs-dir ./log
第一次执行完命令之后会提示需要输入邮箱,和一些协议需要同意和确认,然后每次运行此命令需要记录 IP 信息,需要同意不然不能继续申请
设置DNS TXT记录 将提示的 txt 记录值配置在域名供应商 dns 域名解析中。由于 DNS 记录不会马上生效,所以稍后再按回车键。
# 验证 nslookup -q=txt _acme-challenge.example.com dig +short -t txt _acme-challenge.example.com 或者 host -t txt _acme-challenge.alicdn.com
验证证书有效期
sudo openssl x509 -noout -text -in /Users/7tq6lr/zhengzhu/config/live/aiull.com/fullchain.pem
默认证书目录为:`/etc/letsencrypt/live/$domain`
查看证书
certbot certificates
自动续订
# 手动续订 sudo certbot renew -dry-run # 帮助 certbot --help renew # --force-renewal 强制更新忽略有效期,但证书颁发机构有速率限制不适合每天更新,一周内不超过5次。 https://letsencrypt.org/docs/duplicate-certificate-limit/ # 设置自动续订 我们建议运行以下行,这会将 cron 作业添加到默认 crontab。 echo "0 0,12 * * * root $(command -v python3) -c 'import random; import time; time.sleep(random.random() * 3600)' && sudo $(command -v certbot) renew -q" | sudo tee -a /etc/crontab > /dev/null # 验证证书有效性
使用dns申请的证书是没办法直接使用certbot renew来更新证书的. 幸亏 cerbot 提供了一个 manual-auth-hook hook 可以编写一个脚本,由这个脚本来先完成 DNS 验证,然后再进行 renew。对应的脚本会自动添加 DNS 记录,从而完成 DNS 校验,并自动 renew 证书。
参考:https://blog.k4nz.com/704d5b9b81a53f14ffc04a07befc7e55/ https://www.cnblogs.com/ellisonzhang/p/14298492.html
pip install certbot-dns-aliyun # 前往 https://ram.console.aliyun.com 申请阿里云子账号并授予 AliyunDNSFullAccess 权限 # 创建 AccessKey AccessToken cat > /etc/letsencrypt/dns-aliyun-credentials.ini <<EOF certbot_dns_aliyun:dns_aliyun_access_key = 12345678 certbot_dns_aliyun:dns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef EOF chmod 600 /etc/letsencrypt/dns-aliyun-credentials.ini certbot certonly \ -a certbot-dns-aliyun:dns-aliyun \ --certbot-dns-aliyun:dns-aliyun-credentials /etc/letsencrypt/dns-aliyun-credentials.ini \ -d "*.example.com" \ --dry-run
配置 nginx
证书脚本
00-安装配置管理
- 证书申请
1.1 证书域名申请服务器
rsync-server 10.200.46.63
1.2 自动申请脚本
[root@rsync-server ~]# crontab -l # update certificate 30 09 * * * /bin/sh /home/script/update_cer.sh > /dev/null # 域名检查 40 10 * * * cd /home/yongzhi.shi/python && /usr/bin/python /home/yongzhi.shi/python/check_domain.py &> /dev/null
- nginx证书管理
2.1 挂载NAS
对应机房申请容量型NAS。
# 挂载NAS yum install nfs-utils -y mkdir /usr/local/zhengshu/ vim /etc/fstab 对应NAS地址:/ /usr/local/zhengshu nfs4 vers=4.0,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev,noresvport 0 0 mount -a
2.2 ansible推送脚本
1 登录sw执行推送脚本
sudo su - oap cd tools/ansible/ # hosts文件[ssl]下加上有证书的nginx的IP ansible-playbook change_ssl.yml
2 修改证书更新策略
证书剩余28天升级
vim change_ssl.yml # 修改--days 参数 - parameters: "--days 28"
3 修改证书脚本
sudo su - oap cd tools/ansible/ vim files/ssl_monitor.sh # 只修改server_name 和 curl_name内容
nginx本地shell邮件配置
rsync -r [email protected]::file/mailssl/certs.tar.gz /tmp/ # 密码:wowonetwork tar xf /tmp/certs.tar.gz -C /root/ cat >>/etc/mail.rc<< \EOF set bsdcompat set [email protected] set smtp=smtps://smtp.cici.com:994 set [email protected] set smtp-auth-password='LZ6t0IoL' set smtp-auth=login set ssl-verify=ignore set nss-config-dir=/root/.certs EOF
01-申请过程
### 阿里key
export Ali_Key="LhIS" export Ali_Secret="yjUJFu3Ut"
测试nginx的公网地址
~/.acme.sh/acme.sh --issue --dnssleep 10 --dns dns_ali -d cici.com -d "*.cici.com" \ --installcert\ --key-file /usr/local/nginx/conf/cici.com.key \ --fullchain-file /usr/local/nginx/conf/fullchain.cer \ --reloadcmd "service nginx force-reload" #更新 ~/.acme.sh/acme.sh --renew --dns -d c2c.com -d *.c2c.com
*.cici.com域名证书申请
~/.acme.sh/acme.sh --issue --dns dns_ali -d cici.com -d "*.cici.com" -d "*.yf.cici.com" -d "*.partnergate.cici.com" -d "*.crossacc.cici.com" #更新 ~/.acme.sh/acme.sh --renew --force --dns -d cici.com -d "*.cici.com" -d "*.yf.cici.com" -d "*.partnergate.cici.com" -d "*.crossacc.cici.com"
#查看证书具体内容 openssl x509 -in /data/zhengshu/cici.com/fullchain.cer -noout -text #查看证书时间 openssl x509 -in /data/zhengshu/cici.com/fullchain.cer -noout -dates
02-自动申请续期
ssl证书自动申请
本证书申请是在rsync的服务器上进行更新证书的,处理逻辑是根据现有的域名证书去检查剩余的时间,如果时间低于28天则去申请更新证书,如果大于28天则只发送邮件告诉sa证书的剩余时间;
定时任务
# update certificate 30 09 * * * /bin/sh /home/script/update_cer.sh > /dev/null
脚本
cat >/home/script/update_cer.sh <<\EOF #!/bin/sh ############################### #system variable timestamp=`date +%s` cer_dir="/root/.acme.sh/" domain_name=" cici.com xxx.com " renew_flag=1 ############################### #yunzong domain name info Cici_Key () { export Ali_Key="LTAh" export Ali_Secret="Nl2aJ4e" } #xxx domain name info XXX._Key () { export Ali_Key="LTAIICw" export Ali_Secret="SnpWvDX" } #application certificate App_Cer () { ~/.acme.sh/acme.sh --force --renew-all --dnssleep 10 } #copy certificate to directory Copy_Cer () { /bin/cp -af /root/.acme.sh/cici.com/fullchain.cer /data/zhengshu/cici.com /bin/cp -af /root/.acme.sh/XXX.com/fullchain.cer /data/zhengshu/XXX.com } #check certificate endtime declare -A apply_certs declare -A certs_days Check_Cer () { for domain in ${domain_name}; do cd ${cer_dir}${domain} check_key=`/bin/openssl x509 -in fullchain.cer -noout -dates|head -n 2|awk -F= '{print $2}'|tail -n 1` key_time=$[(`date -d "${check_key}" +%s`-${timestamp})/24/3600] if [ ${key_time} -le 28 ];then apply_certs[${#apply_certs[*]}]="`pwd` Certificate time remaining ${key_time} day,this applying\n" renew_flag=0 else certs_days[${#certs_days[*]}]="`pwd` ${key_time} day \n" fi done if [ $renew_flag -eq 0 ]; then Cici_Key App_Cer sleep 30 XXX._Key App_Cer Copy_Cer fi } #dingding push dingding() { local access_tokens=$1 local ding_url='https://oapi.dingtalk.com/robot/send' local ding_header='Content-Type: application/json' local msg=$2 for key in $access_tokens; do curl -connect-timeout 40 -XPOST -H "$ding_header" "$ding_url?access_token=$key" \ -d '{"msgtype": "text", "text": { "content": "【ssl证书源文件状态】 \n'"$msg"'" }, "at": { "atMobiles": [], "isAtAll": true } }' done } tokens="a618438957c" main (){ Check_Cer [ ${#apply_certs[@]} -gt 0 ] && { echo -e "${apply_certs[@]}" | mail -s 'Ali Certificate Details' [email protected] dingding "$tokens" "$(echo -e "(${apply_certs[@]}")" } echo -e "rsync服务器上已申请证书的有效期:\n ${certs_days[@]}" | mail -s 'Ali Certificate Details' [email protected] dingding "$tokens" "$(echo -e "rsync服务器上已申请证书的有效期:\n ${certs_days[@]}")" } main EOF
03-nginx证书过期同步
脚本逻辑:
- 从rsync服务器获取证书,并存放在/tmp下,脚本执行结束后删除临时文件
- 根据server_name域名变量对比本地证书目录,如果不存在域名目录,则创建
- 如果本地证书剩40天时,则替换证书。分2种情况替换
- 假设1:检验证书存储点的证书,如果证书CN域名与证书存储目录不对应,则从/tmp中替换证书,并备份变化之前证书
- 假设2:如果/tmp目录下的证书剩余大于40天,则从/tmp目录中替换证书
- 每次脚本运行会生成证书信息,以待下次对比变化。
- 如果发现一个证书事件,则重载nginx配置文件。
```mermaid graph TD A(第一次检查)-->|内部|B(循环并发执行) B-->|内部|C(删除临时证书目录) C-->D(删除老的证书备份文件) D-->|删除30天前备份|E(第二次检查) E-->|内部|F(nginx重启) A-->AA{证书更新记录是否存在} AA-->|存在|AA1{本地证书文件是否存在} AA1-->|存在|AA2{本地证书是否更新过-比对在1天以内误差} AA2-->|没更新|AA3(创建nginx重启触发文件) AA3-->F B-->BB[从rsync服务端拉取证书] BB-->|放到临时证书目录/tmp/zhengshuXXX下|BB1[备份证书] BB1-->|备份原证书到/data/backup/zhengshu_bak目录下|BB2[循环域名检查] BB2-->|内部|C BB2-->|并发执行|BBB{证书目录及文件是否存在} BBB-->|不存在|OO[替换此证书到本地证书目录] BBB-->|存在|BBB1{判断本地证书是否有效} BBB1-->|无效|BBB2[备份无效证书] BBB2-->OO BBB-->|存在|BBB11{判断证书剩余时间} BBB11-->|小于等于26天|OO OO-->OOO[更新证书记录-证书要大于26天] OOO-->C E-->EE[访问本地证书] EE-->EE1{判断本地curl_name是否能访问} EE1-->|能访问|EE2{判断证书剩余时间} EE2-->|判断证书剩余时间与证书更新记录文件中不相等|AA3 ```
免交互式
1.1 rsync同步,免除手动输入密码
yum install -y expect [root@agent01 ~]# cat rsync_pull.exp #!/usr/bin/expect -f set user "sa" set ip "124.x7.109.95" set rs_name "zhengshu" set src_path [lindex $argv 0] set dest_path [lindex $argv 1] set passwd "wowonetwork" set timeout 10 spawn rsync -q -az ${user}@${ip}::${rs_name}/${src_path} ${dest_path} expect { "*Password:" {send "$passwd\r"} } expect eof
定时任务
# ssl_monitor 剩余28天更新 0 23 * * * /bin/bash /home/sh/ssl_monitor.sh - -days 28 &>/dev/null
nginx服务器更新证书脚本
3.1 简单版本
[root@agent01 ~]# cat rsync_pull.sh #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" max_process=5 ssl_path=/data ssl_pem=fullchain.cer now_seconds=$(date '+%s') server_name=" cici.com xxx.com " cd $DIR Multi_process_init() { trap 'exec 5>&-;exec 5<&-;exit 0' 2 pipe=`mktemp -u tmp.XXXX` mkfifo $pipe exec 5<>$pipe rm -f $pipe seq $1 >&5 } function ssl_pull_all() { for s in ${server_name}; do src_path=$s dest_path=${ssl_path}/$s expect rsync_pull.exp $src_path/ $dest_path done } function ssl_pull_one() { for s in $1; do src_path=$s dest_path=${ssl_path}/$s expect rsync_pull.exp $src_path/ $dest_path &>/dev/null done wait } # check #end_date=$(echo |\ # openssl s_client -host cmserver.zonghengke.com -port 443 -showcerts </dev/null 2>/dev/null |\ # openssl x509 -noout -dates |\ # sed -n 's@notAfter=@@p') function ssl_check_time() { for s in ${server_name}; do read -u5 { [ ! -e ${ssl_path}/${s}/ ] && ssl_pull_one $s end_date=$(openssl x509 -noout -dates -in ${ssl_path}/${s}/${ssl_pem} | sed -n 's@notAfter=@@p') echo $s $end_date last_time=$((($(date '+%s' --date "$end_date") - $now_seconds)/24/3600)) echo $last_time if [ $last_time -le 55 ]; then ssl_pull_one $s fi echo >&5 echo ======${ssl_path}/${s}/ certificate finshed }& done wait } #ssl_pull function main() { Multi_process_init $max_process ssl_check_time exec 5>&-;exec 5<&- } main
3.2 完整版
[root@agent01 ~]# cat ssl_monitor.sh #!/bin/bash #======================== # version= 0.1 # 功能:证书过期替换,nginx服务重载配置。 # 场景: # 1. 适用于单机nginx环境 # 2. 适用于有共享存储的多机nginx环境 # 运行限制:提前指定每个server_name变量,nginx证书目录规范统一放在脚本定义的ssl_path变量下。 # 脚本逻辑: # 0. 从rsync服务器获取证书,并存放在/tmp下,脚本执行结束后删除临时文件 # 1. 根据server_name域名变量对比本地证书目录,如果不存在域名目录,则创建 # 2. 如果本地证书剩40天时,则替换证书。分2种情况替换 # 2.1. 假设1:检验证书存储点的证书,如果证书CN域名与证书存储目录不对应,则从/tmp中替换证书,并备份变化之前证书 # 2.2. 假设2:如果/tmp目录下的证书剩余大于40天,则从/tmp目录中替换证书 # 3. 每次脚本运行会生成证书信息,以待下次对比变化。 # 4. 如果发现一个证书事件,则重载nginx���置文件。 ######################### #source /etc/profile export LANG=en_US.UTF8 host_ip=$(/usr/sbin/ifconfig eth0 | sed -n '/inet /p'| awk '{print $2}') host_name=$(hostname) DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" max_process=5 tmpfile=$(mktemp -u zhengshu.XXXX) tmp_path=/tmp/.${tmpfile} backup_path=/data/backup/zhengshu_bak ssl_path=/usr/local/zhengshu ssl_pem=fullchain.cer now_seconds=$(date '+%s') event_file=/tmp/ssl_monitor_event old_info=/data/backup/ssl_old_info email_file=/tmp/email_file tokens="a6184389010ffc41f045ab23eab3d40f8ebe1cd9526e7832a131d718aa13057c" min_days=28 #证书域名变量(重要) server_name=" cici.com c2c.com c3c.com " #本地证书有效检查变量(可选) curl_name=" pur.cici.com emc.c2c.com " # 检查目录是否存存 cd $DIR [ ! -e $tmp_path ] && mkdir -p $tmp_path [ ! -e $backup_path ] && mkdir -p $backup_path [ ! -e $ssl_path ] && mkdir -p $ssl_path >$email_file #echo $tmp_path [ -z "$(which mail)" ] && yum install -y mailx # 查检程序依赖 create_expect() { [ -z "$(which expect)" ] && yum install -y expect [ -z "$(which rsync)" ] && yum install -y rsync # 创建免交互文件 cat > ssl_monitor.exp << \EOF #!/usr/bin/expect -f set user "sa" #set ip "124.x7.109.95" set ip "47.xx.11.58" set rs_name "zhengshu" set src_path [lindex $argv 0] set dest_path [lindex $argv 1] set passwd "wowonetwork" set timeout 10 spawn rsync -q -az ${user}@${ip}::${rs_name}/${src_path} ${dest_path} expect { "*Password:" {send "$passwd\r"} } expect eof EOF chmod +x ssl_monitor.exp } # 进度条函数 load() { local i=0; local str="" while [ $i -le 100 ]; do printf "\e[0;1m[%-100s]1/1\r\e[0m" "$str" usleep 5000 let i++ #str+='█' str+='#' done printf "\n" } # 并发函数 Multi_process_init() { trap 'exec 5>&-;exec 5<&-;exit 0; rm -fr $tmp_path' 2 pipe=`mktemp -u tmp.XXXX` mkfifo $pipe exec 5<>$pipe rm -f $pipe seq $1 >&5 } # 拉取rsync服务器中证书函数 ssl_pull_all() { for s in ${server_name}; do read -u5 { src_path=$s dest_path=${tmp_path}/$s expect ssl_monitor.exp $src_path/ $dest_path &>/dev/null echo >&5 }& done wait } # 本地旧证书备份函数 ssl_old_backup() { ssl_change=$1 ssl_server_name=$2 if [ "$ssl_change" == "true" ]; then echo "Problem: Local ssl $ssl_server_name err " rsync -rlpgoDz --delete ${ssl_path}/${ssl_server_name} ${backup_path}/${ssl_path##*/}_ssl_problem_change_before.$(date +%Y%m%d%H)/ else rsync -rlpgoDz --delete ${ssl_path}/ ${backup_path}/${ssl_path##*/}.$(date +%Y%m%d%H%M)/ #cp -dR ${ssl_path}/ ${backup_path}/${ssl_path##*/}.$(date +%Y%m%d%H%M)/ fi } # 本地旧证书过期删除函数 ssl_old_del() { find ${backup_path} -maxdepth 1 -type d -mtime +30 -iname "${ssl_path##*/}*" | xargs rm -rf } # 邮件内容函数 send_mail() { : host_name=$host_name host_ip=$host_ip email_file=$email_file domain=$1 msg=$2 [ "`grep $domain $email_file`" ] || echo "[$domain]" >> $email_file [ "`sed -rn "/\[$domain\]/,/[^\[]/p" $email_file |grep "$msg"`" ] || sed -i "/\[${domain}\]/a$host_ip $host_name $msg" $email_file } #dingding push dingding() { local access_tokens=$1 local ding_url='https://oapi.dingtalk.com/robot/send' local ding_header='Content-Type: application/json' local msg=$2 for key in $access_tokens; do curl -connect-timeout 40 -XPOST -H "$ding_header" "$ding_url?access_token=$key" \ -d '{"msgtype": "text", "text": { "content": "【nginx证书更新通知】 \n'"$msg"'" }, "at": { "atMobiles": [], "isAtAll": true } }' done } # 本地证书与拉取到证书比对函数,有效则更新本地证书(重要) ssl_copy_one() { for s in $1; do local src_path=${tmp_path}/$s local dest_path=${ssl_path}/$s local remot_end_date=$(openssl x509 -noout -dates -in ${tmp_path}/${s}/${ssl_pem} | sed -n 's@notAfter=@@p') local remot_last_time=$((($(date '+%s' --date "${remot_end_date}") - $now_seconds)/24/3600)) if [ $remot_last_time -gt $min_days ]; then echo "remot_last_time $remot_last_time > min_days $min_days " rsync -az --delete --timeout=100 $src_path/ $dest_path touch $event_file send_mail $s "$2" fi done wait } # 本地nginx重载配置函数(重要) ng_reload() { echo "... Reload nginx?" ng_p=$(ps -ef|grep nginx|grep master |awk '{print $2}') [ -z "$ng_p" ] && { echo nginx is not running; exit 1; } ng_cmd=$(ls -l /proc/${ng_p}/exe| awk '{print $11}') nginx_reload_sum=0 if [ -f "$event_file" ]; then if $ng_cmd -t &>/dev/null; then let nginx_reload_sum++ #echo $nginx_reload_sum else let nginx_reload_sum-- #echo $nginx_reload_sum fi rm -f $event_file [ $nginx_reload_sum -ge 0 ] && { echo "yes, done" ; $ng_cmd -s reload; } || { $ng_cmd -t; exit 1; } else echo "no, nothing is changed" fi } # 检查本地更新文件,变化则生成重载nginx触发事件文件 ssl_check_first() { if [ -f $old_info ]; then for s in ${server_name}; do if [ -e ${ssl_path}/${s}/ -a -f ${ssl_path}/${s}/fullchain.cer -a -f ${ssl_path}/${s}/${s}.key ]; then end_date=$(openssl x509 -noout -dates -in ${ssl_path}/${s}/${ssl_pem} | sed -n 's@notAfter=@@p') last_time=$((($(date '+%s' --date "$end_date") - $now_seconds)/24/3600)) yes_time=$((($(date '+%s' --date "$end_date") - $now_seconds)/24/3600+1)) old_time=$(cat ${old_info} |sed -rn "/${s}/s@${s},(.*)@\1@gp" | head -1) if [ "$last_time" != "$old_time" -a "$yes_time" != "$old_time" ]; then echo "$s $last_time != $old_time Create event file: $event_file" touch $event_file send_mail $s "触发nginx reload事件. 与本次的更新记录${new_time}不一致,本地证书有效期${last_time}" sleep 2 fi fi done else touch $old_info fi } # 本地检查nginx重载后,证书验证函数 ssl_check_second() { : for s in ${curl_name}; do if curl -I -v -m 1 --resolve "${s}:443:${host_ip}" "https://${s}/" &>/dev/null; then end_date=$(curl -I -v -m 1 --resolve "${s}:443:${host_ip}" "https://${s}/" 2>&1 |sed -nr 's@.*expire date: @@p') last_time=$((($(date '+%s' --date "$end_date") - $now_seconds)/24/3600)) s_name=$(echo ${s#*.}) new_time=$(cat ${old_info} |sed -rn "/${s_name}/s@${s_name},(.*)@\1@gp" | head -1) if [ "$last_time" != "$new_time" ]; then echo "$s $last_time != $new_time Create event file: $event_file" touch $event_file send_mail ${s_name} "触发nginx reload事件. 与本次的更新记录${new_time}不一致,模拟访问本地域名证书有效期${last_time}" sleep 1 fi fi done } # check #end_date=$(echo |\ # openssl s_client -host cmserver.zizi.com -port 443 -showcerts </dev/null 2>/dev/null |\ # openssl x509 -noout -dates |\ # sed -n 's@notAfter=@@p') # 主逻辑函数,本地证书小于min_days天触发 ssl_check_time() { echo "Monitor the certificates ..." ssl_pull_all ssl_old_backup echo "Local certificate validation ..." echo 'Remote certificate validation ...' > $old_info for s in ${server_name}; do read -u5 { if [ ! -e ${ssl_path}/${s}/ -o ! -f ${ssl_path}/${s}/fullchain.cer -o ! -f ${ssl_path}/${s}/${s}.key ]; then rsync -az --delete --timeout=100 ${tmp_path}/$s/ ${ssl_path}/$s send_mail $s "证书文件不存在,已同步" end_date=$(openssl x509 -noout -dates -in ${ssl_path}/${s}/${ssl_pem} | sed -n 's@notAfter=@@p') last_time=$((($(date '+%s' --date "$end_date") - $now_seconds)/24/3600)) echo "$s,$lasttime" >>$old_info else end_date=$(openssl x509 -noout -dates -in ${ssl_path}/${s}/${ssl_pem} | sed -n 's@notAfter=@@p') last_time=$((($(date '+%s' --date "$end_date") - $now_seconds)/24/3600)) #echo "$s,$last_time" >>/data/backup/ssl_old_info local_ssl_CN=$(openssl x509 -noout -subject -in ${ssl_path}/${s}/${ssl_pem} |sed -r 's@subject= /CN=(.*)@\1@g') if ! echo ${local_ssl_CN} | grep "$s" &>/dev/null ; then ssl_old_backup "true" "$s" rsync -az --delete --timeout=100 ${tmp_path}/$s/ ${ssl_path}/$s send_mail $s "证书文件不在该域名中,已替换" fi if [ $last_time -le $min_days ]; then ssl_copy_one $s "证书文件更新,当前剩余$last_time天" fi end_date=$(openssl x509 -noout -dates -in ${ssl_path}/${s}/${ssl_pem} | sed -n 's@notAfter=@@p') #echo $s $end_date last_time=$((($(date '+%s' --date "$end_date") - $now_seconds)/24/3600)) echo "$s,$last_time" >>$old_info fi echo >&5 echo ... ${ssl_path}/${s}/ certificate is fished }& done wait #load } #ssl_pull main() { ssl_check_first Multi_process_init $max_process ssl_check_time rm -fr $tmp_path ssl_old_del exec 5>&-;exec 5<&- ssl_check_second ng_reload if `grep 'nss-config-dir' /etc/mail.rc`; then [ `wc -l $email_file|awk '{print $1}'` != "0" ] && cat $email_file|mail -s "【证书更新】$host_ip" [email protected] fi [ `wc -l $email_file|awk '{print $1}'` != "0" ] && dingding "$tokens" "$(echo -e "$(cat $email_file)")" rm -f $email_file } #create_expect #main #------------ usage(){ cat <<EOF Usage: $0 [MODE] [OPTION] OPTION: -s, --show default show dir of old ssl, show ssl information you can exec ssl -s or https -s -r, --rollback <date> rollback dir, before you cmd -s. -d, --days <num> unit day. --version show version --help show help MODE: ssl, https EOF } getopt -T &>/dev/null;[ $? -ne 4 ] && { echo "not enhanced version";exit 1; } parameters=$(getopt -o r:d:s --long rollback:,days:,show,help,version -n "$0" -- "$@") [ $? -ne 0 ] && { echo "Try '$0 --help' for more information."; exit 1; } eval set -- "$parameters" while true; do case "$1" in -s|--show) Type="show"; shift ;; -r|--rollback) Type="rollback" RollFile=$2 ; shift 2 ;; -d|--days) Type="days" Days_to_empty=$2 ; shift 2 ;; --version) echo "$0 version V0.1"; exit ;; --help) usage;exit ;; --) shift [ "$1" = "" -o "$1" = "ssl" -o "$1" = "https" ] && MODE=$1 || { echo -e "$0: invalid floating point argument: $@\nTry '$0 --help' for more information." ; exit 1; } [ "$#" -ge 2 ] && { echo -e "$0: invalid floating point argument: $@\nTry '$0 --help' for more information." ; exit 1; } FIRST=$1 SECOND=$2 LAST=$3 break ;; *) echo "0k" ;; esac done #echo $parameters case "$Type" in "") [ -n "$MODE" ] && { echo -e "$0: invalid floating point argument: $@\nTry '$0 --help' for more information." ; exit 1; } create_expect main rm -f ssl_monitor.exp echo "exec main" ;; show) if [ -n "$MODE" ]; then echo "Before the change ssl information" [ -f $old_info ] && cat $old_info || echo "cannot access $old_info: No such file" else [ -d $backup_path ] && ls -l $backup_path | less || echo "cannot access $backup_path: No such directory" fi ;; rollback) [ -n "$MODE" ] && { echo -e "$0: invalid floating point argument: $@\nTry '$0 --help' for more information." ; exit 1; } [ ! -d $backup_path/zhengshu.$RollFile ] && { echo "cannot access $backup_path/zhengshu.$RollFile: No such directory"; exit 1; } echo $RollFile ;; days) #[ -n "$MODE" ] && { echo -e "$0: invalid floating point argument: $@\nTry '$0 --help' for more information." ; exit 1; } [ $(echo ${Days_to_empty} |grep '^[^[:digit:]]*$') ] && { echo -e "$0: invalid floating point argument: $@\nTry '$0 --help' for more information." ; exit 1; } min_days=${Days_to_empty} create_expect main rm -f ssl_monitor.exp echo "exec main" ;; *) echo "no" ;; esac
04-使用Python检查域名证书剩余时间
通过Python检查设定好的域名,通过域名的访问去检查证书的剩余时间,并且发送邮件通知
# !/usr/bin/env python # -*- coding:utf-8 -*- # import commands import smtplib from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.header import Header def send_email(to_addr, cc_addr, subject, content): # to_addr = ['[email protected]'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱 # cc_addr = ['xcwhxxe'] # 抄送 # subject = 'Python SMTP 邮件测试' # 主题 mail_host = "smtp.cici.com" # 设置服务器 mail_user = "[email protected]" # 用户名 mail_pass = "LZ" # 口令 sender = 'scans' msgRoot = MIMEMultipart('related') msgRoot['From'] = Header(mail_user, 'utf-8') msgRoot['To'] = Header(",".join(to_addr), 'utf-8') #msgRoot['Cc'] = Header(",".join(cc_addr)) # 抄送 msgRoot['Subject'] = Header(subject, 'utf-8') if is_html: msgAlternative = MIMEMultipart('alternative') msgRoot.attach(msgAlternative) mail_msg ='<h2>图示:</h2>' msgAlternative.attach(MIMEText(content+mail_msg, 'html', 'utf-8')) else: msg_content = MIMEText(content, 'plain', 'utf-8') msgRoot.attach(msg_content) try: smtpObj = smtplib.SMTP_SSL() smtpObj.connect(mail_host, 994) # 25 为 SMTP 端口号 smtpObj.login(mail_user, mail_pass) smtpObj.sendmail(mail_user, to_addr, msgRoot.as_string()) #smtpObj.sendmail(mail_user, to_addr+cc_addr, msgRoot.as_string()) print("邮件发送成功") except smtplib.SMTPException: print("Error: 无法发送邮件") dict = {'xy_hd1':['emc.cici.net'], \ 'xy_hd2':['hd2-1.btrade.cici.com'], \ 'zhiyunshan':['hd2-1.zysmul.zizi.com','hb2-1.zysmul.zizi.com'] } f = open('~/python/domain_info.txt', 'w') for machine_room in dict.keys(): for domain in dict[machine_room]: print "========%s"%domain cmd = "echo | openssl s_client -connect %s:443 2>/dev/null | openssl x509 -noout -dates | awk -F= '{print $2}'|tail -n 1" %domain cer_time = commands.getoutput(cmd) print cer_time cmd1 = "date -d '%s' +%%s" %cer_time end_time = commands.getoutput(cmd1) print end_time now_time = commands.getoutput('date +%s') time = (int(end_time) - int(now_time))/24/3600 print "time = %s"%time info = "机房:%s 域名:%s 剩余天数%s\n" %(machine_room,domain,time) f.write(info) f.close() send_cmd = "mail -s '域名证书检查报告' [email protected] < domain_info.txt" send_mail = commands.getoutput(send_cmd) f1 = open("domain_info.txt","r") content = "".join(f1.readlines()) f1.close() to_addr = ['[email protected]'] cc_addr = ['[email protected]'] subject = '域名证书检查报告' # 主题 is_html = False send_email(to_addr,cc_addr,subject,content)
05-自动扫描自动监控
邮件
# 创建证书目录 mkdir -p /root/.certs/ # 获取证书内容(可分开执行查看过程) echo -n | openssl s_client -connect smtp.exmail.qq.com:465 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/.certs/qq.crt # 添加一个证书到证书数据库中 certutil -A -n "GeoTrust SSL CA" -t "C,," -d ~/.certs -i ~/.certs/qq.crt certutil -A -n "GeoTrust Global CA" -t "C,," -d ~/.certs -i ~/.certs/qq.crt # 列出目录下证书 certutil -L -d /root/.certs # 指明受新人证书、防报错 cd /root/.certs/ certutil -A -n "GeoTrust SSL CA - G3" -t "Pu,Pu,Pu" -d ./ -i qq.crt # 编辑mail的配置文件 cat >>/etc/mail.rc<< \EOF set bsdcompat set [email protected] set smtp=smtps://smtp.exmail.qq.com:465 set [email protected] set smtp-auth-password='NHFNpVxQrshes' set smtp-auth=login set ssl-verify=ignore set nss-config-dir=/root/.certs EOF echo "aaa" |mail -s "title" -a /etc/fstab [email protected]
依赖
# 安装py3 yum -y install python36 python36-devel python3.6 -m venv /opt/py3 source /opt/py3/bin/activate cat << EOF >/data/ops/requirements.txt requests == 2.24.0 aliyun-python-sdk-alidns == 2.6.18 aliyun-python-sdk-core == 2.13.25 EOF pip install -r /data/ops/requirements.txt
配置文件
cat <<EOF > /data/ops/qsdnskeyconfig.conf # login aliyun rest api info # slb type: nginx/none(dubbo)/slb # if slb_type = slb which be need [aliyun_u_p] aki = LTAIF aks = GRlA0T domains = cici.com #mail config mail_host = mail_user = user = 'scans' mail_pass = #config more mail, plsae use ',' split [email protected] [email protected] #dingdingconfig dindin_token=14896a600456c3c661bcc69 EOF
执行文件
cat <<\EOF >start_check_ssl.sh #!/usr/bin/env bash tokens="14896a600bcc69" #-------------------- #dingding push function dingding() { local access_tokens=$1 local ding_url='https://oapi.dingtalk.com/robot/send' local ding_header='Content-Type: application/json' local msg=$2 for key in $access_tokens; do curl -s -connect-timeout 40 -XPOST -H "$ding_header" "$ding_url?access_token=$key" \ -d '{"msgtype": "text", "text": { "content": "【k8s报警】 \n'"$msg"'" }, "at": { "atMobiles": [], "isAtAll": true } }' done } /opt/py3/bin/python /data/ops/check_ssl.py if [ `wc -l dns.log |awk '{print $1}'` -gt 0 ] ; then conect=`cat dns.log|sed '1d'|awk '{print $1}' | grep -v 'CNAME'|sort -t '|' -nr -k1,1` conect="${conect}\\n `cat dns.log|sed '1d'|awk '{print $1}' | grep 'CNAME'|sort -t '|' -nr -k1,1`" dingding "$tokens" "$(echo -e "域名证书小于28天有效期如下:\\n ${conect}")\\n" echo -e "域名证书小于28天有效期如下:\\n ${conect}" |mail -s "证书检测" -a /data/ops/dns.log [email protected] fi EOF
脚本内容
cat <<\EOF > check_ssl.py #coding=utf-8 #!/usr/bin/env python #基础内置模块 import json import ssl import sys import configparser import telnetlib import time import datetime import os import math import requests #邮箱模块 from email.mime.text import MIMEText from email.utils import formatdate from email.header import Header import smtplib from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase #导入阿里模块 from aliyunsdkcore.client import AcsClient from aliyunsdkalidns.request.v20150109.DescribeDomainRecordsRequest import DescribeDomainRecordsRequest #设置全局每次请求阿里云不使用证书 ssl._create_default_https_context = ssl._create_unverified_context ''' 配置文件中读取阿里验证需要的key信息 ''' class ali_operation(): ''' 配置文件中读取阿里验证需要的key信息 ''' def dnsconfig(self): cf = configparser.ConfigParser() cf.read("qsdnskeyconfig.conf") secs = cf.sections() #print(secs) if secs.count('aliyun_u_p') != 1: sys.exit(' Error: need a aliyun_u_p config') ali_auth={'aki':cf.get('aliyun_u_p','aki'),'aks':cf.get('aliyun_u_p','aks'), 'domains':cf.get('aliyun_u_p','domains'), 'mail_host':cf.get('aliyun_u_p','mail_host'), 'mail_user':cf.get('aliyun_u_p','mail_user'), 'user':cf.get('aliyun_u_p','user'), 'mail_pass':cf.get('aliyun_u_p','mail_pass'), 'to_addr':cf.get('aliyun_u_p','to_addr'), 'cc_addr':cf.get('aliyun_u_p','cc_addr'), 'dindin_token':cf.get('aliyun_u_p','dindin_token'), } return ali_auth ''' 对阿里接口发起请求 ''' def connect_retry(self,aki,aks,request, print_def): client = AcsClient(aki,aks) for i in range(3): try: responese = client.do_action_with_exception(request) except Exception as e: print('--Error ' + print_def + ' error, error info: \n'+' '*8+'%s'%(e)) if i == 2: return(1) else: continue return responese #域名查询接口参数配置 def dns_query(self,domainname,pagenum): request = DescribeDomainRecordsRequest() #request.set_action_name('DescribeDomainRecords') request.set_DomainName(domainname) if pagenum >= 1: request.set_PageNumber(pagenum) return request #分页获取二级域名 def dnslist(self,pagenum,domainnames): ali_key = self.dnsconfig() if pagenum >= 1: dnslists = self.dns_query(domainnames,pagenum=pagenum) else: dnslists = self.dns_query(domainnames,pagenum=0) info = self.connect_retry(ali_key['aki'],ali_key['aks'],dnslists,'dns') return info #域名数据处理,默认是bite数据需要转换为unicode def dataprocess(self,pagenum,domainname): dnsdata = json.loads(self.dnslist(pagenum,domainname).decode('utf-8')) return dnsdata #域名做判断,过滤调'@'和'*'的二级域名 def dnsifprocess(self,dnsdata): subdomainnames=[] for i in dnsdata['DomainRecords']['Record']: if i['RR'] == "@": continue subdomainname=i['Value']+'|'+i['DomainName']+'|'+i['Type'] elif '*' in i['RR']: continue elif 'TXT' in i['Type']: continue else: subdomainname=i['Value']+'|'+i['RR']+'.'+i['DomainName']+'|'+i['Type'] subdomainnames.append(subdomainname) return subdomainnames #汇总所有二级域名 def dnslists(self): domains = self.dnsconfig()['domains'] allsubdomainnames=[] for domainname in domains.split(','): print(domainname) domainname = domainname pagenum=1 dnsdata = self.dataprocess(pagenum,domainname) pagenum=math.ceil(dnsdata['TotalCount']/dnsdata['PageSize']) if pagenum >= 1: for c in range(pagenum): c += 1 dnsdata = self.dataprocess(c,domainname) allsubdomainnames.append(self.dnsifprocess(dnsdata)) #print(allsubdomainnames) return allsubdomainnames class TelnetClient(): ''' telnet 模块 ''' def __init__(self,): self.tn = telnetlib.Telnet() # 此函数实现telnet登录主机 def login_host(self,host_ip): try: # self.tn = telnetlib.Telnet(host_ip,port=23) self.tn.open(host_ip,port=443,timeout=3) return True except: return False def logout_host(self): self.tn.write(b"exit\n") class https_certificate_verification(): ''' 获取每个域名的证书时间 ''' #执行shell命令 def commd(self,commnd): #data=subprocess.Popen(commnd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.readlines() data=os.popen(commnd).readlines() return data #下载证书,并且获取证书时间 def https_download(self,domain): # commd = "echo | openssl s_client -connect %s:443 2>/dev/null | openssl x509 -noout -dates | awk -F= '{print $2}'|tail -n 1" %domain commd = "curl -I -v -m 1 'https://%s/' 2>&1 |sed -nr 's@.*expire date: @@p'" %domain return self.commd(commd) #将证书的GMT时间字符串转换为时间格式 def string_toDatetime(self,st): return datetime.datetime.strptime(st, "%b %d %H:%M:%S %Y GMT") #获取证书有效期剩余天数 def https_ca_verification(self,domain): dm=domain.split('|')[1] cur_time = self.https_download(dm) if cur_time: cur_time="%s" % (cur_time[0].strip('\n')) cur_time=self.string_toDatetime(cur_time) ca_time=time.mktime(cur_time.timetuple()) nowtime=time.time() time1 = (int(ca_time+28800) - int(nowtime))/24/3600 return "%s,%s,%d" % (domain,True,math.floor(time1)) else: return "%s,%s" % (domain,False) #域名拆分 def domanin_split(self,domain): domain_split=domain.split('.') if len(domain_split) > 2: return '.'.join(domain_split[1:]) else: return domain #确认证书是否和域名相匹配 def ca_is_match(self,domain): commd = "echo | openssl s_client -connect %s:443 2>/dev/null | openssl x509 -noout -text | grep -i 'DNS' | sed 's/[ ]*//g'" % domain ca_support_domain=self.commd(commd) ca_support_domain = ca_support_domain[0].replace('DNS:','').replace('*.','').strip('\n') domain_split=self.domanin_split(domain) if domain_split in ca_support_domain.split(','): return True else: return False class call(): def __init__(self): self.confg = ali_operation().dnsconfig() ''' 通知模块、邮箱、钉钉 ''' def dindin(self,msg): ''' 只是把钉钉的接口封装了下,目的大家别重复造轮子 url请求的模块是requests 调用函数方式 dindin(token=token,msg=msg) token是钉钉机器人的token msg是要报警的内容 参考文档: https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.43964a97im55WS&treeId=257&articleId=105735&docType=1 ''' token=self.confg['dindin_token'] dindinapi = 'https://oapi.dingtalk.com/robot/send?access_token=%s' % token #token钉钉机器人认证的token headers = {'content-type': 'application/json','charset':'utf-8'} #http头内存,钉钉必填项 data = { "msgtype": "text", "text": { "content": msg }, "at": { "atMobiles": [ ], "isAtAll": True } } try: req=requests.post(dindinapi,json=data,headers=headers) except: return 0 def send_email(self,subject,content): mail_host=self.confg['mail_host'] #mail_host='smtp.yunzongnet.com' mail_user = self.confg["mail_user"] user = self.confg['user'] mail_pass = self.confg["mail_pass"] to_addr=self.confg['to_addr'].split(',') cc_addr=self.confg['cc_addr'].split(',') print (to_addr,cc_addr) msg = MIMEMultipart() msg['From'] = mail_user msg['To'] = ','.join(to_addr) if cc_addr: msg['Cc'] = ','.join(cc_addr) msg['Subject'] = subject msg.attach(MIMEText(content, 'plain')) smtp = smtplib.SMTP_SSL(mail_host, 994) smtp.ehlo() smtp.login(mail_user, mail_pass) smtp.sendmail(mail_user,to_addr+cc_addr,msg.as_string()) if __name__ == '__main__': cm='>dns.log' i=os.popen(cm) validlist=[] normal_list=[] d_err_list=[] domainlist = ali_operation().dnslists() for i in domainlist: for j in i: if TelnetClient().login_host(j.split('|')[1]): #TelnetClient().logout_host() j='%s' % j f = https_certificate_verification().https_ca_verification(j) if "False" in f: validlist.append(f) elif "True" in f: normal_list.append(f) else: d_err_list.append(j) continue ca_normalmsg='' ca_warmsg='' ca_crimsg='' no_camsg='' not_is_match_ca='' for i in normal_list: if i: domain=i.split(',')[0] ca_time=int(i.split(',')[2]) # if https_certificate_verification().ca_is_match(domain): # pass # else: # not_is_match_ca += '%s' % domain +'\n' if ca_time > 29: ca_normalmsg+='%s %d 天' % (domain,ca_time) +'\n' elif ca_time <= 28 and ca_time>=1 : ca_warmsg+='%s %d 天' % (domain,ca_time) +'\n' elif ca_time < 1 : ca_crimsg+='%s %d 天' % (domain,ca_time) +'\n' if validlist: for i in validlist: no_camsg+='%s' % i.split(',')[0] +'\n' no_camsg='未获取到证书域名如下:'+'\n'+'%s' % no_camsg #call().dindin(no_camsg) if ca_normalmsg: ca_normalmsg='域名证书正常有效期如下:'+'\n'+'%s'% ca_normalmsg #call().dindin(ca_normalmsg) # call().send_email('域名证书有效期报警',ca_normalmsg) if ca_warmsg: ca_warmsg='域名证书小于28天有效期如下:'+'\n'+'%s' % ca_warmsg #call().dindin(ca_warmsg) with open('dns.log', mode='w+',encoding='utf-8') as f: f.seek(0) f.writelines(ca_warmsg) # call().send_email('证书小于28天有效期域名',ca_normalmsg) if ca_crimsg: ca_crimsg='证书已过期域名如下:'+'\n'+'%s' % ca_crimsg #call().dindin(ca_crimsg) # call().send_email('证书已过期域名',ca_normalmsg) EOF
06-云厂商产品证书自动更新
SLB证书自动更新
环境 python3 pip包
cat >request.txt<<EOF aliyun-python-sdk-core-v3==2.13.11 aliyun-python-sdk-slb==3.2.16 requests==2.22.0 EOF pip install -r request.txt
计划任务
# update xiaoqian ssl 0 0 * * * /root/.pyenv/versions/py367/bin/python /home/scripts/py/xiaoqian-slb.py &>/dev/null
脚本内容
#!/usr/bin/env python #coding=utf-8 import json import requests import datetime import subprocess import sys from aliyunsdkcore.client import AcsClient from aliyunsdkcore.acs_exception.exceptions import ClientException from aliyunsdkcore.acs_exception.exceptions import ServerException from aliyunsdkslb.request.v20140515.UploadServerCertificateRequest import UploadServerCertificateRequest from aliyunsdkslb.request.v20140515.SetLoadBalancerHTTPSListenerAttributeRequest import SetLoadBalancerHTTPSListenerAttributeRequest from aliyunsdkslb.request.v20140515.DescribeLoadBalancerHTTPSListenerAttributeRequest import DescribeLoadBalancerHTTPSListenerAttributeRequest from aliyunsdkslb.request.v20140515.DeleteServerCertificateRequest import DeleteServerCertificateRequest '''Judge the validity of the certificate''' def CheckDomain(domain): cmd = "echo | openssl s_client -connect %s:443 2>/dev/null | openssl x509 -noout -dates | awk -F= '{print $2}'|tail -n 1" %domain cer_time = subprocess.getoutput(cmd) cmd1 = "date -d '%s' +%%s" %cer_time end_time, now_time = subprocess.getoutput(cmd1), subprocess.getoutput('date +%s') time = int((int(end_time) - int(now_time))/24/3600) info = "机房:%s 域名:%s 剩余天数%s\n" %('社餐华东1',domain,time) print(info) return time def GetNewCertInfo(domain): cmd =r''' cat >tmp.sh <<\EOF #!/usr/bin/env bash export LANG=en_US.UTF8 [ -z "$(which rsync)" ] && yum install -y rsync [ -z "$(which jq)" ] && wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo && yum install -y jq export RSYNC_PASSWORD=wowonetwork tmpfile=$(mktemp -u zhengshu.XXXX) tmp_path=/tmp/.${tmpfile} now_seconds=$(date '+%s') ssl_pem=fullchain.cer domain=$1 [ -z "$domain" ] && echo "err, please: .sh domain" && exit 1 [ ! -e $tmp_path ] && mkdir -p $tmp_path rsync -r [email protected]::zhengshu/$domain/ $tmp_path/$domain cert_info=$(cat $tmp_path/$domain/${ssl_pem}|grep -vE '^$'|sed ':a;N;$!ba;s/\n/\\n/g') key_info=$(cat $tmp_path/$domain/${domain}.key|grep -vE '^$'|sed ':a;N;$!ba;s/\n/\\n/g') end_date=$(openssl x509 -noout -dates -in $tmp_path/$domain/${ssl_pem} | sed -n 's@notAfter=@@p') last_time=$((($(date '+%s' --date "$end_date") - $now_seconds)/24/3600)) rm -fr $tmp_path [ $last_time -lt 30 ] && echo "" && exit 0 echo '{"cert_info":"'$cert_info'","key_info":"'$key_info'"}' EOF ''' subprocess.getoutput(cmd) cmd1=r'bash tmp.sh %s|jq .cert_info' %(domain) cmd2=r'bash tmp.sh %s|jq .key_info && rm -f tmp.sh' %(domain) cert_info, key_info =subprocess.getoutput(cmd1), subprocess.getoutput(cmd2) return {"cert_info":cert_info, "key_info":key_info} '''upload a new certificate''' def UpLoadCert(cert_info,key_info,now=None): request = UploadServerCertificateRequest() request.set_accept_format('json') request.set_ServerCertificate(cert_info) request.set_PrivateKey(key_info) request.set_ServerCertificateName("xq"+now) response = client.do_action_with_exception(request) print(str(response, encoding='utf-8')) return str(response, encoding='utf-8') '''delete certificate''' def DelCert(id): request = DeleteServerCertificateRequest() request.set_accept_format('json') print(id) request.set_ServerCertificateId(id) response = client.do_action_with_exception(request) print(str(response, encoding='utf-8')) return str(response, encoding='utf-8') '''get an old certificate id''' def DesSlbhttps(slb_id): request = DescribeLoadBalancerHTTPSListenerAttributeRequest() request.set_accept_format('json') request.set_ListenerPort(443) request.set_LoadBalancerId(slb_id) response = client.do_action_with_exception(request) print(str(response, encoding='utf-8')) return str(response, encoding='utf-8') '''replace slb certificate''' def SetSlbHttps(slb_id,cert_id): request = SetLoadBalancerHTTPSListenerAttributeRequest() request.set_accept_format('json') request.set_ListenerPort(443) request.set_LoadBalancerId(slb_id) request.set_ServerCertificateId(cert_id) response = client.do_action_with_exception(request) print(str(response, encoding='utf-8')) return str(response, encoding='utf-8') '''get aliyun RAM key''' def GetAliRamKey(): sw_url = "http://10.200.41.37:10435/api/v1/sw_decode_passwd" headers = {'content-type': 'application/json'} data = {"pass_key":"bbf79631a4de0836c1b2b97887359a50"} parameters = {"url": sw_url,"headers": headers,"timeout":10,"data": json.dumps(data)} res_discache = requests.post(**parameters) res_discache = json.loads(res_discache.text) return res_discache["username"], res_discache["passwd"], '''push dingding''' def dingding(token,msg): try: dindinapi = 'https://oapi.dingtalk.com/robot/send?access_token=%s' % token headers = {'content-type': 'application/json','charset':'utf-8'} data = { "msgtype": "text", "text": { "content": msg }, "at": { "atMobiles": [ ], "isAtAll": True } } req=requests.post(dindinapi,json=data,headers=headers) except Exception as e: print(str(e)) if __name__ == '__main__': now = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") username,passwd = GetAliRamKey() client = AcsClient(username, passwd, 'cn-hangzhou') slb_id ='lb-bp10x2hqas6pv5cfliiab' domain ='xqapic.zizi.com' domain1 = 'cici..com' token = 'a6184389010ffc418aa13057c' #1. Judge the validity of the certificate, 27day remaining days=CheckDomain(domain) if days < 28: print("the certificate will be expired, continue...") else: try: sys.exit(0) except: print('the certificate is not expired') sys.exit(0) #2. upload a new certificate #2.1 get a new certificate # cert_info,key_info = GetNewCertInfo(domain1) # 此处为了解决字符串加r问题。如r"a\n" 变成"a\n",使用"%s"%r"a\n".replace('\\n',"\n") cert_info = "%s"% GetNewCertInfo(domain1)["cert_info"].replace('\\n',"\n").replace('"',"") key_info = "%s"% GetNewCertInfo(domain1)["key_info"].replace('\\n',"\n").replace('"',"") print(cert_info) print(key_info) # cert_info_origin="<CERTIFICATE>" # key_info_origin="<PRIVATE KEY>" #2.2 upload a new certificate upload_cert_info=json.loads(UpLoadCert(cert_info,key_info,now)) cert_name,cert_id=upload_cert_info['ServerCertificateName'],upload_cert_info['ServerCertificateId'] #print(cert_name,cert_id) #3. replace slb certificate #3.1 get an old certificate id old_cert_id=json.loads(DesSlbhttps(slb_id))['ServerCertificateId'] # print(old_cert_id) #3.2 replace slb certificate SetSlbHttps(slb_id,cert_id) #3.3 delete old certificate DelCert(old_cert_id) #4. dingding newdays=CheckDomain(domain) msg = '证书更新通知:\n机房:%s, 小千SLB: %s 域名:%s, 新证书有效期:%s天, 证书名:%s' % ('1',slb_id, domain, newdays, cert_name) print(msg) dingding(token,msg)
07-检查-nginx有哪些443域名
需求: 统计所有证书情况,区分域名不在116.213.103段的证书。
统计结果格式:配置文件位置,域名,证书名
cat >check_ssl.sh << \EOF #!/bin/bash ng_p=$(ps -ef|grep nginx|grep master |awk '{print $2}') [ -z "$ng_p" ] && exit 1 ng_dir=$(ls -l /proc/${ng_p}/exe| awk '{print $11}' | awk -F'/sbin/nginx' '{print $1}') #/usr/local/nginx/sbin/nginx -V 2>&1 |sed -rn '/conf-path/s@.*conf-path=([[:graph:]]{1,}).*@\1@gp' #如果没有,则编译用手默认--prefix目录下的conf ng_main_conf=$(echo ${ng_dir}/conf/nginx.conf) ng_main_path=$(echo ${ng_main_conf%/nginx.conf*}) ng_conf=$(grep '^[^#][[:space:]\t]\+include' ${ng_main_conf}|grep -v mime.types|awk -F'[\t ;]+' '{print $3}') ng_conf_dir=$(echo ${ng_conf%/*}) cd ${ng_main_path}/${ng_conf_dir} for f in *.conf; do if grep -A 11 '[^#]listen.*443' $f &>/dev/null; then for server_name in $(grep -A 11 -w '[^#]listen.*443' $f | grep -EA 11 '^[^#][[:space:]\t]+listen.*443' | grep -E '^[^#][[:space:]\t]+server_name' | awk -F'server_name[ ]+' '{print $2}' |awk -F';' '{print $1}'); do #server_name=$(grep -A 4 '[^#]listen.*443' $f | grep -E 'server_name' | awk -F'[ ;]+' '{print $3}') pc_ip=$(ping -c1 -W 1 $server_name | grep -o '\([0-9]\{1,3\}\.\)\{3\}' 2>/dev/null) if [ "210.14.157." == "$pc_ip" ]; then key=$(grep -A 11 '^[^#][[:space:]\t]\+listen.*443' $f|grep -A 11 -E '^[^#][[:space:]\t]+server_name'| grep -A 9 -E "${server_name}" | grep -E "^[^#][[:space:]\t]+ssl_certificate_key"| awk -F'[ ;]+' /ssl/'{print "key="$(NF-1)}') echo "HLGconf=$f,server_name=${server_name},$key" else key=$(grep -A 11 '^[^#][[:space:]\t]\+listen.*443' $f| grep -A 11 -E '^[^#][[:space:]\t]+server_name'|grep -A 9 -E "${server_name}" | grep -E "^[^#][[:space:]\t]+ssl_certificate_key" | awk -F'[ ;]+' /ssl/'{print "key="$(NF-1)}') echo "otherconf=$f,server_name=${server_name},$key" fi done fi done EOF sh check_ssl.sh | sort |grep -v '^key' >sc-$(hostname).csv sed -nr 's@.*server_name=(.*),.*@\1@gp' sc-$(hostname).csv
08-ansible批量修改
role ssl
# 执行命令 ansible-playbook change_ssl.yml
# 创建基础环境 mkdir ansible/ cd ansible cat >ansible.cfg<<EOF [defaults] inventory = ./hosts forks = 5 remote_port = 22 roles_path = ./roles host_key_checking = False timeout = 30 log_path = ./logs/ansible.log private_key_file = /home/oap/.ssh/id_rsa [inventory] [privilege_escalation] become=True become_method=sudo become_user=root become_ask_pass=False [paramiko_connection] record_host_keys=False [ssh_connection] [persistent_connection] [accelerate] [selinux] [colors] [diff] EOF mkdir {logs,roles,files}
# 创建证书role环境 mkdir -pv roles/cron_ssl/{defaults,files,handlers,tasks,templates} # 生成执行yml cat >change_ssl.yml<< \EOF --- - name: Change cron for ssl manage hosts: ssl vars: - script_name: "ssl_monitor.sh" - script_dest_path: "/home/sh" - script_type: "sh" - parameters: "--days 26" #- state: "absent" - state: "present" - {minute: "0", hour: "23", day: "*", month: "*", weekday: "*"} roles: - cron_ssl EOF # 生成证书环境变量 cat >roles/cron_ssl/defaults/main.yml<<\EOF ssl_script: "ssl_monitor.sh" script_name: "" script_source_path: "" script_dest_path: "/home/sh" script_type: "sh" state: "present" user: "root" minute: "*" hour: "*" day: "*" month: "*" weekday: "*" EOF # 生成证书任务 cat >roles/cron_ssl/tasks/main.yml<< EOF --- - include: cron.yml EOF cat >roles/cron_ssl/tasks/cron.yml<< \EOF --- - name: Copy {{ script_name }} to copy: src={{ item.src }} dest={{ item.dest }} with_items: - { src: "./files/{{ script_name }}", dest: "{{ script_dest_path }}/{{ script_name }}" } - name: Backup {{ script_name }} shell: "cp {{ script_dest_path }}/{{ script_name }} /data/backup/{{ script_name }}.$(date +'%F-%H')" ignore_errors: yes - name: Cat cron shell: crontab -l register: cat_cron ignore_errors: yes - name: Cat cron debug: msg="{{ cat_cron.stdout_lines }}" - name: Del cron that which is "{{ script_name }}" cron: name={{ script_name }} state="absent" when: state == "absent" - name: Del notcron that which is "{{ script_name }}" shell: "[ $(crontab -l |grep {{ script_name }}|wc -l) != $(crontab -l |grep -A 2 Ansible| grep {{ script_name }}|wc -l) ] && sed -ri '/ssl_monitor.sh/d' /var/spool/cron/root" ignore_errors: yes when: state == "present" - name: Cron bash "{{ script_name }}" to "{{ state }}" cron: name={{ script_name }} minute={{ minute }} hour={{ hour }} day={{ day }} month={{ month }} weekday={{ weekday }} job='/bin/bash {{ script_dest_path }}/{{ script_name }} {{ parameters }} &>/dev/null' when: script_type == "sh" and state == "present" - name: Cat cron shell: crontab -l register: cat_cron - name: Cat cron debug: msg="{{ cat_cron.stdout_lines }}" EOF
09-客户私有化部署
目标:
- 自动申请证书
- 证书管理,到期天数
计划:
使用Let's Encrypt 免费泛域名证书 [https://letsencrypt.org/](https://letsencrypt.org/)
- 自动申请证书
1.0 安装rsync 服务端
服务器地址后面证书自动更新脚本要用到。
# 安装rsync服务 yum -y install rsync systemctl enable rsyncd systemctl start rsyncd # 修改配置 cat >/etc/rsyncd.conf <<\EOF pid file = /var/run/rsyncd.pid port = 873 address = 0.0.0.0 uid = root gid = root use chroot = yes read only = no write only = no #host allow = 172.16.1.0/24 #hosts deny = 0.0.0.0/32 max connections = 10 list = false log file = /var/log/rsyncd.log transfer logging = yes log format = %t %a %m %f %b syslog facility = local3 timeout = 300 [zhengshu] comment = "certificate dir" path=/data/zhengshu list=yes auth users = sa secrets file = /etc/rsyncd.secrets EOF # 认证文件 cat >/etc/rsyncd.secrets <<\EOF sa:helloworld EOF chmod 600 /etc/rsyncd.secrets systemctl restart rsyncd
1.1 安装客户端
curl https://get.acme.sh | sh alias acme.sh=~/.acme.sh/acme.sh echo 'alias acme.sh=~/.acme.sh/acme.sh' >>/etc/profile
自动为你创建 cronjob, 每天 0:00 点自动检测所有的证书, 如果快过期了, 需要更新, 则会自动更新证书.
1.2 申请阿里云证书-dns方式
定义全局变量
# 定义全局变量 export Ali_Key="LTAI4Fqxb" export Ali_Secret="whTtVJmhiEjg" # 申请证书 ~/.acme.sh/acme.sh --issue --dnssleep 10 --dns dns_ali -d qxhcloud.com -d "*.qxd.com" # 等待... # 查看证书具体内容 openssl x509 -in /root/.acme.sh/qxhcloud.com/fullchain.cer -noout -text
1.3 上传自动申请证书脚本并配置定时任务
脚本update_cer.sh上传到/home/script/目录下。
mkdir /home/script/ cat >/home/script/update_cer.sh <<\EOF #!/bin/sh ############################### #system variable timestamp=`date +%s` cer_dir="/root/.acme.sh/" domain_name=" cici.com " renew_flag=1 ############################### #qxhcloud domain name info Qxhcloud_Key () { export Ali_Key="LTAI3Wcxb" export Ali_Secret="wmhiEjg" } #application certificate App_Cer () { ~/.acme.sh/acme.sh --force --renew-all --dnssleep 10 } #copy certificate to directory Copy_Cer () { mkdir -p /data/zhengshu/ /bin/cp -dR /root/.acme.sh/qxhcloud.com /data/zhengshu } #check certificate endtime declare -A apply_certs declare -A certs_days Check_Cer () { for domain in ${domain_name}; do cd ${cer_dir}${domain} check_key=`/bin/openssl x509 -in fullchain.cer -noout -dates|head -n 2|awk -F= '{print $2}'|tail -n 1` key_time=$[(`date -d "${check_key}" +%s`-${timestamp})/24/3600] if [ ${key_time} -le 28 ];then apply_certs[${#apply_certs[*]}]="`pwd` Certificate time remaining ${key_time} day,this applying\n" renew_flag=0 else certs_days[${#certs_days[*]}]="`pwd` ${key_time} day \n" fi done if [ $renew_flag -eq 0 ]; then Qxhcloud_Key App_Cer Copy_Cer fi } main (){ Check_Cer #[ ${#apply_certs[@]} -gt 0 ] && echo -e "${apply_certs[@]}" | mail -s 'Ali Certificate Details' [email protected] #echo -e "${certs_days[@]}" | mail -s 'Ali Certificate Details' [email protected] } main EOF ``` ```bash # 配置定时任务 echo -e "# update certificate\n30 09 * * * /bin/sh /home/script/update_cer.sh > /dev/null" >>/var/spool/cron/root
2 上传证书自动更新脚本并配置定时任务
2.1 上传证书自动更新脚本
脚本ssl_monitor.sh上传到/home/script/目录下,目录没有刚创建
注意:
- 脚本配置在nginx主机上。
- nginx配置证书目录必须为/usr/local/zhengshu
2.2 配置定时任务
# 配置定时任务 echo -e "# ssl_monitor 剩余28天更新\n0 23 * * * /bin/bash /home/script/ssl_monitor.sh -days 28 &>/dev/null" >>/var/spool/cron/root
注意:
- 修改脚本中rsync服务端地址
3 nginx ssl配置
server { #listen 80; listen 443 ssl http2; server_name *.qxd.com; ssl on; ssl_certificate /usr/local/zhengshu/qxd.com/fullchain.cer; ssl_certificate_key /usr/local/zhengshu/qxd.com/nfin.com.key; ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; ssl_prefer_server_ciphers on; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_session_cache shared:SSL:1024m; ssl_session_timeout 10m; ssl_session_tickets on; ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /usr/local/zhengshu/qxd.com/fullchain.cer; ... ...