]>
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
, cmd
)
142 subprocess
.check_call(cmd
)
144 # Try to revoke the old one.
145 cmd
= ['certbot', 'revoke', '--cert-path', cert_path
]
146 logging
.info('%s: revoking old cert', domain
)
147 logging
.info('%s: %s', domain
, cmd
)
149 subprocess
.check_call(cmd
)
151 logging
.info('%s: up-to-date!', domain
)
157 """The main() entry point!"""
158 parser
= get_parser()
159 opts
= parser
.parse_args(argv
)
163 domains
= [x
[:-5] for x
in os
.listdir('/etc/letsencrypt/renewal')]
164 for domain
in domains
:
165 cnt
+= process_domain(domain
, dry_run
=opts
.dry_run
)
174 if __name__
== '__main__':
175 sys
.exit(main(sys
.argv
[1:]))