-
Notifications
You must be signed in to change notification settings - Fork 4
/
README
371 lines (297 loc) · 15.2 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
Certbot ACME Client embedded/IoT integration utility
====================================================
Certbot is a most powerful ACME client for Let's Encrypt certificate
authority with lot of domain authentication and service configuration
plugins.
Written in Python with a lot of dependencies it might be unsuitable
for use directly in embedded and IoT world.
On the other hand it might be undesirable for large IoT deployments to
directly contact Let's Encrypt servers playing at the edge of their
rate limits.
This utility tries to address both of the above and present POSIX shell
helper that uses certbot to talk with Let's Encrypt and presents proxy
infrastructure to issue certs for thousands of IoT devices.
Features
--------
o Single/Multiple account(s) access to Let's Encrypt services from
controllable set of hosts acting as proxy for IoT devices
o Minimal dependencies for proxy client on target devices:
- POSIX compatible /bin/sh
- install
- mktemp
- mv
- rm
- chmod
- cmp
- openssl
- wget/curl
most of which may be available through busybox or similar toolboxes.
o Standard certbot utility command line interface, no extra options.
o Uses DNS-01 authenticator and can be integrated with any DNS service
hosting providers (not only with explicitly supported by certbot
plugins) through CNAMEs pointing to different domain or delegated
subdomain that supports rfc2136 compliant dynamic updates.
o Run Certbot ACME Client under specified unprivileged user
(letsencrypt) on proxy host for improved security
o Supplementary group (certbot) whose members can execute certbotsh
to allow set of unprivileged users to access certbot instance under
specified user
o All in one script containing installer and client/server applets
to simplify deployments at both client (e.g. IoT device) and servers
Requirements
------------
Domain or subdomain must be up and running either on hosting provider
provided authoritative nameservers or privately controlled (delegated
domain).
While proxy server side configuration tested only on RHEL/CentOS 7.x and
8.x releases it should work as well on other Linux (or BSD) systems
possibly with slight modifications.
Client side has it's minimal requirements described in Features section.
Description
-----------
There are multiple usage scenarious possible (e.g. simple certbot
wrapper to run under unprivileged user) with this utility but most
common is a proxy between Let's Encrypt and own services consuming
authority issued certificates.
Consider following diagram with example.com as domain:
+----------------------------+ CNAMEs -> TXTs
| Hosting provider | _acme-challenge.www -> www._acme-le
| authoritative DNS servers | ...
| managed by customer via | _acme-challenge -> self._acme-le
| some UI (e.g. web portal) |
| | +------------------------------+
| +-----------------+ CNAME | | Let's Encrypt infrastructure |
| | ns1.hosting.net |<.......o.. | |
| +-----------------+ | . | +-------------------+ |
| +-----------------+ CNAME | .......o..>| DNS-01 Validation | |
| | ns2.hosting.net |<.......o.. . | +---------^^--------+ |
| +-----------------+ | . | || |
+----------------------------+ . | +---------vv--------+ |
DNS . | | ACME frontend | |
. | +-------------------+ |
+--------------------------------+ . | ^ |
| certbotsh server (proxy) | . +--------------o---------------+
| | . .
| +-------------------------+ TXT| . .
| | ns._acme-le.example.com |<...o... .
| +-------------------------+bind| .
| +----------------------------+ | HTTP/ACME .
| | acme-le.gw.api.example.com |<o....................
| +----------------------------+ | certbot
| ^ lighttpd |
+--------------o-----------------+
.
...................
.
+--------------------------------o----------------------------------+
| certbotsh client(s) . |
| . HTTP GET over TLS/SSL (HTTPS) |
| .................................................... |
| . . . . . . |
| +---v---+ +---v---+ +--v--+ +---v----+ +--v--+ +--v---+ |
| | dev-1 | | dev-2 | | www | | portal | | ftp | | smtp | |
| +-------+ +-------+ +-----+ +--------+ +-----+ +------+ |
| ph1 ph2 ph3 ph4 ph5 ph6 |
| |
| Fetch & unpack PKCS#12 encrypted with per client passphrase (phX) |
| file with privkey, chain, cert to /etc/letsencrypt/live/<fqdn> |
| to retain compatibility with certbot filesystem pathes and naming |
| |
+-------------------------------------------------------------------+
In this diagram certbotsh server side running on RHEL/CentOS 7.x or 8.x
with following components:
ns._acme-le.example.com
~~~~~~~~~~~~~~~~~~~~~~~
ISC BIND 9.11.x supporting rfc2136 dynamic updates
authoritative master (only) DNS name server serving
_acme-le.example.com subdomain zone.
Example of zone file, named(8) config snippet among with
README file with instructions on how to configure BIND
can be found in ~${runas}/extra/bind subdirectory.
acme-le.gw.api.example.com
~~~~~~~~~~~~~~~~~~~~~~~~~~
Let's Encrypt ACME gateway for domain example.com running
certbot to get, update revoke certificates.
Single LE account for whole domain created by certbot
on this host.
Deploy hook (certbotsh-publish) is used to create PKCS#12
with privkey, chain and cert files created by certbot.
Either certbot-renew.timer or cron job takes care on
updating certificates that is near to expiry and not
revoked.
Each PKCS#12 file encrypted with per client passphrase
that is only known to this gateway/proxy and client this
certificate intended for. Strong cryptography algorithms
(AES256-CBC with SHA256) used for this by default and
can be adjusted in specific config files (publish.cfg)
if necessary.
At certificate issue time client hostname (domain) should
be known and resolvable to all IPv4 and IPv6 addresses as
specific configuration for lighttpd implemented to ensure
that client can only download it's own PKCS#12. Download
is done using HTTP GET method over TLS/SSL secure channel
with acme-le.gw.api.example.com certificate issued by
trusted certificate authority (e.g. Let's Encrypt).
From certbotsh client side there is two actions required
1) Download and unpack PKCS#12 files to /etc/letsencrypt/live/<fqdn>
subdirectory (see certbotsh-update). There is crontab(5) file to
run certbotsh-update periodically to pull updated certificates
from gateway/proxy.
2) Execute pre, post and deploy hooks (see certbotsh-hook) to
stop service, install certificates and start service that
uses them.
In most cases certbotsh-hook $runas root to make sure it
have privileges necessary to restart service. It is started
by certbotsh-update and switched to root using sudo(8).
Security
--------
Proxy configuration shipped with secure by default.
However additional configurations like firewall and HTTP
authentication might be implemented if desired (however last one
has very little impact on security as compromise of single
certbotsh client would give access to HTTP authentication
credentials).
Whenever possible least privilege principle used (e.g. certbot
is running as unprivileged user to perform all actions).
PKCS#12 files encrypted with strong cryptography algorithms
(AES256-CBC with SHA256 for MAC by default) using per client
passphrase shared only between proxy and client so that compromise
of single client passphrase does not compromise the rest.
PKCS#12 files can only be downloaded by clients they intended to.
This check is done by lighttpd based on network (IPv4 and/or IPv6)
address of connecting client. Therefore whole TCP session needs to
be spoofed to access PKCS#12 of other client.
Lighttpd configured with minimal set of modules with HTTP to HTTPS
redirect and HTTP Strict Transport Security. Server certificate
must be issued by trusted by certbotsh clients authority (e.g.
Let's Encrypt). Only static content is served by lighttpd.
Digest authentication might be enabled to enhance protection
in environments where more specific network address prefix can be
injected (way to spoof TCP session) to make possible for unauthorized
parties to download PKCS#12 file. Password for digest authentication
is shared among all trusted hosts therefore it's compromise on single
host will reveal access from others.
Where appropriate (e.g. hooks running by certbotsh-update) code
injection protection mechanisms applied.
Usage
-----
usage: [vars] certbot.sh install [client|server|all]
If argument to install is not specified 'client' is assumed.
Accepted environment variables (e.g. in [vars]) with their defaults are
runas=letsencrypt
User to install (and run) services as
certmgr=certbot
Group whose members allowed to run services
httpd_domain=example.com
Domain used for default (wildcard) server
httpd_hostname=acme-le.gw.api.$httpd_domain
Virtual host name to configure in httpd
Report bugs to http://github.com/serhepopovych/certbotsh
Examples
--------
(1) Deploy gateway/proxy acme-le.gw.api.example.com on CentOS 7.x or 8.x
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Install EPEL
$ sudo yum install -y epel-release && sudo yum update -y
# Install dependencies
$ sudo yum install -y diffutils psmisc curl lighttpd certbot
# Install certbot-dns-rfc2136 plugin on
# CentOS 7.x (python2) or CentOS 8.x (python3)
$ sudo yum install -y python2-certbot-dns-rfc2136 ||
sudo yum install -y python3-certbot-dns-rfc2136
# Fetch certbotsh
$ curl -s -o ~/certbot.sh \
https://raw.githubusercontent.com/serhepopovych/certbotsh/master/certbot.sh &&
chmod 0755 certbot.sh
# Install certbotsh
$ sudo \
httpd_domain='example.com' \
httpd_hostname='acme-le.gw.api.example.com \
~/certbot.sh install server
# Patch dns_rfc2136.py to support CNAME/DNAME lookups
# CentOS 7.x (python2) or CentOS 8.x (python3)
$ if cd /usr/lib/python2.7/site-packages/certbot_dns_rfc2136/_internal ||
cd /usr/lib/python3.6/site-packages/certbot_dns_rfc2136/_internal
then
sudo patch <${s}${n}
cd - >/dev/null
fi
# Needed to recompile .pyo/.pyc as we run as unprivileged user later
sudo /usr/bin/certbot -h all
# Make sure to configure at least keyname and secret in
/var/lib/letsencrypt/.config/letsencrypt/rfc2136.ini
with values from step(2)
(2) Deploy _acme-le.example.com on CentOS 7.x or CentOS 8.x
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Install ISC BIND with chrooted configuration
$ sudo yum install -y bind-chroot
# Get /var/lib/letsencrypt/extra/bind/*
scp acme-le.gw.api.example.com:/var/lib/letsencrypt/extra/bind/* ~
# Generate TSIG key
$ sudo \
sh -c 'tsig-keygen -a hmac-sha256 acme-le.gw.api.example.com-key >>/etc/named.tsig.key'
# Install named(8) config snippet
$ sudo install \
-m 640 -o root -g named \
~/named._acme-le.zones /etc/named._acme-le.zones
# Add named.tsig.key and named._acme-le.zones to /etc/named-chroot.files
$ grep '/etc/named\.tsig\.key' /etc/named-chroot.files ||
sudo sh -c 'echo "/etc/named.tsig.key" >>/etc/named-chroot.files'
$ grep '/etc/named\._acme-le\.zones' /etc/named-chroot.files ||
sudo sh -c 'echo "/etc/named._acme-le.zones" >>/etc/named-chroot.files'
# Make sure to add following configuration to /etc/named.conf
options {
/* This is needed for dns-rfc2136 plugin patch to resolve CNAMEs */
allow-recursion { <host -t a acme-le.gw.api.example.com>; };
recursion yes;
};
include "/etc/named.tsig.key";
include "/etc/named._acme-le.zones";
# Install named(8) zone file and adjust Resource Records (RRs) to point
# to correct IP addresses instead of 127.0.1.1 (edit named._acme-le.example.com)
$ sudo install -m 0644 -o named -g named \
~/named._acme-le.example.com \
/var/named/named._acme-le.example.com
# Make sure dynamic zone updates can be commited by named(8)
$ sudo chown root:named /var/named && sudo chmod 1770 /var/named
# Start named(8)
$ sudo systemctl enable --now named-chroot
# At this point you can configure delegation of _acme-le.example.com
# from your hosting provider to ns._acme-le.example.com.
(3) Request trusted certificates for acme-le.gw.api.example.com and start lighttpd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Note that when certbot is running as root it will switch to unprivileged
# user as certbot is an "alias" installed in /usr/local/bin/certbot and
# /usr/local/sbin/certbot.
#
# This will work only when /usr/local/bin and/or /usr/local/sbin preceeding
# /usr/bin, /bin and /usr/sbin, /sbin in PATH variable.
#
# If this fails to obtain certificates check _acme-le.example.com BIND settings,
# subdomain _acme-le.example.com delegation from your hosting provider and
# ~letsencrypt/.config/letsencrypt/rfc2136.ini. Make sure CNAMEs for
# _acme-challenge.acme-le.gw.api.example.com and _acme-challenge.example.com
# setup correctly and point to (non-existent as certbot will create them with
# dynamic updates) TXT records in _acme-le.example.com domain.
# Request certificates from Let's Encrypt for proxy/gateway (including wildcard)
$ sudo certbot -d acme-le.gw.api.example.com -d '*.example.com'
# Point lighttpd certificate dirs to /etc/letsencrypt/live
$ sudo ln -sf ../../letsencrypt/live/example.com /etc/lighttpd/pki/
$ sudo ln -sf ../../letsencrypt/live/acme-le.gw.api.example.com /etc/lighttpd/pki/
$ sudo ln -sf example.com /etc/lighttpd/pki/wildcard
# Adjust SELinux settings for lighttpd
$ sudo yum install -y policycoreutils
$ sudo setsebool -P httpd_setrlimit 1
# Start lighttpd(8)
$ sudo systemctl enable --now lighttpd
(4) Install certbotsh client side
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is fairly simple but depends on client type. At least you need to copy
certbot.sh to client, run installation and configure hooks:
# Fetch certbotsh
$ curl -s -o ~/certbot.sh \
https://raw.githubusercontent.com/serhepopovych/certbotsh/master/certbot.sh &&
chmod 0755 certbot.sh
# Install certbotsh
$ sudo ~/certbot.sh install client
# Use example ~letsencrypt/.config/letsencrypt/update.cfg to configure hooks.