Documentation
¶
Overview ¶
Package microproxy is an embeddable HTTP and HTTPS forward proxy.
It offers Basic and Digest authentication, client network ACLs, CONNECT port restrictions, header rewriting, an access log, and per-domain routing to upstream HTTP or SOCKS5 proxies. Everything is configured from Go; nothing is read from a file unless the program chooses to read one.
The simplest useful proxy listens on an ephemeral port and sends everything through a SOCKS5 server, which is what fronting a VPN client looks like:
srv, err := microproxy.New(microproxy.Config{
ForwardProxyURL: "socks5://127.0.0.1:1080",
AllowedNetworks: []string{"127.0.0.1/32"},
})
if err != nil {
return err
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return err
}
go srv.Serve(listener)
defer srv.Shutdown(context.Background())
// the port the clients have to be pointed at
port := listener.Addr().(*net.TCPAddr).Port
The same can be done with a dialer instead of a url, which is what a program that owns the tunnel rather than a SOCKS port in front of it wants:
srv, err := microproxy.New(cfg, microproxy.WithDialer(tunnel))
Authentication is a credential store the program supplies, so users can be added and removed at runtime without writing an htpasswd file:
users := microproxy.NewBasicUsers("proxy")
users.Set("alice", "open sesame")
srv, err := microproxy.New(cfg, microproxy.WithCredentials(users))
A Config can be replaced while the proxy is serving with Server.Reload, and the credentials with Server.SetCredentials. Config.Listen and Config.BindIP are the exception: they are consumed while the proxy is being built.
The cmd/microproxy command is a standalone proxy built on this package, and is what keeps the TOML configuration file, the signal handling and the log files that microproxy has always had.
Index ¶
- Constants
- Variables
- func DigestHA1(user, realm, password string) string
- func ParseProxyURL(rawURL string) (*url.URL, error)
- type AccessEntry
- type AccessLogger
- type AccessLoggerFunc
- type BasicCredentials
- type BasicUsers
- type Config
- type ContextDialer
- type Credentials
- type DialerFunc
- type DigestCredentials
- type DigestUsers
- type FileAccessLogger
- type FileLogger
- type Health
- type Logger
- type LoggerFunc
- type Option
- func WithAccessLogger(access AccessLogger) Option
- func WithCredentials(creds Credentials) Option
- func WithDialer(dialer ContextDialer) Option
- func WithHealth(health *Health) Option
- func WithInsecureUpstream(insecure bool) Option
- func WithListenerTLS(config *tls.Config) Option
- func WithLogger(logger Logger) Option
- func WithRouter(router Router) Option
- func WithTLSClientConfig(config *tls.Config) Option
- func WithVerbose(verbose bool) Option
- type Route
- type Router
- type RouterFunc
- type Server
- func (s *Server) Addr() net.Addr
- func (s *Server) Close() error
- func (s *Server) Config() Config
- func (s *Server) Handler() http.Handler
- func (s *Server) Health() *Health
- func (s *Server) ListenAndServe() error
- func (s *Server) ListenAndServeSOCKS() error
- func (s *Server) Reload(cfg Config) error
- func (s *Server) SOCKSAddr() net.Addr
- func (s *Server) Serve(l net.Listener) error
- func (s *Server) ServeSOCKS(l net.Listener) error
- func (s *Server) SetCredentials(creds Credentials) error
- func (s *Server) Shutdown(ctx context.Context) error
Constants ¶
const ( // DefaultListenAddress is used when Config.Listen is empty. DefaultListenAddress = "127.0.0.1:3128" // DefaultAllowedNetwork is the only network allowed when neither // Config.AllowedNetworks nor authentication is configured. DefaultAllowedNetwork = "127.0.0.1/32" // DefaultAllowedConnectPort is the only port CONNECT may reach when // Config.AllowedConnectPorts is empty. DefaultAllowedConnectPort = 443 // DefaultReadTimeout bounds how long a single read on a proxied connection // may take. DefaultReadTimeout = 15 * time.Minute // DefaultWriteTimeout bounds how long a single write on a proxied // connection may take. DefaultWriteTimeout = 15 * time.Minute // GenericProxyRule is the Rules key standing for "any host". It is more // specific than ForwardProxyURL and less specific than any domain rule. GenericProxyRule = "." )
const DefaultHealthFailureLimit = 5
DefaultHealthFailureLimit is how many consecutive failures put a proxy in the unhealthy state when Config.HealthFailureLimit is not set.
Variables ¶
var ErrServerRunning = errors.New("microproxy: the server is already serving")
ErrServerRunning is returned when a server that is already serving is asked to serve again.
var SupportedProxySchemes = []string{"http", "https", "socks5", "socks5h"}
SupportedProxySchemes are the upstream proxy url schemes this package knows how to reach.
http and https are spoken by the proxy itself; socks5 and socks5h are handed to a SOCKS5 dialer, which always resolves the target name at the SOCKS server, so the two behave identically here.
Functions ¶
Types ¶
type AccessEntry ¶
type AccessEntry struct {
Time time.Time
RemoteAddr string
Method string
URL string
StatusCode int
ContentLength int64
// User is the authenticated user, empty when the request was not
// authenticated.
User string
Err error
}
AccessEntry is one line of the access log: a request that was served, or a tunnel that was opened.
func (*AccessEntry) String ¶
func (e *AccessEntry) String() string
String formats the entry the way microproxy has always written its access log: fields separated by spaces, "-" for the ones a request did not carry.
type AccessLogger ¶
type AccessLogger interface {
LogAccess(*AccessEntry)
}
AccessLogger records the requests the proxy serves. It is called from the goroutine serving the request, so an implementation that can block should hand the entry to a writer of its own, the way FileAccessLogger does.
type AccessLoggerFunc ¶
type AccessLoggerFunc func(*AccessEntry)
AccessLoggerFunc adapts a function to AccessLogger.
func (AccessLoggerFunc) LogAccess ¶
func (f AccessLoggerFunc) LogAccess(entry *AccessEntry)
LogAccess implements AccessLogger.
type BasicCredentials ¶
type BasicCredentials interface {
Credentials
VerifyBasic(user, password string) bool
}
BasicCredentials verifies a user and password sent with the Basic scheme. The password reaches it in the clear, so the store is the right place to apply a constant-time comparison against whatever it keeps.
func BasicCredentialsFunc ¶
func BasicCredentialsFunc(realm string, verify func(user, password string) bool) BasicCredentials
BasicCredentialsFunc turns a verification function into credentials for the Basic scheme. verify is called from the goroutine serving the request and has to be safe for concurrent use.
type BasicUsers ¶
type BasicUsers struct {
// contains filtered or unexported fields
}
BasicUsers is an in-memory set of users for the Basic scheme. It is safe for concurrent use, so a program can add and remove users while the proxy it serves is running.
func LoadBasicUsers ¶
func LoadBasicUsers(realm string, r io.Reader) (*BasicUsers, error)
LoadBasicUsers reads the "user:password" lines that htpasswd -p writes.
func LoadBasicUsersFile ¶
func LoadBasicUsersFile(realm, path string) (*BasicUsers, error)
LoadBasicUsersFile reads a file in the format LoadBasicUsers accepts.
func NewBasicUsers ¶
func NewBasicUsers(realm string) *BasicUsers
NewBasicUsers returns an empty user set announcing realm.
func (*BasicUsers) Remove ¶
func (u *BasicUsers) Remove(user string)
Remove drops a user. Connections it has already opened are not affected.
func (*BasicUsers) Set ¶
func (u *BasicUsers) Set(user, password string)
Set adds a user or replaces its password.
func (*BasicUsers) Users ¶
func (u *BasicUsers) Users() []string
Users returns the user names currently known.
func (*BasicUsers) VerifyBasic ¶
func (u *BasicUsers) VerifyBasic(user, password string) bool
VerifyBasic implements BasicCredentials. The comparison takes the same time whether the user exists or not.
type Config ¶
type Config struct {
// Listen is the "host:port" the HTTP frontend binds to in ListenAndServe.
// Serve ignores it and takes the listener from the caller. Applied at
// startup only.
Listen string `toml:"listen"`
// SOCKSListen is the "host:port" the SOCKS5 frontend binds to in
// ListenAndServeSOCKS. Empty means no SOCKS frontend unless ServeSOCKS is
// called with a listener of the caller's own. Applied at startup only.
SOCKSListen string `toml:"socks_listen"`
// BindIP is the local address outgoing connections are made from. Applied
// at startup only.
BindIP string `toml:"bind_ip"`
// AllowedNetworks, when non-empty, is the whitelist of client networks;
// every other client is refused. Entries are CIDR blocks or bare IP
// addresses. When it is empty and no credentials are configured, New
// installs DefaultAllowedNetwork.
AllowedNetworks []string `toml:"allowed_networks"`
// DisallowedNetworks is the blacklist of client networks, applied after
// AllowedNetworks.
DisallowedNetworks []string `toml:"disallowed_networks"`
// AllowedConnectPorts limits the ports CONNECT may reach. Empty means
// DefaultAllowedConnectPort only.
AllowedConnectPorts []int `toml:"allowed_connect_ports"`
// ForwardedForHeader is what to do with X-Forwarded-For: "on", "off",
// "delete" or "truncate". Empty means "on".
ForwardedForHeader string `toml:"forwarded_for_header"`
// ViaHeader is what to do with Via: "on", "off" or "delete". Empty means
// "on".
ViaHeader string `toml:"via_header"`
// ViaProxyName is the name this proxy announces in Via. Empty means the
// host name.
ViaProxyName string `toml:"via_proxy_name"`
// AddHeaders are [name, value] pairs added to a request that does not
// already carry the header.
AddHeaders [][]string `toml:"add_headers"`
// Proxies maps an alias to an upstream proxy url and Rules maps a domain
// to one of those aliases. The longest matching domain wins. See
// GenericProxyRule and ForwardProxyURL for the fallbacks.
Proxies map[string]string `toml:"proxies"`
Rules map[string]string `toml:"rules"`
// ForwardProxyURL is the upstream proxy for the hosts no rule matches.
// Empty falls back to the environment (HTTP_PROXY and friends).
ForwardProxyURL string `toml:"forward_proxy_url"`
// ReadTimeout and WriteTimeout bound a single read or write on a proxied
// connection. Zero means the Default values; a negative value disables
// the deadline.
ReadTimeout time.Duration `toml:"read_timeout"`
WriteTimeout time.Duration `toml:"write_timeout"`
// HealthCheckEnabled turns health tracking on when it is "on". The state
// is then readable through Server.Health, which also serves it over HTTP.
// Applied at startup only.
HealthCheckEnabled string `toml:"enable_health_check"`
// HealthFailureLimit is how many consecutive failures make the proxy
// unhealthy. Zero means DefaultHealthFailureLimit.
HealthFailureLimit int `toml:"health_failure_limit"`
}
Config is the data-only description of a proxy. Every field can be set from Go; the toml tags exist so that a program that does keep a configuration file can decode one into this struct.
Callers construct a Config, hand it to New, and are free to reuse or modify it afterwards: New takes its own copy of the slices and maps.
type ContextDialer ¶
type ContextDialer interface {
DialContext(ctx context.Context, network, addr string) (net.Conn, error)
}
ContextDialer establishes the connections the proxy makes on behalf of a client. It is what a program embedding this package implements to send the traffic somewhere of its own: a VPN tunnel, a SOCKS port, a test double.
func SOCKS5Dialer ¶
func SOCKS5Dialer(addr, user, password string, forward ContextDialer) (ContextDialer, error)
SOCKS5Dialer reaches targets through the SOCKS5 server at addr, which is how a proxy is pointed at a VPN client or any other SOCKS port. user and password may be empty when the server wants no authentication.
The target host name is resolved by the SOCKS server rather than locally, so the names a client asks for are looked up on the far side of the tunnel.
forward is how the SOCKS server itself is reached; nil dials it directly.
type Credentials ¶
type Credentials interface {
// Realm is the protection space announced in the 407 challenge.
Realm() string
}
Credentials is what the two credential store interfaces have in common. A store is consulted concurrently, so its methods have to be safe for use from several goroutines.
type DialerFunc ¶
DialerFunc adapts a function to ContextDialer. It also satisfies the Dial method that golang.org/x/net/proxy expects, so that it can be handed to a SOCKS5 dialer as the way to reach the SOCKS server itself.
func (DialerFunc) Dial ¶
func (f DialerFunc) Dial(network, addr string) (net.Conn, error)
Dial implements the dialer that golang.org/x/net/proxy expects, with the background context.
func (DialerFunc) DialContext ¶
DialContext implements ContextDialer.
type DigestCredentials ¶
type DigestCredentials interface {
Credentials
HA1(user, realm string) (string, bool)
}
DigestCredentials supplies the HA1 digest, MD5(user:realm:password), that the Digest scheme is verified against. The password itself never travels, so a store can only ever hold or derive HA1.
realm is the one the client echoed back, which a store is free to reject in favour of its own.
func DigestCredentialsFunc ¶
func DigestCredentialsFunc(realm string, ha1 func(user, realm string) (string, bool)) DigestCredentials
DigestCredentialsFunc turns an HA1 lookup into credentials for the Digest scheme. ha1 is called from the goroutine serving the request and has to be safe for concurrent use.
type DigestUsers ¶
type DigestUsers struct {
// contains filtered or unexported fields
}
DigestUsers is an in-memory set of users for the Digest scheme, keyed by user and realm. It is safe for concurrent use.
Digest never sees the password, only HA1 = MD5(user:realm:password), which is what this store keeps.
func LoadDigestUsers ¶
func LoadDigestUsers(realm string, r io.Reader) (*DigestUsers, error)
LoadDigestUsers reads the "user:realm:ha1" lines that htdigest writes.
func LoadDigestUsersFile ¶
func LoadDigestUsersFile(realm, path string) (*DigestUsers, error)
LoadDigestUsersFile reads a file in the format LoadDigestUsers accepts.
func NewDigestUsers ¶
func NewDigestUsers(realm string) *DigestUsers
NewDigestUsers returns an empty user set announcing realm.
func (*DigestUsers) HA1 ¶
func (u *DigestUsers) HA1(user, realm string) (string, bool)
HA1 implements DigestCredentials.
func (*DigestUsers) Remove ¶
func (u *DigestUsers) Remove(user string)
Remove drops a user from this store's realm.
func (*DigestUsers) Set ¶
func (u *DigestUsers) Set(user, password string)
Set adds a user, deriving HA1 from the password for this store's realm.
func (*DigestUsers) SetHA1 ¶
func (u *DigestUsers) SetHA1(user, realm, ha1 string)
SetHA1 adds a user whose HA1 was computed elsewhere, which is how an htdigest file and any store that never held the password are loaded.
type FileAccessLogger ¶
type FileAccessLogger struct {
// contains filtered or unexported fields
}
FileAccessLogger writes the access log to a file from a goroutine of its own, so that a slow disk delays the writer rather than the requests.
func NewFileAccessLogger ¶
func NewFileAccessLogger(path string) (*FileAccessLogger, error)
NewFileAccessLogger appends the access log to path.
func (*FileAccessLogger) Close ¶
func (l *FileAccessLogger) Close() error
Close flushes the entries already handed over and releases the file. Entries submitted afterwards are discarded rather than panicking on a closed channel.
func (*FileAccessLogger) LogAccess ¶
func (l *FileAccessLogger) LogAccess(entry *AccessEntry)
LogAccess implements AccessLogger. It hands the entry to the writing goroutine, and discards it once the logger has been closed.
func (*FileAccessLogger) Reopen ¶
func (l *FileAccessLogger) Reopen() error
Reopen makes the logger write to a freshly opened file, for logrotate.
type FileLogger ¶
type FileLogger struct {
// contains filtered or unexported fields
}
FileLogger writes to a file, or to stderr when its path is empty, and can reopen it in place for logrotate. Reopening in place means nobody has to swap the logger of a running proxy while requests are being served.
func NewFileLogger ¶
func NewFileLogger(path string) (*FileLogger, error)
NewFileLogger logs to path, or to stderr when path is empty.
func (*FileLogger) Close ¶
func (l *FileLogger) Close() error
Close releases the file, after which the logger writes to stderr again.
func (*FileLogger) Printf ¶
func (l *FileLogger) Printf(format string, v ...interface{})
Printf implements Logger.
func (*FileLogger) Reopen ¶
func (l *FileLogger) Reopen() error
Reopen points the logger at a freshly opened file. log.Logger serializes writes with the change of destination, so concurrent Printf calls either reach the old file before it is closed or the new one.
type Health ¶
type Health struct {
// contains filtered or unexported fields
}
Health tracks whether the proxy is answering requests successfully. A run of consecutive failures marks it unhealthy; a single success clears the run.
It is safe for concurrent use, and a program embedding this package can read it directly or serve it over HTTP with Handler.
func NewHealth ¶
NewHealth returns a tracker that turns unhealthy after failureLimit consecutive failures. A limit of zero or less means DefaultHealthFailureLimit. A new tracker starts out healthy.
func (*Health) Failures ¶
Failures is the number of consecutive failures recorded since the last success.
func (*Health) Handler ¶
Handler serves the state as a health endpoint: 200 while the proxy is healthy, 503 once it is not, which is what a container or a load balancer probes.
func (*Health) RecordFailure ¶
func (h *Health) RecordFailure()
RecordFailure counts a failed request, and marks the proxy unhealthy once enough of them have happened in a row.
func (*Health) RecordSuccess ¶
func (h *Health) RecordSuccess()
RecordSuccess counts a served request, which clears the run of failures.
type Logger ¶
type Logger interface {
Printf(format string, v ...interface{})
}
Logger is where the proxy reports what it is doing and what went wrong. It is the shape goproxy expects, so a *log.Logger satisfies it as is.
var DiscardLogger Logger = discardLogger{}
DiscardLogger throws away everything the proxy reports.
type LoggerFunc ¶
type LoggerFunc func(format string, v ...interface{})
LoggerFunc adapts a function to Logger.
func (LoggerFunc) Printf ¶
func (f LoggerFunc) Printf(format string, v ...interface{})
Printf implements Logger.
type Option ¶
Option is a part of a proxy that a Config can't describe, because it is a piece of the embedding program rather than data: where the logs go, what the credentials are, how the connections are made.
func WithAccessLogger ¶
func WithAccessLogger(access AccessLogger) Option
WithAccessLogger records every request the proxy serves. The default records nothing.
func WithCredentials ¶
func WithCredentials(creds Credentials) Option
WithCredentials authenticates the clients against creds. A BasicCredentials makes the proxy challenge with the Basic scheme and a DigestCredentials with Digest. The default is to not authenticate at all, which is also what limits an otherwise unconfigured proxy to the loopback.
The credentials can be replaced afterwards with Server.SetCredentials.
func WithDialer ¶
func WithDialer(dialer ContextDialer) Option
WithDialer makes every connection the proxy opens on behalf of a client go through dialer, instead of through the network directly. This is how the traffic is handed to a tunnel the embedding program owns.
It replaces the dialing, not the routing: a request that a rule sends to an upstream proxy still goes there, with dialer opening the connection to the proxy. Config.BindIP no longer applies, since the dialer decides where the connection comes from.
func WithHealth ¶
WithHealth tracks whether the proxy is answering requests successfully, overriding Config.HealthCheckEnabled. Passing nil turns tracking off.
The state is readable through Server.Health, and Health.Handler serves it as an endpoint for a container or a load balancer to probe.
func WithInsecureUpstream ¶
WithInsecureUpstream disables certificate verification towards an https:// upstream proxy. It is the -i flag of the command, and is a bad idea anywhere else.
func WithListenerTLS ¶ added in v0.0.3
WithListenerTLS makes the HTTP frontend accept TLS connections: clients connect to the proxy over TLS and speak the proxy protocol inside it, which is what a browser calls an "HTTPS proxy".
This keeps the Proxy-Authorization credentials and the host names of CONNECT requests off the local network, which a plain proxy sends in the clear. It does not decrypt anything a client tunnels: what goes through CONNECT is still opaque to the proxy.
The certificate is the proxy's own, for the name its clients reach it by. Use tls.Config.GetCertificate to serve one that can be replaced without a restart. A MinVersion of TLS 1.2 is applied when config does not set one, and config is copied, so later changes to it are not picked up.
This is the server side. WithTLSClientConfig is the unrelated client side, used when this proxy talks to an https:// upstream.
The SOCKS frontend is unaffected: SOCKS5 has no TLS convention and no client speaks one.
func WithLogger ¶
WithLogger sends what the proxy reports to logger. The default discards it; NewFileLogger and NewSlogLogger are the ready-made ones.
func WithRouter ¶
WithRouter decides the upstream per target host, replacing the routing that Config.Proxies, Config.Rules and Config.ForwardProxyURL describe.
func WithTLSClientConfig ¶
WithTLSClientConfig is the TLS configuration used when the proxy itself speaks TLS as a client, which is to an https:// upstream proxy. See WithListenerTLS for the server side.
func WithVerbose ¶
WithVerbose makes the proxy report every request it serves through its logger.
type Route ¶
type Route struct {
// Proxy is an HTTP or HTTPS upstream proxy. Tunnelled traffic reaches it
// with CONNECT and plain HTTP with an absolute-form request line, which is
// why it can't be reduced to a Dialer.
Proxy *url.URL
// Dialer establishes the connection to the target itself. A SOCKS5
// upstream is expressed this way, as is any custom transport.
Dialer ContextDialer
}
Route is how the proxy reaches one target host.
The zero Route is a direct connection. At most one field is set: Proxy for the upstreams that speak HTTP, Dialer for everything else.
type Router ¶
Router decides how a target host is reached. host is the "host:port" of the target, never of an upstream proxy.
A Router has to be safe for concurrent use, and its answer has to depend on nothing but the host: connections are pooled per host, so a route that varies between two requests to the same target would not be honoured for the second.
type RouterFunc ¶
RouterFunc adapts a function to Router.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a proxy. It is created by New, and serves requests through Serve or ListenAndServe, or as the http.Handler that Handler returns.
A Server is safe for concurrent use, and its configuration and credentials can be replaced while it is serving.
func New ¶
New builds a proxy from cfg. The options replace the parts a configuration can't describe: where the logs go, what the credentials are, and how the connections are made.
cfg is copied, so the caller may reuse or modify it afterwards.
func (*Server) Addr ¶
Addr is the address the proxy is listening on, or nil when it is not serving. It is how a caller that listened on port 0 learns the port it got.
func (*Server) Close ¶
Close stops the proxy at once, dropping the requests and the SOCKS connections in flight.
func (*Server) Handler ¶
Handler returns the proxy as an http.Handler, for a caller that would rather run a server of its own.
func (*Server) Health ¶
Health returns the proxy's health tracker, or nil when health tracking was not enabled with WithHealth.
func (*Server) ListenAndServe ¶
ListenAndServe listens on Config.Listen and serves until Shutdown is called.
func (*Server) ListenAndServeSOCKS ¶
ListenAndServeSOCKS listens on Config.SOCKSListen and serves SOCKS5 there.
func (*Server) Reload ¶
Reload installs cfg for the requests that follow. The settings that are consumed while the proxy is being built — Listen and BindIP — keep their startup values, and a change to them is reported through the logger.
The configuration in force is left untouched when cfg can't be used.
func (*Server) SOCKSAddr ¶
SOCKSAddr is the address the SOCKS frontend is listening on, or nil when it is not serving.
func (*Server) Serve ¶
Serve accepts connections on l until it is closed or Shutdown is called.
When WithListenerTLS is in force, l is wrapped so that clients speak the proxy protocol inside a TLS connection. Do not pass an already wrapped listener as well.
func (*Server) ServeSOCKS ¶
ServeSOCKS accepts SOCKS5 connections on l until it is closed or the server is shut down.
The clients reaching it are subject to the same configuration as the ones speaking HTTP: the network ACLs, the CONNECT port restrictions, the credentials and the upstream routing all apply, and the connections are written to the same access log.
Only the CONNECT command is implemented; BIND and UDP ASSOCIATE are refused.
func (*Server) SetCredentials ¶
func (s *Server) SetCredentials(creds Credentials) error
SetCredentials installs the credentials the clients are authenticated against. A nil argument turns authentication off.
The digest nonces already issued stay valid, so replacing the credentials does not interrupt the exchanges in flight.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
microproxy
command
Command microproxy is a standalone HTTP and HTTPS forward proxy built on the github.com/dobrevit/microproxy package.
|
Command microproxy is a standalone HTTP and HTTPS forward proxy built on the github.com/dobrevit/microproxy package. |