network

package
v0.0.0-...-1a28f28 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2026 License: MIT Imports: 9 Imported by: 0

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

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

func IsBlockedAddressError(err error) bool

IsBlockedAddressError returns true if err is or wraps a BlockedAddressError.

func SafeClient

func SafeClient() *http.Client

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

type BlockedAddressError struct {
	Host   string
	Reason string
}

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

type DialContextFunc func(ctx context.Context, network, address string) (net.Conn, error)

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

func WithAllowedCIDRs(prefixes ...netip.Prefix) Option

WithAllowedCIDRs allows explicit CIDR allowlisting.

func WithAllowedHosts

func WithAllowedHosts(hosts ...string) Option

WithAllowedHosts allows explicit host allowlisting.

func WithBlockedHosts

func WithBlockedHosts(hosts ...string) Option

WithBlockedHosts sets additional blocked hostnames.

func WithDialer

func WithDialer(dialer *net.Dialer) Option

WithDialer sets a custom underlying net.Dialer.

func WithResolver

func WithResolver(resolver *net.Resolver) Option

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

func (d *SafeDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL