proxy

package
v0.1.12 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2021 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package proxy provides a simple proxy. The proxy can be protected with basic auth. It can also forward connections to a parent proxy, and authorize connections against that. Both local, and parent credentials can be set via environment variables. For local proxy credential, set `PROXY_CREDENTIAL`. For remote proxy credential, set `PROXY_PARENT_CREDENTIAL`.

Index

Examples

Constants

View Source
const (
	ConstantBackoff = 300
	DNSTimeout      = 1 * time.Minute
	MaxRetry        = 3
)
View Source
const (
	HTTP   = "http"
	HTTPS  = "https"
	SOCKS5 = "socks5"
	SOCKS  = "socks"
	QUIC   = "quic"
)

Valid proxy schemes.

Variables

View Source
var (
	ErrFailedToAllocatePort    = customerror.New("No available port to use", "", http.StatusInternalServerError, nil)
	ErrFailedToDialToDNS       = customerror.NewFailedToError("dial to DNS", "", nil)
	ErrFailedToStartProxy      = customerror.NewFailedToError("start proxy", "", nil)
	ErrInvalidDNSURI           = customerror.NewInvalidError("dns URI", "", nil)
	ErrInvalidLocalProxyURI    = customerror.NewInvalidError("local proxy URI", "", nil)
	ErrInvalidOrParentOrPac    = customerror.NewInvalidError("params. Can't set upstream proxy, and PAC at the same time", "", nil)
	ErrInvalidPACProxyURI      = customerror.NewInvalidError("PAC proxy URI", "", nil)
	ErrInvalidPACURI           = customerror.NewInvalidError("PAC URI", "", nil)
	ErrInvalidProxyParams      = customerror.NewInvalidError("params", "", nil)
	ErrInvalidUpstreamProxyURI = customerror.NewInvalidError("upstream proxy URI", "", nil)
)
View Source
var ErrFailedToCopyOptions = customerror.NewFailedToError("deepCopy options", "", nil)

Functions

This section is empty.

Types

type LoggingOptions

type LoggingOptions = logger.Options

LoggingOptions defines logging options.

type Mode added in v0.1.3

type Mode string

Possible ways to run Forwarder.

const (
	Direct   Mode = "DIRECT"
	Upstream Mode = "Upstream"
	PAC      Mode = "PAC"
)

type Options added in v0.1.3

type Options struct {
	*LoggingOptions

	*RetryPortOptions

	// AutomaticallyRetryPort if `true`, and the specified port is in-use, will
	// try to automatically allocate a free port.
	AutomaticallyRetryPort bool

	// DNSURIs are DNS URIs:
	// - Known protocol: udp, tcp
	// - Some hostname (x.io - min 4 chars), or IP
	// - Port in a valid range: 53 - 65535.
	// Example: udp://10.0.0.3:53
	DNSURIs []string `json:"dns_uris" validate:"omitempty,dive,dnsURI"`

	// ProxyLocalhost if `true`, requests to `localhost`/`127.0.0.1` will be
	// forwarded to any upstream - if set.
	ProxyLocalhost bool
}

Options definition.

func (*Options) Default added in v0.1.4

func (o *Options) Default()

Default sets `Options` default values.

type Proxy

type Proxy struct {
	// LocalProxyURI is the local proxy URI:
	// - Known schemes: http, https, socks, socks5, or quic
	// - Some hostname (x.io - min 4 chars), or IP
	// - Port in a valid range: 80 - 65535.
	// Example: http://127.0.0.1:8080
	LocalProxyURI string `json:"local_proxy_uri" validate:"required,proxyURI"`

	// UpstreamProxyURI is the upstream proxy URI:
	// - Known schemes: http, https, socks, socks5, or quic
	// - Some hostname (x.io - min 4 chars), or IP
	// - Port in a valid range: 80 - 65535.
	// Example: http://u456:p456@127.0.0.1:8085
	UpstreamProxyURI string `json:"upstream_proxy_uri" validate:"omitempty,proxyURI"`

	// PACURI is the PAC URI:
	// - Known schemes: http, https, socks, socks5, or quic
	// - Some hostname (x.io - min 4 chars), or IP
	// - Port in a valid range: 80 - 65535.
	// Example: http://127.0.0.1:8087/data.pac
	PACURI string `json:"pac_uri" validate:"omitempty,gte=6"`

	// Mode the Proxy is running.
	Mode Mode

	// Current state of the proxy. Multiple calls to `Run`, if running, will do
	// nothing.
	State State

	// Options to setup proxy.
	*Options
	// contains filtered or unexported fields
}

Proxy definition. Proxy can be protected, or not. It can forward connections to an upstream proxy protected, or not. The upstream proxy can be automatically setup via PAC. PAC content can be retrieved from multiple sources, e.g.: a HTTP server, also, protected or not.

Protection means basic auth protection.

func New

func New(
	localProxyURI string,
	upstreamProxyURI string,
	pacURI string, pacProxiesCredentials []string,
	options *Options,
) (*Proxy, error)

New is the Proxy factory. Errors can be introspected, and provide contextual information.

Example

Complete, and complex example.

client -> protected local proxy -> protected pac server - connection setup -> protected upstream proxy -> protected target.

//////
// Setup demo logger.
//////

// Only `stdout`, and `stderr`
loggingOptions := &LoggingOptions{
	FileLevel: level.None.String(),
	FilePath:  "-",

	// Change to `Trace` for debugging, and demonstration purposes.
	Level: level.None.String(),
}

l := logger.Setup(loggingOptions)

//////
// Randomness automates port allocation, ensuring no collision happens
// between tests, and examples.
//////

r, err := randomness.New(49000, 50000, 100, true)
if err != nil {
	log.Fatalln("Failed to create randomness.", err)
}

//////
// Target/end server.
//////

targetServer := createMockedHTTPServer(http.StatusOK, "body", "dXNlcjE6cGFzczE=")

defer func() { targetServer.Close() }()

targetServerURI, err := url.ParseRequestURI(targetServer.URL)
if err != nil {
	//nolint:gocritic
	log.Fatalln("Failed to parse target server URL.", err)
}

targetServerURI.User = url.UserPassword("user1", "pass1")

l.Debuglnf("Target/end server started @ %s", targetServerURI.Redacted())

//////
// PAC content.
//////

// Use `int(r.MustGenerate())` for testing purposes. Specify a port if using
// a manual - external proxy (e.g.: NGINX). Good for debugging, and demo
// purposes.
upstreamProxyPort := int(r.MustGenerate())

templateMap := map[string]int{
	"port": upstreamProxyPort,
}

var pacText strings.Builder
_ = template.Must(template.New("pacTemplate").Parse(pacTemplate)).Execute(&pacText, templateMap)

l.Debuglnf("PAC template parsed: \n%s", pacText.String())

//////
// PAC server.
//////

pacServer := createMockedHTTPServer(http.StatusOK, pacText.String(), "dXNlcjpwYXNz")

defer func() { pacServer.Close() }()

pacServerURI, err := url.ParseRequestURI(pacServer.URL)
if err != nil {
	log.Fatalln("Failed to parse PAC server URL.", err)
}

pacServerURI.User = url.UserPassword("user", "pass")

l.Debuglnf("PAC server started @ %s", pacServerURI.Redacted())

//////
// URL for both proxies, local, and upstream.
//////

// Local proxy.
localProxyURI := URIBuilder(defaultProxyHostname, r.MustGenerate(), localProxyCredentialUsername, localProxyCredentialPassword)

// Upstream proxy.
upstreamProxyURI := URIBuilder(defaultProxyHostname, int64(upstreamProxyPort), upstreamProxyCredentialUsername, upstreamProxyCredentialPassword)

//////
// Local proxy.
//
// It's protected with Basic Auth. Upstream proxy will be automatically, and
// dynamically setup via PAC, including credentials for proxies specified
// in the PAC content.
//////

localProxy, err := New(
	// Local proxy URI.
	localProxyURI.String(),

	// Upstream proxy URI.
	"",

	// PAC URI.
	pacServerURI.String(),

	// PAC proxies credentials in standard URI format.
	[]string{upstreamProxyURI.String()},

	// Logging settings.
	&Options{
		LoggingOptions: loggingOptions,
	},
)
if err != nil {
	log.Fatalln("Failed to create proxy.", err)
}

go localProxy.Run()

// Give enough time to start, and be ready.
time.Sleep(1 * time.Second)

//////
// Upstream Proxy.
//////

upstreamProxy, err := New(
	// Local proxy URI.
	upstreamProxyURI.String(),

	// Upstream proxy URI.
	"",

	// PAC URI.
	"",

	// PAC proxies credentials in standard URI format.
	nil,

	// Logging settings.
	&Options{
		LoggingOptions: loggingOptions,
	},
)
if err != nil {
	log.Fatalln("Failed to create upstream proxy.", err)
}

go upstreamProxy.Run()

// Give enough time to start, and be ready.
time.Sleep(1 * time.Second)

//////
// Client.
//////

l.Debuglnf("Client is using %s as proxy", localProxyURI.Redacted())

// Client's proxy settings.
tr := &http.Transport{
	Proxy: http.ProxyURL(localProxyURI),
}

client := &http.Client{
	Transport: tr,
}

statusCode, body, err := executeRequest(client, targetServerURI.String())
if err != nil {
	log.Fatalf("Failed to execute request: %v", err)
}

fmt.Println(statusCode)
fmt.Println(body)
Output:
200
body
Example (AutomaticallyRetryPort)

Automatically retry port example.

if os.Getenv("FORWARDER_TEST_MODE") != "integration" {
	fmt.Println("true")

	return
}

//////
// Randomness automates port allocation, ensuring no collision happens.
//////

r, err := randomness.New(55000, 65000, 100, true)
if err != nil {
	log.Fatalln("Failed to create randomness.", err)
}

randomPort := r.MustGenerate()

errored := false

proxy1, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, &Options{
	LoggingOptions: &LoggingOptions{
		Level:     "none",
		FileLevel: "none",
	},
})
if err != nil {
	errored = true
}

go proxy1.Run()

time.Sleep(1 * time.Second)

proxy2, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, &Options{
	AutomaticallyRetryPort: true,

	LoggingOptions: &LoggingOptions{
		Level:     "none",
		FileLevel: "none",
	},
})
if err != nil {
	errored = true
}

go proxy2.Run()

time.Sleep(1 * time.Second)

fmt.Println(errored == false)
Output:
true
Example (SypplyingLogger)

Supplying a logger, and calling multiple times Run example.

//////
// Custom logger
//////

customLogger := sypl.NewDefault("customLogger", level.Trace)

//////
// Randomness automates port allocation, ensuring no collision happens.
//////

r, err := randomness.New(55000, 65000, 100, true)
if err != nil {
	log.Fatalln("Failed to create randomness.", err)
}

randomPort := r.MustGenerate()

errored := false

proxy1, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, &Options{
	LoggingOptions: &LoggingOptions{
		Logger:    customLogger,
		Level:     level.Trace.String(),
		FileLevel: "none",
	},
})
if err != nil {
	log.Fatalln(err)
}

go proxy1.Run()

time.Sleep(1 * time.Second)

fmt.Println(errored == false)
Output:
true

func (*Proxy) Run

func (p *Proxy) Run()

Run starts the proxy. it fails to start, it will exit with fatal. It's safe to call it multiple times - nothing will happen.

type RetryPortOptions added in v0.1.4

type RetryPortOptions struct {
	// MaxRange defines the max port number. Default value is `65535`.
	MaxRange int

	// MaxRetry defines how many times to retry, until fail.
	MaxRetry int
}

RetryPortOptions defines port's retry options.

func (*RetryPortOptions) Default added in v0.1.4

func (r *RetryPortOptions) Default() *RetryPortOptions

Default sets `RetryPortOptions` default values.

type State added in v0.1.9

type State string

State helps the proxy to don't run the same state multiple times.

const (
	// Initializing means that a new proxy has been instantiated, but has not
	// yet finished setup.
	Initializing State = "Initializing"

	// Setup state means it's done setting it up, but not running yet.
	Setup State = "Setup"

	// Running means proxy is running.
	Running State = "Running"
)

Jump to

Keyboard shortcuts

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