]>
git.wh0rd.org - home.git/blob - .bin/le-renew
3 # pylint: disable=invalid-name
5 """Renew Let's Encrypt certs!
7 To generate a new set of certs:
8 $ certbot certonly --webroot \\
9 --webroot-path /var/www/wh0rd/ -d wh0rd.org -d www.wh0rd.org \\
10 --webroot-path /var/www/rss/ -d rss.wh0rd.org
13 from __future__
import print_function
19 import ConfigParser
as configparser
20 import cryptography
.hazmat
.backends
21 from cryptography
import x509
23 from cStringIO
import StringIO
25 from io
import StringIO
28 import logging
.handlers
34 LE_BASE
= '/etc/letsencrypt'
38 """Return an ArgumentParser() for this module."""
39 parser
= argparse
.ArgumentParser(description
=__doc__
,
40 formatter_class
=argparse
.RawTextHelpFormatter
)
41 parser
.add_argument('-n', '--dry-run', default
=False,
43 help='Do not actually update certs')
44 parser
.add_argument('--cronjob', default
=False,
46 help='Exit non-zero if no certs were changed')
50 def setup_logging(debug
=False, syslog
=None):
51 """Setup the logging module just the way we like it."""
53 syslog
= not os
.isatty(sys
.stdin
.fileno())
56 handler
= logging
.handlers
.SysLogHandler(address
='/dev/log')
58 handler
= logging
.StreamHandler(stream
=sys
.stdout
)
60 fmt
= '%(asctime)s: %(levelname)-7s: %(message)s'
62 datefmt
= '%a, %d %b %Y %H:%M:%S letsencrypt'
64 level
= logging
.DEBUG
if debug
else logging
.INFO
66 formatter
= logging
.Formatter(fmt
, datefmt
)
67 handler
.setFormatter(formatter
)
69 logger
= logging
.getLogger()
70 logger
.addHandler(handler
)
71 logger
.setLevel(level
)
75 """Load the cert at |path|"""
76 with
open(path
, 'rb') as f
:
78 return x509
.load_pem_x509_certificate(
79 data
, cryptography
.hazmat
.backends
.default_backend())
82 def load_live_cert(domain
):
83 """Load the live cert for |domain|"""
84 path
= os
.path
.join(LE_BASE
, 'live', domain
, 'cert.pem')
85 return load_cert(path
)
88 def load_conf(domain
):
89 """Load the LE config file for |domain|"""
90 path
= os
.path
.join(LE_BASE
, 'renewal', domain
+ '.conf')
91 # The config file format is almost enough for the configparser.
92 # We need to insert a section header for the first few items.
94 fp
.write('[globals]\n')
95 fp
.write(open(path
).read())
98 conf
= configparser
.RawConfigParser()
103 def process_domain(domain
, dry_run
=False):
104 """Update |domain|'s certs as needed."""
107 logging
.info('%s: checking', domain
)
109 conf
= load_conf(domain
)
111 webroot_path
= conf
.get('[webroot_map', domain
)
112 except configparser
.NoOptionError
:
113 webroot_path
= conf
.get('renewalparams', 'webroot_path')
114 # The conf writing has a bug here where it appends a comma.
115 webroot_path
= webroot_path
.rstrip(',')
117 cert_path
= os
.path
.realpath(conf
.get('globals', 'cert'))
119 cert
= load_cert(cert_path
)
120 delta
= cert
.not_valid_after
- datetime
.datetime
.utcnow()
121 logging
.info('%s: expires in %2s days', domain
, delta
.days
)
125 'certonly', '--webroot',
126 '--webroot-path', webroot_path
,
131 san
= cert
.extensions
.get_extension_for_oid(
132 x509
.oid
.ExtensionOID
.SUBJECT_ALTERNATIVE_NAME
)
133 domains
= san
.value
.get_values_for_type(x509
.DNSName
)
134 except x509
.ExtensionNotFound
:
139 logging
.info('%s: renewing', domain
)
140 logging
.info('%s: %s', domain
, ' '.join(cmd
))
143 subprocess
.check_call(cmd
)
144 except subprocess
.CalledProcessError
:
145 logging
.error('failed', exc_info
=True)
148 # Try to revoke the old one.
149 cmd
= ['certbot', 'revoke', '--no-delete-after-revoke', '--cert-path',
151 logging
.info('%s: revoking old cert', domain
)
152 logging
.info('%s: %s', domain
, ' '.join(cmd
))
155 subprocess
.check_call(cmd
, stdin
=open('/dev/null', 'r'))
156 except subprocess
.CalledProcessError
:
157 logging
.error('failed', exc_info
=True)
159 logging
.info('%s: up-to-date!', domain
)
165 """The main() entry point!"""
166 parser
= get_parser()
167 opts
= parser
.parse_args(argv
)
171 domains
= [x
[:-5] for x
in os
.listdir('/etc/letsencrypt/renewal')]
172 for domain
in domains
:
173 cnt
+= process_domain(domain
, dry_run
=opts
.dry_run
)
182 if __name__
== '__main__':
183 sys
.exit(main(sys
.argv
[1:]))