# ___ ____ ____ ____
# / _ \ _ __ ___ _ __ | __ ) ___|| _ \
# | | | | '_ \ / _ \ '_ \| _ \___ \| | | |
# | |_| | |_) | __/ | | | |_) |__) | |_| |
# \___/| .__/ \___|_| |_|____/____/|____/
# |_|
# __ ___ _
# \ \ / (_)_ __ ___ __ _ _ _ __ _ _ __ __| |
# \ \ /\ / /| | '__/ _ \/ _` | | | |/ _` | '__/ _` |
# \ V V / | | | | __/ (_| | |_| | (_| | | | (_| |
# \_/\_/ |_|_| \___|\__, |\__,_|\__,_|_| \__,_|
# |___/
#
~~Here are the references you want for the moment.
This was originally taken from @Emdnaia’s OpenOCD, but there were several aspects of that implementation that were either unsuitable or deprecated, so it has been altered since.
This project gets you:
Dynamic IP Update Script (getpara.sh
): This script resolves the current IP address for a specified FQDN and updates the PF table with any changes.
PF Configuration (pf.conf
): Includes rules for blocking, allowing SSH from specific IPs, handling Wireguard traffic, and default deny policies.
Cron Jobs: Automates system updates, VPN renewals, and the dynamic IP update script. If on the go, reduce the cronjob size to refreshing every 2-5 minutes on getpara.sh
, like this you can unlock a laptop on a hotspot in a train. The laptop would only need a dynamic dns client unlocking the public IP, which is also set to a maximum number kicking off the older ones.
DNS Configuration: Setup guides for DoT and ODoH, including configuration changes for dnscrypt-proxy
.
Ad Blocker Script: Instructions and script for setting up ad blocking on OpenBSD using Unbound.
From experience, you should not attempt to run pkg_add -u
or `` as a cron task. This would be a newb mistake.
crontab -e
30 4 * * 3 /usr/local/getpara.sh 0 0 * * * /usr/local/getpara.sh 2 0 * * * /usr/sbin/rcctl restart unbound
### Firewall: OpenBSD PF rules for static & dynamic ips
- ~~You edit the pf rules on ` /etc/pf.conf ` and check via `pfctl -nf /etc/pf.conf` and load them via `pfctl -f /etc/pf.conf`~~
#
dynamic_hosts_file=”/usr/local/gotten-para” # Location for dynamic hosts wireguard_port=”51820” # Your WireGuard VPN port wireguard_net=”10.0.0.0/24” # Your WireGuard VPN network ssh_allowed_ips=”{6.6.6.6/32, 7.7.7.7/32}” # IPs allowed for SSH wireguard_iface=”wg0” # WireGuard interface identifier
block quick inet6
set skip on lo
block in on vio0 all
match out on egress inet from !(egress:network) to any nat-to (egress:0)
pass in on vio0 proto tcp from $ssh_allowed_ips to (vio0) port 22 keep state pass out quick on vio0 keep state
pass in on vio0 proto udp from
pass in on $wireguard_iface from $wireguard_net to any pass out on $wireguard_iface from any to $wireguard_net
block return in on ! lo0 proto tcp to port 6000:6010
block return out log proto {tcp udp} user _pbuild
### Skript to add the dynamic hosts
- Important: All your clients will need to get FQDNs, and you can do that by adding for example containers in your home, work etc, that use tools like `ddclient` + any DYNDNS-hoster.
- Next, *Ensure you replace `hoster1 + hoster2` with your actual domains to allow dynamic access from.*
- The script below will access the FQDN and add it to the firewall ruletable for access.
I've deployed the following script on `/usr/local/getpara.sh` it creates `temp_gotten_para` as well as `gotten-para` which contains the dynamic IPs to be added to the firewall for access.
```sh
#!/bin/sh
# FQDNs
FQDN1="myhost1.hoster1.org"
FQDN2="myhost2.hoster2.org"
# Variables
WORK_DIR="/usr/local"
TEMP_IP_FILE="$WORK_DIR/temp_gotten_para"
FINAL_IP_FILE="$WORK_DIR/gotten-para"
LOG_FILE="$WORK_DIR/firewall_update.log"
MAX_IP_COUNT=3 # Adjusted if needed to allow more IPs
IP_RETENTION_DAYS=7
# Setup and cleanup environment
if [ ! -f "$TEMP_IP_FILE" ]; then
echo "Creating $TEMP_IP_FILE as it does not exist."
touch "$TEMP_IP_FILE"
fi
# Function to log messages with timestamps
log() {
echo "$(date "+%Y-%m-%d %H:%M:%S") - $1" >> "$LOG_FILE"
}
# Function to resolve IP addresses and avoid duplicates
resolve_ip() {
local FQDN=$1
log "Resolving IP address for $FQDN"
# Resolve the current IP address of the FQDN
CURRENT_IP=$(dig +short $FQDN)
CURRENT_TIMESTAMP=$(date +%s)
# Exit if no IP is resolved
[ -z "$CURRENT_IP" ] && log "No IP address found for $FQDN" && return
# Check if the IP already exists in the TEMP_IP_FILE to avoid duplicates
if ! grep -q "$CURRENT_IP" "$TEMP_IP_FILE"; then
# Append current IP with timestamp to TEMP_IP_FILE for processing
echo "$CURRENT_TIMESTAMP $CURRENT_IP" >> "$TEMP_IP_FILE"
log "Added IP $CURRENT_IP to $TEMP_IP_FILE"
else
log "IP $CURRENT_IP already exists in $TEMP_IP_FILE, not adding again."
fi
}
# Ensure FINAL_IP_FILE exists
if [ ! -f "$FINAL_IP_FILE" ]; then
echo "Creating $FINAL_IP_FILE as it does not exist."
touch "$FINAL_IP_FILE"
fi
# Resolve IPs for both FQDNs
resolve_ip $FQDN1
resolve_ip $FQDN2
# Process TEMP_IP_FILE to ensure uniqueness, limit the number of IPs, and consider the retention period
log "Processing $TEMP_IP_FILE to update $FINAL_IP_FILE"
awk -v max_count=$MAX_IP_COUNT -v retention_days=$IP_RETENTION_DAYS -v current_time=$(date +%s) '{
timestamp = $1
ip = $2
if (!seen[ip]++ && (current_time - timestamp) <= (retention_days * 86400)) {
print ip
if (++count >= max_count) exit
}
}' "$TEMP_IP_FILE" | sort -u | tail -n $MAX_IP_COUNT > "$FINAL_IP_FILE"
# Cleanup old entries from TEMP_IP_FILE
log "Cleaning up old entries from $TEMP_IP_FILE"
awk -v current_time=$(date +%s) -v retention_days=$IP_RETENTION_DAYS '{
timestamp = $1
if ((current_time - timestamp) <= (retention_days * 86400)) {
print $0
}
}' "$TEMP_IP_FILE" > "${TEMP_IP_FILE}.tmp" && mv "${TEMP_IP_FILE}.tmp" "$TEMP_IP_FILE"
# Check if there are any changes
if [ "$(wc -l < "$FINAL_IP_FILE")" -eq 0 ]; then
log "No changes."
else
# Reload the PF table with the updated IP list
log "Reloading PF table 'dynamic_hosts' with updated IP list from $FINAL_IP_FILE"
pfctl -t dynamic_hosts -T replace -f "$FINAL_IP_FILE" && log "PF table 'dynamic_hosts' reloaded with updated IP list."
# Output the contents of the PF table
log "Contents of PF table 'dynamic_hosts':"
pfctl -t dynamic_hosts -T show >> "$LOG_FILE"
fi
# Log completion message
log "Firewall update complete."
pkg_add wireguard-tools
,your gateways wg0.conf
looks like this on /etc/wireguard/wg0.conf
and you can trigger restarts for the interface like wg-quick down wg0 && sleep 5 && wg-quick up wg0
.You generate client priv and pub keys via ` sh -c ‘umask 077; wg genkey | tee privatekey | wg pubkey > publickey’` |
Server side:
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = gateways-private-key
[Peer]
PublicKey = publickey-client1
AllowedIPs = 10.0.0.2/32
[Peer]
PublicKey = publickey-client-2
AllowedIPs = 10.0.0.3/32
Client side:
[Interface]
PrivateKey = client-private-key
#DNS=10.0.0.1
Address = 10.0.0.2/24
#ListenPort=
[Peer]
PublicKey = server-pubkey
AllowedIPs = 10.0.0.0/24
Endpoint = public-gateway-ip:51820
PersistentKeepalive = 15
/etc/dnscrypt-proxy.toml
The DNScrypt runs at 127.0.0.1@54, which Unbound at 127.0.0.1@53 will just forward requests to, while only accessible from Wireguard clients:
pkg_add dnscrypt-proxy
pkg_info -L dnscrypt-proxy
...
rcctl enable dnscrypt_proxy
rcctl restart unbound
rcctl restart dnscrypt_proxy
server_names = ['odoh-cloudflare']
odoh_servers = true
require_dnssec = true
require_nofilter = true # this parameter depends on your relay selected next
cache = false
[sources.odoh-servers]
urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-servers.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh-servers.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/odoh-servers.md']
cache_file = 'odoh-servers.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 24
prefix = ''
[sources.odoh-relays]
urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh-relays.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/odoh-relays.md']
cache_file = 'odoh-relays.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 24
prefix = ''
routes = [
{ server_name='odoh-cloudflare', via=['odohrelay-your-pick1', 'oodohrelay-your-pick2'] },
{ server_name='odohrelay-koki-ams', via=['odohrelay-koki-ams', 'odohrelay-koki-bcn'] }
]
server:
interface: 0.0.0.0@53
do-ip6: no
access-control: 10.0.0.0/24 allow
access-control: 0.0.0.0/0 refuse
access-control: ::0/0 refuse
forward-zone:
name: "."
forward-addr: 127.0.0.1@54
#Include the blocklist for ads
include: "/home/lowpriv_user/blacklist.conf"
server: interface: 0.0.0.0@53 #interface: 127.0.0.1@5353 # listen on alternative port #interface: ::1 do-ip6: no
auto-trust-anchor-file: “/var/unbound/db/root.key” tls-cert-bundle: “/etc/ssl/cert.pem”
access-control: 10.0.0.0/24 allow access-control: 0.0.0.0/0 refuse access-control: ::0/0 refuse
forward-zone: name: “.” forward-tls-upstream: yes # use DNS-over-TLS forwarder
# Cloudflare pick # forward-addr: 1.1.1.1@53 # forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com
# forward-addr: 1.1.1.1@853#cloudflare-dns.com
# forward-addr: 2606:4700:4700::1001@853#cloudflare-dns.com
# forward-addr: 1.0.0.1@853#cloudflare-dns.com
# Quad9 pick
forward-addr: 2620:fe::fe@853#dns.quad9.net
forward-addr: 9.9.9.9@853#dns.quad9.net
forward-addr: 2620:fe::9@853#dns.quad9.net
forward-addr: 149.112.112.112@853#dns.quad9.net
# Include the blocklist for ads include: “/home/lowpriv_user/blacklist.conf”
- In case you still need to generate the key:
unbound-anchor -a /var/unbound/db/root.key
#### Optional Linux side for dynamic IP adding
- Here is the same `/usr/local/getpara.sh` but adjusted for Linux:
#!/bin/bash
FQDN1=”myhost1.myhoster1.org” FQDN2=”myhost2.myhoster2.org”
WORK_DIR=”/usr/local” TEMP_IP_FILE=”$WORK_DIR/temp_gotten_para” FINAL_IP_FILE=”$WORK_DIR/gotten-para” LOG_FILE=”$WORK_DIR/firewall_update.log”
MAX_IP_COUNT=3 # Adjust if needed to allow more IPs IP_RETENTION_DAYS=6 IP_SET_NAME=”dynamic_hosts” WG_ZONES=(“wireguard0” “public”) # List of WireGuard zones WG_PORT=”51820” # WireGuard port, adjust as needed
“$TEMP_IP_FILE” # Clear temporary file “$FINAL_IP_FILE” # Clear final IP file touch “$LOG_FILE” # Ensure log file exists exec 3>&1 1»“$LOG_FILE” 2>&1 # Redirect stdout and stderr to log file
log() { echo “$(date “+%Y-%m-%d %H:%M:%S”) - $1” }
execute_command() { echo “Executing command: ${}” >&3 # Log command to LOG_FILE “${@}” local status=$? if [ $status -ne 0 ]; then log “ERROR: Failed to execute: ${}” exit $status else log “SUCCESS: Executed: ${*}” fi }
ensure_files_exist() { touch “$1” 2>/dev/null || { log “Failed to touch $1. Check permissions.” exit 1 } }
ensure_files_exist “$TEMP_IP_FILE” ensure_files_exist “$FINAL_IP_FILE”
resolve_ip() { local FQDN=$1 local CURRENT_IP=$(dig +short $FQDN | grep -Eo ‘([0-9]{1,3}.){3}[0-9]{1,3}$’) local CURRENT_TIMESTAMP=$(date +%s)
if [ -z "$CURRENT_IP" ]; then
log "No valid IP address found for $FQDN"
return
fi
# Check if the same IP with the same timestamp already exists
if ! grep -q "$CURRENT_TIMESTAMP $CURRENT_IP" "$TEMP_IP_FILE"; then
echo "$CURRENT_TIMESTAMP $CURRENT_IP" >> "$TEMP_IP_FILE"
else
log "Duplicate IP entry for $CURRENT_IP at $CURRENT_TIMESTAMP skipped"
fi }
resolve_ip $FQDN1 resolve_ip $FQDN2
awk -v max_count=$MAX_IP_COUNT -v retention_days=$IP_RETENTION_DAYS -v current_time=$(date +%s) ‘{ ip = $2 timestamp = $1 if (!seen[ip]++ && (current_time - timestamp) <= (retention_days * 86400)) { print ip if (++count >= max_count) exit } }’ “$TEMP_IP_FILE” | sort -u | tail -n $MAX_IP_COUNT > “$FINAL_IP_FILE”
log “Current IP set entries before deletion:” firewall-cmd –ipset=$IP_SET_NAME –get-entries » “$LOG_FILE”
log “Deleting and recreating IP set” execute_command firewall-cmd –permanent –delete-ipset=$IP_SET_NAME 2>/dev/null execute_command firewall-cmd –permanent –new-ipset=$IP_SET_NAME –type=hash:ip
mapfile -t current_ips < <(firewall-cmd –ipset=$IP_SET_NAME –get-entries)
log “Adding IPs to the new IP set” for ip in $(cat “$FINAL_IP_FILE”); do if [[ ! “ ${current_ips[*]} “ =~ “ ${ip} “ ]]; then execute_command firewall-cmd –permanent –ipset=$IP_SET_NAME –add-entry=$ip fi done
for zone in “${WG_ZONES[@]}”; do log “Updating WireGuard rules for zone: $zone” if ! firewall-cmd –permanent –zone=”$zone” –query-rich-rule=”rule family=’ipv4’ source ipset=’$IP_SET_NAME’ port port=’$WG_PORT’ protocol=’udp’ accept”; then execute_command firewall-cmd –permanent –zone=”$zone” –add-rich-rule=”rule family=’ipv4’ source ipset=’$IP_SET_NAME’ port port=’$WG_PORT’ protocol=’udp’ accept” fi done
execute_command firewall-cmd –reload
log “Firewall and WireGuard rules updated with latest IPs.”
exec 1>&3 3>&-
echo “Firewall update complete. See $LOG_FILE for details.”
- In case we wanna confirm para entries:
firewall-cmd –ipset=dynamic_hosts –get-entries
- In case we wanna remove entries:
firewall-cmd –ipset=dynamic_hosts –get-entries | xargs -I{} firewall-cmd –permanent –ipset=dynamic_hosts –remove-entry={} && firewall-cmd –reload
- We need to also add this to the cronjob of the Linux server, for example daily:
0 0 * * * /usr/local/getpara.sh 0 0 * * * /usr/local/updatepara.sh
#### Optional Addblocking
- Reference on the top to the original one, I only care about OpenBSD and want this to be ran on a lowpriv user for it!
- Please add your own custom lists, I prefer to not show mine!
- You could save this at ` /home/lowpriv_user/blocklister.sh` with an own account on a user named `lowpriv_user`
- Get the lowpriv_user to run this daily, unbound has an `include` for it.
#!/bin/sh
TYPE=always_nxdomain TEMP=”/home/lowpriv_user/unbound_temp” ECHO=1 FILE=”/home/lowpriv_user/blacklist.conf” TEMP_FILE=”/lowpriv_user/temp_blacklist.conf”
[ “${ECHO}” != “0” ] && echo “mkdir: create ‘${TEMP}’ temp dir” mkdir -p ${TEMP}
[ “${ECHO}” != “0” ] && echo “fetch: ${TEMP}/lists-domains”
curl -s ‘https://your-1st-list.txt’
‘https://your-snd-list.txt’
‘https://your-trd-list.txt’
‘https://your-fourth-list.txt’
‘https://your-fifth-list.txt/ ‘
> “${TEMP}/lists-domains”
[ “${ECHO}” != “0” ] && echo “echo: add ‘${FILE}’ header” echo ‘server:’ > ${FILE}
[ “${ECHO}” != “0” ] && echo “echo: add ‘${FILE}’ rules”
cat “${TEMP}/lists-domains”
| grep -v ‘^(.)#’ -E
| grep -v ‘^#’
| grep -v ‘^$’
| grep -v ‘^!’
| awk ‘{print $1}’
| sed -e s/$’\r’//g
-e ‘s/^||//’
-e ‘s/\^$//’
-e ‘s/^|//’
-e ‘/^\/.\/$/d’
-e ‘/https?:\/\//d’
-e ‘s|.$||g’
-e ‘s|^.||g’
| grep -v -e ‘127.0.0.1’
-e ‘0.0.0.0’
-e ‘255.255.255.255’
-e ‘::’
-e ‘localhost’
-e ‘localhost.localdomain’
-e ‘ip6-localhost’
-e ‘ip6-loopback’
-e ‘ip6-localnet’
-e ‘ip6-mcastprefix’
-e ‘ip6-allnodes’
-e ‘ip6-allrouters’
-e ‘broadcasthost’
-e ‘ff02::’
| tr ‘[:upper:]’ ‘[:lower:]’
| tr -d ‘\r’
| tr -d ‘#’
| sort -u
| sed 1,2d
| while read I; do
echo “local-zone: "${I}" ${TYPE}”
done > ${TEMP_FILE} 2>”${TEMP}/debug-errors.txt”
echo ‘server:’ > ${FILE} sort ${TEMP_FILE} | uniq » ${FILE} rm -f ${TEMP_FILE}
[ “${ECHO}” != “0” ] && echo “Reminder: Manually check the unbound configuration with ‘unbound-checkconf ${FILE}’ and, if valid, restart the unbound service with appropriate permissions.”
[ “${ECHO}” != “0” ] && echo “rm: remove ‘${TEMP}’ temp dir BUT keep debug files” rm -rf ${TEMP}/lists-domains
unset FILE unset TYPE unset TEMP unset ECHO unset UNAME
- This should be now ran as the lowpriv_user in a crontab for getting the blocklists:
0 0 * * * /home/lowpriv_user/blocklister.sh ``` Much love if you read until here