Tested with OpenBSD 6.4
httpd supports TLS 1.2 and works well with acme-client. In this example, relayd(8) only adds some HTTP headers to get higher grades from the following tests:
a+ observatory by mozilla a+ ssl labs by qualys a cryptcheck a+ security headers + hsts preload 100 lighthouse by google
There are some drawbacks:
Because relayd(8) is fronting httpd(8): REMOTE_ADDR in
access.log is always 127.0.0.1. Here is a diff for httpd(8)
to include X-Forwarded-For and X-Forwarded-Port to the log.
Also httpd(8) doesn't support gzip
compression for static files. You can use gzip via FastCGI, if
needed.
httpd(8) listens on ports 80 and 8080, serves plain HTTP,
redirects //www.tld to //tld and http://tld:80 to https://tld:443.
relayd(8) listens on ports 443 and terminates TLS for IPv4 and
IPv6 addresses, acme-client(1) issues a certificate via Let's
Encrypt, cron(8) runs acme-client(1) to check and renew the
certifictate.
In this example, TLD is rgz.ee, IPv4 address of the server is 46.23.88.178
and IPv6 is 2a03:6000:1015::178.
https://rgz.ee → relayd 46.23.88.178 :443 or relayd 2a03:6000:1015::178:443 → httpd 127.0.0.1 :8080 HTTP 200 OK https://www.rgz.ee → relayd * :443 → httpd 127.0.0.1 :8080 HTTP 301 https://rgz.ee http://rgz.ee or http://www.rgz.ee → httpd * :80 HTTP 301 https://rgz.ee
acme-client(1) stores a challenge in /var/www/acme directory,
Let's Encrypt sends an HTTP request GET /.well-known/acme-challengs/*,
and httpd(8) serves static files from that directory on such requests.
Note: httpd(8) is chrooted in /var/www/, so httpd(8) sees it as /acme/.
# > /etc/httpd.conf echo '
server "rgz.ee" {
listen on 127.0.0.1 port 8080
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
}
server "www.rgz.ee" {
listen on 127.0.0.1 port 8080
block return 301 "https://rgz.ee$REQUEST_URI"
}
server "rgz.ee" {
alias "www.rgz.ee"
listen on * port 80
block return 301 "https://rgz.ee$REQUEST_URI"
}
'
#
Verify the configuration, enable and restart httpd(8).
# httpd -n configuration OK # # rcctl enable httpd # rcctl restart httpd httpd (ok) #
relayd(8) listens on port 443 and relays all HTTP requests
to port 8080 to be served by httpd(8).
Must read before setting HTTP headers:
HSTS deployment recommendations
Content security policy
Feature policy
TLS configurations
# > /etc/relayd.conf echo '
ipv4="46.23.88.178"
ipv6="2a03:6000:1015::178"
table <local> { 127.0.0.1 }
http protocol https {
tls ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
match request header append "X-Forwarded-For" value "$REMOTE_ADDR"
match request header append "X-Forwarded-Port" value "$REMOTE_PORT"
match response header set "Content-Security-Policy" value "default-src 'none'; style-src 'self'; img-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'"
match response header set "Feature-Policy" value "camera 'none'; microphone 'none'"
match response header set "Referrer-Policy" value "no-referrer"
match response header set "Strict-Transport-Security" value "max-age=31536000; includeSubDomains; preload"
match response header set "X-Content-Type-Options" value "nosniff"
match response header set "X-Frame-Options" value "deny"
match response header set "X-XSS-Protection" value "1; mode=block"
return error
pass
}
relay wwwtls {
listen on $ipv4 port 443 tls
listen on $ipv6 port 443 tls
protocol https
forward to <local> port 8080
}
'
#
relayd(8) loads a full-chain certificate for both IPv4 and IPv6
addresses from $address.crt file and private key from
private/$address.key from /etc/ssl directory.
Generate a temporary key and certificate, then create symbolic links for IPv4 and IPv6 addresses. Later that key and certificate will be replaced by acme-client(1).
# mkdir -p -m 0700 /etc/ssl/private
#
# openssl req -x509 -newkey rsa:4096 \
-days 365 -nodes \
-subj '/CN=rgz.ee' \
-keyout /etc/ssl/private/rgz.ee.key \
-out /etc/ssl/rgz.ee.pem
Generating a 4096 bit RSA private key
.................................................++
....................................................................++
writing new private key to '/etc/ssl/private/rgz.ee.key'
-----
#
# ln -fs /etc/ssl/private/{rgz.ee,46.23.88.178}.key
# ln -fs /etc/ssl/private/{rgz.ee,2a03:6000:1015::178}.key
# ln -fs /etc/ssl/{rgz.ee.pem,46.23.88.178.crt}
# ln -fs /etc/ssl/{rgz.ee.pem,2a03:6000:1015::178.crt}
#
# chmod 0600 /etc/ssl/private/*.key
#
Verify the configuration, enable and restart relayd(8).
# relayd -n configuration OK # # rcctl enable relayd # rcctl restart relayd relayd (ok) #
acme-client(1) generates an account key letsencrypt.key, a domain
key rgz.ee.key and stores them in /etc/ssl/private, stores
challenges in /var/www/acme directory, a cerficifate in
/etc/ssl/rgz.ee.crt (not needed for this setup), a full-chain
cerficifate in /etc/ssl/rgz.ee.pem (needed for relayd).
# > /etc/acme-client.conf echo '
authority letsencrypt {
api url "https://acme-v01.api.letsencrypt.org/directory"
account key "/etc/ssl/private/letsencrypt.key"
}
domain rgz.ee {
alternative names { www.rgz.ee }
domain key "/etc/ssl/private/rgz.ee.key"
domain certificate "/etc/ssl/rgz.ee.crt"
domain full chain certificate "/etc/ssl/rgz.ee.pem"
sign with "letsencrypt"
}
'
#
Remove the temporary cerficifate and keys, if any. Create the directory for challenges.
# rm -f /etc/ssl/rgz.ee.pem # rm -f /etc/ssl/rgz.ee.crt # rm -f /etc/ssl/private/rgz.ee.key # rm -f /etc/ssl/private/letsencrypt.key # # mkdir -p -m 755 /var/www/acme #
Verify the configuration, run acme-client(1), and reload relayd(8).
# acme-client -n rgz.ee
authority letsencrypt {
api url "https://acme-v01.api.letsencrypt.org/directory"
account key "/etc/ssl/private/letsencrypt.key"
}
domain rgz.ee {
domain key "/etc/ssl/private/rgz.ee.key"
domain certificate "/etc/ssl/rgz.ee.crt"
domain full chain certificate "/etc/ssl/rgz.ee.pem"
sign with "letsencrypt"
}
#
# acme-client -vFAD rgz.ee
acme-client: /etc/ssl/private/letsencrypt.key: generated RSA account key
acme-client: /etc/ssl/private/rgz.ee.key: generated RSA domain key
acme-client: https://acme-v01.api.letsencrypt.org/directory: directories
acme-client: acme-v01.api.letsencrypt.org: DNS: 23.15.57.150
acme-client: https://acme-v01.api.letsencrypt.org/acme/new-reg: new-reg
acme-client: https://acme-v01.api.letsencrypt.org/acme/new-authz: req-auth: rgz.ee
acme-client: /var/www/acme/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: created
acme-client: https://acme-v01.api.letsencrypt.org/acme/challenge/yyyyyyyyyyy_yyyyyyyyyyyyyyyyy-yyyyyyyyyyyyy/yyyyyyyyyyy: challenge
acme-client: https://acme-v01.api.letsencrypt.org/acme/challenge/yyyyyyyyyyy_yyyyyyyyyyyyyyyyy-yyyyyyyyyyyyy/yyyyyyyyyyy: status
acme-client: https://acme-v01.api.letsencrypt.org/acme/new-cert: certificate
acme-client: http://cert.int-x3.letsencrypt.org/: full chain
acme-client: cert.int-x3.letsencrypt.org: DNS: 23.13.65.208
acme-client: /etc/ssl/rgz.ee.crt: created
acme-client: /etc/ssl/rgz.ee.pem: created
#
# rcctl reload relayd
relayd(ok)
#
Schedule a new crontab to check and renew the certificate.
# echo '0 0 * * * acme-client rgz.ee && rcctl reload relayd' | crontab - #© roman zolotarev