commit 6e18b1ee81a6023d30bc59fa19e2aeba2202028a from: Bradley Taunt date: Mon Oct 28 00:31:58 2024 UTC more progress, move things over to haproxy, config changes commit - 6f5121b2fe9bd31eb2b4affbaca4102476941251 commit + 6e18b1ee81a6023d30bc59fa19e2aeba2202028a blob - de4723502052d2d2ada53fd2fe14582901ea7ed0 blob + a57283d627820e6bdd9f9ec44d3208d95e73d0f9 --- _header.html +++ _header.html @@ -3,7 +3,7 @@ - httpd.rocks + httpd.rocks - Setup an HTTPS-enabled webserver with httpd on OpenBSD blob - 9479a182ccc48373cc830413a3dd6560e0087018 blob + ff08ab3e43b81d011ab7d64c630647e678169258 --- index.html +++ index.html @@ -3,14 +3,17 @@ - httpd.rocks + httpd.rocks - Setup an HTTPS-enabled webserver with httpd on OpenBSD

OpenBSD mascot

httpd rocks

-

A barebones guide to setup an HTTPS-enabled web server with httpd on OpenBSD

-

Help improve this website: git.btxx.org/httpd.rocks

+

Setup an HTTPS-enabled web server with httpd on OpenBSD

+

Includes A+ security report configuration with haproxy.

+
+

I’m far from an expert! Please help improve this project

+

Before You Begin…

This guide assumes you have already setup OpenBSD on your desired server of choice. Most commands will need to run via doas, since you should be logged in as a created user - never root directly.

@@ -21,10 +24,11 @@

pf.conf

Before doing anything else, you need to make sure your /etc/pf.conf is allowing traffic on ports 80 and 443. Make sure you include the following:

-
pass in on egress proto tcp from any to any port 80
-pass in on egress proto tcp from any to any port 443
+
pass in on any proto { tcp, udp } from any to any port 53
+pass out on any proto { tcp, udp } from any to any port 53
 
-pass out on egress from any to any
+pass in on any proto tcp from any to any port {80 443}
+pass out on any proto tcp from any to any port {80 443}
 

httpd.conf

Make initial website folder and files:

@@ -44,87 +48,129 @@ doas chown -R www:www /var/www/htdocs/ request strip 2 } } - -server "www.httpd.rocks" { - listen on * port 80 - block return 301 "http://httpd.rocks$REQUEST_URI" -}
-

We need to create proper directories for acme-client (our next steps) and set their permissions:

-
doas mkdir -p -m 750 /etc/ssl/private
-doas mkdir -p -m 755 /var/www/acme
-

Then get httpd up and running:

doas rcctl start httpd
 

Note: If you encounter runtime errors with httpd, you might be required to add the following to your /etc/rc.conf.local file:

httpd_flags=""
 
-

If everything was setup properly, you should be able to visit the HTTP-only version of your website online.

+

If everything was setup properly, you should be able to visit the HTTP-only version of your website online. The only problem is HTTPS isn’t setup…

+

..yet!

acme-client.conf

+

Before anything else, we need to create proper directories for acme-client (our next steps) and set their permissions:

+
doas mkdir -p -m 750 /etc/ssl/private
+doas mkdir -p -m 755 /var/www/acme
+

Create the /etc/acme-client.conf file and include the following:

authority letsencrypt {
     api url "https://acme-v02.api.letsencrypt.org/directory"
     account key "/etc/acme/letsencrypt-privkey.pem"
 }
+
 domain httpd.rocks {
-    alternative names { www.httpd.rocks }
     domain key "/etc/ssl/private/httpd.rocks.key"
-    domain certificate "/etc/ssl/private/httpd.rocks.crt"
+    domain full chain certificate "/etc/ssl/httpd.rocks.fullchain.pem"
     sign with letsencrypt
 }
 
-

Note: The alternative names { www.httpd.rocks } will be needed later to forward all www. requests to standard https://.

Now we can run the core acme-client command to generate our certificates:

doas acme-client -v httpd.rocks
 
-

If everything goes smoothly, your new certificates should be generated and issued. The next thing you will want to do is automatically check for expired certs. Setup the following cronjob by running crontab -e and entering in:

-
0 0 * * * acme-client httpd.rocks && rcctl reload httpd
+

If everything goes smoothly, your new certificates should be generated and issued. The next thing you will want to do is automatically check for expired certs.

+

First create a separate script (this will be helpful if you plan to host multiple sites on a single server). Name it something like renew_certs.sh and save it under a local directory (ie. /home/username/scripts):

+
#!/bin/sh
+doas acme-client httpd.rocks || exit 1
+doas acme-client example.com || exit 1
+doas acme-client anotherdomain.com || exit 1
+doas rcctl reload httpd
 
+
+

For reference I have included multiple domains if you decide to host several websites through one server. Remove these if you only plan to host a single domain.

+
+

Set executable permissions:

+
doas chmod +x /path/to/renew_certs.sh
+
+

Then setup the following cronjob by running crontab -e and entering in:

+
0 0 * * * /path/to/renew_certs.sh
+

This will check if you need to renew certificates every day at midnight (server time).

-

httpd.conf (again)

-

Now we alter our existing /etc/httpd.conf to properly setup HTTPS and forward all HTTP traffic:

-
server "httpd.rocks" {
-  listen on * tls port 443
-  root "/htdocs/httpd.rocks"
-  tls {
-    certificate "/etc/ssl/private/httpd.rocks.crt"
-    key "/etc/ssl/private/httpd.rocks.key"
-  }
-}
+

haproxy.cfg

+

Many people tend to reach for relayd in OpenBSD when deciding to setup proxies or include security headers for their sites. Maybe I’m just too dull, but I always struggle to get things running smoothly with it.

+

That’s why I opt for using HAProxy.

+

First, install the package:

+
doas pkg_add haproxy
+
+

Now configure the core /etc/haproxy/haproxy.cfg (take note of the extension! OpenBSD uses cfg rather than the standard conf for HAProxy) and add the following to the existing file:

+
defaults
+    log     global
+    mode    http
+    option  httplog
+    option  dontlognull
+    timeout connect 5000ms
+    timeout client  50000ms
+    timeout server  50000ms
 
-server "www.httpd.rocks" {
-  listen on * tls port 443
-  tls {
-    certificate "/etc/ssl/private/httpd.rocks.crt"
-    key "/etc/ssl/private/httpd.rocks.key"
-  }
-  block return 301 "https://httpd.rocks$REQUEST_URI"
-}
+frontend http_in
+    bind *:80
+    # Redirect all www requests to non-www
+    http-request redirect prefix https://%[hdr(host),regsub(^www\.,)] code 301 if { hdr_beg(host) -i www }
+    redirect scheme https if !{ ssl_fc }
+    default_backend main_backend
 
-server "httpd.rocks" {
-  listen on * port 80
-  location "/.well-known/acme-challenge/*" {
-    root "/acme"
-    request strip 2
-  }
-  block return 301 "https://httpd.rocks$REQUEST_URI"
-}
+frontend https_in
+    bind *:443 ssl crt /etc/ssl/certs/
+    # Redirect all www requests to non-www
+    http-request redirect prefix https://%[hdr(host),regsub(^www\.,)] code 301 if { hdr_beg(host) -i www }
+    default_backend main_backend
 
-server "www.httpd.rocks" {
-  listen on * port 80
-  block return 301 "http://httpd.rocks$REQUEST_URI"
-}
+# Backend to httpd with security headers, no TLS
+backend main_backend
+    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
+    http-response set-header X-Content-Type-Options "nosniff"
+    http-response set-header X-Frame-Options "DENY"
+    http-response set-header X-XSS-Protection "1; mode=block"
+    http-response set-header Content-Security-Policy "script-src 'self'"
+    http-response set-header Referrer-Policy "no-referrer"
+    http-response set-header Permissions-Policy "microphone=()"
+    server local_httpd 127.0.0.1:8080
 
-

Then test and restart httpd:

-
httpd -n
-doas rcctl restart httpd
+

The haproxy.cfg in a nutshell:

+
    +
  • frontend http_in: +
      +
    • redirects all www requests to non-www
    • +
    • redirects all HTTP requests to HTTPS
    • +
    • uses main_backend to set security headers
    • +
  • +
  • frontend https_in: +
      +
    • redirects all www requests to non-www
    • +
    • uses main_backend to set security headers
    • +
  • +
  • backend main_backend: +
      +
    • sets sane security header defaults (change these as you please)
    • +
  • +
+

Important: Take note of the line:

+
bind *:443 ssl crt /etc/ssl/certs/
 
-

Be sure to also have httpd start on boot (in case of accidental server restarts!):

-
doas rcctl enable httpd
+

This tells HAProxy to dynamically scan a directory containing our certificates. This is handy if you decide to host multiple sites on a single server. Otherwise, you would have to edit and reload your HAProxy config everytime you setup a new website.

+

So we need to create our certs directory:

+
doas mkdir /etc/ssl/certs
+doas chmod 644 /etc/ssl/certs
 
+

And then create /etc/ssl/certs/httpd.rocks.pem by combining your certificate and private key if not done:

+
cat /etc/ssl/httpd.rocks.crt /etc/ssl/private/httpd.rocks.key > /etc/ssl/certs/httpd.rocks.pem
+
+

Once that’s complete, test that everything is working, and if so, enable and start HAProxy:

+
doas haproxy -c -f /etc/haproxy/haproxy.cfg
+doas rcctl enable haproxy
+doas rcctl start haproxy
+

It’s Alive!

-

Now check out your website! Everything should work as intended. You should have valid TLS and your standard HTTP request should forward to HTTPS.

+

Now check out your website! Everything should work as intended. You should have valid TLS, your standard HTTP request should forward to HTTPS, www requests should forward to non-www, and your security headers should score an A+.

That’s it!


References

blob - 0e9842a5db2596281fa3b721edaea066efbf06be blob + d0de539fbc8dc73232278b49b571f175f83c1622 --- index.md +++ index.md @@ -2,10 +2,12 @@ # httpd rocks -A barebones guide to setup an HTTPS-enabled web server with `httpd` on [OpenBSD](https://openbsd.org) +Setup an [HTTPS-enabled web server](https://www.ssllabs.com/ssltest/analyze.html?d=httpd.rocks) with `httpd` on [OpenBSD](https://openbsd.org) -Help improve this website: [git.btxx.org/httpd.rocks]() +Includes [A+ security report](https://securityheaders.com/?q=https%3A%2F%2Fhttpd.rocks%2F&followRedirects=on) configuration with `haproxy`. +> I'm far from an expert! Please [help improve this project](https://git.btxx.org/httpd.rocks) + --- ## Before You Begin... @@ -27,10 +29,11 @@ dig httpd.rocks Before doing anything else, you need to make sure your `/etc/pf.conf` is allowing traffic on ports `80` and `443`. Make sure you include the following: ``` -pass in on egress proto tcp from any to any port 80 -pass in on egress proto tcp from any to any port 443 +pass in on any proto { tcp, udp } from any to any port 53 +pass out on any proto { tcp, udp } from any to any port 53 -pass out on egress from any to any +pass in on any proto tcp from any to any port {80 443} +pass out on any proto tcp from any to any port {80 443} ``` ## `httpd.conf` @@ -60,20 +63,8 @@ server "httpd.rocks" { request strip 2 } } - -server "www.httpd.rocks" { - listen on * port 80 - block return 301 "http://httpd.rocks$REQUEST_URI" -} ``` -We need to create proper directories for `acme-client` (our next steps) and set their permissions: - -``` -doas mkdir -p -m 750 /etc/ssl/private -doas mkdir -p -m 755 /var/www/acme -``` - Then get `httpd` up and running: ``` @@ -86,11 +77,20 @@ doas rcctl start httpd httpd_flags="" ``` -If everything was setup properly, you should be able to visit the HTTP-only version of your website online. +If everything was setup properly, you should be able to visit the HTTP-only version of your website online. The only problem is HTTPS isn't setup... +..yet! + ## `acme-client.conf` +Before anything else, we need to create proper directories for `acme-client` (our next steps) and set their permissions: + +``` +doas mkdir -p -m 750 /etc/ssl/private +doas mkdir -p -m 755 /var/www/acme +``` + Create the `/etc/acme-client.conf` file and include the following: ``` @@ -98,84 +98,143 @@ authority letsencrypt { api url "https://acme-v02.api.letsencrypt.org/directory" account key "/etc/acme/letsencrypt-privkey.pem" } + domain httpd.rocks { - alternative names { www.httpd.rocks } domain key "/etc/ssl/private/httpd.rocks.key" - domain certificate "/etc/ssl/private/httpd.rocks.crt" + domain full chain certificate "/etc/ssl/httpd.rocks.fullchain.pem" sign with letsencrypt } ``` -**Note**: The `alternative names { www.httpd.rocks }` will be needed later to forward all `www.` requests to standard `https://`. - Now we can run the core `acme-client` command to generate our certificates: ``` doas acme-client -v httpd.rocks ``` -If everything goes smoothly, your new certificates should be generated and issued. The next thing you will want to do is automatically check for expired certs. Setup the following `cronjob` by running `crontab -e` and entering in: +If everything goes smoothly, your new certificates should be generated and issued. The next thing you will want to do is automatically check for expired certs. +First create a separate script (this will be helpful if you plan to host multiple sites on a single server). Name it something like `renew_certs.sh` and save it under a local directory (ie. `/home/username/scripts`): + ``` -0 0 * * * acme-client httpd.rocks && rcctl reload httpd +#!/bin/sh +doas acme-client httpd.rocks || exit 1 +doas acme-client example.com || exit 1 +doas acme-client anotherdomain.com || exit 1 +doas rcctl reload httpd ``` +> For reference I have included multiple domains if you decide to host several websites through one server. Remove these if you only plan to host a single domain. + +Set executable permissions: + +``` +doas chmod +x /path/to/renew_certs.sh +``` + +Then setup the following `cronjob` by running `crontab -e` and entering in: + +``` +0 0 * * * /path/to/renew_certs.sh +``` + This will check if you need to renew certificates every day at midnight (server time). -## `httpd.conf` (again) +## `haproxy.cfg` -Now we alter our existing `/etc/httpd.conf` to properly setup HTTPS and forward all HTTP traffic: +Many people tend to reach for `relayd` in OpenBSD when deciding to setup proxies or include security headers for their sites. Maybe I'm just too dull, but I always struggle to get things running smoothly with it. +That's why I opt for using HAProxy. + +First, install the package: + ``` -server "httpd.rocks" { - listen on * tls port 443 - root "/htdocs/httpd.rocks" - tls { - certificate "/etc/ssl/private/httpd.rocks.crt" - key "/etc/ssl/private/httpd.rocks.key" - } -} +doas pkg_add haproxy +``` -server "www.httpd.rocks" { - listen on * tls port 443 - tls { - certificate "/etc/ssl/private/httpd.rocks.crt" - key "/etc/ssl/private/httpd.rocks.key" - } - block return 301 "https://httpd.rocks$REQUEST_URI" -} +Now configure the core `/etc/haproxy/haproxy.cfg` (take note of the extension! OpenBSD uses `cfg` rather than the standard `conf` for HAProxy) and add the following to the existing file: -server "httpd.rocks" { - listen on * port 80 - location "/.well-known/acme-challenge/*" { - root "/acme" - request strip 2 - } - block return 301 "https://httpd.rocks$REQUEST_URI" -} +``` +defaults + log global + mode http + option httplog + option dontlognull + timeout connect 5000ms + timeout client 50000ms + timeout server 50000ms -server "www.httpd.rocks" { - listen on * port 80 - block return 301 "http://httpd.rocks$REQUEST_URI" -} +frontend http_in + bind *:80 + # Redirect all www requests to non-www + http-request redirect prefix https://%[hdr(host),regsub(^www\.,)] code 301 if { hdr_beg(host) -i www } + redirect scheme https if !{ ssl_fc } + default_backend main_backend + +frontend https_in + bind *:443 ssl crt /etc/ssl/certs/ + # Redirect all www requests to non-www + http-request redirect prefix https://%[hdr(host),regsub(^www\.,)] code 301 if { hdr_beg(host) -i www } + default_backend main_backend + +# Backend to httpd with security headers, no TLS +backend main_backend + http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains" + http-response set-header X-Content-Type-Options "nosniff" + http-response set-header X-Frame-Options "DENY" + http-response set-header X-XSS-Protection "1; mode=block" + http-response set-header Content-Security-Policy "script-src 'self'" + http-response set-header Referrer-Policy "no-referrer" + http-response set-header Permissions-Policy "microphone=()" + server local_httpd 127.0.0.1:8080 ``` -Then test and restart `httpd`: +The `haproxy.cfg` in a nutshell: +- `frontend http_in`: + - redirects all `www` requests to `non-www` + - redirects all HTTP requests to HTTPS + - uses `main_backend` to set security headers + +- `frontend https_in`: + - redirects all `www` requests to `non-www` + - uses `main_backend` to set security headers + +- `backend main_backend`: + - sets sane security header defaults (change these as you please) + +**Important**: Take note of the line: + ``` -httpd -n -doas rcctl restart httpd +bind *:443 ssl crt /etc/ssl/certs/ ``` -Be sure to also have `httpd` start on boot (in case of accidental server restarts!): +This tells HAProxy to dynamically scan a directory containing our certificates. This is handy if you decide to host multiple sites on a single server. Otherwise, you would have to edit and reload your HAProxy config everytime you setup a new website. +So we need to create our `certs` directory: + ``` -doas rcctl enable httpd +doas mkdir /etc/ssl/certs +doas chmod 644 /etc/ssl/certs ``` +And then create `/etc/ssl/certs/httpd.rocks.pem` by combining your certificate and private key if not done: + +``` +cat /etc/ssl/httpd.rocks.crt /etc/ssl/private/httpd.rocks.key > /etc/ssl/certs/httpd.rocks.pem +``` + +Once that's complete, test that everything is working, and if so, enable and start HAProxy: + +``` +doas haproxy -c -f /etc/haproxy/haproxy.cfg +doas rcctl enable haproxy +doas rcctl start haproxy +``` + ## It's Alive! -Now check out your website! Everything should work as intended. You should have valid TLS and your standard HTTP request should forward to HTTPS. +Now check out your website! Everything should work as intended. You should have valid TLS, your standard HTTP request should forward to HTTPS, `www` requests should forward to `non-www`, and your security headers should [score an A+](https://securityheaders.com/?q=https%3A%2F%2Fhttpd.rocks%2F&followRedirects=on). That's it! blob - 90dd4eb49ec91615229acfabacd5a46e220645c9 blob + 6695cce0e3a8f38262b55914f2944e566243e17c --- style.css +++ style.css @@ -29,9 +29,21 @@ h2 { margin: 2rem 0 0; } +blockquote { + background: rgba(0,0,0,0.04); + border-left: 4px solid; + margin: 2rem 0; + padding: 6px 10px; +} +blockquote p { + margin: 0; +} + pre { border: 1px solid; + overflow: scroll; padding: 10px; + max-width:100%; } hr {