From: Michael Tremer Date: Mon, 30 Sep 2024 16:53:45 +0000 (+0200) Subject: wireguard: Implement creating an extra interface per N2N peer X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=eb48782ee7184b5c397efeda14f27d83e3c6995b;p=people%2Fms%2Fipfire-2.x.git wireguard: Implement creating an extra interface per N2N peer When importing a configuration, we will receive a new private key which we cannot apply to the original interface. Therefore we need to create a new one for each peer. RW peers will remain on wg0 which will always exist. Signed-off-by: Michael Tremer --- diff --git a/html/cgi-bin/wireguard.cgi b/html/cgi-bin/wireguard.cgi index e46147240..a27245974 100644 --- a/html/cgi-bin/wireguard.cgi +++ b/html/cgi-bin/wireguard.cgi @@ -134,8 +134,8 @@ if ($cgiparams{"ACTION"} eq $Lang::tr{'save'}) { # Fetch type my $type = $Wireguard::peers{$key}[1]; - my @remote_subnets = &Wireguard::decode_subnets($Wireguard::peers{$key}[6]); - my @local_subnets = &Wireguard::decode_subnets($Wireguard::peers{$key}[8]); + my @remote_subnets = &Wireguard::decode_subnets($Wireguard::peers{$key}[8]); + my @local_subnets = &Wireguard::decode_subnets($Wireguard::peers{$key}[10]); # Flush CGI parameters & load configuration %cgiparams = ( @@ -144,13 +144,15 @@ if ($cgiparams{"ACTION"} eq $Lang::tr{'save'}) { "TYPE" => $Wireguard::peers{$key}[1], "NAME" => $Wireguard::peers{$key}[2], "PUBLIC_KEY" => $Wireguard::peers{$key}[3], - "ENDPOINT_ADDRESS" => $Wireguard::peers{$key}[4], - "ENDPOINT_PORT" => $Wireguard::peers{$key}[5], + "PRIVATE_KEY" => $Wireguard::peers{$key}[4], + "PORT" => $Wireguard::peers{$key}[5], + "ENDPOINT_ADDRESS" => $Wireguard::peers{$key}[6], + "ENDPOINT_PORT" => $Wireguard::peers{$key}[7], "REMOTE_SUBNETS" => join(", ", @remote_subnets), - "REMARKS" => &MIME::Base64::decode_base64($Wireguard::peers{$key}[7]), + "REMARKS" => &MIME::Base64::decode_base64($Wireguard::peers{$key}[9]), "LOCAL_SUBNETS" => join(", ", @local_subnets), - "PSK" => $Wireguard::peers{$key}[9], - "KEEPALIVE" => $Wireguard::peers{$key}[10], + "PSK" => $Wireguard::peers{$key}[11], + "KEEPALIVE" => $Wireguard::peers{$key}[12], ); # Jump to the editor @@ -184,6 +186,13 @@ if ($cgiparams{"ACTION"} eq $Lang::tr{'save'}) { push(@errormessages, $Lang::tr{'wg invalid public key'}); } + # Check private key + #if ($cgiparams{'PRIVATE_KEY'} eq '') { + # # The private key may be empty + #} elsif (!&Wireguard::key_is_valid($cgiparams{'PRIVATE_KEY'})) { + # push(@errormessages, $Lang::tr{'wg invalid private key'}); + #} + # Check PSK if ($cgiparams{'PSK'} eq '') { # The PSK may be empty @@ -191,6 +200,11 @@ if ($cgiparams{"ACTION"} eq $Lang::tr{'save'}) { push(@errormessages, $Lang::tr{'wg invalid psk'}); } + # Check port + #unless (&General::validport($cgiparams{'PORT'})) { + # push(@errormessages, $LANG::tr{'invalid port'}); + #} + # Check the endpoint address if ($cgiparams{'ENDPOINT_ADDRESS'} eq '') { # The endpoint address may be empty @@ -251,21 +265,25 @@ if ($cgiparams{"ACTION"} eq $Lang::tr{'save'}) { "net", # 2 = Name $cgiparams{"NAME"}, - # 3 = Pubkey + # 3 = Public Key $cgiparams{"PUBLIC_KEY"}, - # 4 = Endpoint Address + # 4 = Private Key + $cgiparams{"PRIVATE_KEY"}, + # 5 = Port + $cgiparams{"PORT"}, + # 6 = Endpoint Address $cgiparams{"ENDPOINT_ADDRESS"}, - # 5 = Endpoint Port + # 7 = Endpoint Port $cgiparams{"ENDPOINT_PORT"}, - # 6 = Remote Subnets + # 8 = Remote Subnets &Wireguard::encode_subnets(@remote_subnets), - # 7 = Remark + # 9 = Remark &Wireguard::encode_remarks($cgiparams{"REMARKS"}), - # 8 = Local Subnets + # 10 = Local Subnets &Wireguard::encode_subnets(@local_subnets), - # 9 = PSK + # 11 = PSK $cgiparams{"PSK"} || "", - # 10 = Keepalive + # 12 = Keepalive $cgiparams{"KEEPALIVE"} || 0, ]; @@ -348,8 +366,8 @@ if ($cgiparams{"ACTION"} eq $Lang::tr{'save'}) { # Fetch some configuration parts } else { $cgiparams{"PUBLIC_KEY"} = $Wireguard::peers{$key}[3]; - $cgiparams{'CLIENT_ADDRESS'} = $Wireguard::peers{$key}[6]; - $cgiparams{"PSK"} = $Wireguard::peers{$key}[9]; + $cgiparams{'CLIENT_ADDRESS'} = $Wireguard::peers{$key}[8]; + $cgiparams{"PSK"} = $Wireguard::peers{$key}[11]; } # Save the connection @@ -360,21 +378,25 @@ if ($cgiparams{"ACTION"} eq $Lang::tr{'save'}) { "host", # 2 = Name $cgiparams{"NAME"}, - # 3 = Pubkey + # 3 = Public Key $cgiparams{"PUBLIC_KEY"}, - # 4 = Endpoint Address + # 4 = Private Key "", - # 5 = Endpoint Port + # 5 = Port "", - # 6 = Remote Subnets + # 6 = Endpoint Address + "", + # 7 = Endpoint Port + "", + # 8 = Remote Subnets $cgiparams{'CLIENT_ADDRESS'}, - # 7 = Remark + # 9 = Remark &Wireguard::encode_remarks($cgiparams{"REMARKS"}), - # 8 = Local Subnets + # 10 = Local Subnets &Wireguard::encode_subnets(@local_subnets), - # 9 = PSK + # 11 = PSK $cgiparams{"PSK"}, - # 10 = Keepalive + # 12 = Keepalive 0, ]; @@ -550,10 +572,12 @@ END my $type = $Wireguard::peers{$key}[1]; my $name = $Wireguard::peers{$key}[2]; my $pubkey = $Wireguard::peers{$key}[3]; - my $endpoint = $Wireguard::peers{$key}[4]; - my $port = $Wireguard::peers{$key}[5]; - my $routes = $Wireguard::peers{$key}[6]; - my $remarks = &Wireguard::decode_remarks($Wireguard::peers{$key}[7]); + #my $privkey = $Wireguard::peers{$key}[4] + #my $port = $Wireguard::peers{$key}[5]; + my $endpoint = $Wireguard::peers{$key}[6]; + #my $endpport = $Wireguard::peers{$key}[7]; + my $routes = $Wireguard::peers{$key}[8]; + my $remarks = &Wireguard::decode_remarks($Wireguard::peers{$key}[9]); my $connected = $Lang::tr{'capsclosed'}; my $country = "ZZ"; @@ -790,6 +814,8 @@ EDITNET: + +
@@ -1007,9 +1033,9 @@ sub show_peer_configuration($$) { my %peer = ( "NAME" => $Wireguard::peers{$key}[2], "PUBLIC_KEY" => $Wireguard::peers{$key}[3], - "CLIENT_ADDRESS" => $Wireguard::peers{$key}[6], - "LOCAL_SUBNETS" => &Wireguard::decode_subnets($Wireguard::peers{$key}[8]), - "PSK" => $Wireguard::peers{$key}[9], + "CLIENT_ADDRESS" => $Wireguard::peers{$key}[8], + "LOCAL_SUBNETS" => &Wireguard::decode_subnets($Wireguard::peers{$key}[10]), + "PSK" => $Wireguard::peers{$key}[11], # Other stuff "PRIVATE_KEY" => $private_key, diff --git a/src/initscripts/system/wireguard b/src/initscripts/system/wireguard index cb78d80ab..df50259f0 100644 --- a/src/initscripts/system/wireguard +++ b/src/initscripts/system/wireguard @@ -23,19 +23,94 @@ . ${rc_functions} . /etc/rc.d/init.d/networking/functions.network -INTF="wg0" - eval $(/usr/local/bin/readhash /var/ipfire/wireguard/settings) -generate_config() { - echo "[Interface]" - echo "PrivateKey = ${PRIVATE_KEY}" +interfaces() { + local id + local enabled + local type + local _rest + + local IFS=',' + + # wg0 will always be created for roadwarrior + echo "wg0" + + while read -r id enabled type _rest; do + # Skip peers that are not enabled + [ "${enabled}" = "on" ] || continue + + # Skip anything that isn't a net-to-net connection + [ "${type}" = "net" ] || continue + + echo "wg${id}" + done < /var/ipfire/wireguard/peers + + return 0 +} + +interface_is_rw() { + local intf="${1}" + + [ "${intf}" = "wg0" ] +} + +setup_interface() { + local intf="${1}" + + # Create the interface if it does not exist + if [ ! -d "/sys/class/net/${intf}" ]; then + ip link add "${intf}" type wireguard || return $? + fi - # Optionally set the port - if [ -n "${PORT}" ]; then - echo "ListenPort = ${PORT}" + # Set up the interface + ip link set "${intf}" up + + # Set the MTU + if [ -n "${MTU}" ]; then + ip link set "${intf}" mtu "${MTU}" || return $? fi + # Load the configuration into the kernel + wg syncconf "${intf}" <(generate_config "${intf}") || return $? + + return 0 +} + +cleanup_interfaces() { + local interfaces=( "$(interfaces)" ) + + local intf + for intf in /sys/class/net/wg[0-9]*; do + [ -d "${intf}" ] || continue + + # Remove the path + intf="${intf##*/}" + + local found=0 + local i + + for i in ${interfaces[@]}; do + if [ "${intf}" = "${i}" ]; then + found=1 + break + fi + done + + if [ "${found}" -eq 0 ]; then + ip link del "${intf}" + fi + done + + return 0 +} + +generate_config() { + local intf="${1}" + + # Flush all previously set routes + ip route flush dev "${intf}" + local IFS=',' local id @@ -43,8 +118,10 @@ generate_config() { local type local name local pubkey - local endpoint + local privkey local port + local endpoint_addr + local endpoint_port local remote_subnets local remarks local local_subnets @@ -52,39 +129,83 @@ generate_config() { local keepalive local _rest - local local_subnet - local remote_subnet + # Handles the special case of the RW interface + if interface_is_rw "${intf}"; then + echo "[Interface]" + echo "PrivateKey = ${PRIVATE_KEY}" - # Flush firewall rules - iptables -F WGBLOCK + # Optionally set the port + if [ -n "${PORT}" ]; then + echo "ListenPort = ${PORT}" + fi - # Flush all previously set routes - ip route flush dev "${INTF}" + # Add the client pool + if [ -n "${CLIENT_POOL}" ]; then + ip route add "${CLIENT_POOL}" dev "${intf}" + fi + + while read -r id enabled type name pubkey privkey port endpoint_addr endpoint_port \ + remote_subnets remarks local_subnets psk keepalive _rest; do + # Skip peers that are not hosts or not enabled + [ "${type}" = "host" ] || continue + [ "${enabled}" = "on" ] || continue + + echo "[Peer]" + echo "PublicKey = ${pubkey}" + + # Set PSK (if set) + if [ -n "${psk}" ]; then + echo "PresharedKey = ${psk}" + fi + + # Set routes + if [ -n "${remote_subnets}" ]; then + echo "AllowedIPs = ${remote_subnets//|/, }" + fi + + echo # newline + done < /var/ipfire/wireguard/peers - # Add the client pool - if [ -n "${CLIENT_POOL}" ]; then - ip route add "${CLIENT_POOL}" dev "${INTF}" + return 0 fi - local args - local src + local local_subnet + local remote_subnet + + while read -r id enabled type name pubkey privkey port endpoint_addr endpoint_port \ + remote_subnets remarks local_subnets psk keepalive _rest; do + # Check for the matching connection + [ "${type}" = "net" ] || continue + [ "${intf}" = "wg${id}" ] || continue - while read -r id enabled type name pubkey endpoint port remote_subnets \ - remarks local_subnets psk keepalive _rest; do # Skip peers that are not enabled [ "${enabled}" = "on" ] || continue + echo "[Interface]" + + if [ -n "${privkey}" ]; then + echo "PrivateKey = ${privkey}" + fi + + # Optionally set the port + if [ -n "${port}" ]; then + echo "ListenPort = ${port}" + + # Open the port + iptables -A WGINPUT -p udp --dport "${port}" -j ACCEPT + fi + echo "[Peer]" echo "PublicKey = ${pubkey}" # Set PSK (if set) if [ -n "${psk}" ]; then - echo "PresharedKey= ${psk}" + echo "PresharedKey = ${psk}" fi # Set endpoint - if [ -n "${endpoint}" ]; then - echo "Endpoint = ${endpoint}${port:+:}${port}" + if [ -n "${endpoint_addr}" ]; then + echo "Endpoint = ${endpoint_addr}${endpoint_port:+:}${endpoint_port}" fi # Set routes @@ -92,25 +213,23 @@ generate_config() { echo "AllowedIPs = ${remote_subnets//|/, }" # Apply the routes - if [ "${type}" = "net" ]; then - local_subnets=( "${local_subnets//|/,}" ) + local_subnets=( "${local_subnets//|/,}" ) - # Find an IP address of the firewall that is inside the routed subnet - src="$(ipfire_address_in_networks "${local_subnets[@]}")" + # Find an IP address of the firewall that is inside the routed subnet + local src="$(ipfire_address_in_networks "${local_subnets[@]}")" - for remote_subnet in ${remote_subnets//|/,}; do - args=( - "${remote_subnet}" "dev" "${INTF}" - ) + for remote_subnet in ${remote_subnets//|/,}; do + local args=( + "${remote_subnet}" "dev" "${intf}" + ) - # Add the preferred source if we found one - if [ -n "${src}" ]; then - args+=( "src" "${src}" ) - fi + # Add the preferred source if we found one + if [ -n "${src}" ]; then + args+=( "src" "${src}" ) + fi - ip route add "${args[@]}" - done - fi + ip route add "${args[@]}" + done fi # Set keepalive @@ -121,14 +240,14 @@ generate_config() { # Set blocking rules for local_subnet in ${local_subnets//|/ }; do for remote_subnet in ${remote_subnets//|/ }; do - iptables -A WGBLOCK \ + iptables -I WGBLOCK \ -s "${remote_subnet}" -d "${local_subnet}" -j RETURN done done - done < /var/ipfire/wireguard/peers - # Block all other traffic - iptables -A WGBLOCK -j REJECT --reject-with icmp-admin-prohibited + # There will only be one match, so we can break as soon we get here + break + done < /var/ipfire/wireguard/peers } reload_firewall() { @@ -138,38 +257,45 @@ reload_firewall() { if [ "${ENABLED}" = "on" ]; then iptables -A WGINPUT -p udp --dport "${PORT}" -j ACCEPT fi + + iptables -F WGBLOCK + + # Block all other traffic + iptables -A WGBLOCK -j REJECT --reject-with icmp-admin-prohibited } wg_start() { - # Reload the firewall - reload_firewall + local failed=0 + local intf - # Create the interface if it does not exist - if [ ! -d "/sys/class/net/${INTF}" ]; then - ip link add "${INTF}" type wireguard || return $? - fi + # Find all interfaces + local interfaces=( "$(interfaces)" ) - # Set up the interface - ip link set "${INTF}" up + # Shut down any unwanted interfaces + cleanup_interfaces - # Set the MTU - if [ -n "${MTU}" ]; then - ip link set "${INTF}" mtu "${MTU}" || return $? - fi + # Reload the firewall + reload_firewall - # Load the configuration into the kernel - wg syncconf "${INTF}" <(generate_config) || return $? + # Setup all interfaces + for intf in ${interfaces[@]}; do + setup_interface "${intf}" || failed=1 + done - return 0 + return ${failed} } wg_stop() { + local intf + # Reload the firewall ENABLED=off reload_firewall - if [ -d "/sys/class/net/${INTF}" ]; then - ip link del "${INTF}" || return $? - fi + for intf in /sys/class/net/wg[0-9]*; do + ip link del "${intf##*/}" + done + + return 0 } case "${1}" in