Skip to content

Instantly share code, notes, and snippets.

@my-very-own-account
Last active April 19, 2021 11:25
Show Gist options
  • Save my-very-own-account/dff1cafaca9959cf0f86e1d194ba24bb to your computer and use it in GitHub Desktop.
Save my-very-own-account/dff1cafaca9959cf0f86e1d194ba24bb to your computer and use it in GitHub Desktop.

relayd (OpenBSD 6.8), Rails 6 and Let's Encrypt

CURRENT RESULT

When visiting http://myapp1.com we're redirected to https://www.myapp1.com/ (as expected), but with Secure Connection Failed. An error occurred during a connection to www.myapp1.com. PR_CONNECT_RESET_ERROR. PF is disabled. acme-client -v myapp1.com runs successfully. httpd is set up only to deal with ACME requests. nsd runs fine, and permissions for the keys have been set to chmod 750 /etc/ssl/private && chown apps:wheel /etc/ssl/private/*.key /etc/ssl/*.crt. Below you'll find:

  • /etc/relayd.conf
  • /etc/acme-client.conf
  • /etc/httpd.conf
  • /home/myapp/config/puma.rb
  • rcctl -d start myapp1
  • /var/nsd/zones/master/myapp1.com
  • /etc/rc.d/myapp1
  • /etc/rc.d/__helper
* Rebuilt URL to: https://myapp1.com/
*   Trying [IP REDACTED]...
* TCP_NODELAY set
* Connected to myapp.com ([IP REDACTED]) port 443 (#0)
* schannel: SSL/TLS connection with myapp.com port 443 (step 1/3)
* schannel: checking server certificate revocation
* schannel: sending initial handshake data: sending 177 bytes...
* schannel: sent initial handshake data: sent 177 bytes
* schannel: SSL/TLS connection with myapp.com port 443 (step 2/3)
* schannel: failed to receive handshake, need more data
* schannel: SSL/TLS connection with myapp.com port 443 (step 2/3)
* schannel: failed to receive handshake, SSL/TLS connection failed
* Closing connection 0
* schannel: shutting down SSL/TLS connection with myapp.com port 443
* Send failure: Connection was reset
* schannel: failed to send close msg: Failed sending data to the peer (bytes written: -1)
* schannel: clear security context handle
curl: (35) schannel: failed to receive handshake, SSL/TLS connection failed

/etc/relayd.conf

ext_if="vio0"

table <acme_challenge> { $ext_if }
acme_challenge_port="28793"

table <forward_to_https> { $ext_if }
forward_to_https_port="43670"

table <myapp1> { $ext_if }
myapp_port="46142"

table <myapp2> { $ext_if }
myapp_port="46271"

http protocol "http" {
  pass request quick path "/.well-known/acme-challenge/*" forward to <acme_challenge>

  pass request header "Host" value "myapp1.com" forward to <myapp1>
  pass request header "Host" value "www.myapp1.com" forward to <myapp1>
  pass request header "Host" value "myapp2.com" forward to <myapp2>
  pass request header "Host" value "www.myapp2.com" forward to <myapp2>
}

http protocol "https" {
  pass request header "Host" value "myapp1.com" forward to <myapp1>
  pass request header "Host" value "www.myapp1.com" forward to <myapp1>
  pass request header "Host" value "myapp2.com" forward to <myapp2>
  pass request header "Host" value "www.myapp2.com" forward to <myapp2>
  tls keypair "myapp1"
  tls keypair "www.myapp1"
  tls keypair "myapp2"
  tls keypair "www.myapp2"

  # Preserve address headers
  # match header set "X-Client-IP" value "$REMOTE_ADDR:$REMOTE_PORT"
  # match header set "X-Forwarded-For" value "$REMOTE_ADDR"
  # match header set "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT"

  # Best practice security headers
  # https://securityheaders.com/
  # match response header remove "Server"
  # match response header set "X-Frame-Options" value "SAMEORIGIN"
  # match response header set "X-XSS-Protection" value "1; mode=block"
  # match response header set "Referrer-Policy" value "strict-origin"
  # match response header set "Feature-Policy" value "accelerometer 'none'; ambient-light-sensor 'none'; battery 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; usb 'none';"

  # Log extras
  # match header log "Host"
  # match header log "X-Forwarded-For"
  # match header log "User-Agent"
  # match header log "Referer"
  # match url log
}

relay "http" {
  listen on $ext_if port http

  protocol "http"

  forward to <acme_challenge> port $acme_challenge_port
  forward to <forward_to_https> port $forward_to_https_port
  forward to <myapp1> port $myapp_port1
  forward to <myapp2> port $myapp_port2
}

relay "https" {
  listen on $ext_if port https tls

  protocol "https"

  # Assumes that Rails has force HTTPS enabled
  # https://api.rubyonrails.org/classes/ActionDispatch/SSL.html
  forward to <myapp1> port $myapp_port1
  forward to <myapp2> port $myapp_port2
}

/etc/acme-client.conf

authority letsencrypt {
  api url "https://acme-v02.api.letsencrypt.org/directory"
  account key "/etc/ssl/private/letsencrypt.key"
}

domain myapp.com {
  alternative names { www.myapp.com }
  domain key "/etc/ssl/private/myapp.key"
  domain full chain certificate "/etc/ssl/myapp.crt"
  sign with letsencrypt
}

/etc/httpd.conf

ext_if="vio0"

acme_challenge_port="28793"

# HTTP-01 challenge for Let's Encrypt
# https://letsencrypt.org/docs/challenge-types/
server "*" {
  listen on $ext_if port $acme_challenge_port
  location "/.well-known/acme-challenge/*" {
    root "/acme"
    request strip 2
  }
}

/home/myapp/config/puma.rb

#!/usr/bin/env puma

app = "myapp"
port = "46142"

ssl_bind "[IP REDACTED]", "#{ port }", {
  key: "/etc/ssl/private/#{ app }.key",
  cert: "/etc/ssl/#{ app }.crt",
  no_tlsv1: true,
  no_tlsv1_1: true
}

pidfile "tmp/puma.pid"
state_path "tmp/puma.state"

stdout_redirect "log/puma_access.log", "log/puma_errors.log"

environment "production"

rcctl -d start myapp

Puma starting in single mode...
* Puma version: 5.2.2 (ruby 2.7.1-p83) ("Fettisdagsbulle")
*  Min threads: 0
*  Max threads: 5
*  Environment: production
*          PID: 69861
Puma is not running
doing rc_check
* Listening on ssl://<public IP>:46595?cert=/etc/ssl/myapp.crt&key=/etc/ssl/private/myapp.key&verify_mode=none&no_tlsv1_1=true
Use Ctrl-C to stop
Puma is started
Alarm clock
doing _rc_write_runfile
(ok)

/var/nsd/zones/master/myapp.com

$ORIGIN myapp.com.
$TTL 86400

@ IN SOA ns.some.net. admin.other.net. (
  2021041801 1h 15m 1w 3m
)

@ NS ns.some.net.
@ NS ns.other.net.

@ A [IP REDACTED]

www IN CNAME myapp.com.

; https://letsencrypt.org/docs/caa/
myapp.com. 300 IN CAA 0 issue "letsencrypt.org"

/etc/rc.d/myapp

#!/bin/ksh

app="myapp"

# Get full path to helper
script_name="$0"
script_full_path=$(dirname "$0")

daemon="$script_full_path/__rails_helper"

rc_bg=YES

. /etc/rc.d/rc.subr

rc_start() {
  ${rcexec} "${daemon} start ${app}"
}

rc_check() {
  ${rcexec} "${daemon} status ${app}"
}

rc_restart() {
  ${rcexec} "${daemon} phased-restart ${app}"
}

rc_stop() {
  ${rcexec} "${daemon} stop ${app}"
}

rc_cmd $1

/etc/rc.d/__helper

#!/bin/ksh

# Run as regular user
# Extend rc.subr(8) to support Puma syntax
doas -u apps sh -c "cd /home/apps/$2 && export GEM_HOME=/home/apps/.gem && bundle exec pumactl --config-file /home/apps/$2/config/puma.rb $1" --
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment