#! /bin/bash # Executed on MASTER WG_PORT_MIN=32768 WG_PORT_MAX=65535 COLOR="always" source /sf/bin/funcs.sh source /sf/bin/funcs_redis.sh [[ ! -d "/config/db" ]] && ERREXIT 255 "Not found: /config/db" [[ ! -d "/config/db/wg" ]] && mkdir -p "/config/db/wg" [[ -z $SF_FQDN ]] && SF_FQDN="SF_FQDN-NOT-SET.hack.segfault.net" echo -en "Content-Type: text/plain\r\n\r\n" # BAIL # STDOUT goes to user. # STDERR is logged. BAIL() { echo -e "$1" [[ -n $2 ]] && echo -e >&2 "[${CB}${LID:-$REMOTE_ADDR}${CN}] ${CR}$2${CN}$3" exit 255 } Sanitize() { REQUEST_BODY="${REQUEST_BODY//[^[:alnum:]_+=/&]}" REQUEST_URI="${REQUEST_URI//[^[:alnum:]_+=\/]}" [[ "${#REQUEST_BODY}" -gt 512 ]] && BAIL "To long" "To Long" ": REQUEST_BODY(${#REQUEST_BODY})=${REQUEST_BODY:0:32}..." [[ "${#REQUEST_URI}" -gt 512 ]] && BAIL "To long!" "ATTACK" ": REQUEST_URI(${#REQUEST_URI})=${REQUEST_URI:0:32}..." } GetFormVars() { local ifs ifs=$IFS IFS=\& arr=(${REQUEST_BODY}) local i local str while [[ $i -lt ${#arr[@]} ]]; do str="${arr[$i]}" ((i++)) key=${str%%=*} [[ ${#key} -le 0 ]] && BAIL "Bad Request" "ERROR: " "Body contains bad variable: '$str'" val=${str#*=} [[ ${key} == "nocolor" ]] && unset COLOR [[ ${key} == "verbose" ]] && IS_VERBOSE=1 [[ ${key} == "port" ]] && { val=$((${val//[^0-9]})); [[ $val -ge 32768 && $val -le 65535 ]] && R_PORT="$val"; } [[ ${key} == "portsecret" ]] && R_PORTSECRET="${val//[^[:alnum:]]}" [[ ${key} == "peerprivate" ]] && R_WT_PRIVATE="${val//[^[:alnum:]+\/]}=" [[ ${key} == "private" ]] && R_WG_PRIVATE="${val//[^[:alnum:]+\/]}=" [[ ${key} == "name" ]] && { val="${val//[^[:alnum:]]}"; R_WT_NAME="${val:0:13}"; } done IFS=$ifs } # Load PID of WireGuard container load_config() { source /dev/shm/config.txt && return BAIL "${R}ERROR${N}: Not ready. SF is still booting up..." "Failed to load: " "/dev/shm/config.txt" } GenSecret() { local len len=16 [[ -n $1 ]] && len=$1 str=$(head -c $((len*2)) "/config/db/db-${LID}/wg/port" || BAIL "Failed to store WireGuard Port." echo "WG_PORT=${WG_PORT}" >"/config/db/wg/sec2port-${PORTSECRET}" # Link to LID: ln -sf "../db-${LID}/wg/port" "/config/db/wg/port-${WG_PORT}" } # [WT_NAME] write_wgfile() { local name name="$1" echo -n "\ WG_PRIVATE=\"${WG_PRIVATE}\" WG_PUBLIC=\"${WG_PUBLIC}\" WG_PORT=\"${WG_PORT}\" " >"/config/db/db-${LID}/wg/wg-${name}" || BAIL "Failed to store WG information." ln -sf "../db-${LID}/wg/wg-${name}" "/config/db/wg/wg-${name}" } # [WT_NAME] write_wtfile() { local name name="$1" echo -n "\ WT_PRIVATE=\"${WT_PRIVATE}\" WT_PUBLIC=\"${WT_PUBLIC}\" WG_PORT=\"${WG_PORT}\" " >"/config/db/db-${LID}/wg/wt-${name}" || BAIL "Failed to store WT information." ln -sf "../db-${LID}/wg/wt-${name}" "/config/db/wg/wt-${name}" } # Assign port to _this_ LID # [portsecret] cmd_net_init_move() { local sec local psec psec="$1" source "/config/db/wg/sec2port-${psec}" 2>/dev/null || BAIL "Portsecret ${psec} is not known." [[ ${ASSIGNED_LID} == $LID ]] && return # Already assigned to this LID ln -sf "../db-${LID}/wg/port" "/config/db/wg/port-${WG_PORT}" } # rpc/net/init # Assign/Retrieve WireGuard port for this LID cmd_net_init() { local n local err local arr arr=($(echo /config/db/db-${LID}/wg/wt-*)) [[ ${#arr[@]} -gt 10 ]] && BAIL "${R}ERROR${N}: To many Peers. Delete some first." "${R}PEERS-MAX${N} " "Limit: ${#arr[@]}" [[ -n ${R_PORTSECRET} ]] && cmd_net_init_move "${R_PORTSECRET}" if source "/config/db/db-${LID}/wg/port" 2>/dev/null; then # HERE: Port already assigned to this LID, [[ -n ${R_PORT} ]] && [[ $R_PORT -ne $WG_PORT ]] && echo -e "${Y}WARNING:${N} Ignoring request for Port ${R_PORT}. Port already set to ${WG_PORT}." else # HERE: No Port yet assigned to this LID. # Allow user to pick a port. if [[ -n ${R_PORT} ]]; then [[ -e "/config/db/wg/port-${R_PORT}" ]] && BAIL "\ Port ${R_PORT} is already in use. You can assign it to this server like so:\ ${C}curl rpc/net/init -d portsecret=${N}" WG_PORT=${R_PORT} else # Assign random port 32768...65535 n=0 while :; do WG_PORT="$((WG_PORT_MIN + RANDOM % (WG_PORT_MAX - WG_PORT_MIN + 1)))" [[ ! -e "/config/db/wg/port-${WG_PORT}" ]] && break ((n++)) [[ $n -gt 5 ]] && BAIL "Failed to find free WireGuard Port." done fi fi PORTSECRET="${WG_PORT}_$(GenSecret)" write_portfile [[ -e "/config/db/wg/wt-${R_WT_NAME}" ]] && { WT_NAME=${R_WT_NAME} echo -e "${R}ERROR${N}: '$R_WT_NAME' already exists. Delete it first with ${C}curl rpc/net/del -d name=${R_WT_NAME}${N}" load_wt "${R_WT_NAME}" load_wg "${R_WT_NAME}" cmd_net_init_print_info exit } [[ -n $R_WT_PRIVATE ]] && WT_PRIVATE=$R_WT_PRIVATE || WT_PRIVATE=$(wg genkey) WT_PUBLIC=$(echo "$WT_PRIVATE" | wg pubkey) [[ -n $R_WT_NAME ]] && WT_NAME="${R_WT_NAME}" || { val="${WT_PUBLIC//[^[:alnum:]]}"; WT_NAME="${val:0:4}"; } write_wtfile "${WT_NAME}" WG_PRIVATE=$(wg genkey) WG_PUBLIC=$(echo "$WG_PRIVATE" | wg pubkey) write_wgfile "${WT_NAME}" cmd_net_init_print_info exit } load_port() { source "/config/db/db-${LID}/wg/port" 2>/dev/null || BAIL "${R}ERROR${N}: No port found. Use ${C}curl rpc/net/init${N} first." } load_wt() { source "/config/db/db-${LID}/wg/wt-${1}" || BAIL "Not found: ${1}. Try ${C}curl rpc/net/init -d name=${1}${N}" "ERROR: " "Not found: db-${LID}/wg/wt-${1}" } load_wg() { source "/config/db/db-${LID}/wg/wg-${1}" || BAIL "Not found." "ERROR: " "Not found: db-${LID}/wg/wg-${1}" } xrm() { local fn local err err=0 for fn in "$@"; do [[ ! -f "$fn" ]] && { err=255; continue; } rm -f "$fn" done return $err } net_down() { local name name="$1" # Shut down WG interface if [[ -z $name || $name == "all" ]]; then nsenter -t "${PID}" -n ip link delete group 31337 else # Return early if device did not exist. nsenter -t "${PID}" -n ip link delete "wg${name}" || return fi # Restore default routing echo -e "${Y}WARNING${N}: All traffic exits via the DEFAULT ROUTE now." nsenter -t "${PID}" -n ip route add default via "${SF_NET_LG_ROUTER_IP}" } cmd_net_del() { load_port local fn [[ -z $R_WT_NAME ]] && BAIL "Use ${C}-d name=${N}" net_down "$R_WT_NAME" if [[ "$R_WT_NAME" == "all" ]]; then # Delete PORT assignment xrm "/config/db/wg/sec2port-${PORTSECRET}" "/config/db/wg/port-${WG_PORT}" "/config/db/db-${LID}/wg/port" # Delete all private keys for fn in "/config/db/db-${LID}/wg/wg-"*; do [[ ! -f "$fn" ]] && break str=$(basename "$fn") name="${str#*-}" [[ $fn != "/config/db/db-${LID}/wg/wg-${name}" ]] && continue # BAD # Delete all links xrm "/config/db/wg/wg-${name}" "/config/db/wg/wt-${name}" done rm -rf "/config/db/db-${LID}/wg" echo -en "All private keys deleted." exit fi [[ ! -f "/config/db/db-${LID}/wg/wg-${R_WT_NAME}" ]] && BAIL "${R}Not found${N}: ${R_WT_NAME}" xrm "/config/db/wg/wg-${R_WT_NAME}" "/config/db/db-${LID}/wg/wg-${R_WT_NAME}" xrm "/config/db/wg/wt-${R_WT_NAME}" "/config/db/db-${LID}/wg/wt-${R_WT_NAME}" echo -en "\ ${G}Private key deleted${N} ($R_WT_NAME). " exit } cmd_net() { load_port cmd_net_print_info exit } cmd_net_show() { local str unset IFS if [[ -z $COLOR ]]; then str=$(nsenter -t "${PID}" -n wg show) else # Use 'script' to force color output str=$(script -q -c "nsenter -t \"${PID}\" -n wg show" /dev/null ${N}" exit } cmd_net_down() { local dev net_down "${R_WT_NAME}" exit } cmd_net_help() { echo -en "\ Create new peer : ${C}curl rpc/net/init -d name=${N} Delete peer : ${C}curl rpc/net/del -d name=${N} List peers : ${C}curl rpc/net/list${N} Show connections: ${C}curl rpc/net/show${N} Move port to a new server: ${C}curl rpc/net/init -d portsecret=${N} Connect a peer: ${C}curl rpc/net/up -d name=${N} Disconnect a peer: ${C}curl rpc/net/down -d name=${N} " exit } 0<&- # Close STDIN Sanitize GetFormVars [[ -n $COLOR ]] && { # COLOR is set (to 'always') Y=$CDY C=$CDC R=$CR G=$CDG M=$CDM N=$CN F=$CF } # Split into arguments _IFS=$IFS IFS=/ ARGS=(${REQUEST_URI:1}) # Ignore first '/'. Split into arguements. IFS=$_IFS [[ "${FCGI_CMD}" == "dmesg" ]] && { # dmesg --color=always -f kern --level err,warn -e | tail -n100 dmesg --color="${COLOR:-never}" -f kern --level err -e | tail -n20 exit } [[ -n $SF_DEBUG ]] && [[ "${FCGI_CMD}" == "env" ]] && { env; exit; } # /net/init # INPUT : # OUTPUT: WG-Config & WT-Line # # /net/up # - Do everything fromo INIT. # - # /net -> Show port assignment # /net/init -> Assigned port to this LID or create new port. # /net/up -> Create WireGuard interface # /net/show -> Show WireGuard peers # /net/down # /net/del # /net/list [[ "${FCGI_CMD}" == "net" ]] && { # Retrieve (LID CID PID) arr=($(redr GET "ip:${REMOTE_ADDR}")) || BAIL "Bad Value" "Bad Value: " "ret=$?, ${#arr[@]}" [[ ${#arr[@]} -ne 3 ]] && BAIL "Value != 3" "Value != 3: " "${#arr[@]}" LID="${arr[0]}" [[ ! -d "/config/db/db-${LID}/wg" ]] && mkdir "/config/db/db-${LID}/wg" # CID="${arr[1]}" PID="${arr[2]}" DEBUGF "LID=$LID PID=$PID" # Show current port configuration [[ ${ARGS[0]} == 'net' && ${ARGS[1]} == 'show' ]] && cmd_net_show # Initialize or set port [[ ${ARGS[1]} == 'init' ]] && cmd_net_init [[ ${ARGS[1]} == 'del' ]] && cmd_net_del [[ ${ARGS[1]} == 'down' ]] && cmd_net_down [[ ${ARGS[1]} == 'help' ]] && cmd_net_help [[ ${ARGS[1]} == 'list' ]] && cmd_net_list # NOT 'up' -> EXIT [[ ${ARGS[1]} != 'up' ]] && { echo -e "${R}ERROR${N}: Unknown command."; cmd_net_help; } [[ -z $R_WT_NAME ]] && BAIL "${R}ERROR:${N} Use ${C}curl rpc/net/up -d name=${N}." WT_NAME="$R_WT_NAME" load_port load_wt "${WT_NAME}" load_wg "${WT_NAME}" dev="wg${WT_NAME}" load_config # Delete old interface in either namespace: nsenter -t "${WG_PID}" -n ip link del "${dev}" 2>/dev/null nsenter -t "${PID}" -n ip link del "${dev}" 2>/dev/null err=$(nsenter -t "${WG_PID}" -n ip link add "${dev}" type wireguard 2>&1) || BAIL "Failed: ip link add $dev ($err)." "Failed $dev" ": $err" nsenter -t "${WG_PID}" -n ip link set "${dev}" group 31337 || BAIL "FAIL" echo "$WG_PRIVATE" >/dev/shm/private.$$ nsenter -t "${WG_PID}" -n wg set "${dev}" listen-port "${WG_PORT}" private-key "/dev/shm/private.$$" peer "${WT_PUBLIC}" allowed-ips 0.0.0.0/0 || BAIL "Failed: wg set" rm -f /dev/shm/private.$$ # Move Interface to user's container: err=$(nsenter -t "${WG_PID}" -n ip link set "${dev}" netns "${PID}" 2>&1) || BAIL "Failed to move $dev." "Failed $dev netns $PID" ": $err" # Configure interface after moving nsenter -t "${PID}" -n ip -4 address add 192.168.0.2/32 dev "${dev}" err=$(nsenter -t "${PID}" -n ip -6 address add fd::2/128 dev "${dev}" 2>&1) || echo >&2 "ip -6: $err" nsenter -t "${PID}" -n ip link set mtu 1420 up dev "${dev}" # Add static routes for RPC # nsenter -t "${PID}" -n ip route add "${RPC_IP}/32" dev eth0 # NOT NEEDED: RPC is on same network nsenter -t "${PID}" -n ip route add "${SF_DNS}" via "${SF_NET_LG_ROUTER_IP}" 2>/dev/null nsenter -t "${PID}" -n ip route del default 2>/dev/null nsenter -t "${PID}" -n ip route add default dev "${dev}" # nsenter -t "${PID}" -n ip --color=${COLOR:-never} addr show "${dev}" echo -e "${G}SUCCESS${N}: All traffic is now routed via ${WT_NAME}." exit }