fix(ssrf): use the net.Dialer from http.DefaultTransport BE-13245 (#3242)

This commit is contained in:
andres-portainer
2026-07-22 15:04:20 -03:00
committed by GitHub
parent 2deb46c0e0
commit a1ef4ba0f9
+15 -6
View File
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"sync/atomic"
@@ -56,20 +57,28 @@ type AllowListService interface {
ReadParsed(id portainer.AllowListKey) (*portainer.ParsedAllowList, error)
}
type dialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
type safeDialer struct {
base net.Dialer
service AllowListService
base dialFunc
}
var globalDialer atomic.Pointer[safeDialer]
// Configure initializes the global SSRF policy with the allow list data service.
// It captures http.DefaultTransport's own dial function.
func Configure(svc AllowListService) error {
if svc == nil {
return errors.New("unable to configure ssrf: service must not be nil")
}
globalDialer.Store(&safeDialer{service: svc})
baseTransport, ok := http.DefaultTransport.(*http.Transport)
if !ok {
return errors.New("unable to configure ssrf: http.DefaultTransport is not an *http.Transport")
}
globalDialer.Store(&safeDialer{service: svc, base: baseTransport.DialContext})
return nil
}
@@ -117,7 +126,7 @@ func CheckURL(ctx context.Context, rawURL string) error {
}
// DialContext resolves addr, validates all resolved IPs against the allowlist policy,
// then dials using the first resolved IP to prevent DNS rebinding attacks.
// then dials using d.base with the first resolved IP to prevent DNS rebinding attacks.
func (d *safeDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
allowList, err := d.service.ReadParsed(portainer.AllowListSSRF)
if err != nil {
@@ -125,7 +134,7 @@ func (d *safeDialer) DialContext(ctx context.Context, network, addr string) (net
}
if allowList.Mode == portainer.SSRFModeOff {
return d.base.DialContext(ctx, network, addr)
return d.base(ctx, network, addr)
}
host, port, err := net.SplitHostPort(addr)
@@ -147,7 +156,7 @@ func (d *safeDialer) DialContext(ctx context.Context, network, addr string) (net
dialTarget := net.JoinHostPort(resolved[0].IP.String(), port)
if allowList.Hosts[host] || matchesWildcard(host, allowList.Wilds) {
return d.base.DialContext(ctx, network, dialTarget)
return d.base(ctx, network, dialTarget)
}
for _, a := range resolved {
@@ -161,7 +170,7 @@ func (d *safeDialer) DialContext(ctx context.Context, network, addr string) (net
}
}
return d.base.DialContext(ctx, network, dialTarget)
return d.base(ctx, network, dialTarget)
}
func (d *safeDialer) checkHost(ctx context.Context, host string) error {