Add support for local endpoint interface allowlist

This commit is contained in:
Owen
2026-09-15 11:08:47 -04:00
parent c6d40f3e44
commit acb014e23e
5 changed files with 89 additions and 35 deletions
+29 -27
View File
@@ -98,20 +98,21 @@ type PeerReading struct {
}
type WireGuardService struct {
interfaceName string
mtu int
client *websocket.Client
config WgConfig
key wgtypes.Key
newtId string
lastReadings map[string]PeerReading
mu sync.Mutex
Port uint16
host string
serverPubKey string
token string
stopGetConfig func()
pendingConfigChainId string
interfaceName string
localEndpointInterfaces []string
mtu int
client *websocket.Client
config WgConfig
key wgtypes.Key
newtId string
lastReadings map[string]PeerReading
mu sync.Mutex
Port uint16
host string
serverPubKey string
token string
stopGetConfig func()
pendingConfigChainId string
// Netstack fields
tun tun.Device
tnet *netstack2.Net
@@ -154,7 +155,7 @@ func generateChainId() string {
return hex.EncodeToString(b)
}
func NewWireGuardService(interfaceName string, port uint16, mtu int, host string, newtId string, wsClient *websocket.Client, dns string, useNativeInterface bool) (*WireGuardService, error) {
func NewWireGuardService(interfaceName string, port uint16, mtu int, host string, newtId string, wsClient *websocket.Client, dns string, useNativeInterface bool, localEndpointInterfaces []string) (*WireGuardService, error) {
key, err := wgtypes.GeneratePrivateKey()
if err != nil {
return nil, fmt.Errorf("failed to generate private key: %v", err)
@@ -195,17 +196,18 @@ func NewWireGuardService(interfaceName string, port uint16, mtu int, host string
dnsAddrs := []netip.Addr{netip.MustParseAddr(dns)}
service := &WireGuardService{
interfaceName: interfaceName,
mtu: mtu,
client: wsClient,
key: key,
newtId: newtId,
host: host,
lastReadings: make(map[string]PeerReading),
Port: port,
dns: dnsAddrs,
sharedBind: sharedBind,
useNativeInterface: useNativeInterface,
interfaceName: interfaceName,
localEndpointInterfaces: localEndpointInterfaces,
mtu: mtu,
client: wsClient,
key: key,
newtId: newtId,
host: host,
lastReadings: make(map[string]PeerReading),
Port: port,
dns: dnsAddrs,
sharedBind: sharedBind,
useNativeInterface: useNativeInterface,
}
// Create the holepunch manager
@@ -532,7 +534,7 @@ func (s *WireGuardService) LoadRemoteConfig() error {
"publicKey": s.key.PublicKey().String(),
"port": s.Port,
"chainId": chainId,
"localEndpoints": network.GetLocalEndpoints(s.Port, s.interfaceName),
"localEndpoints": network.GetLocalEndpoints(s.Port, s.interfaceName, s.localEndpointInterfaces),
}, 2*time.Second)
logger.Debug("Requesting WireGuard configuration from remote server")
+40 -7
View File
@@ -53,11 +53,12 @@ type fileSettings struct {
MTU *int `json:"mtu"`
Port *int `json:"port"`
UseNativeInterface *bool `json:"native"`
UseNativeMainInterface *bool `json:"nativeMain"`
NativeMainInterfaceName *string `json:"interfaceMain"`
NoCloud *bool `json:"noCloud"`
PreferEndpoint *string `json:"preferEndpoint"`
UseNativeInterface *bool `json:"native"`
UseNativeMainInterface *bool `json:"nativeMain"`
NativeMainInterfaceName *string `json:"interfaceMain"`
NoCloud *bool `json:"noCloud"`
PreferEndpoint *string `json:"preferEndpoint"`
LocalEndpointInterfaces []string `json:"localEndpointInterfaces"`
PingInterval *string `json:"pingInterval"`
PingTimeout *string `json:"pingTimeout"`
@@ -294,6 +295,10 @@ func loadNewtConfig() newtpkg.Config {
applyStr(&cfg.NativeMainInterfaceName, fileCfg.NativeMainInterfaceName, "interface-main", sources, sourceFile)
applyBool(&cfg.NoCloud, fileCfg.NoCloud, "no-cloud", sources, sourceFile)
applyStr(&cfg.PreferEndpoint, fileCfg.PreferEndpoint, "prefer-endpoint", sources, sourceFile)
if len(fileCfg.LocalEndpointInterfaces) > 0 {
cfg.LocalEndpointInterfaces = fileCfg.LocalEndpointInterfaces
sources["local-endpoint-interfaces"] = string(sourceFile)
}
applyStr(&pingIntervalStr, fileCfg.PingInterval, "ping-interval", sources, sourceFile)
applyStr(&pingTimeoutStr, fileCfg.PingTimeout, "ping-timeout", sources, sourceFile)
@@ -351,6 +356,16 @@ func loadNewtConfig() newtpkg.Config {
applyEnvBool(&cfg.UseNativeMainInterface, "USE_NATIVE_MAIN_INTERFACE", "native-main", sources)
applyEnvStr(&cfg.NativeMainInterfaceName, "INTERFACE_MAIN", "interface-main", sources)
applyEnvBool(&cfg.NoCloud, "NO_CLOUD", "no-cloud", sources)
if v := os.Getenv("LOCAL_ENDPOINT_INTERFACES"); v != "" {
var names []string
for _, n := range strings.Split(v, ",") {
if t := strings.TrimSpace(n); t != "" {
names = append(names, t)
}
}
cfg.LocalEndpointInterfaces = names
sources["local-endpoint-interfaces"] = string(sourceEnv)
}
applyEnvStr(&pingIntervalStr, "PING_INTERVAL", "ping-interval", sources)
applyEnvStr(&pingTimeoutStr, "PING_TIMEOUT", "ping-timeout", sources)
@@ -416,6 +431,8 @@ func loadNewtConfig() newtpkg.Config {
origTLSCert, origTLSKey, origDockerEnforce := cfg.TLSClientCert, cfg.TLSClientKey, dockerEnforceStr
origHealthFile, origBlueprintFile, origProvBlueprintFile := cfg.HealthFile, cfg.BlueprintFile, cfg.ProvisioningBlueprintFile
origNoCloud, origTLSPrivateKey := cfg.NoCloud, cfg.TLSPrivateKey
localEndpointInterfacesStr := strings.Join(cfg.LocalEndpointInterfaces, ",")
origLocalEndpointInterfaces := localEndpointInterfacesStr
origMetrics, origOTLP, origAdminAddr := cfg.MetricsEnabled, cfg.OTLPEnabled, cfg.AdminAddr
origMetricsAsync, origPprof, origRegion := cfg.MetricsAsyncBytes, cfg.PprofEnabled, cfg.Region
origADKey, origADPrincipals, origADCACert := cfg.AuthDaemonKey, cfg.AuthDaemonPrincipalsFile, cfg.AuthDaemonCACertPath
@@ -442,6 +459,7 @@ func loadNewtConfig() newtpkg.Config {
flag.StringVar(&pingTimeoutStr, "ping-timeout", pingTimeoutStr, "Timeout for each ping (default 7s)")
flag.StringVar(&udpProxyIdleTimeoutStr, "udp-proxy-idle-timeout", udpProxyIdleTimeoutStr, "Idle timeout for UDP proxied client flows before cleanup")
flag.StringVar(&cfg.PreferEndpoint, "prefer-endpoint", cfg.PreferEndpoint, "Prefer this endpoint for the connection (if set, will override the endpoint from the server)")
flag.StringVar(&localEndpointInterfacesStr, "local-endpoint-interfaces", localEndpointInterfacesStr, "Comma-separated list of network interface names to restrict reported local endpoints to (default: report all interfaces)")
flag.StringVar(&cfg.ProvisioningKey, "provisioning-key", cfg.ProvisioningKey, "One-time provisioning key used to obtain a newt ID and secret from the server")
flag.StringVar(&cfg.NewtName, "name", cfg.NewtName, "Name for the site created during provisioning (supports {{env.VAR}} interpolation)")
flag.StringVar(&cfg.ConfigFile, "config-file", configPath, "Path to config file (overrides CONFIG_FILE env var and default location)")
@@ -517,6 +535,7 @@ func loadNewtConfig() newtpkg.Config {
markCLI("blueprint-file", cfg.BlueprintFile != origBlueprintFile)
markCLI("provisioning-blueprint-file", cfg.ProvisioningBlueprintFile != origProvBlueprintFile)
markCLI("no-cloud", cfg.NoCloud != origNoCloud)
markCLI("local-endpoint-interfaces", localEndpointInterfacesStr != origLocalEndpointInterfaces)
markCLI("metrics", cfg.MetricsEnabled != origMetrics)
markCLI("otlp", cfg.OTLPEnabled != origOTLP)
markCLI("metrics-admin-addr", cfg.AdminAddr != origAdminAddr)
@@ -538,7 +557,7 @@ func loadNewtConfig() newtpkg.Config {
}
if *showConfig {
printShowConfig(cfg, sources, configPath, mtuStr, portStr, pingIntervalStr, pingTimeoutStr, udpProxyIdleTimeoutStr, dockerEnforceStr)
printShowConfig(cfg, sources, configPath, mtuStr, portStr, pingIntervalStr, pingTimeoutStr, udpProxyIdleTimeoutStr, dockerEnforceStr, localEndpointInterfacesStr)
os.Exit(0)
}
@@ -554,6 +573,19 @@ func loadNewtConfig() newtpkg.Config {
}
}
// Parse local endpoint interface allowlist (after flag.Parse so CLI takes effect)
if localEndpointInterfacesStr != "" {
var names []string
for _, n := range strings.Split(localEndpointInterfacesStr, ",") {
if t := strings.TrimSpace(n); t != "" {
names = append(names, t)
}
}
cfg.LocalEndpointInterfaces = names
} else {
cfg.LocalEndpointInterfaces = nil
}
// Parse MTU
if mtuStr == "" {
mtuStr = "1280"
@@ -581,7 +613,7 @@ func loadNewtConfig() newtpkg.Config {
}
// printShowConfig prints the resolved configuration and the source of each value
func printShowConfig(cfg newtpkg.Config, sources map[string]string, configPath, mtuStr, portStr, pingIntervalStr, pingTimeoutStr, udpProxyIdleTimeoutStr, dockerEnforceStr string) {
func printShowConfig(cfg newtpkg.Config, sources map[string]string, configPath, mtuStr, portStr, pingIntervalStr, pingTimeoutStr, udpProxyIdleTimeoutStr, dockerEnforceStr, localEndpointInterfacesStr string) {
getSource := func(key string) string {
if s, ok := sources[key]; ok && s != "" {
return s
@@ -629,6 +661,7 @@ func printShowConfig(cfg newtpkg.Config, sources map[string]string, configPath,
fmt.Printf(" native-main = %v [%s]\n", cfg.UseNativeMainInterface, getSource("native-main"))
fmt.Printf(" interface-main = %s [%s]\n", cfg.NativeMainInterfaceName, getSource("interface-main"))
fmt.Printf(" no-cloud = %v [%s]\n", cfg.NoCloud, getSource("no-cloud"))
fmt.Printf(" local-endpoint-interfaces = %s [%s]\n", mask("local-endpoint-interfaces", localEndpointInterfacesStr), getSource("local-endpoint-interfaces"))
fmt.Println("\nLogging:")
fmt.Printf(" log-level = %s [%s]\n", cfg.LogLevel, getSource("log-level"))
+18 -1
View File
@@ -98,15 +98,27 @@ func interfaceScore(name string) int {
// name of our own WireGuard/TUN interface, whose address is the tunnel IP
// and not a useful endpoint to advertise.
//
// allowedInterfaces, if non-empty, restricts the result to only those
// interface names (an allowlist), letting callers report a single known-good
// interface instead of every candidate on the host.
//
// If interfaces cannot be enumerated (e.g. insufficient OS permissions),
// an info message is logged and an empty slice is returned.
func GetLocalEndpoints(port uint16, excludeInterface string) []string {
func GetLocalEndpoints(port uint16, excludeInterface string, allowedInterfaces []string) []string {
ifaces, err := net.Interfaces()
if err != nil {
logger.Info("Unable to enumerate local network interfaces, localEndpoints will not be reported: %v", err)
return nil
}
var allowedSet map[string]struct{}
if len(allowedInterfaces) > 0 {
allowedSet = make(map[string]struct{}, len(allowedInterfaces))
for _, name := range allowedInterfaces {
allowedSet[name] = struct{}{}
}
}
type candidate struct {
score int
ip string
@@ -117,6 +129,11 @@ func GetLocalEndpoints(port uint16, excludeInterface string) []string {
if excludeInterface != "" && iface.Name == excludeInterface {
continue
}
if allowedSet != nil {
if _, ok := allowedSet[iface.Name]; !ok {
continue
}
}
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
+1
View File
@@ -42,6 +42,7 @@ func (n *Newt) setupClients() {
n.client,
n.config.DNS,
n.config.UseNativeInterface,
n.config.LocalEndpointInterfaces,
)
if err != nil {
logger.Fatal("Failed to create WireGuard service: %v", err)
+1
View File
@@ -29,6 +29,7 @@ type Config struct {
NativeMainInterfaceName string
NoCloud bool
PreferEndpoint string
LocalEndpointInterfaces []string
// Timing
PingInterval time.Duration