From 87d9a034d7f2fe48ee6ded006d5936ca3680394e Mon Sep 17 00:00:00 2001 From: SalmaElsoly Date: Mon, 16 Feb 2026 13:12:07 +0200 Subject: [PATCH 1/2] refactor: enhance backend validation to prevent infinite loops --- pkg/gateway/gateway.go | 8 ++++---- pkg/gateway_light/gateway.go | 16 +++++++++++---- pkg/gridtypes/zos/gw.go | 40 ++++++++++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/pkg/gateway/gateway.go b/pkg/gateway/gateway.go index febb545c..606e54a3 100644 --- a/pkg/gateway/gateway.go +++ b/pkg/gateway/gateway.go @@ -588,7 +588,7 @@ func (g *gatewayModule) SetNamedProxy(wlID string, config zos.GatewayNameProxy) }, } - if err := g.setupRouting(ctx, wlID, fqdn, gatewayTLSConfig, config.GatewayBase); err != nil { + if err := g.setupRouting(ctx, wlID, fqdn, gatewayTLSConfig, config.GatewayBase, cfg.IPv4.IP, cfg.IPv6.IP); err != nil { return "", err } @@ -622,14 +622,14 @@ func (g *gatewayModule) SetFQDNProxy(wlID string, config zos.GatewayFQDNProxy) e }, } - return g.setupRouting(ctx, wlID, config.FQDN, gatewayTLSConfig, config.GatewayBase) + return g.setupRouting(ctx, wlID, config.FQDN, gatewayTLSConfig, config.GatewayBase, cfg.IPv4.IP, cfg.IPv6.IP) } -func (g *gatewayModule) setupRouting(ctx context.Context, wlID string, fqdn string, tlsConfig TlsConfig, config zos.GatewayBase) error { +func (g *gatewayModule) setupRouting(ctx context.Context, wlID string, fqdn string, tlsConfig TlsConfig, config zos.GatewayBase, nodeIPs ...net.IP) error { g.domainLock.Lock() defer g.domainLock.Unlock() - if err := zos.ValidateBackends(config.Backends, config.TLSPassthrough); err != nil { + if err := zos.ValidateBackends(config.Backends, config.TLSPassthrough, nodeIPs...); err != nil { return err } diff --git a/pkg/gateway_light/gateway.go b/pkg/gateway_light/gateway.go index 26ec1d4c..ef55560b 100644 --- a/pkg/gateway_light/gateway.go +++ b/pkg/gateway_light/gateway.go @@ -584,6 +584,10 @@ func (g *gatewayModule) SetNamedProxy(wlID string, config zos.GatewayNameProxy) return "", errors.New("node doesn't support name proxy (doesn't have a domain)") } + // Get public config for node IP validation + netStub := stubs.NewNetworkerLightStub(g.cl) + pubConfig, _ := netStub.LoadPublicConfig(ctx) + if err := g.validateNameContract(config.Name, twinID); err != nil { return "", errors.Wrap(err, "failed to verify name contract") } @@ -599,7 +603,7 @@ func (g *gatewayModule) SetNamedProxy(wlID string, config zos.GatewayNameProxy) }, } - if err := g.setupRouting(ctx, wlID, fqdn, gatewayTLSConfig, config.GatewayBase); err != nil { + if err := g.setupRouting(ctx, wlID, fqdn, gatewayTLSConfig, config.GatewayBase, pubConfig.IPv4.IP, pubConfig.IPv6.IP); err != nil { return "", err } @@ -618,6 +622,10 @@ func (g *gatewayModule) SetFQDNProxy(wlID string, config zos.GatewayFQDNProxy) e return err } + // Get public config for node IP validation + netStub := stubs.NewNetworkerLightStub(g.cl) + pubConfig, _ := netStub.LoadPublicConfig(ctx) + if domain != "" && strings.HasSuffix(config.FQDN, domain) { return errors.New("can't create a fqdn workload with a subdomain of the gateway's managed domain") } @@ -633,14 +641,14 @@ func (g *gatewayModule) SetFQDNProxy(wlID string, config zos.GatewayFQDNProxy) e }, } - return g.setupRouting(ctx, wlID, config.FQDN, gatewayTLSConfig, config.GatewayBase) + return g.setupRouting(ctx, wlID, config.FQDN, gatewayTLSConfig, config.GatewayBase, pubConfig.IPv4.IP, pubConfig.IPv6.IP) } -func (g *gatewayModule) setupRouting(ctx context.Context, wlID string, fqdn string, tlsConfig TlsConfig, config zos.GatewayBase) error { +func (g *gatewayModule) setupRouting(ctx context.Context, wlID string, fqdn string, tlsConfig TlsConfig, config zos.GatewayBase, nodeIPs ...net.IP) error { g.domainLock.Lock() defer g.domainLock.Unlock() - if err := zos.ValidateBackends(config.Backends, config.TLSPassthrough); err != nil { + if err := zos.ValidateBackends(config.Backends, config.TLSPassthrough, nodeIPs...); err != nil { return err } diff --git a/pkg/gridtypes/zos/gw.go b/pkg/gridtypes/zos/gw.go index 3c159efd..d02b2d29 100644 --- a/pkg/gridtypes/zos/gw.go +++ b/pkg/gridtypes/zos/gw.go @@ -6,6 +6,7 @@ import ( "math" "net" "net/url" + "slices" "strconv" "github.com/hashicorp/go-multierror" @@ -45,14 +46,49 @@ func (b Backend) Valid(tlsPassthrough bool) error { return nil } -func ValidateBackends(backends []Backend, tlsPassthrough bool) error { +func ValidateBackends(backends []Backend, tlsPassthrough bool, nodeIPs ...net.IP) error { var errs error for _, backend := range backends { if err := backend.Valid(tlsPassthrough); err != nil { errs = multierror.Append(errs, errors.Wrapf(err, "failed to validate backend '%s'", backend)) } } - return errs + if errs != nil { + return errs + } + + // Check that backends don't point to the node's own public IPs (prevents infinite loops) + for _, backend := range backends { + backendIP, err := backend.ExtractIP() + if err != nil { + return errors.Wrapf(err, "failed to extract IP from backend '%s'", backend) + } + if slices.ContainsFunc(nodeIPs, backendIP.Equal) { + return fmt.Errorf("backend %s points to the node's own public IP address", backend) + } + } + return nil +} + +// ExtractIP extracts the IP address from a backend string. +func (b Backend) ExtractIP() (net.IP, error) { + // Try ip:port format first + if ip, _, err := asIpPort(string(b)); err == nil { + return ip, nil + } + + // Try URL format + u, err := url.Parse(string(b)) + if err != nil { + return nil, fmt.Errorf("failed to parse backend: %w", err) + } + + ip := net.ParseIP(u.Hostname()) + if ip == nil { + return nil, fmt.Errorf("invalid ip address in backend: %s", u.Hostname()) + } + + return ip, nil } func asIpPort(a string) (ip net.IP, port uint16, err error) { From 37af78e3d4bfc16cd286e6d43a1cc52ef49c7a88 Mon Sep 17 00:00:00 2001 From: SalmaElsoly Date: Tue, 17 Feb 2026 16:00:05 +0200 Subject: [PATCH 2/2] refactor: handle errors when loading public config --- pkg/gateway_light/gateway.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/gateway_light/gateway.go b/pkg/gateway_light/gateway.go index ef55560b..d3fdd053 100644 --- a/pkg/gateway_light/gateway.go +++ b/pkg/gateway_light/gateway.go @@ -586,7 +586,10 @@ func (g *gatewayModule) SetNamedProxy(wlID string, config zos.GatewayNameProxy) // Get public config for node IP validation netStub := stubs.NewNetworkerLightStub(g.cl) - pubConfig, _ := netStub.LoadPublicConfig(ctx) + pubConfig, err := netStub.LoadPublicConfig(ctx) + if err != nil { + return "", errors.Wrap(err, "failed to load public config") + } if err := g.validateNameContract(config.Name, twinID); err != nil { return "", errors.Wrap(err, "failed to verify name contract") @@ -624,7 +627,10 @@ func (g *gatewayModule) SetFQDNProxy(wlID string, config zos.GatewayFQDNProxy) e // Get public config for node IP validation netStub := stubs.NewNetworkerLightStub(g.cl) - pubConfig, _ := netStub.LoadPublicConfig(ctx) + pubConfig, err := netStub.LoadPublicConfig(ctx) + if err != nil { + return errors.Wrap(err, "failed to load public config") + } if domain != "" && strings.HasSuffix(config.FQDN, domain) { return errors.New("can't create a fqdn workload with a subdomain of the gateway's managed domain")