IIS证书到期更新自动化脚本

发布于 2021-12-02  1.75k 次阅读


公司目前有数十个域名,几百个子域名,每个站点又至少有两台后端做负载均衡,主域名证书一年需要更新一次,手动更换实在繁琐缓慢,主要是windows 更新证书后需要去IIS 手动给每个绑定重新指定新证书,太痛苦了。为了实现自动化几乎把appcmd 文档翻了个遍,又找了半天才找到certutil 和 netsh http 来导入pfx证书以及指定证书,而且有好多坑...

先上笔记


1. 添加pfx证书
certutil -f -p 密码  -importpfx pfx文件

2. 修改证书友好名称
新建 friendlyname.inf
[Version]
Signature = "$Windows NT$"
[Properties]
11 = "{text}*.xixxxxxx.com-20221203"

设置友好名称
# wget -Uri "https://zb-daily-check.oss-cn-hangzhou.aliyuncs.com/xxxxxxxxxxx/friendlyname.inf" -OutFile "C:\friendlyname.inf"
certutil -repairstore  my 证书序列号 friendlyname.inf

3.列出绑定的域名
C:\\Windows\\System32\\inetsrv\\appcmd.exe list sites /text:bindings

4.删除现有证书
netsh http delete sslcert hostnameport=子域名:443

5.指定新证书
# netsh http show sslcert 所显示 ‘应用程序 ID’ 的值(仅示例,自行替换命令行内的值,appid带花括号)
appid = '{4dc3e181-e14b-4a21-b022-59fc669b0914}'
# 新证书哈希,即证书详细信息内 ‘指纹’ 的值(仅示例,自行替换命令行内的值,无花括号)
thumbprint = 'a3ec0a00536205ee40e6fe2488c46xxxxxxxxxxxxx'

netsh http add sslcert hostnameport=子域名:443 appid={appid} certhash={thumbprint} certstorename=MY

python 自动化脚本


import os


# 需要更新证书的主域名
domain_name = 'xxxxxxx.com'

# netsh http show sslcert 所显示 ‘应用程序 ID’ 的值(请先本地证书控制台导入一次查看)
appid = '{4dc3e181-e14b-4a21-b022-59fc669b0914}'

# 新证书哈希,即证书详细信息内 ‘指纹’ 的值
thumbprint = 'b84f704a562b51ea67b6a5d69f715xxxxxxxxxxx'

# 证书序列号,即证书详细信息内 ‘序列号’ 的值
serial_number = '0aae05257282c6f7341b80xxxxxxxxx'

# 友好名称配置文件链接
propertyinffile = 'https://zb-daily-check.oss-cn-hangzhou.aliyuncs.com/xxxxxxxxx/friendlyname.inf'
'''
友好名称配置文件格式:
[Version]
Signature = "$Windows NT$"
[Properties]
11 = "{text}*.xxxxxxxx.com-20221203"
'''

# pfx证书文件链接
pfx_file = 'https://zb-daily-check.oss-cn-hangzhou.aliyuncs.com/xxxxxxxxx/xxxxxxxxxxxxx.pfx'

# pfx 证书密码
pfx_password = 'De4yxxxx'


# 远程执行命令(自行启用winrs)
def excecute_cmd(ip, cmd):
    return os.popen(f'winrs -r:http://{ip}:5985 -u:USERNAME -p:PASSWORD "{cmd}"').read()


def import_pfx_and_set_friendlyname(ip):
    # 导入证书
    pfx_file_name = pfx_file.split("/")[-1]
    excecute_cmd(ip, f'powershell wget -Uri "{pfx_file}" -OutFile "C:\\Users\\Administrator\\Desktop\\{pfx_file_name}"')
    excecute_cmd(ip, f'certutil -f -p {pfx_password}  -importpfx C:\\Users\\Administrator\\Desktop\\{pfx_file_name}')
    # 设置友好名称
    excecute_cmd(ip, f'powershell wget -Uri "{propertyinffile}" -OutFile "C:\\Users\\Administrator\\Desktop\\friendlyname.inf"')
    excecute_cmd(ip, f'certutil -repairstore  my {serial_number} C:\\Users\\Administrator\\Desktop\\friendlyname.inf')
    # 清理文件
    excecute_cmd(ip, f'del C:\\Users\\Administrator\\Desktop\\{pfx_file_name}')
    excecute_cmd(ip, f'del C:\\Users\\Administrator\\Desktop\\friendlyname.inf')


def get_ssl_subdomains(ip):
    cmd = f'C:\\Windows\\System32\\inetsrv\\appcmd.exe list sites /text:bindings'
    r = excecute_cmd(ip, cmd)
    bingdings = []
    for b in r.split('\n'):
        if b:
            bingdings.extend(b.split(','))

    # 带证书的子域名列表
    ssl_subdomain_list = []
    for i in bingdings:
        if domain_name in i and ':443:' in i:
            subdomain = i.split(':')[-1]
            ssl_subdomain_list.append(subdomain)

    if ssl_subdomain_list:
        print(ssl_subdomain_list)
        return ssl_subdomain_list
    return False


def renew_ssl(ip, ssl_subdomain_list):
    for sd in ssl_subdomain_list:
        # 删除现有证书
        excecute_cmd(ip, f'netsh http delete sslcert hostnameport={sd}:443')
        print(f'删除{sd} 证书')
        # 指定新证书
        cmd = f'netsh http add sslcert hostnameport={sd}:443 appid={appid} certhash={thumbprint} certstorename=MY'
        excecute_cmd(ip, cmd)
        print(f'添加{sd} 新证书')


name_list = ['WEB001', 'WEB002', 'WEB003', 'WEB004', 'WEB005', 'WEB006', 'WEB007', 'WEB008', 'WEB009', 'WEB010',
             'WEB011', 'WEB012', 'WEB013', 'WEB014']
ip_list = ['192.168.10.185', '192.168.10.186', '192.168.10.187', '192.168.10.188', '192.168.10.209', '192.168.10.207',
           '192.168.10.24', '192.168.10.25', '192.168.10.29', '192.168.10.86', '192.168.10.83', '192.168.10.82', '192.168.10.51', '192.168.10.102']
name_ip_dict = dict(zip(name_list, ip_list))

# 也可直接只用后端ip 列表
for name, ip in name_ip_dict.items():
    print(name)
    ssl_subdomain_list = get_ssl_subdomains(ip)
    if ssl_subdomain_list:
        import_pfx_and_set_friendlyname(ip)
        print('证书导入完成')
        renew_ssl(ip, ssl_subdomain_list)

测试了一下,更换证书并不影响现有连接,客户端依然暂用原证书,强制刷新后使用新证书。最好在连接数少的时候做更换吧~