Certbot泛域名自动续期(非Nginx默认安装路径可用)

2019/2/26 21:6 下午 posted in  技术 comments

主域名近期SSL证书到期了,今晚登进服务器进行了手动续期,但是不满意于手动续期,因此后来研究了下自动续期。最终找到了解决方法。

注意:本文首次撰写于2018-11-17,最近修改时间为2019-02-26,请注意相关程序的可用性与安全性。

具体解决方案是采用了近期有大牛在Github提供了个脚本,把这件事解决了。部署下述脚本就可以了。

脚本地址:

https://github.com/zning1994/certbot-letencrypt-wildcardcertificates-alydns-au

看个人需要,是阿里云DNS还是DNSPod,下载相应脚本,然后获取相关可以访问云资源的ID、SECRET填写进脚本相应位置(PHP、Shell脚本可能都有需要编辑的内容)。

注意:DNSPod需要前往DNSPod对应的腾讯云账号申请腾讯云服务资源访问权限。

然后执行命令申请,但是申请命令与脚本作者记录的不太一样,现记录如下:

执行以下命令,测试是否成功:

certbot certonly  -d *.example.com --manual --preferred-challenges dns --dry-run  --manual-auth-hook /脚本目录/autxy.sh #以腾讯云为例

执行成功,执行以下命令进行第一次申请:

certbot certonly -d *.example.com --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory --manual-auth-hook /脚本目录/autxy.sh --post-hook "执行命令更新后服务重启命令"

更新证书命令:

certbot renew --manual --preferred-challenges dns --manual-auth-hook /脚本目录/autxy.sh --post-hook "执行命令更新后服务重启命令"

强制更新证书命令:

certbot renew --manual --preferred-challenges dns  --manual-auth-hook /脚本目录/autxy.sh --force-renewal --post-hook "执行命令更新后服务重启命令"

但是我们需要将命令写进计划任务,来自动续期证书。输入命令sudo crontab -e编辑计划任务,添加更新证书命令并设置计划任务时间:

# 证书有效期<30天才会renew,所以crontab可以配置为1天或1周
# 每月的 1,7,21,28号, 3点29分 更新证书
29 3 1,7,21,28 * * root certbot renew --manual --preferred-challenges dns --manual-auth-hook /脚本目录/autxy.sh --post-hook "执行命令更新后服务重启命令"

以下是原文,可以不看

具体解决方案,执行下述代码,并将下述代码加入cron(具体加入cron的格式在下文):

shell~~certbot renew --pre-hook "nginx停止代码" --post-hook "nginx启动代码"

解决思路与问题分析

本来寻思用certbot renew --dry-run这个命令直接执行命令尝试试验自动续期的,但是出现了下述错误:

Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/zning.me.conf
-------------------------------------------------------------------------------
Cert is due for renewal, auto-renewing...
Could not choose appropriate plugin: The manual plugin is not working; there may be problems with your existing configuration.
The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.',)
Attempting to renew cert (zning.me) from /etc/letsencrypt/renewal/zning.me.conf produced an unexpected error: The manual plugin is not working; there may be problems with your existing configuration.
The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.',). Skipping.
All renewal attempts failed. The following certs could not be renewed:
  /etc/letsencrypt/live/zning.me/fullchain.pem (failure)

-------------------------------------------------------------------------------
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

All renewal attempts failed. The following certs could not be renewed:
  /etc/letsencrypt/live/zning.me/fullchain.pem (failure)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
-------------------------------------------------------------------------------
1 renew failure(s), 0 parse failure(s)

查看日志,查看错误情况:

2018-11-17 22:46:38,466:DEBUG:certbot.main:certbot version: 0.24.0
2018-11-17 22:46:38,466:DEBUG:certbot.main:Arguments: ['--dry-run']
2018-11-17 22:46:38,466:DEBUG:certbot.main:Discovered plugins: PluginsRegistry(PluginEntryPoint#manual,PluginEntryPoint#nginx,PluginEntryPoint#null,PluginEntryPoint#standalone,PluginEntryPoint#webroot)
2018-11-17 22:46:38,480:DEBUG:certbot.log:Root logging level set at 20
2018-11-17 22:46:38,480:INFO:certbot.log:Saving debug log to /var/log/letsencrypt/letsencrypt.log
2018-11-17 22:46:38,495:DEBUG:certbot.plugins.selection:Requested authenticator <certbot.cli._Default object at 0x7f161ba95190> and installer <certbot.cli._Default object at 0x7f161ba95190>
2018-11-17 22:46:38,495:DEBUG:certbot.cli:Var dry_run=True (set by user).
2018-11-17 22:46:38,495:DEBUG:certbot.cli:Var server=set(['staging', 'dry_run']) (set by user).
2018-11-17 22:46:38,495:DEBUG:certbot.cli:Var dry_run=True (set by user).
2018-11-17 22:46:38,495:DEBUG:certbot.cli:Var server=set(['staging', 'dry_run']) (set by user).
2018-11-17 22:46:38,495:DEBUG:certbot.cli:Var account=set(['server']) (set by user).
2018-11-17 22:46:38,503:INFO:certbot.renewal:Cert not due for renewal, but simulating renewal for dry run
2018-11-17 22:46:38,507:DEBUG:certbot.plugins.selection:Requested authenticator manual and installer None
2018-11-17 22:46:38,508:DEBUG:certbot.plugins.disco:Other error:(PluginEntryPoint#manual): An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/certbot/plugins/disco.py", line 126, in prepare
    self._initialized.prepare()
  File "/usr/lib/python2.7/site-packages/certbot/plugins/manual.py", line 118, in prepare
    self.option_name('auth-hook')))
PluginError: An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.
2018-11-17 22:46:38,508:DEBUG:certbot.plugins.selection:No candidate plugin
2018-11-17 22:46:38,508:DEBUG:certbot.plugins.selection:Selected authenticator None and installer None
2018-11-17 22:46:38,508:INFO:certbot.main:Could not choose appropriate plugin: The manual plugin is not working; there may be problems with your existing configuration.
The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.',)
2018-11-17 22:46:38,508:WARNING:certbot.renewal:Attempting to renew cert (zning.me) from /etc/letsencrypt/renewal/zning.me.conf produced an unexpected error: The manual plugin is not working; there may be problems with your existing configuration.
The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.',). Skipping.
2018-11-17 22:46:38,509:DEBUG:certbot.renewal:Traceback was:
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/certbot/renewal.py", line 422, in handle_renewal_request
    main.renew_cert(lineage_config, plugins, renewal_candidate)
  File "/usr/lib/python2.7/site-packages/certbot/main.py", line 1144, in renew_cert
    installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly")
  File "/usr/lib/python2.7/site-packages/certbot/plugins/selection.py", line 207, in choose_configurator_plugins
    diagnose_configurator_problem("authenticator", req_auth, plugins)
  File "/usr/lib/python2.7/site-packages/certbot/plugins/selection.py", line 303, in diagnose_configurator_problem
    raise errors.PluginSelectionError(msg)
PluginSelectionError: The manual plugin is not working; there may be problems with your existing configuration.
The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.',)

2018-11-17 22:46:38,509:ERROR:certbot.renewal:All renewal attempts failed. The following certs could not be renewed:
2018-11-17 22:46:38,510:ERROR:certbot.renewal:  /etc/letsencrypt/live/zning.me/fullchain.pem (failure)
2018-11-17 22:46:38,510:DEBUG:certbot.log:Exiting abnormally:
Traceback (most recent call last):
  File "/usr/bin/certbot", line 9, in <module>
    load_entry_point('certbot==0.24.0', 'console_scripts', 'certbot')()
  File "/usr/lib/python2.7/site-packages/certbot/main.py", line 1315, in main
    return config.func(config, plugins)
  File "/usr/lib/python2.7/site-packages/certbot/main.py", line 1228, in renew
    renewal.handle_renewal_request(config)
  File "/usr/lib/python2.7/site-packages/certbot/renewal.py", line 443, in handle_renewal_request
    len(renew_failures), len(parse_failures)))
Error: 1 renew failure(s), 0 parse failure(s)

看着好长,然后还是晚上比较困的时候,并不想看这么多日志去分析,于是先Google其他解决方案再说。

后来从网上搜到了用./certbot-auto进行操作,但是也报了上述错误。查看错误日志。

2018-11-17 22:44:58,251:DEBUG:certbot.log:Root logging level set at 20
2018-11-17 22:44:58,251:INFO:certbot.log:Saving debug log to /var/log/letsencrypt/letsencrypt.log
2018-11-17 22:44:58,252:DEBUG:certbot.plugins.selection:Requested authenticator None and installer None
2018-11-17 22:44:58,270:DEBUG:certbot.plugins.util:Failed to find executable apachectl in PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
2018-11-17 22:44:58,270:DEBUG:certbot.plugins.disco:No installation (PluginEntryPoint#apache): Cannot find Apache executable apachectl
Traceback (most recent call last):
  File "/opt/eff.org/certbot/venv/lib/python2.7/site-packages/certbot/plugins/disco.py", line 132, in prepare
    self._initialized.prepare()
  File "/opt/eff.org/certbot/venv/lib/python2.7/site-packages/certbot_apache/configurator.py", line 232, in prepare
    self._verify_exe_availability(self.option("ctl"))
  File "/opt/eff.org/certbot/venv/lib/python2.7/site-packages/certbot_apache/configurator.py", line 277, in _verify_exe_availability
    'Cannot find Apache executable {0}'.format(exe))
NoInstallationError: Cannot find Apache executable apachectl
2018-11-17 22:44:58,271:DEBUG:certbot.plugins.disco:No installation (PluginEntryPoint#nginx): Could not find a usable 'nginx' binary. Ensure nginx exists, the binary is executable, and your PATH is set correctly.
Traceback (most recent call last):
  File "/opt/eff.org/certbot/venv/lib/python2.7/site-packages/certbot/plugins/disco.py", line 132, in prepare
    self._initialized.prepare()
  File "/opt/eff.org/certbot/venv/lib/python2.7/site-packages/certbot_nginx/configurator.py", line 145, in prepare
    "Could not find a usable 'nginx' binary. Ensure nginx exists, "
NoInstallationError: Could not find a usable 'nginx' binary. Ensure nginx exists, the binary is executable, and your PATH is set correctly.
2018-11-17 22:44:58,272:DEBUG:certbot.plugins.selection:No candidate plugin
2018-11-17 22:44:58,272:DEBUG:certbot.plugins.selection:Selected authenticator None and installer None

还是硬着头皮抵抗困意看了这一堆日志。经过分析可知,certbot-auto原来是进行Apache和Nginx的默认安装路径进行的操作,然而这个服务器的Web提供服务并不是默认路径的,因此它找不到就正常了。

没辙,最后去官网看了眼官方文档,看看是不是有没有官方解决方法,没想到真有个说明。估计能解决我的问题,就是文章开篇所说的解决方法。不过在执行的时候出现了如下内容:

Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/zning.me.conf
-------------------------------------------------------------------------------
Cert not yet due for renewal

-------------------------------------------------------------------------------

The following certs are not due for renewal yet:
  /etc/letsencrypt/live/zning.me/fullchain.pem expires on 2019-02-15 (skipped)
No renewals were attempted.
No hooks were run.
-------------------------------------------------------------------------------

但是毕竟没有出现报错情况,估计这个代码是可以运行的,于是先把这个加进cron了,等有空再研究下具体情况。

shell0 3 */7 * * certbot renew --pre-hook "nginx停止代码" --post-hook "nginx启动代码"

以上含义是:每隔 7 天,夜里 3 点整自动执行该命令一次。

最后在下面附上官方的说明文档,先放在这,有空我再自己做详细解读。

官网说明部分:Renewing certificates

Note

Let’s Encrypt CA issues short-lived certificates (90 days). Make sure you renew the certificates at least once in 3 months.

See also

Many of the certbot clients obtained through a distribution come with automatic renewal out of the box, such as Debian and Ubuntu versions installed through apt, CentOS/RHEL 7 through EPEL, etc. See Automated Renewals for more details.

As of version 0.10.0, Certbot supports a renew action to check all installed certificates for impending expiry and attempt to renew them. The simplest form is simply

certbot renew

This command attempts to renew any previously-obtained certificates that expire in less than 30 days. The same plugin and options that were used at the time the certificate was originally issued will be used for the renewal attempt, unless you specify other plugins or options. Unlike certonlyrenew acts on multiple certificates and always takes into account whether each one is near expiry. Because of this, renew is suitable (and designed) for automated use, to allow your system to automatically renew each certificate when appropriate. Since renew only renews certificates that are near expiry it can be run as frequently as you want - since it will usually take no action.

The renew command includes hooks for running commands or scripts before or after a certificate is renewed. For example, if you have a single certificate obtained using the standalone plugin, you might need to stop the webserver before renewing so standalone can bind to the necessary ports, and then restart it after the plugin is finished. Example:

certbot renew --pre-hook "service nginx stop" --post-hook "service nginx start"

If a hook exits with a non-zero exit code, the error will be printed to stderr but renewal will be attempted anyway. A failing hook doesn’t directly cause Certbot to exit with a non-zero exit code, but since Certbot exits with a non-zero exit code when renewals fail, a failed hook causing renewal failures will indirectly result in a non-zero exit code. Hooks will only be run if a certificate is due for renewal, so you can run the above command frequently without unnecessarily stopping your webserver.

--pre-hook and --post-hook hooks run before and after every renewal attempt. If you want your hook to run only after a successful renewal, use --deploy-hook in a command like this.

certbot renew --deploy-hook /path/to/deploy-hook-script

For example, if you have a daemon that does not read its certificates as the root user, a deploy hook like this can copy them to the correct location and apply appropriate file permissions.

/path/to/deploy-hook-script

#!/bin/sh

set -e

for domain in $RENEWED_DOMAINS; do
        case $domain in
        example.com)
                daemon_cert_root=/etc/some-daemon/certs

                # Make sure the certificate and private key files are
                # never world readable, even just for an instant while
                # we're copying them into daemon_cert_root.
                umask 077

                cp "$RENEWED_LINEAGE/fullchain.pem" "$daemon_cert_root/$domain.cert"
                cp "$RENEWED_LINEAGE/privkey.pem" "$daemon_cert_root/$domain.key"

                # Apply the proper file ownership and permissions for
                # the daemon to read its certificate and key.
                chown some-daemon "$daemon_cert_root/$domain.cert" \
                        "$daemon_cert_root/$domain.key"
                chmod 400 "$daemon_cert_root/$domain.cert" \
                        "$daemon_cert_root/$domain.key"

                service some-daemon restart >/dev/null
                ;;
        esac
done

You can also specify hooks by placing files in subdirectories of Certbot’s configuration directory. Assuming your configuration directory is /etc/letsencrypt, any executable files found in/etc/letsencrypt/renewal-hooks/pre/etc/letsencrypt/renewal-hooks/deploy, and/etc/letsencrypt/renewal-hooks/post will be run as pre, deploy, and post hooks respectively when any certificate is renewed with the renew subcommand. These hooks are run in alphabetical order and are not run for other subcommands. (The order the hooks are run is determined by the byte value of the characters in their filenames and is not dependent on your locale.)

Hooks specified in the command line, configuration file, or renewal configuration files are run as usual after running all hooks in these directories. One minor exception to this is if a hook specified elsewhere is simply the path to an executable file in the hook directory of the same type (e.g. your pre-hook is the path to an executable in /etc/letsencrypt/renewal-hooks/pre), the file is not run a second time. You can stop Certbot from automatically running executables found in these directories by including --no-directory-hooks on the command line.

More information about hooks can be found by running certbot --help renew.

If you’re sure that this command executes successfully without human intervention, you can add the command to crontab (since certificates are only renewed when they’re determined to be near expiry, the command can run on a regular basis, like every week or every day). In that case, you are likely to want to use the -q or --quiet quiet flag to silence all output except errors.

If you are manually renewing all of your certificates, the --force-renewal flag may be helpful; it causes the expiration time of the certificate(s) to be ignored when considering renewal, and attempts to renew each and every installed certificate regardless of its age. (This form is not appropriate to run daily because each certificate will be renewed every day, which will quickly run into the certificate authority rate limit.)

Note that options provided to certbot renew will apply to every certificate for which renewal is attempted; for example, certbot renew --rsa-key-size 4096 would try to replace every near-expiry certificate with an equivalent certificate using a 4096-bit RSA public key. If a certificate is successfully renewed using specified options, those options will be saved and used for future renewals of that certificate.

An alternative form that provides for more fine-grained control over the renewal process (while renewing specified certificates one at a time), is certbot certonly with the complete set of subject domains of a specific certificate specified via -d flags. You may also want to include the -n or --noninteractive flag to prevent blocking on user input (which is useful when running the command from cron).

certbot certonly -n -d example.com -d www.example.com

All of the domains covered by the certificate must be specified in this case in order to renew and replace the old certificate rather than obtaining a new one; don’t forget any www. domains! Specifying a subset of the domains creates a new, separate certificate containing only those domains, rather than replacing the original certificate. When run with a set of domains corresponding to an existing certificate, the certonly command attempts to renew that specific certificate.

Please note that the CA will send notification emails to the address you provide if you do not renew certificates that are about to expire.

Certbot is working hard to improve the renewal process, and we apologize for any inconvenience you encounter in integrating these commands into your individual environment.

Note

certbot renew exit status will only be 1 if a renewal attempt failed. This means certbot renewexit status will be 0 if no certificate needs to be updated. If you write a custom script and expect to run a command only after a certificate was actually renewed you will need to use the --deploy-hook since the exit status will be 0 both on successful renewal and when renewal is not necessary.