Documentation
¶
Overview ¶
Package network provides secure networking primitives for Deputy.
SSRF Protection ¶
The SafeDialer prevents Server-Side Request Forgery (SSRF) attacks by validating IP addresses at connection time, after DNS resolution. This eliminates DNS rebinding vulnerabilities where an attacker-controlled domain resolves to internal IPs.
Two-Layer Defense ¶
For complete SSRF protection, use both targets.ValidateRemoteTarget (fast early rejection with helpful error messages) and SafeDialer (connection-time validation):
// Layer 1: Early rejection with helpful error messages
if err := targets.ValidateRemoteTarget(userInput); err != nil {
return err // User gets guidance like "use git URL or container reference"
}
// Layer 2: Connection-time validation (catches DNS rebinding)
client := &http.Client{
Transport: &http.Transport{
DialContext: network.NewSafeDialer().DialContext,
},
}
Composability ¶
SafeDialer implements the standard DialContextFunc signature, making it composable with any Go networking code:
// HTTP clients
client := network.SafeClient()
// Custom http.Transport
transport := &http.Transport{
DialContext: network.NewSafeDialer().DialContext,
}
// gRPC connections
conn, err := grpc.Dial(target,
grpc.WithContextDialer(network.NewSafeDialer().DialContext),
)
// ConnectRPC clients
client := scanv1connect.NewScanServiceClient(
network.SafeClient(),
serverURL,
)
// Database drivers with custom connectors
dialer := network.NewSafeDialer()
db.SetConnector(&customConnector{dial: dialer.DialContext})
// Wrapping existing dialers
existingTransport := http.DefaultTransport.(*http.Transport).Clone()
existingTransport.DialContext = network.WrapDialer(existingTransport.DialContext)
Configuration ¶
SafeDialer blocks by default:
- Loopback addresses (127.x.x.x, ::1, 0.0.0.0)
- Private networks (10.x, 172.16-31.x, 192.168.x, fc00::/7)
- Link-local addresses (169.254.x.x, fe80::) including cloud metadata
- Known metadata hostnames (localhost, metadata.google.internal, etc.)
Use options to customize:
// Allow private networks for internal service mesh
dialer := network.NewSafeDialerWithOptions(network.WithAllowPrivate())
// Allow explicit internal hosts and CIDRs
dialer := network.NewSafeDialerWithOptions(
network.WithAllowedHosts(".corp.local"),
network.WithAllowedCIDRs(netip.MustParsePrefix("10.0.0.0/8")),
)
// Allow loopback for development
dialer := network.NewSafeDialerWithOptions(network.WithAllowLoopback())
// Custom timeout
dialer := network.NewSafeDialerWithOptions(
network.WithDialer(&net.Dialer{Timeout: 5 * time.Second}),
)
// Block additional hostnames
dialer := network.NewSafeDialerWithOptions(
network.WithBlockedHosts("internal.company.com"),
)
Process-wide defaults:
// Apply default options to all new SafeDialer instances
network.SetDefaultSafeDialerOptions(
network.WithAllowedHosts(".corp.local"),
)
Error Handling ¶
When a connection is blocked, SafeDialer returns a BlockedAddressError:
conn, err := dialer.DialContext(ctx, "tcp", "169.254.169.254:80")
if network.IsBlockedAddressError(err) {
// Handle SSRF attempt
log.Warn("blocked SSRF attempt", "error", err)
}
Package network provides secure networking primitives for Deputy.
The SafeDialer prevents SSRF attacks by validating resolved IP addresses at connection time, after DNS resolution. This eliminates DNS rebinding vulnerabilities where an attacker could use a domain that resolves to internal IPs.
Composability ¶
SafeDialer implements the standard DialContext signature, making it composable with any Go networking code that accepts a dialer function:
// With http.Transport
transport := &http.Transport{
DialContext: network.NewSafeDialer().DialContext,
}
// With grpc.Dial
conn, err := grpc.Dial(target,
grpc.WithContextDialer(network.NewSafeDialer().DialContext),
)
// With database drivers
dialer := network.NewSafeDialer()
db.SetConnector(&customConnector{dial: dialer.DialContext})
// Wrapping an existing dialer
existingDialer := &net.Dialer{Timeout: 10 * time.Second}
safeDialer := &network.SafeDialer{Dialer: existingDialer}
Two-Layer Defense ¶
For complete SSRF protection, use both ValidateRemoteTarget (fast early rejection) and SafeDialer (connection-time validation):
// Layer 1: Early rejection with helpful error messages
if err := targets.ValidateRemoteTarget(userInput); err != nil {
return err // User gets guidance on valid inputs
}
// Layer 2: Connection-time validation (catches DNS rebinding)
client := &http.Client{
Transport: &http.Transport{
DialContext: network.NewSafeDialer().DialContext,
},
}
Index ¶
- func DefaultBlockedHosts() []string
- func IsBlockedAddressError(err error) bool
- func SafeClient() *http.Client
- func SafeTransport() http.RoundTripper
- func SafeTransportWithOptions(opts ...Option) http.RoundTripper
- func SetDefaultSafeDialerOptions(opts ...Option)
- type BlockedAddressError
- type DialContextFunc
- type Option
- func WithAllowLinkLocal() Option
- func WithAllowLoopback() Option
- func WithAllowPrivate() Option
- func WithAllowedCIDRs(prefixes ...netip.Prefix) Option
- func WithAllowedHosts(hosts ...string) Option
- func WithBlockedHosts(hosts ...string) Option
- func WithDialer(dialer *net.Dialer) Option
- func WithResolver(resolver *net.Resolver) Option
- type SafeDialer
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultBlockedHosts ¶
func DefaultBlockedHosts() []string
DefaultBlockedHosts returns the default list of blocked hostnames.
func IsBlockedAddressError ¶
IsBlockedAddressError returns true if err is or wraps a BlockedAddressError.
func SafeClient ¶
SafeClient returns an http.Client that uses SafeDialer for SSRF protection. Use this client for any HTTP requests to user-controlled URLs on remote servers.
func SafeTransport ¶
func SafeTransport() http.RoundTripper
SafeTransport returns an http.RoundTripper that uses SafeDialer for SSRF protection. It prevents connections to private networks, loopback, and cloud metadata endpoints.
func SafeTransportWithOptions ¶
func SafeTransportWithOptions(opts ...Option) http.RoundTripper
SafeTransportWithOptions returns an http.RoundTripper with custom SafeDialer options.
func SetDefaultSafeDialerOptions ¶
func SetDefaultSafeDialerOptions(opts ...Option)
SetDefaultSafeDialerOptions sets default SafeDialer options applied to all new dialers. This is intended for process-wide configuration, such as server egress allowlists.
Types ¶
type BlockedAddressError ¶
BlockedAddressError is returned when a connection is blocked due to SSRF protection.
func (*BlockedAddressError) Error ¶
func (e *BlockedAddressError) Error() string
func (*BlockedAddressError) Is ¶
func (e *BlockedAddressError) Is(target error) bool
Is implements errors.Is for BlockedAddressError.
type DialContextFunc ¶
DialContextFunc is the standard signature for context-aware dial functions. This matches net.Dialer.DialContext, http.Transport.DialContext, and grpc.WithContextDialer.
func WrapDialer ¶
func WrapDialer(dial DialContextFunc, opts ...Option) DialContextFunc
WrapDialer wraps an existing DialContextFunc with SSRF protection. This is useful when you need to add safety to an existing dialer function.
existingDial := myTransport.DialContext myTransport.DialContext = network.WrapDialer(existingDial)
type Option ¶
type Option func(*SafeDialer)
Option configures a SafeDialer. Use the With* functions to create options.
func WithAllowLinkLocal ¶
func WithAllowLinkLocal() Option
WithAllowLinkLocal allows connections to link-local addresses.
func WithAllowLoopback ¶
func WithAllowLoopback() Option
WithAllowLoopback allows connections to loopback addresses.
func WithAllowPrivate ¶
func WithAllowPrivate() Option
WithAllowPrivate allows connections to private network ranges.
func WithAllowedCIDRs ¶
WithAllowedCIDRs allows explicit CIDR allowlisting.
func WithAllowedHosts ¶
WithAllowedHosts allows explicit host allowlisting.
func WithBlockedHosts ¶
WithBlockedHosts sets additional blocked hostnames.
func WithDialer ¶
WithDialer sets a custom underlying net.Dialer.
func WithResolver ¶
WithResolver sets a custom DNS resolver.
type SafeDialer ¶
type SafeDialer struct {
// Dialer is the underlying dialer. If nil, a default dialer is used.
// You can set this to wrap an existing *net.Dialer with custom settings.
Dialer *net.Dialer
// AllowPrivate allows connections to private network ranges (10.x, 172.16-31.x, 192.168.x).
// Default: false (block private networks)
AllowPrivate bool
// AllowLoopback allows connections to loopback addresses (127.x.x.x, ::1).
// Default: false (block loopback)
AllowLoopback bool
// AllowLinkLocal allows connections to link-local addresses (169.254.x.x, fe80::).
// This includes cloud metadata endpoints.
// Default: false (block link-local)
AllowLinkLocal bool
// AllowedHosts is a list of hostnames allowed to resolve to private IPs.
// Entries may be exact hosts or suffixes prefixed with a dot (e.g., ".corp.local").
AllowedHosts []string
// AllowedCIDRs is a list of CIDR ranges allowed for outbound connections.
AllowedCIDRs []netip.Prefix
// BlockedHosts is a list of hostnames to block (case-insensitive).
// Common metadata hostnames are blocked by default.
BlockedHosts []string
// Resolver is used for DNS lookups. If nil, net.DefaultResolver is used.
Resolver *net.Resolver
}
SafeDialer wraps a dialer with IP validation to prevent SSRF attacks. It resolves hostnames and validates IP addresses before connecting, eliminating DNS rebinding vulnerabilities.
SafeDialer is safe for concurrent use.
func NewSafeDialer ¶
func NewSafeDialer() *SafeDialer
NewSafeDialer creates a SafeDialer with secure defaults. It blocks private networks, loopback, link-local, and common metadata hostnames.
Use NewSafeDialerWithOptions to customize behavior:
dialer := network.NewSafeDialerWithOptions(
network.WithAllowPrivate(),
network.WithDialer(&net.Dialer{Timeout: 10 * time.Second}),
)
func NewSafeDialerWithOptions ¶
func NewSafeDialerWithOptions(opts ...Option) *SafeDialer
NewSafeDialerWithOptions creates a SafeDialer with custom options.
// Allow connections to private networks (for internal services)
dialer := network.NewSafeDialerWithOptions(network.WithAllowPrivate())
// Use a custom timeout
dialer := network.NewSafeDialerWithOptions(
network.WithDialer(&net.Dialer{Timeout: 5 * time.Second}),
)
func (*SafeDialer) Dial ¶
func (d *SafeDialer) Dial(network, address string) (net.Conn, error)
Dial is a convenience method that calls DialContext with context.Background(). Prefer DialContext for production code.
func (*SafeDialer) DialContext ¶
DialContext connects to the address on the named network, validating that resolved IPs are not in blocked ranges.
This method implements the standard DialContextFunc signature, making it composable with http.Transport, grpc.Dial, and other Go networking APIs.