Added initial federation code

master
Keith Irwin 2022-11-27 16:00:43 -07:00
parent 3690136d34
commit 4cf03e88e2
Signed by: ki9
GPG Key ID: DF773B3F4A88DA86
13 changed files with 251 additions and 81 deletions

View File

@ -8,7 +8,6 @@ RUN apt-get update && apt-get install --yes \
# Create dirs and temp files
RUN mkdir /usr/lib/wgapi /var/log/wgapi
RUN touch /var/local/wgapi_tokens /var/log/wgapi/wgapi.log
RUN chown -R www-data:www-data /usr/lib/wgapi /var/log/wgapi /var/local/wgapi_tokens
# Configure apache

View File

@ -8,7 +8,6 @@ RUN apt-get update && apt-get install --yes \
# Create dirs and temp files
RUN mkdir /usr/lib/wgapi /var/log/wgapi
RUN touch /var/local/wgapi_tokens /var/log/wgapi/wgapi.log
RUN chown -R www-data:www-data /usr/lib/wgapi /var/log/wgapi /var/local/wgapi_tokens
# Configure apache

View File

@ -1,31 +1,31 @@
FROM debian:latest
# Change these
ENV LISTEN_PORT=4400
ENV ADMIN_EMAIL='me@example.com'
ARG PORT
# Install deps
RUN apt-get update \
&& apt-get install --yes curl apache2 \
RUN apt-get update && apt-get install --yes \
curl apache2 wireguard-tools \
&& rm -rf /var/lib/apt/lists/*
# Create dirs and temp files
RUN mkdir /usr/lib/wgapi /var/log/wgapi
RUN chown -R www-data:www-data /usr/lib/wgapi /var/log/wgapi
# Configure apache
RUN a2enmod cgi rewrite
RUN sed -i "s/^Listen 80$/Listen ${LISTEN_PORT}/" \
/etc/apache2/ports.conf
RUN sed -i "s/^<VirtualHost \*:80>$/<VirtualHost *:${LISTEN_PORT}>/" \
/etc/apache2/sites-available/000-default.conf
RUN sed -i "s/ServerAdmin .*$/ServerAdmin ${ADMIN_EMAIL}/" \
/etc/apache2/sites-available/000-default.conf
RUN sed -i "s|DocumentRoot .*$|DocumentRoot /var/www/cgi-bin\n\tScriptAlias / /var/www/cgi-bin/index.cgi|" \
RUN sed -i "s/^Listen 80$/Listen ${PORT}/" /etc/apache2/ports.conf
RUN sed -i -e "s/^<VirtualHost \*:80>$/<VirtualHost *:${PORT}>/" \
-e "s|DocumentRoot .*$|DocumentRoot /usr/lib/cgi-bin\n\tSetHandler cgi-script\n\tOptions +ExecCGI|" \
/etc/apache2/sites-available/000-default.conf
# Allow http user to run these binaries as root with sudo
RUN echo "www-data ALL=(ALL:ALL) NOPASSWD: /usr/bin/wg, /usr/bin/[, /usr/bin/tee" \
| sudo EDITOR='tee -a' visudo
# Copy over cgi and libs
COPY fed.cgi /usr/lib/cgi-bin/index.cgi
# TODO: Copy only needed libs
RUN mkdir /var/www/cgi-bin/ && chown www-data:www-data /var/www/cgi-bin/
COPY dashboard.cgi /var/www/cgi-bin/index.cgi
COPY lib/ /usr/lib/wgapi/
# Run time!
EXPOSE ${LISTEN_PORT}
CMD ["apachectl", "-D", "FOREGROUND"]
EXPOSE ${PORT}
CMD ["apachectl", "-D", "FOREGROUND"]

View File

@ -16,19 +16,16 @@ fi; source "${CONFIG_FILE}"
case "${REQUEST_METHOD}" in
# List
#'GET') "${LIB_DIR}/peer_list" "${HTTP_X_REAL_IP}";;
# Add
#'POST') "${LIB_DIR}/peer_add" "${HTTP_X_REAL_IP}" "${QUERY_STRING}";;
'POST') "${LIB_DIR}/fed/peer/add" "${HTTP_X_REAL_IP}" "${QUERY_STRING}";;
# Delete
#'DELETE') "${LIB_DIR}/peer_del" "${HTTP_X_REAL_IP}" "${QUERY_STRING}";;
'DELETE') "${LIB_DIR}/fed/peer/del" "${HTTP_X_REAL_IP}" "${QUERY_STRING}";;
# Needed for CORS preflight
'OPTIONS') "${LIB_DIR}/http_res" 200;;
# Bad request
*) printf 'Invalid HTTP verb' | "${LIB_DIR}/http_res" 405;;
esac

View File

@ -126,20 +126,17 @@ while IFS=$'\t' read -r server_hostname server_ipv4 server_ipv6 server_pubkey se
printf 'Added %s to local wireguard server.\n' "${domain}" >>"${LOGFILE}"
else
printf 'ERROR! Failed to add %s to local wireguard server!\n' "${domain}" >>"${LOGFILE}"
# TODO: clear existing progress
"${LIB_DIR}/http_res" 500; exit
fi
# Remote server
else
server_blocks="${server_blocks}\n[Peer] # ${server_hostname}.${TLD}\nPublicKey=${server_pubkey}\nPresharedKey=${server_psk}\nAllowedIPs=${server_ipv4}/32,${server_ipv6}/128\nEndpoint=${server_endpoint}\n"
# TODO: Send new user config to federated server
# if "${LIB_DIR}/fed_peer_add" "${server_admin}" "${pubkey}" "${server_psk}" "${ipv4}/32,${ipv6}/128" "${server_secret}"; then
# printf 'Sent %s to remote wireguard server %s.\n' "${domain}" "${server_hostname}" >>"${LOGFILE}"
#else
# printf 'ERROR! Failed to send %s to remote wireguard server %s!\n' "${domain}" "${server_hostname}" >>"${LOGFILE}"
# # TODO: clear existing progress
# exit 16
#fi
# Send new user config to federated server
if "${LIB_DIR}/fed_peer_add" "${server_admin}" "${pubkey}" "${server_psk}" "${ipv4}/32,${ipv6}/128" "${server_secret}"; then
printf 'Sent %s to remote wireguard server %s.\n' "${domain}" "${server_hostname}" >>"${LOGFILE}"
else
printf 'ERROR! Failed to send %s to remote wireguard server %s!\n' "${domain}" "${server_hostname}" >>"${LOGFILE}"
fi
fi
done <"${SERVERS_FILE}"
wg_config="[Interface] # ${hostname}.${username}.${TLD}\nPrivateKey=${privkey:?}\nAddress=${address:?}\n${WG_DNS}\n${server_blocks:?}"

View File

@ -123,14 +123,12 @@ while IFS=$'\t' read -r server_hostname server_ipv4 server_ipv6 server_pubkey se
# Remote server
else
server_blocks="${server_blocks}\n[Peer] # ${server_hostname}.${TLD}\nPublicKey=${server_pubkey}\nPresharedKey=${server_psk}\nAllowedIPs=${server_ipv4}/32,${server_ipv6}/128\nEndpoint=${server_endpoint}\n"
# TODO: Send new user config to federated server
# if "${LIB_DIR}/fed_peer_add" "${server_admin}" "${pubkey}" "${server_psk}" "${ipv4}/32,${ipv6}/128" "${server_secret}"; then
# printf 'Sent %s to remote wireguard server %s.\n' "${domain}" "${server_hostname}" >>"${LOGFILE}"
#else
# printf 'ERROR! Failed to send %s to remote wireguard server %s!\n' "${domain}" "${server_hostname}" >>"${LOGFILE}"
# # TODO: clear existing progress
# exit 17
#fi
# Send new user config to federated server
if "${LIB_DIR}/fed_peer_add" "${server_admin}" "${pubkey}" "${server_psk}" "${ipv4}/32,${ipv6}/128" "${server_secret}"; then
printf 'Sent %s to remote wireguard server %s.\n' "${domain}" "${server_hostname}" >>"${LOGFILE}"
else
printf 'ERROR! Failed to send %s to remote wireguard server %s!\n' "${domain}" "${server_hostname}" >>"${LOGFILE}"
fi
fi
done <"${SERVERS_FILE}"
wg_config="[Interface] # ${hostname}.${username}.${TLD}\nPrivateKey=${privkey:?}\nAddress=${address:?}\n${WG_DNS}\n${server_blocks:?}"

34
back/lib/fed/peer/add Executable file
View File

@ -0,0 +1,34 @@
#!/bin/bash
# FILE: fed/peer/add
# DESCRIPTION: Add a new peer from a federated server
# USAGE: add $remote_ip $querystring
# QUERYSTRING: ?pubkey=$pubkey&psk=$psk&ips=$allowedips
CONFIG_FILE='/etc/wgapi/config'
if ! [ ${#} -eq 2 ]; then
printf 'ERROR! Bad input: %s %s\n' "${0}" "${*}" >>"${LOGFILE}"
"${LIB_DIR}/http_res" 500; exit
fi & if ! [ -x '/usr/bin/wg' ]; then
printf 'ERROR! %s could not find /usr/bin/wg\n' "${0}" >>"${LOGFILE}"
"${LIB_DIR}/http_res" 500; exit
fi & if ! [ -f "${CONFIG_FILE}" ]; then
printf 'ERROR! %s could not find %s!\n' "${0}" "${CONFIG_FILE}" >>"${LOGFILE}"
"${LIB_DIR}/http_res" 500; exit
fi; source "${CONFIG_FILE}"
ip="${1}"
qs="$(<<<"${2}" tr '&' '\n' | sed 's/?//')"
pubkey="$(<<<"${qs}" grep -oP 'pubkey=(.*)' | sed 's/^pubkey//' | xargs)"
psk="$(<<<"${qs}" grep -oP 'psk=(.*)' | sed 's/^psk//' | xargs)"
allowedips="$(<<<"${qs}" grep -oP 'ips=(.*)' | sed 's/^ips//' | xargs)"
# TODO: Check that ${ip} is on the list
# Add peer to wireguard
if "${LIB_DIR}/wg_peer_add" "${pubkey}" "${server_psk}" "${allowedips}"; then
printf 'Added %s to local wireguard server.\n' "${pubkey}" >>"${LOGFILE}"
else
printf 'ERROR! Failed to add %s to wireguard server!\n' "${pubkey}" >>"${LOGFILE}"
# TODO: clear existing progress
"${LIB_DIR}/http_res" 500; exit
fi
"${LIB_DIR}/http_res" 200

127
back/lib/fed/peer/del Executable file
View File

@ -0,0 +1,127 @@
#!/bin/bash
# FILE: fed/peer/del
# DESCRIPTION: Delete a peer from a federated server
# USAGE: del $remote_ip $querystring
# QUERYSTRING: ?t=$token&pubkey=$pubkey
CONFIG_FILE='/etc/wgapi/config'
SERVERS_FILE='/etc/wgapi/servers'
if ! [ ${#} -eq 2 ]; then
printf 'ERROR! Bad input: %s %s\n' "${0}" "${*}" >>"${LOGFILE}"
"${LIB_DIR}/http_res" 500; exit
fi & if ! [ -x '/usr/bin/wg' ]; then
printf 'ERROR! %s could not find /usr/bin/wg\n' "${0}" >>"${LOGFILE}"
"${LIB_DIR}/http_res" 500; exit
fi & if ! [ -f "${CONFIG_FILE}" ]; then
printf 'ERROR! %s could not find %s!\n' "${0}" "${CONFIG_FILE}" >>"${LOGFILE}"
"${LIB_DIR}/http_res" 500; exit
fi
source "${CONFIG_FILE}"
if ! [ -f "${SERVERS_FILE}" ]; then
printf 'ERROR! %s could not find %s!\n' "${0}" "${SERVERS_FILE}" >>"${LOGFILE}"
"${LIB_DIR}/http_res" 500; exit
fi & if ! [ -f "${TOKENS_FILE}" ]; then
printf 'ERROR! %s could not find %s!\n' "${0}" "${TOKENS_FILE}" >>"${LOGFILE}"
"${LIB_DIR}/http_res" 500; exit
fi
ip="${1}"
qs="$(<<<"${2}" tr '&' '\n' | sed 's/?//')"
# Parse pubkey
pubkey="$(<<<"${qs#}" grep 'pubkey=' | sed 's/pubkey=//')"
printf '%s requested to delete %s\n' "${ip}" "${pubkey:?}" >>"${LOGFILE}"
# Check token
token_fail(){
printf 'Rejecting %s request to delete peer due to %s token\n' "${ip}" "${1}" >>"${LOGFILE}"
printf 'Invalid token\n' | "${LIB_DIR}/http_res" 403; exit
}
saved_token="$(grep "${ip}" "${TOKENS_FILE}" | cut -f2)"
[ "${saved_token}" == "" ] && token_fail 'missing' &
<<<"${qs}" grep -qx "t=${saved_token}" || token_fail 'mismatched'
printf '%s token was valid\n' "${ip}" >>"${LOGFILE}"
# Get peer IP list
if ! wg_output="$(sudo /usr/bin/wg show "${TLD}" allowed-ips)"; then
printf 'ERROR! Wireguard failed!\n' >>"${LOGFILE}"
"${LIB_DIR}/http_res" 500; exit
fi
# Filter out this user's
user_peers="$(grep "${ip%[.:]*}" <<<"${wg_output}" 2>/dev/null)"
if [ "${user_peers}" == "" ]; then
printf "ERROR! %s accessed the dashboard but isn't on the network!\n" "${ip}" >>"${LOGFILE}"
"${LIB_DIR}/http_res" 500; exit
fi
# Get peer domains
if ! peer="$("${LIB_DIR}/ips_to_peers" tsv <<<"${user_peers}" | grep "${pubkey}")"; then
printf 'ERROR! Peer %s not found for user %s!\n' "${pubkey}" "${ip}" >>"${LOGFILE}" &
printf 'Peer not found\n' | "${LIB_DIR}/http_res" 404; exit
fi
domain="$(<<<"${peer}" cut -f1)"
ipv4="$(<<<"${peer}" cut -f2)"
ipv6="$(<<<"${peer}" cut -f3)"
if ! printf 'Delete request was for %s %s %s\n' "${domain:?}" "${ipv4:?}" "${ipv6:?}" >>"${LOGFILE}"; then
printf 'ERROR! Failed to collect peer data: %s %s %s\n' "${domain}" "${ipv4}" "${ipv6}" >>"${LOGFILE}" &
"${LIB_DIR}/http_res" 500; exit
fi
# Make sure user isn't deleting their own peer
if [ "${ip}" == "${ipv4}" ] || [ "${ip}" == "${ipv6}" ]; then
printf 'User requested to delete peer from itself: %s.\n' "${ip}" >>"${LOGFILE}"
printf 'You cannot delete a peer from itself!' | "${LIB_DIR}/http_res" 400; exit
fi
hostname="$(<<<"${domain}" cut -d'.' -f1)"
username="$(<<<"${domain}" cut -d'.' -f2)"
# Wireguard
# Run this function in parallel in the while loop below
# https://stackoverflow.com/a/33058618
for_server_do() {
[[ ${server_hostname:0:1} = \# ]] && return # Ignore comments
server_hostname="${1}"; server_ipv4="${2}"; server_ipv6="${3}"; server_pubkey="${4}"
server_endpoint="${5}"; server_admin="${6}"; server_secret="${7}"
if [ "${server_hostname}" == "${LOCAL_SERVER}" ]; then
if "${LIB_DIR}/wg_peer_del" "${pubkey}"; then
printf 'Deleted %s from local wireguard server.\n' "${domain}" >>"${LOGFILE}"
else
printf 'ERROR! Failed to delete %s from local wireguard server!\n' "${domain}" >>"${LOGFILE}"
# TODO: clear existing progress
"${LIB_DIR}/http_res" 500; exit
fi
# TODO Add federated peer
#else
# if "${LIB_DIR}/fed_peer_add" "${server_admin}" "${pubkey}" "${server_psk}" "${ipv4}/32,${ipv6}/128" "${server_secret}"; then
# printf 'Deleted %s from remote wireguard server %s.\n' "${domain}" "${server_hostname}" >>"${LOGFILE}"
# else
# printf 'ERROR! Failed to delete %s from remote wireguard server %s!\n' "${domain}" "${server_hostname}" >>"${LOGFILE}"
# # TODO: Send a 500 error
# # TODO: clear existing progress
# exit 16
# fi"${LIB_DIR}/fed_peer_del" "${server_admin}" "${pubkey}" "${server_secret}"
fi
}; while IFS=$'\t' read -r server_hostname server_ipv4 server_ipv6 server_pubkey server_endpoint server_admin server_secret
do for_server_do "${server_hostname}" "${server_ipv4}" "${server_ipv6}" "${server_pubkey}" "${server_endpoint}" "${server_admin}" "${server_secret}" &
# Uncomment if SERVERS_FILE is very big
#[ $( jobs | wc -l ) -ge $( nproc ) ] && wait
done <"${SERVERS_FILE}" &
# Update nameserver
if "${LIB_DIR}/ns_update_del" "${domain:?}" "${ipv4:?}" "${ipv6:?}"
then printf 'Successfully deleted %s from DNS server.\n' "${domain}" >>"${LOGFILE}"
else printf 'ERROR! Failed to delete %s %s %s from DNS server!\n' "${domain}" "${ipv4}" "${ipv6}" >>"${LOGFILE}"
fi &
# Create SSL cert
if "${LIB_DIR}/ssl_peer_del" "${hostname:?}" "${username:?}"
then printf 'Successfully deleted SSL certs for %s\n' "${domain}" >>"${LOGFILE}"
else printf 'ERROR! Failed to delete certs for %s!\n' "${domain}" >>"${LOGFILE}"
fi
# Respond to user
# Do it before updating nameserver and certs because
# if wireguard worked, there's no going back. The admin
# can clean up missing records and certs after checking the logs
printf 'Deleted %s.%s.%s' "${hostname}" "${username}" "${TLD}" | "${LIB_DIR}/http_res" 202

View File

@ -1,8 +0,0 @@
#!/bin/bash
# FILE: wgapi:back/lib/fed/add
# DESCRIPTION: Send a new device to a federated server
# USAGE: add endpoint pubkey psk allowed-ips secret
[ "$#" == "5" ] || exit
payload="{\"pubkey\":\"${2}\",\"psk\":\"${3}\",\"ips\":\"${4}\",\"secret\":\"${5}\"}"
curl -s -X POST --data "${payload}" "${1}"

View File

@ -1,8 +0,0 @@
#!/bin/bash
# FILE: wgapi:back/lib/fed/del
# DESCRIPTION: Send device deletion request to a federated server
# USAGE: del endpoint pubkey secret
[ "$#" == "3" ] || exit
payload="{\"pubkey\":\"${2}\",\"secret\":\"${3}\"}"
curl -s -X DELETE --data "${payload}" "${1}"

32
back/lib/fed_peer_add Normal file
View File

@ -0,0 +1,32 @@
#!/bin/bash
# FILE: fed_peer_add
# DESCRIPTION: Sends details about a new peer to a federated server
# USAGE: fed_peer_add server pubkey psk allowedips
# ERRORS:
# 3: Bad usage
# 4: Config file not found
# 5: wg binary not found
# 6: curl command failed
CONFIG_FILE='/etc/wgapi/config'
if ! [ ${#} -eq 4 ]; then
printf '%s Bad usage: %s\n' "${0}" "${*}" >>"${LOGFILE}"
exit 3
fi & if [ -f "${CONFIG_FILE}" ]; then
printf 'ERROR: %s could not find config at %s!\n' "${0}" "${CONFIG_FILE}" >>"${LOGFILE}"
exit 4
fi & if [ -x /usr/bin/wg ]; then
printf 'ERROR: /usr/bin/wg not found\n' >>"${LOGFILE}"
exit 5
fi; source "${CONFIG_FILE}"
server="${1}"
pubkey="${2}"
psk="${3}"
allowedips="${4}"
if res="$(curl --silent --request POST "wg-test-fed.${server}.${TLD}?pubkey=${pubkey}&psk=${psk}&ips=${allowedips}")"; then
printf 'Sent peer %s to federated server %s\n' "${pubkey}" "${server}" >>"${LOGFILE}"
else
printf 'ERROR: Failed to send peer to federated server %s: %s\n' "${server}" "${res}" "${res}" >>"${LOGFILE}"
exit 6
fi

View File

@ -4,23 +4,26 @@
# USAGE: add pubkey psk allowedips
# ERRORS:
# 3: Bad usage
# 4: wg binary not found
# 5: vars not found
# 4: config not found
# 5: wg binary not found
# 6: wg command failed
CONFIG_FILE='/etc/wgapi/config'
[ "$#" == "3" ] || (
if ! [ ${#} -eq 3 ]; then
printf '%s Bad usage: %s\n' "${0}" "${*}" >>"${LOGFILE}"
exit 3
)
[ -x /usr/bin/wg ] || (
printf '/usr/bin/wg not found\n' >>"${LOGFILE}"
fi & if [ -f "${CONFIG_FILE}" ]; then
printf 'ERROR: %s could not find config at %s!\n' "${0}" "${CONFIG_FILE}" >>"${LOGFILE}"
exit 4
)
[ -f "${CONFIG_FILE}" ] || exit 5
source "${CONFIG_FILE}"
fi & if [ -x /usr/bin/wg ]; then
printf 'ERROR: /usr/bin/wg not found\n' >>"${LOGFILE}"
exit 5
fi; source "${CONFIG_FILE}"
pubkey="${1}"
psk="${2}"
allowedips="${3}"
res="$(printf '%s\n' "${2}" | sudo /usr/bin/wg set "${TLD}" peer "${1}" preshared-key /dev/stdin allowed-ips "${3}")" || (
if ! res="$(printf '%s\n' "${psk}" | sudo /usr/bin/wg set "${TLD}" peer "${pubkey}" preshared-key /dev/stdin allowed-ips "${allowedips}")"; then
printf '%s %s\n' "${?}" "${res}" >>"${LOGFILE}"
exit 6
)
fi

View File

@ -55,15 +55,15 @@ services:
wgapi:
ipv4_address: 172.19.0.3
# fed-backend:
# build:
# context: back
# dockerfile: fed.Dockerfile
# args:
# PORT: 4443
# cap_add:
# - NET_ADMIN
# network_mode: host
# container_name: wgapi-fed-backend
# volumes:
# - '/var/log/wgapi:/var/log/wgapi'
fed-backend:
build:
context: back
dockerfile: fed.Dockerfile
args:
PORT: 4443
cap_add:
- NET_ADMIN
network_mode: host
container_name: wgapi-fed-backend
volumes:
- '/var/log/wgapi:/var/log/wgapi'