commit f436a7665bd7e1aded037f8ec6bb97c6122abd97 from: Isaac Meerleo date: Tue Mar 24 01:44:13 2026 UTC Add wg_gen: generate WireGuard configs for a /24 subnet Generate a complete WireGuard deployment from a single command: one server hostname.if and all 253 client configs (.conf and OpenBSD hostname.wg) for a /24 subnet. Public key derivation uses openssl(1) X25519 DER construction (RFC 8410) instead of wireguard-tools or temporary interfaces. commit - d1172766f4e19c7760b5a64856b4a78bda069262 commit + f436a7665bd7e1aded037f8ec6bb97c6122abd97 blob - /dev/null blob + a971eaef403f229b6cc8e9cca377c46160b24154 (mode 755) --- /dev/null +++ bin/wg_gen @@ -0,0 +1,192 @@ +#!/bin/sh -e +# +# Generate WireGuard server and client configs for an entire /24 subnet. +# Server gets .1, clients get .2 through .254. +# +# Usage: wg_gen [-d dns] [-o dir] [-i wgN] wg_server_ip port endpoint +# + +# Show usage and exit. +usage() { + echo "usage: ${0##*/} [-d dns] [-o dir] [-i wgN] wg_server_ip port endpoint" >&2 + exit 1 +} + +# Derive X25519 public key from a base64-encoded private key using only +# openssl(1). Constructs a PKCS#8 DER-encoded X25519 private key per +# RFC 8410 (OID 1.3.101.110) and extracts the raw 32-byte public key +# from the SubjectPublicKeyInfo DER output. +# +# Reference: RFC 8410, Section 7 — "Algorithm Identifiers for Ed25519, +# Ed448, X25519, and X448 for Use in the Internet X.509 Public Key +# Infrastructure" (https://www.rfc-editor.org/rfc/rfc8410) +# Prior art: +# https://gist.github.com/Aleksanaa/9886c9d7d50f1c815400657578ee9a76 +# https://gist.github.com/yrpeng/0b51f6f91931b70f6523db9c2d3ba835 +get_pubkey() { + { + printf '\x30\x2e\x02\x01\x00\x30\x05\x06\x03\x2b\x65\x6e\x04\x22\x04\x20' + echo "$1" | openssl enc -base64 -d + } | openssl pkey -inform DER -pubout -outform DER 2>/dev/null \ + | tail -c 32 \ + | openssl enc -base64 +} + +# Write a standard WireGuard .conf for one client. +# Arguments: name ip privkey server_pubkey psk allowed_ips endpoint port [dns] +write_client_conf() { + _name="$1" _ip="$2" _privkey="$3" + _srv_pubkey="$4" _psk="$5" _aips="$6" + _endpoint="$7" _port="$8" _dns="$9" + + cat <<-EOF > "${OUTDIR}/${_name}.conf" + [Interface] + # ${_name} private key + PrivateKey = ${_privkey} + Address = ${_ip}/32 + EOF + + if [ -n "$_dns" ]; then + echo "DNS = ${_dns}" >> "${OUTDIR}/${_name}.conf" + fi + + cat <<-EOF >> "${OUTDIR}/${_name}.conf" + + [Peer] + # ${_endpoint} public key + PublicKey = ${_srv_pubkey} + PresharedKey = ${_psk} + AllowedIPs = ${_aips} + Endpoint = ${_endpoint}:${_port} + PersistentKeepalive = 25 + EOF +} + +# Write an OpenBSD hostname.wg for one client. +# Arguments: name ip privkey server_pubkey psk allowed_ips endpoint port +write_client_hn() { + _name="$1" _ip="$2" _privkey="$3" + _srv_pubkey="$4" _psk="$5" _aips="$6" + _endpoint="$7" _port="$8" _dns="$9" + + cat < "${OUTDIR}/${_name}.hostname.wg" +# ${_name} +description "${_name}" +wgkey ${_privkey} +inet ${_ip} 255.255.255.0 +up + +# Peer ${_endpoint} +wgpeer ${_srv_pubkey} \\ + wgdescr "${_endpoint}" \\ + wgpsk ${_psk} \\ + wgendpoint ${_endpoint} ${_port} \\ + wgaip ${_aips} wgpka 25 +EOF +} + + +#### Argument parsing + +DNS="" +OUTDIR="./wg-configs" +IFNAME="wg0" + +while getopts "d:i:o:" _opt; do + case $_opt in + d) DNS="$OPTARG";; + i) IFNAME="$OPTARG";; + o) OUTDIR="$OPTARG";; + *) usage;; + esac +done +shift $((OPTIND - 1)) + +if [ $# -ne 3 ]; then + usage +fi + +SERVER_IP="$1" +PORT="$2" +ENDPOINT="$3" + + +#### Validation + +# Server IP must end in .1. +_base="${SERVER_IP%.*}" +_last="${SERVER_IP##*.}" +if [ "$_last" != "1" ]; then + echo "${0##*/}: server_ip must end in .1 (got ${SERVER_IP})" >&2 + exit 1 +fi + +# Derive the /24 subnet base for AllowedIPs. +SUBNET="${_base}.0/24" + +# Set AllowedIPs based on DNS flag. +if [ -n "$DNS" ]; then + ALLOWED_IPS="0.0.0.0/0" +else + ALLOWED_IPS="${SUBNET}" +fi + + +#### Setup + +mkdir -p "$OUTDIR" + +# Generate server keypair. +SERVER_PRIVKEY=$(openssl rand -base64 32) +SERVER_PUBKEY=$(get_pubkey "$SERVER_PRIVKEY") + +# Start building the server hostname.if config. +SERVER_HN="# ${IFNAME} +description \"${IFNAME}\" +wgkey ${SERVER_PRIVKEY} +wgport ${PORT} +inet ${SERVER_IP} 255.255.255.0 +up +" + + +#### Main loop — generate client configs and accumulate server peers. + +_i=2 +while [ $_i -le 254 ]; do + _client_name="client-${_i}" + _client_ip="${_base}.${_i}" + + # Generate client keypair and preshared key. + _client_privkey=$(openssl rand -base64 32) + _client_pubkey=$(get_pubkey "$_client_privkey") + _psk=$(openssl rand -base64 32) + + # Write client configs. + write_client_conf "$_client_name" "$_client_ip" "$_client_privkey" \ + "$SERVER_PUBKEY" "$_psk" "$ALLOWED_IPS" "$ENDPOINT" "$PORT" "$DNS" + + write_client_hn "$_client_name" "$_client_ip" "$_client_privkey" \ + "$SERVER_PUBKEY" "$_psk" "$ALLOWED_IPS" "$ENDPOINT" "$PORT" + + # Append peer block to server config. + SERVER_HN="${SERVER_HN} +# Peer ${_client_name} +wgpeer ${_client_pubkey} \\ + wgdescr \"${_client_name}\" \\ + wgpsk ${_psk} \\ + wgaip ${_client_ip}/32 +" + + _i=$((_i + 1)) +done + + +#### Write server config. + +echo "$SERVER_HN" > "${OUTDIR}/hostname.${IFNAME}" + + +#### Permissions — configs contain private keys. + +echo "Generated configs in ${OUTDIR}/ for ${_base}.0/24 (253 clients)"