Commit Diff


commit - 6f5121b2fe9bd31eb2b4affbaca4102476941251
commit + 6e18b1ee81a6023d30bc59fa19e2aeba2202028a
blob - de4723502052d2d2ada53fd2fe14582901ea7ed0
blob + a57283d627820e6bdd9f9ec44d3208d95e73d0f9
--- _header.html
+++ _header.html
@@ -3,7 +3,7 @@
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>httpd.rocks</title>
+    <title>httpd.rocks - Setup an HTTPS-enabled webserver with httpd on OpenBSD</title>
     <link rel="stylesheet" href="./style.css">
 </head>
 <body>
blob - 9479a182ccc48373cc830413a3dd6560e0087018
blob + ff08ab3e43b81d011ab7d64c630647e678169258
--- index.html
+++ index.html
@@ -3,14 +3,17 @@
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>httpd.rocks</title>
+    <title>httpd.rocks - Setup an HTTPS-enabled webserver with httpd on OpenBSD</title>
     <link rel="stylesheet" href="./style.css">
 </head>
 <body>
     <p><img src="openbsd-logo.svg" alt="OpenBSD mascot" /></p>
 <h1 id="httpd-rocks">httpd rocks</h1>
-<p>A barebones guide to setup an HTTPS-enabled web server with <code>httpd</code> on <a href="https://openbsd.org">OpenBSD</a></p>
-<p>Help improve this website: <a href="">git.btxx.org&#47;httpd.rocks</a></p>
+<p>Setup an <a href="https://www.ssllabs.com/ssltest/analyze.html?d=httpd.rocks">HTTPS-enabled web server</a> with <code>httpd</code> on <a href="https://openbsd.org">OpenBSD</a></p>
+<p>Includes <a href="https://securityheaders.com/?q=https%3A%2F%2Fhttpd.rocks%2F&amp;followRedirects=on">A+ security report</a> configuration with <code>haproxy</code>.</p>
+<blockquote>
+<p>I&#8217;m far from an expert! Please <a href="https://git.btxx.org/httpd.rocks">help improve this project</a></p>
+</blockquote>
 <hr/>
 <h2 id="before-you-begin">Before You Begin&#8230;</h2>
 <p>This guide assumes you have already setup OpenBSD on your desired server of choice. Most commands will need to run via <code>doas</code>, since you should be logged in as a created user - <strong>never</strong> <code>root</code> directly.</p>
@@ -21,10 +24,11 @@
 </code></pre>
 <h2 id="section"><code>pf.conf</code></h2>
 <p>Before doing anything else, you need to make sure your <code>&#47;etc&#47;pf.conf</code> is allowing traffic on ports <code>80</code> and <code>443</code>. Make sure you include the following:</p>
-<pre><code>pass in on egress proto tcp from any to any port 80
-pass in on egress proto tcp from any to any port 443
+<pre><code>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}
 </code></pre>
 <h2 id="section-1"><code>httpd.conf</code></h2>
 <p>Make initial website folder and files:</p>
@@ -44,87 +48,129 @@ doas chown -R www:www &#47;var&#47;www&#47;htdocs&#47;
         request strip 2
     }
 }
-
-server "www.httpd.rocks" {
-    listen on * port 80
-    block return 301 "http:&#47;&#47;httpd.rocks$REQUEST_URI"
-}
 </code></pre>
-<p>We need to create proper directories for <code>acme-client</code> (our next steps) and set their permissions:</p>
-<pre><code>doas mkdir -p -m 750 &#47;etc&#47;ssl&#47;private
-doas mkdir -p -m 755 &#47;var&#47;www&#47;acme
-</code></pre>
 <p>Then get <code>httpd</code> up and running:</p>
 <pre><code>doas rcctl start httpd
 </code></pre>
 <p><strong>Note</strong>: If you encounter runtime errors with <code>httpd</code>, you might be required to add the following to your <code>&#47;etc&#47;rc.conf.local</code> file:</p>
 <pre><code>httpd_flags=""
 </code></pre>
-<p>If everything was setup properly, you should be able to visit the HTTP-only version of your website online.</p>
+<p>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&#8217;t setup&#8230;</p>
+<p>..yet!</p>
 <h2 id="section-2"><code>acme-client.conf</code></h2>
+<p>Before anything else, we need to create proper directories for <code>acme-client</code> (our next steps) and set their permissions:</p>
+<pre><code>doas mkdir -p -m 750 &#47;etc&#47;ssl&#47;private
+doas mkdir -p -m 755 &#47;var&#47;www&#47;acme
+</code></pre>
 <p>Create the <code>&#47;etc&#47;acme-client.conf</code> file and include the following:</p>
 <pre><code>authority letsencrypt {
     api url "https:&#47;&#47;acme-v02.api.letsencrypt.org&#47;directory"
     account key "&#47;etc&#47;acme&#47;letsencrypt-privkey.pem"
 }
+
 domain httpd.rocks {
-    alternative names { www.httpd.rocks }
     domain key "&#47;etc&#47;ssl&#47;private&#47;httpd.rocks.key"
-    domain certificate "&#47;etc&#47;ssl&#47;private&#47;httpd.rocks.crt"
+    domain full chain certificate "&#47;etc&#47;ssl&#47;httpd.rocks.fullchain.pem"
     sign with letsencrypt
 }
 </code></pre>
-<p><strong>Note</strong>: The <code>alternative names { www.httpd.rocks }</code> will be needed later to forward all <code>www.</code> requests to standard <code>https:&#47;&#47;</code>.</p>
 <p>Now we can run the core <code>acme-client</code> command to generate our certificates:</p>
 <pre><code>doas acme-client -v httpd.rocks
 </code></pre>
-<p>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 <code>cronjob</code> by running <code>crontab -e</code> and entering in:</p>
-<pre><code>0 0 * * * acme-client httpd.rocks &#38;&#38; rcctl reload httpd
+<p>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. </p>
+<p>First create a separate script (this will be helpful if you plan to host multiple sites on a single server). Name it something like <code>renew_certs.sh</code> and save it under a local directory (ie. <code>&#47;home&#47;username&#47;scripts</code>):</p>
+<pre><code>#!&#47;bin&#47;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
 </code></pre>
+<blockquote>
+<p>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.</p>
+</blockquote>
+<p>Set executable permissions:</p>
+<pre><code>doas chmod +x &#47;path&#47;to&#47;renew_certs.sh
+</code></pre>
+<p>Then setup the following <code>cronjob</code> by running <code>crontab -e</code> and entering in:</p>
+<pre><code>0 0 * * * &#47;path&#47;to&#47;renew_certs.sh
+</code></pre>
 <p>This will check if you need to renew certificates every day at midnight (server time).</p>
-<h2 id="again"><code>httpd.conf</code> (again)</h2>
-<p>Now we alter our existing <code>&#47;etc&#47;httpd.conf</code> to properly setup HTTPS and forward all HTTP traffic:</p>
-<pre><code>server "httpd.rocks" {
-  listen on * tls port 443
-  root "&#47;htdocs&#47;httpd.rocks"
-  tls {
-    certificate "&#47;etc&#47;ssl&#47;private&#47;httpd.rocks.crt"
-    key "&#47;etc&#47;ssl&#47;private&#47;httpd.rocks.key"
-  }
-}
+<h2 id="section-3"><code>haproxy.cfg</code></h2>
+<p>Many people tend to reach for <code>relayd</code> in OpenBSD when deciding to setup proxies or include security headers for their sites. Maybe I&#8217;m just too dull, but I always struggle to get things running smoothly with it.</p>
+<p>That&#8217;s why I opt for using HAProxy.</p>
+<p>First, install the package:</p>
+<pre><code>doas pkg_add haproxy
+</code></pre>
+<p>Now configure the core <code>&#47;etc&#47;haproxy&#47;haproxy.cfg</code> (take note of the extension! OpenBSD uses <code>cfg</code> rather than the standard <code>conf</code> for HAProxy) and add the following to the existing file:</p>
+<pre><code>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 "&#47;etc&#47;ssl&#47;private&#47;httpd.rocks.crt"
-    key "&#47;etc&#47;ssl&#47;private&#47;httpd.rocks.key"
-  }
-  block return 301 "https:&#47;&#47;httpd.rocks$REQUEST_URI"
-}
+frontend http_in
+    bind *:80
+    # Redirect all www requests to non-www
+    http-request redirect prefix https:&#47;&#47;%[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 "&#47;.well-known&#47;acme-challenge&#47;*" {
-    root "&#47;acme"
-    request strip 2
-  }
-  block return 301 "https:&#47;&#47;httpd.rocks$REQUEST_URI"
-}
+frontend https_in
+    bind *:443 ssl crt &#47;etc&#47;ssl&#47;certs&#47;
+    # Redirect all www requests to non-www
+    http-request redirect prefix https:&#47;&#47;%[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:&#47;&#47;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 &#39;self&#39;"
+    http-response set-header Referrer-Policy "no-referrer"
+    http-response set-header Permissions-Policy "microphone=()"
+    server local_httpd 127.0.0.1:8080
 </code></pre>
-<p>Then test and restart <code>httpd</code>:</p>
-<pre><code>httpd -n
-doas rcctl restart httpd
+<p>The <code>haproxy.cfg</code> in a nutshell:</p>
+<ul>
+<li><code>frontend http_in</code>: 
+<ul>
+<li>redirects all <code>www</code> requests to <code>non-www</code></li>
+<li>redirects all HTTP requests to HTTPS</li>
+<li>uses <code>main_backend</code> to set security headers</li>
+</ul></li>
+<li><code>frontend https_in</code>:
+<ul>
+<li>redirects all <code>www</code> requests to <code>non-www</code></li>
+<li>uses <code>main_backend</code> to set security headers</li>
+</ul></li>
+<li><code>backend main_backend</code>:
+<ul>
+<li>sets sane security header defaults (change these as you please)</li>
+</ul></li>
+</ul>
+<p><strong>Important</strong>: Take note of the line:</p>
+<pre><code>bind *:443 ssl crt &#47;etc&#47;ssl&#47;certs&#47;
 </code></pre>
-<p>Be sure to also have <code>httpd</code> start on boot (in case of accidental server restarts!):</p>
-<pre><code>doas rcctl enable httpd
+<p>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.</p>
+<p>So we need to create our <code>certs</code> directory:</p>
+<pre><code>doas mkdir &#47;etc&#47;ssl&#47;certs
+doas chmod 644 &#47;etc&#47;ssl&#47;certs
 </code></pre>
+<p>And then create <code>&#47;etc&#47;ssl&#47;certs&#47;httpd.rocks.pem</code> by combining your certificate and private key if not done:</p>
+<pre><code>cat &#47;etc&#47;ssl&#47;httpd.rocks.crt &#47;etc&#47;ssl&#47;private&#47;httpd.rocks.key &#62; &#47;etc&#47;ssl&#47;certs&#47;httpd.rocks.pem
+</code></pre>
+<p>Once that&#8217;s complete, test that everything is working, and if so, enable and start HAProxy:</p>
+<pre><code>doas haproxy -c -f &#47;etc&#47;haproxy&#47;haproxy.cfg
+doas rcctl enable haproxy
+doas rcctl start haproxy
+</code></pre>
 <h2 id="its-alive">It&#8217;s Alive!</h2>
-<p>Now check out your website! Everything should work as intended. You should have valid TLS and your standard HTTP request should forward to HTTPS.</p>
+<p>Now check out your website! Everything should work as intended. You should have valid TLS, your standard HTTP request should forward to HTTPS, <code>www</code> requests should forward to <code>non-www</code>, and your security headers should <a href="https://securityheaders.com/?q=https%3A%2F%2Fhttpd.rocks%2F&amp;followRedirects=on">score an A+</a>.</p>
 <p>That&#8217;s it!</p>
 <hr/>
 <h2 id="references">References</h2>
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 {