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
|
. ${BUILDFILE%/*}/common.sh
pkgver=20161111.1
package() {
preamble
# #### SSL
# Use the [certbot][] ACME client to get certificates from
# [Let's Encrypt][].
#
# [certbot]: https://www.parabola.nu/packages/community/any/certbot/
# [Let's Encrypt]: https://letsencrypt.org/
depends+=(certbot)
# All domains handled by the server are shoved in as Subject
# Alternative Names in a single certificate. This makes configuring
# nginx easier.
# ##### keys user and group
# Files affected manually:
#
# * `/etc/passwd`
# * `/etc/shadow`
# * `/etc/group`
# * `/etc/gshadow`
# * `/etc/letsencrypt`
# * `/var/lib/letsencrypt`
# * `/var/log/letsencrypt`
#
# In order to run certbot as a non-root user, the keys user and group
# have been created:
#
# useradd --system --user-group --no-create-home --home-dir /etc/ssl --shell /usr/bin/nologin keys
# chown -R keys:keys /etc/letsencrypt /var/log/letsencrypt /var/lib/letsencrypt
# chmod 750 /etc/letsencrypt/archive /etc/letsencrypt/live
#
# The associated keys group allows users to read the (private) keys in
# /etc/letsencrypt/live.
# ##### issuance, renewal, and installation
# Unlike acmetool, certbot doesn't have an easy way of saying "please
# add this domain as a Subject Alternative Name". You have to re-run
# the same (long) command to get the cert, but with the domain added.
# So, I've encapsulated this into the script
# `/etc/ssl/misc/certbot-get`. Edit `/etc/ssl/misc/certbot-get.d/` to
# manipulate the list of domains, then run the script.
install -d etc/ssl/misc/certbot-get.d
add-file -m755 etc/ssl/misc/certbot-get <<<'#!/bin/bash
{
set -eu
# The first name listed should be the canonical host name
domains=(
$(hostname -f)
$(find -L "$0.d" -type f -executable -exec {} \;)
)
if [[ "`whoami`" != '\''keys'\'' ]]; then
>&2 printf '\''%q: This script must be run as user `%s'\''\'\'''\''\n'\'' "$0" keys
exit 1
fi
msg=(
Our "\`${0##*/}\`" script is used to '\''*add*'\'' or
'\''*remove*'\'' certificates\; use '\''`certbot renew`'\'' to
renew them. To use "${0##*/}," edit "\`${0##*/}.d/\`" to
manipulate the list of domains, '\''then'\'' run it to get a
new certificate with a new Subject Alternative Name field
matching the new list of domains.
$'\''\n\n'\''Are you sure that you are ready to run this?
It will eat into the "Let'\''s Encrypt" usage limit.
)
dialog --yesno "${msg[*]}" '\'''\'' '\'''\'' || { echo; exit 0; }
cmd=(
certbot certonly
--email "`whoami`@${domains[0]}"
--webroot -w /var/lib/letsencrypt
)
for domain in "${domains[@]}"; do
cmd+=(-d "$domain")
done
umask 0027
"${cmd[@]}"
sudo /etc/ssl/misc/certbot-hook
}'
# Renewal, however, is very simple. It is handled by
# `certbot-renew.service` (triggered by the associated `.timer`). It
# runs `certbot renew` with a couple of flags.
add-file etc/systemd/system/certbot-renew.timer <<EOF
[Unit]
Description=Daily renewal of Let's Encrypt's certificates
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
EOF
add-file etc/systemd/system/certbot-renew.service <<EOF
[Unit]
Description=Let's Encrypt certificate renewal
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --renew-hook 'sudo /etc/ssl/misc/certbot-hook'
User=keys
UMask=0027
EOF
add-unit etc/systemd/system/timers.target.wants/certbot-renew.timer
# Both `certbot-get` and `certbot-renew.service` prove ownership of
# the domain via the `http-01` challenge. `/etc/nginx/nginx.conf`
# includes `/etc/nginx/snippets/ssl.conf`, which has a `server{}`
# block that handles ACME http-01 challenges.
# Both `certbot-get` and `certbot-renew.service` have been written to
# run `sudo /etc/ssl/misc/certbot-hook` after certificates have been
# updated, and `sudo` has been configured to allow the keys user to do
# this without a password. Right now `certbot-hook` just runs
# `systemctl reload nginx.service`.
add-file -m755 etc/ssl/misc/certbot-hook <<EOF
#!/bin/bash
systemctl reload nginx.service
EOF
install -dm750 etc/sudoers.d
add-file etc/sudoers.d/10-certbot <<EOF
keys ALL=(ALL) NOPASSWD: /etc/ssl/misc/certbot-hook
EOF
# ##### other
# Files affected manually:
#
# * `/etc/nginx/nginx.conf`
# * `/etc/ssl/private/dhparam-2048.pem`
# `nginx.conf` includes `snippets/ssl.conf`, which is primarily based
# on the output of [Mozilla Security's recommended web server
# configuration generator][0]. It has had the main SSL information
# promoted to be directly into the `http{}` block, instead of having
# to be in each `server{}` block. The HTTP->HTTPS redirector has had
# an exception added to it to have it respond to ACME http-01
# challenges.
#
# [0]: https://mozilla.github.io/server-side-tls/ssl-config-generator/
add-file etc/nginx/snippets/ssl.conf <<EOF
# -*- Mode: nginx; nginx-indent-level: 8; indent-tabs-mode: t -*-
# This is based on Mozilla Security's recommended web server
# configuration generator[1]:
# Generated date: 2016-06-28
# Server: Nginx
# Clients: Intermediate
# Server Version: 1.10.1
# OpenSSL Version: 1.0.2h
# HSTS Enabled: yes
#
# [1]: https://mozilla.github.io/server-side-tls/ssl-config-generator/
#
# Obviously, all of the '/path/to/' paths have been filled in. The
# 'resolver' line has been commented out. The SSL information has
# been promoted to be in the http{} block directly, instead of having
# to be in each server{} block. The HTTP->HTTPS redirector has had
# ACME support added.
# Redirect everything on port 80 to HTTPS
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
location / { return 301 https://\$host\$request_uri; }
# Except for ACME http-01 validations
location /.well-known/acme-challenge {
root /var/lib/letsencrypt;
default_type "text/plain";
}
}
# certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
ssl_certificate /etc/ssl/private/myhostname/fullchain.pem;
ssl_certificate_key /etc/ssl/private/myhostname/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
ssl_dhparam /etc/ssl/private/dhparam-2048.pem;
# intermediate configuration. tweak to your needs.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;
# OCSP Stapling ---
# fetch OCSP records from URL in ssl_certificate and cache them
ssl_stapling on;
ssl_stapling_verify on;
## verify chain of trust of OCSP response using Root CA and Intermediate certs
#ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;
#resolver <IP DNS resolver>;
EOF
# Because certbot is only configured to use http-01 challenges, the
# all challenges happen over pain HTTP, which means that the
# configurations for each subdomain (which only serve over
# HTTPS/HTTP2) do not need to include anything about ACME or SSL
# (other than mentioning `ssl` in the `listen` directive).
# `ssl.conf` needs to refer to a dhparam PEM file. This has been
# generated with the command
#
# openssl dhparam -out /etc/ssl/private/dhparam-2048.pem 2048
postamble
}
|