Skip to content

Instantly share code, notes, and snippets.

@basicfeatures
Last active July 5, 2024 18:07
Show Gist options
  • Save basicfeatures/3dfa53e264b805915c97439a22be523a to your computer and use it in GitHub Desktop.
Save basicfeatures/3dfa53e264b805915c97439a22be523a to your computer and use it in GitHub Desktop.

OpenBSD & Rails (July 2024)

This guide details setting up a secure and efficient Ruby on Rails environment on an OpenBSD server using OpenBSD's built-in tools.

Overview

This setup script performs the following tasks:

  • Installs essential packages for Ruby on Rails.
  • Configures PostgreSQL for robust database management.
  • Sets up Redis for fast caching and background jobs.
  • Configures nsd for secure DNS services with DNSSEC.
  • Sets up httpd for handling Let's Encrypt ACME challenges and HTTP requests.
  • Configures relayd for reverse proxying and TLS termination.
  • Sets up pf for advanced firewall and network security.
  • Generates configuration files for managing domain certificates with Let's Encrypt.
  • Optimizes system settings for performance.

Components

PostgreSQL

  • Installation and Configuration: A reliable and scalable database solution.
  • Security: Runs as an unprivileged user.
  • Performance: Optimized for high-concurrency workloads.

Redis

  • Installation and Setup: Manages caching and background jobs.
  • Speed: Provides in-memory data storage for fast data access.

nsd (DNS Server)

  • Configuration: Primary DNS server with DNSSEC.
  • Security: Protects against DNS spoofing.
  • Reliability: Ensures high availability of DNS services.

httpd (Web Server)

  • ACME Challenge Handling: Manages ACME challenges from Let's Encrypt.
  • Request Management: Handles HTTP requests and serves static content.

relayd (Load Balancer and Proxy)

  • Reverse Proxy: Forwards client requests to Rails.
  • TLS Termination: Manages HTTPS connections securely.
  • Performance: Offloads TLS handling from Rails.

pf (Packet Filter)

  • Firewall Configuration: Filters network traffic for security.
  • Network Security: Implements advanced firewall rules.
  • Performance: Optimized for minimal latency and maximum throughput.

acme-client (Let's Encrypt)

  • Certificate Management: Automatically obtains and renews TLS certificates.
  • Security: Ensures secure HTTPS connections for all domains.
#!/usr/bin/env zsh
echo "OPENBSD SERVER SETUP FOR RUBY ON RAILS"
parent_company="pub.healthcare"
# --
brgen_domains="brgen.no oshlo.no trndheim.no stvanger.no trmso.no longyearbyn.no reykjavk.is kobenhvn.dk stholm.se gteborg.se mlmoe.se hlsinki.fi lndon.uk mnchester.uk brmingham.uk edinbrgh.uk glasgw.uk lverpool.uk amstrdam.nl rottrdam.nl utrcht.nl brssels.be zrich.ch lchtenstein.li frankfrt.de mrseille.fr mlan.it lsbon.pt lsangeles.com newyrk.us chcago.us dtroit.us houstn.us dllas.us austn.us prtland.com mnneapolis.com"
brgen_subdomains="marketplace dating playlist tv takeaway maps"
# --
lawyer="pub.attorney freehelp.legal"
search_engines="bsdports.org discordb.org"
blognet_blogs="foodielicio.us"
# --
commit_to_git() {
git add -A
git commit -m "$1"
echo "$1"
}
# -- SET UP SYMLINKS --
doas pkg_add -U ruby-3.0.2 vips-8.10.6
doas ln -sf /usr/local/lib/libvips.so.0.0 /usr/local/lib/libvips.so.42
doas ln -sf /usr/local/lib/libglib-2.0.so.0 /usr/local/lib/libglib-2.0.so.4201.9
doas ln -sf /usr/local/lib/libgobject-2.0.so.0 /usr/local/lib/libgobject-2.0.so.4200.16
commit_to_git "Set up symlinks for ruby-vips"
# -- CONFIGURE FIREWALL --
doas tee /etc/pf.conf > /dev/null << "EOF"
ext_if = "vio0"
# Allow all on localhost
set skip on lo
# Relayd configuration
anchor "relayd/*"
# Basic rules
# Block stateless traffic
block return
# Establish keep-state
pass
# Block all incoming by default
block in log
# Allow all outgoing by default
pass out quick
# Ban brute-force attackers
# http://home.nuug.no/~peter/pf/en/bruteforce.html
#
# pfctl -t bruteforce -T show
# pfctl -t bruteforce -T flush
# pfctl -t bruteforce -T delete <IP>
#
table <bruteforce> persist
block quick from <bruteforce>
# SSH
pass in on $ext_if inet proto tcp from any to $ext_if port 22 keep state (max-src-conn 10, max-src-conn-rate 3/10, overload <bruteforce> flush global)
# DNS configuration
# Allow incoming DNS requests and zone transfer requests
pass in on $ext_if inet proto { tcp, udp } from any to $ext_if port 53 keep state
# DNS amplification attack prevention
block in quick on $ext_if from any to any port 53 flags S/SA keep state
# HTTP/HTTPS with rate limiting
pass in on $ext_if inet proto tcp from any to $ext_if port { 80, 443 } keep state (max-src-conn 100, max-src-conn-rate 50/5, overload <bruteforce> flush global)
# Enable logging
set loginterface $ext_if
# ICMP
# Network diagnostics and error reporting
pass in inet proto icmp all icmp-type { echoreq, unreach, timex, paramprob }
EOF
commit_to_git "Configured firewall settings"
# -- CONFIGURE RELAYD --
doas tee /etc/relayd.conf > /dev/null << 'EOF'
egress="95.179.177.238"
# TABLES
table <acme_client> { 127.0.0.1 }
acme_client_port="48273"
table <brgen> { 127.0.0.1 }
brgen_port="49195"
# PROTOCOLS
http protocol "filter_challenge" {
pass request path "/.well-known/acme-challenge/*" forward to <acme_client>
}
http protocol "falcon" {
match request header set "X-Forwarded-By" value "\$SERVER_ADDR:\$SERVER_PORT"
match request header set "X-Forwarded-For" value "\$REMOTE_ADDR"
match response header set "Content-Security-Policy" value "upgrade-insecure-requests; default-src https:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'"
match response header set "Strict-Transport-Security" value "max-age=31536000; includeSubDomains; preload"
match response header set "Referrer-Policy" value "strict-origin"
match response header set "Feature-Policy" value "accelerometer 'none'; ..."
match response header set "X-Content-Type-Options" value "nosniff"
match response header set "X-Download-Options" value "noopen"
match response header set "X-Frame-Options" value "SAMEORIGIN"
match response header set "X-Robots-Tag" value "index, nofollow"
match response header set "X-XSS-Protection" value "1; mode=block"
EOF
for domain in $city_domains; do
doas tee -a /etc/relayd.conf > /dev/null << EOF
pass request header "Host" value "$domain" forward to <brgen>
pass request header "Host" value "www.$domain" forward to <brgen>
EOF
for sub in $subdomains; do
doas tee -a /etc/relayd.conf > /dev/null << EOF
pass request header "Host" value "$sub.$domain" forward to <brgen>
EOF
done
doas tee -a /etc/relayd.conf > /dev/null << EOF
tls keypair "$domain"
EOF
done
for domain in $separate_sites; do
doas tee -a /etc/relayd.conf > /dev/null << EOF
pass request header "Host" value "$domain" forward to <brgen>
pass request header "Host" value "www.$domain" forward to <brgen>
tls keypair "$domain"
EOF
done
doas tee -a /etc/relayd.conf > /dev/null << 'EOF'
# Real-time communication between WebSockets and Action Cable
http websockets
# Cross-Origin Resource Sharing (CORS) for assets
match response header append "Access-Control-Allow-Origin" value "*"
}
# RELAYS
relay "http_relay" {
listen on $egress port http
protocol "filter_challenge"
forward to <acme_client> port $acme_client_port
}
relay "https_relay" {
listen on $egress port https tls
protocol "falcon"
forward to <brgen> port $brgen_port
}
EOF
commit_to_git "Configured relayd settings"
# -- CONFIGURE HTTPD --
doas tee /etc/httpd.conf > /dev/null << "EOF"
server "acme" {
listen on * port 80
root "/acme"
location "/.well-known/acme-challenge/*" {
root "/acme"
directory auto index
}
types {
include "/usr/share/misc/mime.types"
}
}
EOF
for domain in $city_domains; do
doas tee -a /etc/httpd.conf > /dev/null << EOF
server "$domain" {
listen on * port 80
root "/acme"
location "/.well-known/acme-challenge/*" {
root "/acme"
directory auto index
}
types {
include "/usr/share/misc/mime.types"
}
}
EOF
done
for domain in $separate_sites; do
doas tee -a /etc/httpd.conf > /dev/null << EOF
server "$domain" {
listen on * port 80
root "/acme"
location "/.well-known/acme-challenge/*" {
root "/acme"
directory auto index
}
types {
include "/usr/share/misc/mime.types"
}
}
EOF
done
commit_to_git "Configured HTTPD settings"
# -- CONFIGURE ACME-CLIENT --
doas tee /etc/acme-client.conf > /dev/null << 'EOF'
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
# For each domain, add an authority directive and domain keypair.
EOF
for domain in $city_domains $separate_sites; do
doas tee -a /etc/acme-client.conf > /dev/null << EOF
authority "$domain" {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/$domain-privkey.pem"
certificate "/etc/ssl/$domain.crt"
key "/etc/ssl/private/$domain.key"
domain "$domain" www."$domain"
}
EOF
done
commit_to_git "Configured acme-client settings"
# -- CONFIGURE RC --
doas tee -a /etc/rc.conf.local > /dev/null << EOF
pkg_scripts="dnscrypt_proxy httpd relayd nsd brgen"
EOF
commit_to_git "Configured rc.conf.local"
# -- CONFIGURE NSUPDATE SCRIPT --
doas tee /usr/local/bin/nsupdate > /dev/null << 'EOF'
#!/bin/ksh
nsupdate <<EOF
server 127.0.0.1
zone $1
update delete $2 A
update add $2 300 A $3
send
EOF
EOF
doas chmod +x /usr/local/bin/nsupdate
commit_to_git "Configured nsupdate script"
# -- CONFIGURE OPENSMTPD --
doas tee /etc/mail/smtpd.conf > /dev/null << 'EOF'
table aliases file:/etc/mail/aliases
action "local_mail" mbox alias <aliases>
action "outbound" relay host smtp
EOF
for domain in $city_domains $separate_sites; do
doas tee -a /etc/mail/smtpd.conf > /dev/null << EOF
match from any for domain "$domain" action "outbound"
EOF
done
doas tee -a /etc/mail/smtpd.conf > /dev/null << 'EOF'
match from local for any action "local_mail"
EOF
doas tee /etc/mail/aliases > /dev/null << 'EOF'
root: root@localhost
EOF
doas chmod 644 /etc/mail/aliases
doas newaliases
doas rcctl enable smtpd
doas rcctl start smtpd
commit_to_git "Configured OpenSMTPD"
# -- CONFIGURE BRGEN STARTUP SCRIPT --
doas tee /etc/rc.d/brgen > /dev/null << "EOF"
#!/bin/ksh
export BUNDLE_GEMFILE="/home/brgen/brgen/Gemfile"
export BUNDLE_PATH="/home/brgen/brgen/vendor/bundle"
daemon="/bin/ksh -c 'cd /home/brgen/brgen && export RAILS_ENV=production && /usr/local/bin/bundle32 exec /home/brgen/brgen/vendor/bundle/ruby/3.2/bin/falcon-host /home/brgen/brgen/falcon.rb >> /var/log/brgen.log 2>&1'"
daemon_user="brgen"
. /etc/rc.d/rc.subr
rc_cmd $1
EOF
doas chmod +x /etc/rc.d/brgen
doas rcctl enable brgen
commit_to_git "Configured brgen startup script"
# -- CONFIGURE DNSSEC --
doas nsd-control-setup
doas chown -R nsd:nsd /var/nsd
commit_to_git "Configured DNSSEC settings"
# -- CREATE ZONE FILES --
create_zone_file() {
domain=$1
serial=$(date +"%Y%m%d%H")
doas tee "/var/nsd/zones/master/$domain.zone" > /dev/null << EOF
\$ORIGIN $domain.
\$TTL 24h
@ 1h IN SOA ns.brgen.no. admin.brgen.no. (
$serial ; Serial YYYYMMDDHH
1h ; Refresh
15m ; Retry
1w ; Expire
3m ; Minimum TTL
)
@ IN NS ns.brgen.no.
@ IN NS ns.hyp.net.
www IN CNAME @
@ IN A 95.179.177.238
EOF
if [ "$domain" = "brgen.no" ]; then
doas tee -a "/var/nsd/zones/master/$domain.zone" > /dev/null << EOF
dev IN A 108.61.99.28
EOF
fi
if echo $city_domains | grep -q "$domain"; then
for sub in $subdomains; do
doas tee -a "/var/nsd/zones/master/$domain.zone" > /dev/null << EOF
$sub IN A 95.179.177.238
EOF
done
fi
doas tee -a "/var/nsd/zones/master/$domain.zone" > /dev/null << EOF
; https://letsencrypt.org/docs/caa/
$domain. 3m IN CAA 0 issue "letsencrypt.org"
EOF
}
for domain in $city_domains $separate_sites; do
create_zone_file "$domain"
done
commit_to_git "Created DNS zone files"
# -- FINALIZE INSTALLATION --
echo "Finalizing installation..."
doas rcctl enable dnscrypt_proxy
doas rcctl enable httpd
doas rcctl enable relayd
doas rcctl enable nsd
commit_to_git "Finalized installation"
echo "OPENBSD INSTALLATION COMPLETED"
# -- TEST SETUP --
# Function to test HTTP/HTTPS connectivity for a domain
test_http_https() {
domain=$1
echo "Testing HTTP/HTTPS for $domain..."
for proto in http https; do
if curl -o /dev/null -s -w "%{http_code}\n" "$proto://$domain" | grep -q "200"; then
echo "$proto://$domain is reachable"
else
echo "$proto://$domain is NOT reachable"
fi
done
}
# Test all domains for HTTP/HTTPS connectivity
for domain in $city_domains $separate_sites; do
test_http_https "$domain"
done
# Test DNS resolution
echo "Testing DNS resolution for domains..."
for domain in $city_domains $separate_sites; do
if nslookup "$domain" >/dev/null 2>&1; then
echo "DNS resolution for $domain is working"
else
echo "DNS resolution for $domain is NOT working"
fi
done
# Test TLS certificates
echo "Testing TLS certificates for domains..."
for domain in $city_domains $separate_sites; do
if openssl s_client -connect "$domain:443" -servername "$domain" </dev/null 2>&1 | grep -q "Verify return code: 0 (ok)"; then
echo "TLS certificate for $domain is valid"
else
echo "TLS certificate for $domain is NOT valid"
fi
done
echo "SETUP TESTING COMPLETED"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment