feat: add web proxy target allowlist support via NEXAVPN_ALWAYS_ALLOW_WEB_PROXY_IPS environment variable
Add alwaysAllowWebProxyTargets function to parse comma-separated IPs from NEXAVPN_ALWAYS_ALLOW_WEB_PROXY_IPS environment variable with deduplication. Update mergeProfileAllowedIPs to accept webProxyTargets parameter and merge them into profile allowed IPs using /32 routes. Add WebProxyTargets field to wireguard.Peer struct and populate it in BuildSyncBundle and device enrollment/policy application
This commit is contained in:
@@ -2,6 +2,7 @@ package device
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -62,7 +63,7 @@ func (s *Service) Enroll(ctx context.Context, userID uuid.UUID, input EnrollRequ
|
|||||||
if len(destinations) == 0 {
|
if len(destinations) == 0 {
|
||||||
destinations = []string{"172.16.10.0/24"}
|
destinations = []string{"172.16.10.0/24"}
|
||||||
}
|
}
|
||||||
profileAllowedIPs := mergeProfileAllowedIPs(destinations, selectedGateway.DNSServers)
|
profileAllowedIPs := mergeProfileAllowedIPs(destinations, selectedGateway.DNSServers, alwaysAllowWebProxyTargets())
|
||||||
|
|
||||||
enrollment.Peer = PeerView{
|
enrollment.Peer = PeerView{
|
||||||
AssignedIP: assignedIP,
|
AssignedIP: assignedIP,
|
||||||
@@ -184,13 +185,13 @@ func (s *Service) applyCurrentPolicy(ctx context.Context, enrollment EnrollmentR
|
|||||||
Label: destination,
|
Label: destination,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
enrollment.Peer.AllowedIPs = mergeProfileAllowedIPs(destinations, enrollment.Peer.DNSServers)
|
enrollment.Peer.AllowedIPs = mergeProfileAllowedIPs(destinations, enrollment.Peer.DNSServers, alwaysAllowWebProxyTargets())
|
||||||
return withDebugProfile(enrollment), nil
|
return withDebugProfile(enrollment), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeProfileAllowedIPs(destinations []string, dnsServers []string) []string {
|
func mergeProfileAllowedIPs(destinations []string, dnsServers []string, webProxyTargets []string) []string {
|
||||||
seen := make(map[string]struct{}, len(destinations)+len(dnsServers))
|
seen := make(map[string]struct{}, len(destinations)+len(dnsServers)+len(webProxyTargets))
|
||||||
merged := make([]string, 0, len(destinations)+len(dnsServers))
|
merged := make([]string, 0, len(destinations)+len(dnsServers)+len(webProxyTargets))
|
||||||
|
|
||||||
for _, destination := range destinations {
|
for _, destination := range destinations {
|
||||||
destination = strings.TrimSpace(destination)
|
destination = strings.TrimSpace(destination)
|
||||||
@@ -216,6 +217,18 @@ func mergeProfileAllowedIPs(destinations []string, dnsServers []string) []string
|
|||||||
merged = append(merged, route)
|
merged = append(merged, route)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, target := range webProxyTargets {
|
||||||
|
route := dnsServerRoute(target)
|
||||||
|
if route == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := seen[route]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[route] = struct{}{}
|
||||||
|
merged = append(merged, route)
|
||||||
|
}
|
||||||
|
|
||||||
return merged
|
return merged
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,3 +242,25 @@ func dnsServerRoute(value string) string {
|
|||||||
}
|
}
|
||||||
return value + "/32"
|
return value + "/32"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func alwaysAllowWebProxyTargets() []string {
|
||||||
|
raw := os.Getenv("NEXAVPN_ALWAYS_ALLOW_WEB_PROXY_IPS")
|
||||||
|
if strings.TrimSpace(raw) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
targets := make([]string, 0)
|
||||||
|
for _, part := range strings.Split(raw, ",") {
|
||||||
|
value := strings.TrimSpace(part)
|
||||||
|
if value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[value]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[value] = struct{}{}
|
||||||
|
targets = append(targets, value)
|
||||||
|
}
|
||||||
|
return targets
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
@@ -118,12 +120,35 @@ func (r *PGRepository) BuildSyncBundle(ctx context.Context, gatewayID uuid.UUID)
|
|||||||
return wireguard.GatewayBundle{}, err
|
return wireguard.GatewayBundle{}, err
|
||||||
}
|
}
|
||||||
peer.DeviceID = deviceID.String()
|
peer.DeviceID = deviceID.String()
|
||||||
|
peer.WebProxyTargets = alwaysAllowWebProxyTargets()
|
||||||
bundle.Peers = append(bundle.Peers, peer)
|
bundle.Peers = append(bundle.Peers, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bundle, rows.Err()
|
return bundle, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func alwaysAllowWebProxyTargets() []string {
|
||||||
|
raw := os.Getenv("NEXAVPN_ALWAYS_ALLOW_WEB_PROXY_IPS")
|
||||||
|
if strings.TrimSpace(raw) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
targets := make([]string, 0)
|
||||||
|
for _, part := range strings.Split(raw, ",") {
|
||||||
|
value := strings.TrimSpace(part)
|
||||||
|
if value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[value]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[value] = struct{}{}
|
||||||
|
targets = append(targets, value)
|
||||||
|
}
|
||||||
|
return targets
|
||||||
|
}
|
||||||
|
|
||||||
func (r *PGRepository) Update(ctx context.Context, gatewayID uuid.UUID, input UpdateRequest) (Gateway, error) {
|
func (r *PGRepository) Update(ctx context.Context, gatewayID uuid.UUID, input UpdateRequest) (Gateway, error) {
|
||||||
row := r.db.QueryRow(ctx, `
|
row := r.db.QueryRow(ctx, `
|
||||||
update gateways
|
update gateways
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ type Peer struct {
|
|||||||
AssignedIP string `json:"assigned_ip"`
|
AssignedIP string `json:"assigned_ip"`
|
||||||
AllowedDestinations []string `json:"allowed_destinations"`
|
AllowedDestinations []string `json:"allowed_destinations"`
|
||||||
DNSServers []string `json:"dns_servers"`
|
DNSServers []string `json:"dns_servers"`
|
||||||
|
WebProxyTargets []string `json:"web_proxy_targets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GatewayBundle struct {
|
type GatewayBundle struct {
|
||||||
|
|||||||
@@ -24,3 +24,4 @@ NEXAVPN_GATEWAY_INTERFACE=wg0
|
|||||||
NEXAVPN_UPLINK_INTERFACE=eth0
|
NEXAVPN_UPLINK_INTERFACE=eth0
|
||||||
NEXAVPN_ENABLE_MASQUERADE=true
|
NEXAVPN_ENABLE_MASQUERADE=true
|
||||||
NEXAVPN_BACKEND_HOST=127.0.0.1
|
NEXAVPN_BACKEND_HOST=127.0.0.1
|
||||||
|
NEXAVPN_ALWAYS_ALLOW_WEB_PROXY_IPS=172.16.0.109
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ EOF
|
|||||||
echo " iifname \"${IFACE}\" ip saddr ${ASSIGNED_IP} ip daddr ${dns_server} udp dport 53 accept"
|
echo " iifname \"${IFACE}\" ip saddr ${ASSIGNED_IP} ip daddr ${dns_server} udp dport 53 accept"
|
||||||
echo " iifname \"${IFACE}\" ip saddr ${ASSIGNED_IP} ip daddr ${dns_server} tcp dport 53 accept"
|
echo " iifname \"${IFACE}\" ip saddr ${ASSIGNED_IP} ip daddr ${dns_server} tcp dport 53 accept"
|
||||||
done
|
done
|
||||||
|
printf '%s' "${peer}" | jq -r '.web_proxy_targets[]?' | while read -r proxy_target; do
|
||||||
|
echo " iifname \"${IFACE}\" ip saddr ${ASSIGNED_IP} ip daddr ${proxy_target} tcp dport 80 accept"
|
||||||
|
echo " iifname \"${IFACE}\" ip saddr ${ASSIGNED_IP} ip daddr ${proxy_target} tcp dport 443 accept"
|
||||||
|
done
|
||||||
printf '%s' "${peer}" | jq -r '.allowed_destinations[]?' | while read -r destination; do
|
printf '%s' "${peer}" | jq -r '.allowed_destinations[]?' | while read -r destination; do
|
||||||
echo " iifname \"${IFACE}\" ip saddr ${ASSIGNED_IP} ip daddr ${destination} accept"
|
echo " iifname \"${IFACE}\" ip saddr ${ASSIGNED_IP} ip daddr ${destination} accept"
|
||||||
done
|
done
|
||||||
|
|||||||
Reference in New Issue
Block a user