From d2766123ecc5af9c5693af1ff27da6c0356950d0 Mon Sep 17 00:00:00 2001 From: Alex Harford Date: Wed, 29 Jun 2022 10:21:24 -0700 Subject: [PATCH 01/10] Add siteCredentials argument to Proxy This adds the Basic auth header to configured URLs when a request is made. --- cmd/run.go | 15 ++-- go.mod | 1 + go.sum | 1 + pkg/proxy/example_test.go | 10 ++- pkg/proxy/proxy.go | 165 ++++++++++++++++++++++++++++---------- pkg/proxy/proxy_test.go | 124 +++++++++++++++++++++++++++- 6 files changed, 264 insertions(+), 52 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index b942143a..4f74edd4 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -16,6 +16,8 @@ var ( localProxyURI string upstreamProxyURI string + siteCredentials []string + pacProxiesCredentials []string pacURI string @@ -46,10 +48,10 @@ Note: Can't setup upstream, and PAC at the same time. Start a proxy listening to http://0.0.0.0:8085: $ forwarder run -l "http://0.0.0.0:8085" - + Start a protected proxy: $ forwarder run -l "http://user:pwd@localhost:8085" - + Start a protected proxy, forwarding connection to an upstream proxy running at http://localhost:8089: $ forwarder run \ @@ -61,19 +63,19 @@ Note: Can't setup upstream, and PAC at the same time. $ forwarder run \ -l "http://user:pwd@localhost:8085" \ -u "http://user1:pwd1@localhost:8089" - + Start a protected proxy, forwarding connection to an upstream proxy, setup via PAC - server running at http://localhost:8090: $ forwarder run \ -l "http://user:pwd@localhost:8085" \ -p "http://localhost:8090" - + Start a protected proxy, forwarding connection to an upstream proxy, setup via PAC - protected server running at http://user2:pwd2@localhost:8090: $ forwarder run \ -l "http://user:pwd@localhost:8085" \ -p "http://user2:pwd2@localhost:8090" - + Start a protected proxy, forwarding connection to an upstream proxy, setup via PAC - protected server running at http://user2:pwd2@localhost:8090, specifying credential for protected proxies specified in PAC: @@ -93,7 +95,7 @@ Note: Can't setup upstream, and PAC at the same time. -d "http://user3:pwd4@localhost:8091,http://user4:pwd5@localhost:8092" `, Run: func(cmd *cobra.Command, args []string) { - p, err := proxy.New(localProxyURI, upstreamProxyURI, pacURI, pacProxiesCredentials, &proxy.Options{ + p, err := proxy.New(localProxyURI, upstreamProxyURI, pacURI, pacProxiesCredentials, siteCredentials, &proxy.Options{ LoggingOptions: &proxy.LoggingOptions{ Level: logLevel, FileLevel: fileLevel, @@ -120,6 +122,7 @@ func init() { runCmd.Flags().StringSliceVarP(&dnsURIs, "dns-uri", "n", nil, "sets dns URI") runCmd.Flags().StringVarP(&pacURI, "pac-uri", "p", "", "sets URI to PAC content, or directly, the PAC content") runCmd.Flags().StringSliceVarP(&pacProxiesCredentials, "pac-proxies-credentials", "d", nil, "sets PAC proxies credentials using standard URI format") + runCmd.Flags().StringSliceVarP(&siteCredentials, "site-credentials", "d", nil, "sets site based credentials") runCmd.Flags().BoolVarP(&proxyLocalhost, "proxy-localhost", "t", false, "if set, will proxy localhost requests to an upstream proxy - if any") runCmd.Flags().BoolVarP(&automaticallyRetryPort, "find-port", "r", true, "if set, and the specified local proxy port is in-use, it will find, and use an available one") } diff --git a/go.mod b/go.mod index ccfa1424..2f6f0a55 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/elazarl/goproxy v0.0.0-20220529153421-8ea89ba92021 github.com/elazarl/goproxy/ext v0.0.0-20220529153421-8ea89ba92021 github.com/go-playground/validator/v10 v10.11.0 + github.com/google/go-cmp v0.5.6 github.com/saucelabs/customerror v1.0.3 github.com/saucelabs/pacman v0.1.1 github.com/saucelabs/randomness v0.0.5 diff --git a/go.sum b/go.sum index e0cbc057..627be61d 100644 --- a/go.sum +++ b/go.sum @@ -207,6 +207,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= diff --git a/pkg/proxy/example_test.go b/pkg/proxy/example_test.go index 8d94de56..67a80d9b 100644 --- a/pkg/proxy/example_test.go +++ b/pkg/proxy/example_test.go @@ -134,6 +134,8 @@ func ExampleNew() { // PAC proxies credentials in standard URI format. []string{upstreamProxyURI.String()}, + nil, + // Logging settings. &Options{ LoggingOptions: loggingOptions, @@ -165,6 +167,8 @@ func ExampleNew() { // PAC proxies credentials in standard URI format. nil, + nil, + // Logging settings. &Options{ LoggingOptions: loggingOptions, @@ -228,7 +232,7 @@ func ExampleNew_automaticallyRetryPort() { errored := false - proxy1, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, &Options{ + proxy1, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, nil, &Options{ LoggingOptions: &LoggingOptions{ Level: "none", FileLevel: "none", @@ -242,7 +246,7 @@ func ExampleNew_automaticallyRetryPort() { time.Sleep(1 * time.Second) - proxy2, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, &Options{ + proxy2, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, nil, &Options{ AutomaticallyRetryPort: true, LoggingOptions: &LoggingOptions{ @@ -285,7 +289,7 @@ func ExampleNew_sypplyingLogger() { errored := false - proxy1, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, &Options{ + proxy1, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, nil, &Options{ LoggingOptions: &LoggingOptions{ Logger: customLogger, Level: level.Trace.String(), diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go index 767b6a12..6fae4891 100644 --- a/pkg/proxy/proxy.go +++ b/pkg/proxy/proxy.go @@ -203,6 +203,9 @@ type Proxy struct { // Credentials for proxies specified in PAC content. pacProxiesCredentials []string + // Credentials for passing basic authentication to requests + siteCredentials map[string]string + // Underlying proxy implementation. proxy *goproxy.ProxyHttpServer } @@ -360,6 +363,48 @@ func setupPACUpstreamProxyConnection(p *Proxy, ctx *goproxy.ProxyCtx) error { return nil } +// encodeSiteCredentials converts credentials (strings of "user:pass") into +// base64 encoded strings to be used as basic authentication headers. +func encodeSiteCredentials(creds string) (string, string, error) { + tokens := strings.Split(creds, ":") + if len(tokens) != 4 { //nolint + return "", "", fmt.Errorf("failed to parse %s as site auth", creds) + } + + for _, token := range tokens { + if len(token) == 0 { + return "", "", fmt.Errorf("failed to find credentials in %s", creds) + } + } + + encodedCredential, err := credential.NewBasicAuth(tokens[2], tokens[3]) + if err != nil { + return "", "", fmt.Errorf("failed to parse credentials from %s", creds) + } + + return fmt.Sprintf("%s:%s", tokens[0], tokens[1]), encodedCredential.ToBase64(), nil +} + +func parseSiteCredentials(creds []string) (map[string]string, error) { + credMap := make(map[string]string, len(creds)) + + for _, credentialText := range creds { + hostport, creds, err := encodeSiteCredentials(credentialText) + if err != nil { + return nil, err + } + + _, found := credMap[hostport] + if found { + return nil, fmt.Errorf("multiple credentials for %s", hostport) + } + + credMap[hostport] = creds + } + + return credMap, nil +} + // DRY on handler's code. // nolint:exhaustive func (p *Proxy) setupHandlers(ctx *goproxy.ProxyCtx) error { @@ -523,6 +568,75 @@ func (p *Proxy) Run() { } } +func (p *Proxy) setupProxyHandlers() { + p.proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { + logger.Get().Debuglnf("%s %s -> %s", ctx.Req.Method, ctx.Req.RemoteAddr, ctx.Req.Host) + logger.Get().Debuglnf("%q", dumpHeaders(ctx.Req)) + + if err := p.setupHandlers(ctx); err != nil { + logger.Get().Errorlnf("Failed to setup handler (HTTPS) for request %s. %+v", ctx.Req.URL.Redacted(), err) + + return goproxy.RejectConnect, host + } + + return nil, host + }) + + p.proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { + logger.Get().Debuglnf("%s %s -> %s %s %s", req.Method, req.RemoteAddr, req.URL.Scheme, req.Host, req.URL.Port()) + logger.Get().Tracelnf("%q", dumpHeaders(ctx.Req)) + + if err := p.setupHandlers(ctx); err != nil { + logger.Get().Errorlnf("Failed to setup handler (HTTP) for request %s. %+v", ctx.Req.URL.Redacted(), err) + + return nil, goproxy.NewResponse( + ctx.Req, + goproxy.ContentTypeText, + http.StatusInternalServerError, + err.Error(), + ) + } + + p.addAuthHeader(req) + + return ctx.Req, nil + }) + + p.proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { + if resp != nil { + logger.Get().Debuglnf("%s <- %s %v (%v bytes)", + resp.Request.RemoteAddr, resp.Request.Host, resp.Status, resp.ContentLength) + } else { + logger.Get().Tracelnf("%s <- %s response is empty", ctx.Req.Host, ctx.Req.RemoteAddr) + } + + return resp + }) +} + +// addAuthHeader modifies the request and adds an authorization header if necessary. +func (p *Proxy) addAuthHeader(req *http.Request) { + hostport := req.Host + + if req.URL.Port() == "" { + var port string + if req.URL.Scheme == "http" { + port = "80" + } + + hostport = fmt.Sprintf("%s:%s", req.Host, port) + } + + // If req.Host is in the auth map, add the basic auth header + // using the credentials. These credentials are already base64 + // encoded. + creds, found := p.siteCredentials[hostport] + if found { + logger.Get().Tracelnf("Found site credentials for %s", req.Host) + req.Header.Set("Authorization", fmt.Sprintf("Basic %s", creds)) + } +} + ////// // Factory ////// @@ -533,6 +647,7 @@ func New( localProxyURI string, upstreamProxyURI string, pacURI string, pacProxiesCredentials []string, + siteCredentials []string, options *Options, ) (*Proxy, error) { // Instantiate validator. @@ -560,6 +675,12 @@ func New( return nil, err } + // Parse site credential list into map of host:port -> base64 encoded credentials. + creds, err := parseSiteCredentials(siteCredentials) + if err != nil { + return nil, err + } + p := &Proxy{ LocalProxyURI: localProxyURI, Mode: Direct, @@ -569,6 +690,7 @@ func New( UpstreamProxyURI: upstreamProxyURI, pacProxiesCredentials: pacProxiesCredentials, mutex: &sync.RWMutex{}, + siteCredentials: creds, } if err := validation.Get().Struct(p); err != nil { @@ -672,47 +794,8 @@ func New( p.pacParser = pacParser } - p.proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { - logger.Get().Debuglnf("%s %s -> %s", ctx.Req.Method, ctx.Req.RemoteAddr, ctx.Req.Host) - logger.Get().Debuglnf("%q", dumpHeaders(ctx.Req)) - - if err := p.setupHandlers(ctx); err != nil { - logger.Get().Errorlnf("Failed to setup handler (HTTPS) for request %s. %+v", ctx.Req.URL.Redacted(), err) - - return goproxy.RejectConnect, host - } - - return nil, host - }) - - p.proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { - logger.Get().Debuglnf("%s %s -> %s", req.Method, req.RemoteAddr, req.Host) - logger.Get().Tracelnf("%q", dumpHeaders(ctx.Req)) - - if err := p.setupHandlers(ctx); err != nil { - logger.Get().Errorlnf("Failed to setup handler (HTTP) for request %s. %+v", ctx.Req.URL.Redacted(), err) - - return nil, goproxy.NewResponse( - ctx.Req, - goproxy.ContentTypeText, - http.StatusInternalServerError, - err.Error(), - ) - } - - return ctx.Req, nil - }) - - p.proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { - if resp != nil { - logger.Get().Debuglnf("%s <- %s %v (%v bytes)", - resp.Request.RemoteAddr, resp.Request.Host, resp.Status, resp.ContentLength) - } else { - logger.Get().Tracelnf("%s <- %s response is empty", ctx.Req.Host, ctx.Req.RemoteAddr) - } - - return resp - }) + // Setup the request and response handlers + p.setupProxyHandlers() // Local proxy authentication. if parsedLocalProxyURI.User.Username() != "" { diff --git a/pkg/proxy/proxy_test.go b/pkg/proxy/proxy_test.go index 98a96435..aa9edb33 100644 --- a/pkg/proxy/proxy_test.go +++ b/pkg/proxy/proxy_test.go @@ -6,6 +6,7 @@ package proxy import ( "context" + "encoding/base64" "errors" "fmt" "io/ioutil" @@ -18,6 +19,7 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/saucelabs/forwarder/internal/logger" "github.com/saucelabs/randomness" "github.com/saucelabs/sypl/fields" @@ -158,6 +160,91 @@ func executeRequest(client *http.Client, uri string) (int, string, error) { // Tests ////// +func TestEncodeSiteCredentials(t *testing.T) { + tests := map[string]struct { + input string + hostport string + creds string + isErr bool + }{ + "no separators": { + input: "asdf", + hostport: "", + creds: "", + isErr: true, + }, + "empty field": { + input: "asdf:::", + hostport: "", + creds: "", + isErr: true, + }, + "valid data": { + input: "asdf:1234:user:pass", + hostport: "asdf:1234", + creds: "dXNlcjpwYXNz", + isErr: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + h, c, err := encodeSiteCredentials(tc.input) + + if h != tc.hostport { + t.Fatalf("%s != %s", h, tc.hostport) + } + if c != tc.creds { + t.Fatalf("%s != %s", c, tc.creds) + } + if tc.isErr != (err != nil) { + t.Fatalf("Err should be %v, is %s", tc.isErr, err) + } + }) + } +} + +func TestParseSiteCredentials(t *testing.T) { + tests := map[string]struct { + in []string + expect map[string]string + }{ + "valid data": { + in: []string{"abc:123:user:pass"}, + expect: map[string]string{ + "abc:123": "dXNlcjpwYXNz", + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + out, _ := parseSiteCredentials(tc.in) + + diff := cmp.Diff(tc.expect, out) + if diff != "" { + t.Fatalf(diff) + } + }) + } +} + +func getSiteCredentials(target string) []string { + uri, _ := url.Parse(target) + port := uri.Port() + if port == "" { + if uri.Scheme == "http" { + port = "80" + } else { + port = "443" + } + } + + creds := fmt.Sprintf("%s:%s:user:pass", uri.Hostname(), port) + + return []string{creds} +} + //nolint:maintidx func TestNew(t *testing.T) { ////// @@ -176,6 +263,7 @@ func TestNew(t *testing.T) { upstreamProxyURI *url.URL pacURI *url.URL pacProxiesCredentials []string + useAuth bool loggingOptions *LoggingOptions } tests := []struct { @@ -202,6 +290,20 @@ func TestNew(t *testing.T) { }, wantErr: false, }, + { + name: "Should work - local proxy with site auth", + args: args{ + localProxyURI: URIBuilder( + defaultProxyHostname, + r.MustGenerate(), + "", + "", + ), + loggingOptions: loggingOptions, + useAuth: true, + }, + wantErr: false, + }, { name: "Should work - local proxy - with DNS", args: args{ @@ -362,7 +464,14 @@ func TestNew(t *testing.T) { // Target/end server. ////// - targetServer := createMockedHTTPServer(http.StatusOK, "body", "") + targetCreds := "" + if tt.args.useAuth { + targetCreds = "dXNlcjpwYXNz" //nolint + targetCreds = base64. + StdEncoding. + EncodeToString([]byte("user:pass")) + } + targetServer := createMockedHTTPServer(http.StatusOK, "body", targetCreds) defer func() { targetServer.Close() }() @@ -402,6 +511,11 @@ func TestNew(t *testing.T) { pacURI = tt.args.pacURI.String() } + var siteCredentials []string + if tt.args.useAuth { + siteCredentials = getSiteCredentials(targetServerURL) + } + ////// // Local proxy. // @@ -423,6 +537,9 @@ func TestNew(t *testing.T) { // PAC proxies credentials in standard URI format. tt.args.pacProxiesCredentials, + // site credentials in host:port:user:pass format + siteCredentials, + // Logging settings. &Options{ DNSURIs: dnsURIs, @@ -497,6 +614,9 @@ func TestNew(t *testing.T) { // PAC proxies credentials in standard URI format. nil, + // site credentials in host:port:user:pass format + nil, + // Logging settings. &Options{ LoggingOptions: loggingOptions, @@ -580,7 +700,7 @@ func BenchmarkNew(b *testing.B) { localProxyURI := URIBuilder(defaultProxyHostname, r.MustGenerate(), "", "") - proxy, err := New(localProxyURI.String(), "", "", nil, nil) + proxy, err := New(localProxyURI.String(), "", "", nil, nil, nil) if err != nil { log.Fatalln("Failed to create proxy.", err) } From 34196147a7ad40598e55fbb072299c5f82bda531 Mon Sep 17 00:00:00 2001 From: Dan Slov Date: Fri, 1 Jul 2022 07:31:26 +0200 Subject: [PATCH 02/10] Minor code refactoring --- cmd/run.go | 4 ++-- pkg/proxy/proxy.go | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 4f74edd4..e6a792c8 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -117,12 +117,12 @@ Note: Can't setup upstream, and PAC at the same time. func init() { rootCmd.AddCommand(runCmd) - runCmd.Flags().StringVarP(&localProxyURI, "local-proxy-uri", "l", "http://localhost:8080", "Sets local proxy URI") + runCmd.Flags().StringVarP(&localProxyURI, "local-proxy-uri", "l", "http://localhost:8080", "sets local proxy URI") runCmd.Flags().StringVarP(&upstreamProxyURI, "upstream-proxy-uri", "u", "", "sets upstream proxy URI") runCmd.Flags().StringSliceVarP(&dnsURIs, "dns-uri", "n", nil, "sets dns URI") runCmd.Flags().StringVarP(&pacURI, "pac-uri", "p", "", "sets URI to PAC content, or directly, the PAC content") runCmd.Flags().StringSliceVarP(&pacProxiesCredentials, "pac-proxies-credentials", "d", nil, "sets PAC proxies credentials using standard URI format") - runCmd.Flags().StringSliceVarP(&siteCredentials, "site-credentials", "d", nil, "sets site based credentials") + runCmd.Flags().StringSliceVar(&siteCredentials, "site-credentials", nil, "sets site based credentials") runCmd.Flags().BoolVarP(&proxyLocalhost, "proxy-localhost", "t", false, "if set, will proxy localhost requests to an upstream proxy - if any") runCmd.Flags().BoolVarP(&automaticallyRetryPort, "find-port", "r", true, "if set, and the specified local proxy port is in-use, it will find, and use an available one") } diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go index 6fae4891..40b44a8e 100644 --- a/pkg/proxy/proxy.go +++ b/pkg/proxy/proxy.go @@ -36,6 +36,7 @@ const ( ConstantBackoff = 300 DNSTimeout = 1 * time.Minute MaxRetry = 3 + httpPort = 80 ) // Possible ways to run Forwarder. @@ -597,7 +598,7 @@ func (p *Proxy) setupProxyHandlers() { ) } - p.addAuthHeader(req) + p.maybeAddAuthHeader(req) return ctx.Req, nil }) @@ -614,25 +615,25 @@ func (p *Proxy) setupProxyHandlers() { }) } -// addAuthHeader modifies the request and adds an authorization header if necessary. -func (p *Proxy) addAuthHeader(req *http.Request) { +// maybeAddAuthHeader modifies the request and adds an authorization header if necessary. +func (p *Proxy) maybeAddAuthHeader(req *http.Request) { hostport := req.Host if req.URL.Port() == "" { - var port string + // When the destination URL doesn't contain an explicit port, Go http-parsed + // URL Port() returns an empty string. if req.URL.Scheme == "http" { - port = "80" + hostport = fmt.Sprintf("%s:%d", req.Host, httpPort) + } else { + logger.Get().Warnlnf("Failed to determine port for %s.", req.URL.Redacted()) } - - hostport = fmt.Sprintf("%s:%s", req.Host, port) } // If req.Host is in the auth map, add the basic auth header // using the credentials. These credentials are already base64 // encoded. - creds, found := p.siteCredentials[hostport] - if found { - logger.Get().Tracelnf("Found site credentials for %s", req.Host) + if creds, found := p.siteCredentials[hostport]; found { + logger.Get().Tracelnf("Adding an auth header for %s", hostport) req.Header.Set("Authorization", fmt.Sprintf("Basic %s", creds)) } } From 893b976ede03e8d42f1e76ad13511fca5dc9c9ea Mon Sep 17 00:00:00 2001 From: Dan Slov Date: Fri, 1 Jul 2022 07:59:27 +0200 Subject: [PATCH 03/10] Fixing benchmark --- Makefile | 3 +++ pkg/proxy/proxy_test.go | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f455c831..a92cb1d4 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,9 @@ endif test: @go test -timeout 120s -short -v -race -cover -coverprofile=coverage.out ./... +bench: + @go test -bench=. -run=XXX ./pkg/proxy + test-integration: @FORWARDER_TEST_MODE=integration go test -timeout 120s -v -race -cover -coverprofile=coverage.out ./... && echo "Test OK" diff --git a/pkg/proxy/proxy_test.go b/pkg/proxy/proxy_test.go index aa9edb33..4b245bfc 100644 --- a/pkg/proxy/proxy_test.go +++ b/pkg/proxy/proxy_test.go @@ -700,7 +700,10 @@ func BenchmarkNew(b *testing.B) { localProxyURI := URIBuilder(defaultProxyHostname, r.MustGenerate(), "", "") - proxy, err := New(localProxyURI.String(), "", "", nil, nil, nil) + proxy, err := New(localProxyURI.String(), "", "", nil, nil, + &Options{ + LoggingOptions: loggingOptions, + }) if err != nil { log.Fatalln("Failed to create proxy.", err) } From 2202bf7be30869a4fa9ac004f106a97029da0f0d Mon Sep 17 00:00:00 2001 From: Alex Harford Date: Tue, 5 Jul 2022 11:59:54 -0700 Subject: [PATCH 04/10] Change siteCredentials to be in a standard "user:pass@host:port" URI format --- pkg/proxy/proxy.go | 45 +++++++-------- pkg/proxy/proxy_test.go | 119 ++++++++++++++++------------------------ 2 files changed, 66 insertions(+), 98 deletions(-) diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go index 40b44a8e..6e9dbf9a 100644 --- a/pkg/proxy/proxy.go +++ b/pkg/proxy/proxy.go @@ -364,43 +364,36 @@ func setupPACUpstreamProxyConnection(p *Proxy, ctx *goproxy.ProxyCtx) error { return nil } -// encodeSiteCredentials converts credentials (strings of "user:pass") into -// base64 encoded strings to be used as basic authentication headers. -func encodeSiteCredentials(creds string) (string, string, error) { - tokens := strings.Split(creds, ":") - if len(tokens) != 4 { //nolint - return "", "", fmt.Errorf("failed to parse %s as site auth", creds) - } - - for _, token := range tokens { - if len(token) == 0 { - return "", "", fmt.Errorf("failed to find credentials in %s", creds) - } - } - - encodedCredential, err := credential.NewBasicAuth(tokens[2], tokens[3]) - if err != nil { - return "", "", fmt.Errorf("failed to parse credentials from %s", creds) - } - - return fmt.Sprintf("%s:%s", tokens[0], tokens[1]), encodedCredential.ToBase64(), nil -} - +// parseSiteCredentials takes a list of "user:pass@host:port" strings and converts them to a +// map of "host:port": base64("user:pass"). func parseSiteCredentials(creds []string) (map[string]string, error) { credMap := make(map[string]string, len(creds)) for _, credentialText := range creds { - hostport, creds, err := encodeSiteCredentials(credentialText) + // Parse the URL, adding fake schema since the url package expects it + uri, err := url.Parse("schema://" + credentialText) + if err != nil { + return nil, err + } + + // Get the base64 of the credentials + pass, found := uri.User.Password() + if !found { + return nil, fmt.Errorf("password not found in %s", credentialText) + } + + basicAuth, err := credential.NewBasicAuth(uri.User.Username(), pass) if err != nil { return nil, err } - _, found := credMap[hostport] + // Fail if the host is listed twice in the list + _, found = credMap[uri.Host] if found { - return nil, fmt.Errorf("multiple credentials for %s", hostport) + return nil, fmt.Errorf("multiple credentials for %s", uri.Host) } - credMap[hostport] = creds + credMap[uri.Host] = basicAuth.ToBase64() } return credMap, nil diff --git a/pkg/proxy/proxy_test.go b/pkg/proxy/proxy_test.go index 4b245bfc..3b2a58f5 100644 --- a/pkg/proxy/proxy_test.go +++ b/pkg/proxy/proxy_test.go @@ -160,57 +160,40 @@ func executeRequest(client *http.Client, uri string) (int, string, error) { // Tests ////// -func TestEncodeSiteCredentials(t *testing.T) { - tests := map[string]struct { - input string - hostport string - creds string - isErr bool - }{ - "no separators": { - input: "asdf", - hostport: "", - creds: "", - isErr: true, - }, - "empty field": { - input: "asdf:::", - hostport: "", - creds: "", - isErr: true, - }, - "valid data": { - input: "asdf:1234:user:pass", - hostport: "asdf:1234", - creds: "dXNlcjpwYXNz", - isErr: false, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - h, c, err := encodeSiteCredentials(tc.input) - - if h != tc.hostport { - t.Fatalf("%s != %s", h, tc.hostport) - } - if c != tc.creds { - t.Fatalf("%s != %s", c, tc.creds) - } - if tc.isErr != (err != nil) { - t.Fatalf("Err should be %v, is %s", tc.isErr, err) - } - }) - } -} - func TestParseSiteCredentials(t *testing.T) { tests := map[string]struct { in []string expect map[string]string + err bool }{ - "valid data": { - in: []string{"abc:123:user:pass"}, + "invalid with schema": { + in: []string{"https://user:pass@abc"}, + err: true, + }, + "empty user": { + in: []string{":pass@abc"}, + err: true, + }, + "empty password": { + in: []string{"user:@abc"}, + err: true, + }, + "missing password": { + in: []string{"user@abc"}, + err: true, + }, + "missing host": { + in: []string{"user:pass"}, + err: true, + }, + "valid host": { + in: []string{"user:pass@abc"}, + expect: map[string]string{ + "abc": "dXNlcjpwYXNz", + }, + }, + "valid host+port": { + in: []string{"user:pass@abc:123"}, expect: map[string]string{ "abc:123": "dXNlcjpwYXNz", }, @@ -219,7 +202,11 @@ func TestParseSiteCredentials(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - out, _ := parseSiteCredentials(tc.in) + out, err := parseSiteCredentials(tc.in) + + if (err == nil) == tc.err { + t.Fatalf("Unexpected error condition: %s", err) + } diff := cmp.Diff(tc.expect, out) if diff != "" { @@ -229,22 +216,6 @@ func TestParseSiteCredentials(t *testing.T) { } } -func getSiteCredentials(target string) []string { - uri, _ := url.Parse(target) - port := uri.Port() - if port == "" { - if uri.Scheme == "http" { - port = "80" - } else { - port = "443" - } - } - - creds := fmt.Sprintf("%s:%s:user:pass", uri.Hostname(), port) - - return []string{creds} -} - //nolint:maintidx func TestNew(t *testing.T) { ////// @@ -263,7 +234,7 @@ func TestNew(t *testing.T) { upstreamProxyURI *url.URL pacURI *url.URL pacProxiesCredentials []string - useAuth bool + siteCredentials []string loggingOptions *LoggingOptions } tests := []struct { @@ -299,8 +270,8 @@ func TestNew(t *testing.T) { "", "", ), - loggingOptions: loggingOptions, - useAuth: true, + loggingOptions: loggingOptions, + siteCredentials: []string{}, }, wantErr: false, }, @@ -465,8 +436,7 @@ func TestNew(t *testing.T) { ////// targetCreds := "" - if tt.args.useAuth { - targetCreds = "dXNlcjpwYXNz" //nolint + if tt.args.siteCredentials != nil { targetCreds = base64. StdEncoding. EncodeToString([]byte("user:pass")) @@ -512,8 +482,13 @@ func TestNew(t *testing.T) { } var siteCredentials []string - if tt.args.useAuth { - siteCredentials = getSiteCredentials(targetServerURL) + if tt.args.siteCredentials != nil { + uri, err := url.Parse(targetServerURL) + if err != nil { + panic(err) + } + + siteCredentials = append(siteCredentials, "user:pass@"+uri.Host) } ////// @@ -537,7 +512,7 @@ func TestNew(t *testing.T) { // PAC proxies credentials in standard URI format. tt.args.pacProxiesCredentials, - // site credentials in host:port:user:pass format + // site credentials in standard URI format. siteCredentials, // Logging settings. @@ -614,7 +589,7 @@ func TestNew(t *testing.T) { // PAC proxies credentials in standard URI format. nil, - // site credentials in host:port:user:pass format + // site credentials in standard URI format. nil, // Logging settings. From 186d1162bc4a5258401298b5ce0466fcee35257b Mon Sep 17 00:00:00 2001 From: Alex Harford Date: Wed, 6 Jul 2022 08:41:39 -0700 Subject: [PATCH 05/10] Add note about ulimit --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index a92cb1d4..4de234a8 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,7 @@ endif test: @go test -timeout 120s -short -v -race -cover -coverprofile=coverage.out ./... +# If you hit too many open files: ulimit -Sn 10000 bench: @go test -bench=. -run=XXX ./pkg/proxy From b155107598222ea2b82b7121864b4f201abeb94c Mon Sep 17 00:00:00 2001 From: Alex Harford Date: Fri, 8 Jul 2022 10:12:21 -0700 Subject: [PATCH 06/10] Fix spelling mistake in log --- internal/logger/logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/logger/logger.go b/internal/logger/logger.go index aba0f932..13e54f03 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -61,7 +61,7 @@ func (o *Options) Default() { // Get returns the logger. If the logger isn't configured, it will exit with fatal. func Get() *sypl.Sypl { if proxyLogger == nil { - log.Fatalln("Logger is not configired") + log.Fatalln("Logger is not configured") } return proxyLogger From 811f468d87d824f126a54a5689b20b5b39cf6e81 Mon Sep 17 00:00:00 2001 From: Alex Harford Date: Fri, 8 Jul 2022 12:47:18 -0700 Subject: [PATCH 07/10] Handle wildcards in credentials --- pkg/proxy/proxy.go | 141 ++++++++++++++++++++++++++++++++-------- pkg/proxy/proxy_test.go | 57 ++++++++++++++-- 2 files changed, 165 insertions(+), 33 deletions(-) diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go index 6e9dbf9a..4266b145 100644 --- a/pkg/proxy/proxy.go +++ b/pkg/proxy/proxy.go @@ -204,9 +204,18 @@ type Proxy struct { // Credentials for proxies specified in PAC content. pacProxiesCredentials []string - // Credentials for passing basic authentication to requests + // host:port credentials for passing basic authentication to requests siteCredentials map[string]string + // host (wildcard port) credentials for passing basic authentication to requests + siteCredentialsHost map[string]string + + // port (wildcard host) credentials for passing basic authentication to requests + siteCredentialsPort map[string]string + + // Global wildcard credentials for passing basic authentication to requests + siteCredentialsWildcard string + // Underlying proxy implementation. proxy *goproxy.ProxyHttpServer } @@ -364,39 +373,85 @@ func setupPACUpstreamProxyConnection(p *Proxy, ctx *goproxy.ProxyCtx) error { return nil } -// parseSiteCredentials takes a list of "user:pass@host:port" strings and converts them to a -// map of "host:port": base64("user:pass"). -func parseSiteCredentials(creds []string) (map[string]string, error) { - credMap := make(map[string]string, len(creds)) +// parseSiteCredentials takes a list of "user:pass@host:port" strings. +// +// A port of '0' means a wildcard port +// A host of '*' means a wildcard host +// A host:port of '*:0' will match everything +// +// They are converted to a map of: +// - "host:port": base64("user:pass"). +// - "port": base64("user:pass") +// - "host": base64("user:pass") +// and a global wildcard string. +func parseSiteCredentials(creds []string) (map[string]string, map[string]string, map[string]string, string, error) { + hostportMap := make(map[string]string, len(creds)) + hostMap := make(map[string]string, len(creds)) + portMap := make(map[string]string, len(creds)) + global := "" for _, credentialText := range creds { // Parse the URL, adding fake schema since the url package expects it uri, err := url.Parse("schema://" + credentialText) if err != nil { - return nil, err + return nil, nil, nil, "", err } // Get the base64 of the credentials pass, found := uri.User.Password() if !found { - return nil, fmt.Errorf("password not found in %s", credentialText) + return nil, nil, nil, "", fmt.Errorf("password not found in %s", credentialText) } basicAuth, err := credential.NewBasicAuth(uri.User.Username(), pass) if err != nil { - return nil, err + return nil, nil, nil, "", err } - // Fail if the host is listed twice in the list - _, found = credMap[uri.Host] + encoded := basicAuth.ToBase64() + + if uri.Hostname() == "*" && uri.Port() == "0" { + if global != "" { + return nil, nil, nil, "", fmt.Errorf("multiple credentials for global wildcard") + } + + global = encoded + + continue + } + + if uri.Hostname() == "*" { + _, found = portMap[uri.Port()] + if found { + return nil, nil, nil, "", fmt.Errorf("multiple credentials for wildcard host with port %s", uri.Port()) + } + + portMap[uri.Port()] = encoded + + continue + } + + if uri.Port() == "0" { + _, found = hostMap[uri.Hostname()] + if found { + return nil, nil, nil, "", fmt.Errorf("multiple credentials for wildcard port with host %s", uri.Hostname()) + } + + hostMap[uri.Hostname()] = encoded + + continue + } + + // No wildcards, add the host:port directly + _, found = hostportMap[uri.Host] if found { - return nil, fmt.Errorf("multiple credentials for %s", uri.Host) + return nil, nil, nil, "", fmt.Errorf("multiple credentials for %s", uri.Host) } - credMap[uri.Host] = basicAuth.ToBase64() + hostportMap[uri.Host] = encoded } - return credMap, nil + return hostportMap, hostMap, portMap, global, nil } // DRY on handler's code. @@ -612,22 +667,53 @@ func (p *Proxy) setupProxyHandlers() { func (p *Proxy) maybeAddAuthHeader(req *http.Request) { hostport := req.Host + var requestPort string + if req.URL.Port() == "" { // When the destination URL doesn't contain an explicit port, Go http-parsed // URL Port() returns an empty string. if req.URL.Scheme == "http" { + requestPort = fmt.Sprintf("%d", httpPort) hostport = fmt.Sprintf("%s:%d", req.Host, httpPort) } else { logger.Get().Warnlnf("Failed to determine port for %s.", req.URL.Redacted()) } + } else { + requestPort = req.URL.Port() } - // If req.Host is in the auth map, add the basic auth header - // using the credentials. These credentials are already base64 - // encoded. + /* Priority is exact match, then host, then port, then global wildcard */ + + // Check the hostport table if creds, found := p.siteCredentials[hostport]; found { logger.Get().Tracelnf("Adding an auth header for %s", hostport) req.Header.Set("Authorization", fmt.Sprintf("Basic %s", creds)) + + return + } + + // Host wildcard - check the port only + if creds, found := p.siteCredentialsPort[requestPort]; found { + logger.Get().Tracelnf("Adding an auth header for host wildcard and port match %s", requestPort) + req.Header.Set("Authorization", fmt.Sprintf("Basic %s", creds)) + + return + } + + // Port wildcard - check the host only + if creds, found := p.siteCredentialsHost[req.URL.Hostname()]; found { + logger.Get().Tracelnf("Adding an auth header for port wildcard and host match %s", req.URL.Hostname()) + req.Header.Set("Authorization", fmt.Sprintf("Basic %s", creds)) + + return + } + + // Global wildcard was set + if p.siteCredentialsWildcard != "" { + logger.Get().Tracelnf("Adding an auth header for global wildcard") + req.Header.Set("Authorization", fmt.Sprintf("Basic %s", p.siteCredentialsWildcard)) + + return } } @@ -670,21 +756,24 @@ func New( } // Parse site credential list into map of host:port -> base64 encoded credentials. - creds, err := parseSiteCredentials(siteCredentials) + hostportMap, hostMap, portMap, global, err := parseSiteCredentials(siteCredentials) if err != nil { return nil, err } p := &Proxy{ - LocalProxyURI: localProxyURI, - Mode: Direct, - Options: finalOptions, - PACURI: pacURI, - State: Initializing, - UpstreamProxyURI: upstreamProxyURI, - pacProxiesCredentials: pacProxiesCredentials, - mutex: &sync.RWMutex{}, - siteCredentials: creds, + LocalProxyURI: localProxyURI, + Mode: Direct, + Options: finalOptions, + PACURI: pacURI, + State: Initializing, + UpstreamProxyURI: upstreamProxyURI, + pacProxiesCredentials: pacProxiesCredentials, + mutex: &sync.RWMutex{}, + siteCredentials: hostportMap, + siteCredentialsHost: hostMap, + siteCredentialsPort: portMap, + siteCredentialsWildcard: global, } if err := validation.Get().Struct(p); err != nil { diff --git a/pkg/proxy/proxy_test.go b/pkg/proxy/proxy_test.go index 3b2a58f5..c36fc2bd 100644 --- a/pkg/proxy/proxy_test.go +++ b/pkg/proxy/proxy_test.go @@ -162,9 +162,12 @@ func executeRequest(client *http.Client, uri string) (int, string, error) { func TestParseSiteCredentials(t *testing.T) { tests := map[string]struct { - in []string - expect map[string]string - err bool + in []string + hostport map[string]string + port map[string]string + host map[string]string + global string + err bool }{ "invalid with schema": { in: []string{"https://user:pass@abc"}, @@ -188,27 +191,67 @@ func TestParseSiteCredentials(t *testing.T) { }, "valid host": { in: []string{"user:pass@abc"}, - expect: map[string]string{ + hostport: map[string]string{ "abc": "dXNlcjpwYXNz", }, + host: map[string]string{}, + port: map[string]string{}, }, "valid host+port": { in: []string{"user:pass@abc:123"}, - expect: map[string]string{ + hostport: map[string]string{ "abc:123": "dXNlcjpwYXNz", }, + host: map[string]string{}, + port: map[string]string{}, + }, + "wildcard host": { + in: []string{"user:pass@*:123"}, + port: map[string]string{ + "123": "dXNlcjpwYXNz", + }, + host: map[string]string{}, + hostport: map[string]string{}, + }, + "wildcard port": { + in: []string{"user:pass@abc:0"}, + host: map[string]string{ + "abc": "dXNlcjpwYXNz", + }, + hostport: map[string]string{}, + port: map[string]string{}, + }, + "global wildcard": { + in: []string{"user:pass@*:0"}, + global: "dXNlcjpwYXNz", + hostport: map[string]string{}, + host: map[string]string{}, + port: map[string]string{}, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { - out, err := parseSiteCredentials(tc.in) + hostport, host, port, global, err := parseSiteCredentials(tc.in) if (err == nil) == tc.err { t.Fatalf("Unexpected error condition: %s", err) } - diff := cmp.Diff(tc.expect, out) + diff := cmp.Diff(tc.hostport, hostport) + if diff != "" { + t.Fatalf(diff) + } + + diff = cmp.Diff(tc.host, host) + if diff != "" { + t.Fatalf(diff) + } + diff = cmp.Diff(tc.port, port) + if diff != "" { + t.Fatalf(diff) + } + diff = cmp.Diff(tc.global, global) if diff != "" { t.Fatalf(diff) } From 1b41abf94f94fa7d8a1ce38934b4880f1b3e3760 Mon Sep 17 00:00:00 2001 From: Dan Slov Date: Mon, 11 Jul 2022 07:25:35 -0700 Subject: [PATCH 08/10] Refactoring site credentials handling --- .github/workflows/go.yml | 4 +- .golangci.yml | 3 +- cmd/run.go | 11 ++- go.mod | 4 + go.sum | 28 +++--- internal/util/doc.go | 7 ++ internal/util/util.go | 44 ++++++++++ internal/util/util_test.go | 68 +++++++++++++++ pkg/proxy/example_test.go | 10 +-- pkg/proxy/proxy.go | 134 +++++++++++++---------------- pkg/proxy/proxy_test.go | 20 ++--- pkg/proxy/site_credentials.go | 81 +++++++++++++++++ pkg/proxy/site_credentials_test.go | 98 +++++++++++++++++++++ pkg/proxy/utils.go | 13 ++- 14 files changed, 410 insertions(+), 115 deletions(-) create mode 100644 internal/util/doc.go create mode 100644 internal/util/util.go create mode 100644 internal/util/util_test.go create mode 100644 pkg/proxy/site_credentials.go create mode 100644 pkg/proxy/site_credentials_test.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8d5d78f2..48aa633c 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -19,9 +19,9 @@ jobs: go-version: 1.17 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.1.0 + uses: golangci/golangci-lint-action@v3.2.0 with: - version: v1.45.0 + version: v1.46.2 args: "--timeout 5m -v -c .golangci.yml" - name: Lint diff --git a/.golangci.yml b/.golangci.yml index 2ea358ae..f49c4e47 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,6 +15,7 @@ linters: - testpackage - gochecknoglobals - exhaustivestruct + - exhaustruct - paralleltest - godox - cyclop @@ -29,7 +30,7 @@ linters-settings: min-complexity: 40 funlen: - lines: 200 + lines: 220 statements: 75 nestif: diff --git a/cmd/run.go b/cmd/run.go index e6a792c8..d3259bd6 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -40,6 +40,7 @@ All credentials can be set via env vars: - Upstream proxy: FORWARDER_UPSTREAMPROXY_AUTH - PAC URI: PACMAN_AUTH - PAC proxies: PACMAN_PROXIES_AUTH +- Target URLs: FORWARDER_SITE_CREDENTIALS Note: Can't setup upstream, and PAC at the same time. `, @@ -93,9 +94,16 @@ Note: Can't setup upstream, and PAC at the same time. -l "http://user:pwd@localhost:8085" \ -p "http://user2:pwd2@localhost:8090" \ -d "http://user3:pwd4@localhost:8091,http://user4:pwd5@localhost:8092" + + Start a protected proxy that adds basic auth header to requests to foo.bar:8090 + and qux.baz:80. + $ forwarder run \ + -t \ + -l "http://user:pwd@localhost:8085" \ + --site-credentials "user1:pwd1@foo.bar:8090,user2:pwd2@qux:baz:80" `, Run: func(cmd *cobra.Command, args []string) { - p, err := proxy.New(localProxyURI, upstreamProxyURI, pacURI, pacProxiesCredentials, siteCredentials, &proxy.Options{ + p, err := proxy.New(localProxyURI, upstreamProxyURI, pacURI, pacProxiesCredentials, &proxy.Options{ LoggingOptions: &proxy.LoggingOptions{ Level: logLevel, FileLevel: fileLevel, @@ -105,6 +113,7 @@ Note: Can't setup upstream, and PAC at the same time. AutomaticallyRetryPort: automaticallyRetryPort, DNSURIs: dnsURIs, ProxyLocalhost: proxyLocalhost, + SiteCredentials: siteCredentials, }) if err != nil { cliLogger.Fatalln(customerror.NewFailedToError("run", customerror.WithError(err))) diff --git a/go.mod b/go.mod index 2f6f0a55..5d688843 100644 --- a/go.mod +++ b/go.mod @@ -13,10 +13,12 @@ require ( github.com/saucelabs/randomness v0.0.5 github.com/saucelabs/sypl v1.5.12 github.com/spf13/cobra v1.3.0 + github.com/stretchr/testify v1.8.0 ) require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41 // indirect github.com/emirpasic/gods v1.12.0 // indirect @@ -29,10 +31,12 @@ require ( github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/saucelabs/lumberjack/v3 v3.0.2 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect golang.org/x/text v0.3.7 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 627be61d..9107378d 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dop251/goja v0.0.0-20211006122547-7efcb634c641 h1:FeL9DrCQOmJ0Xw5V3hNa3MVDYAvNaa/fVGJkYUgfgLY= -github.com/dop251/goja v0.0.0-20211006122547-7efcb634c641/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41 h1:yRPjAkkuR/E/tsVG7QmhzEeEtD3P2yllxsT1/ftURb0= github.com/dop251/goja v0.0.0-20220516123900-4418d4575a41/go.mod h1:TQJQ+ZNyFVvUtUEtCZxBhfWiH7RJqR3EivNmvD6Waik= @@ -106,15 +104,9 @@ github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac h1:XDAn206aIqKPdF5YczuuJXSQPx+WOen0Pxbxp5Fq8Pg= -github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/elazarl/goproxy v0.0.0-20220417044921-416226498f94 h1:VIy7cdK7ufs7ctpTFkXJHm1uP3dJSnCGSPysEICB1so= -github.com/elazarl/goproxy v0.0.0-20220417044921-416226498f94/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy v0.0.0-20220529153421-8ea89ba92021 h1:EbF0UihnxWRcIMOwoVtqnAylsqcjzqpSvMdjF2Ud4rA= github.com/elazarl/goproxy v0.0.0-20220529153421-8ea89ba92021/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= -github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac h1:9yrT5tmn9Zc0ytWPASlaPwQfQMQYnRf0RSDe1XvHw0Q= -github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/elazarl/goproxy/ext v0.0.0-20220529153421-8ea89ba92021 h1:XO62HGrPPZne8dYsNMZJGCCBOHkhcGUWNxyQdggKE3o= github.com/elazarl/goproxy/ext v0.0.0-20220529153421-8ea89ba92021/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= @@ -150,7 +142,6 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= @@ -288,9 +279,11 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= @@ -354,6 +347,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -362,17 +356,11 @@ github.com/saucelabs/customerror v1.0.3 h1:LfQUuQ9iK6/ExBzRXgLFKx0SLAVmz1s1P2HYX github.com/saucelabs/customerror v1.0.3/go.mod h1:16/zfic7+i7QHOi+i7IQC5/6aL4HYOLocOtjXOM0KXY= github.com/saucelabs/lumberjack/v3 v3.0.2 h1:d2xl3L4gtuwhFOnBEWTcTRxZ64wQWyFfUK8cadpe5NA= github.com/saucelabs/lumberjack/v3 v3.0.2/go.mod h1:YWvEpPjHrjk7jKET9K4Vphyk6RFlXFD1e/rP60Fr+JA= -github.com/saucelabs/pacman v0.0.13 h1:kdHLdZCminN/TGbKgXncZvXATNT7NTbXtVAfJQpVAlQ= -github.com/saucelabs/pacman v0.0.13/go.mod h1:CiqtUweQyceGUDccdu65+LK+UxUzfERJFILMwoyQ5us= -github.com/saucelabs/pacman v0.1.0 h1:2LpWMsGaISW79ziXTdtzgpqMLaGJsMemVo8Y8TGwtnw= -github.com/saucelabs/pacman v0.1.0/go.mod h1:sjyZ/L3RVWB0/s5aGasliVlOJgyCnq7EvYsnZPfASJQ= github.com/saucelabs/pacman v0.1.1 h1:QtL6rGSRS5HuXQlo1d5mnD8ElXMdMDnVD3VEatOrFOU= github.com/saucelabs/pacman v0.1.1/go.mod h1:sjyZ/L3RVWB0/s5aGasliVlOJgyCnq7EvYsnZPfASJQ= github.com/saucelabs/randomness v0.0.5 h1:IBgMdKOWb4zCKUbQ03tTjIUXZcxG1rT/cH1iggYjCJE= github.com/saucelabs/randomness v0.0.5/go.mod h1:jleEVfS8aVUKZ6Js4NTnqqM62SyvAaiRs23WEgckP+g= github.com/saucelabs/sypl v1.5.8/go.mod h1:ubSLpo9I9awtabutiS6Npjof7s/km+HJ/9aOOPClMW0= -github.com/saucelabs/sypl v1.5.10 h1:EptSBygwDminwfyg9SJP81ZwNcazX+1x1P4HWi18nkI= -github.com/saucelabs/sypl v1.5.10/go.mod h1:ubSLpo9I9awtabutiS6Npjof7s/km+HJ/9aOOPClMW0= github.com/saucelabs/sypl v1.5.12 h1:48Qtq/A7JtWdXcTFngJzzHI5731ccReedrkZimkslfA= github.com/saucelabs/sypl v1.5.12/go.mod h1:r/KHXrhgQ0XFnmXAazNmRXi1AtmVs/K4VJ1OoNWkRBk= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -391,14 +379,16 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -431,7 +421,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -681,6 +670,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -829,6 +819,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -841,8 +832,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/util/doc.go b/internal/util/doc.go new file mode 100644 index 00000000..c1e98724 --- /dev/null +++ b/internal/util/doc.go @@ -0,0 +1,7 @@ +// Copyright 2021 The forwarder Authors. All rights reserved. +// Use of this source code is governed by a MIT +// license that can be found in the LICENSE file. + +// Package util provides common (cross-package) utilities. +// It must not depend on another package in the project. +package util diff --git a/internal/util/util.go b/internal/util/util.go new file mode 100644 index 00000000..d50a8cb8 --- /dev/null +++ b/internal/util/util.go @@ -0,0 +1,44 @@ +// Copyright 2021 The forwarder Authors. All rights reserved. +// Use of this source code is governed by a MIT +// license that can be found in the LICENSE file. + +package util + +import ( + "fmt" + "net/url" + "strings" +) + +// normalizeURLScheme ensures that the URL starts with the scheme. +func normalizeURLScheme(uri string) string { + u := uri + scheme := "http" + if strings.HasPrefix(u, "://") { + u = uri[3:] + } + + if strings.Contains(u, "://") { + return u + } + + if strings.HasSuffix(u, ":443") { + scheme = "https" + } + + return fmt.Sprintf("%s://%s", scheme, u) +} + +// NormalizeURI ensures that the url has a scheme. +func NormalizeURI(uriToParse string) (*url.URL, error) { + // Using ParseRequestURI instead of Parse since our use-case is + // full URLs only. url.ParseRequestURI expects uriToParse to have a scheme. + localURL, err := url.ParseRequestURI(normalizeURLScheme(uriToParse)) + if err != nil { + return nil, err + } + if localURL.Scheme == "" { + localURL.Scheme = "http" + } + return localURL, nil +} diff --git a/internal/util/util_test.go b/internal/util/util_test.go new file mode 100644 index 00000000..cbec5c5a --- /dev/null +++ b/internal/util/util_test.go @@ -0,0 +1,68 @@ +// Copyright 2021 The forwarder Authors. All rights reserved. +// Use of this source code is governed by a MIT +// license that can be found in the LICENSE file. + +package util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNormalizeURI(t *testing.T) { + testCases := []struct { + name string + url string + expected string + err error + }{ + { + name: "Adds http scheme", + url: "example.com", + expected: "http://example.com", + err: nil, + }, + { + name: "Adds http scheme", + url: "example.com:8888", + expected: "http://example.com:8888", + err: nil, + }, + { + name: "Adds http scheme", + url: "://example.com:8888", + expected: "http://example.com:8888", + err: nil, + }, + { + name: "Adds https scheme", + url: "://example.com:443", + expected: "https://example.com:443", + err: nil, + }, + { + name: "Adds https scheme", + url: "example.com:443", + expected: "https://example.com:443", + err: nil, + }, + { + name: "Preserves the scheme", + url: "https://example.com", + expected: "https://example.com", + err: nil, + }, + } + + for _, tc := range testCases { + result, err := NormalizeURI(tc.url) + if tc.err == nil { + assert.Equalf(t, tc.expected, result.String(), "%s: Unexpected result: %v", tc.name, result) + assert.NoErrorf(t, err, + "%s: Unexpected error: %s", tc.name, err) + } else { + assert.Errorf(t, err, "%s: Expected error: %s", tc.name, tc.err) + } + } +} diff --git a/pkg/proxy/example_test.go b/pkg/proxy/example_test.go index 67a80d9b..8d94de56 100644 --- a/pkg/proxy/example_test.go +++ b/pkg/proxy/example_test.go @@ -134,8 +134,6 @@ func ExampleNew() { // PAC proxies credentials in standard URI format. []string{upstreamProxyURI.String()}, - nil, - // Logging settings. &Options{ LoggingOptions: loggingOptions, @@ -167,8 +165,6 @@ func ExampleNew() { // PAC proxies credentials in standard URI format. nil, - nil, - // Logging settings. &Options{ LoggingOptions: loggingOptions, @@ -232,7 +228,7 @@ func ExampleNew_automaticallyRetryPort() { errored := false - proxy1, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, nil, &Options{ + proxy1, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, &Options{ LoggingOptions: &LoggingOptions{ Level: "none", FileLevel: "none", @@ -246,7 +242,7 @@ func ExampleNew_automaticallyRetryPort() { time.Sleep(1 * time.Second) - proxy2, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, nil, &Options{ + proxy2, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, &Options{ AutomaticallyRetryPort: true, LoggingOptions: &LoggingOptions{ @@ -289,7 +285,7 @@ func ExampleNew_sypplyingLogger() { errored := false - proxy1, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, nil, &Options{ + proxy1, err := New(fmt.Sprintf("http://0.0.0.0:%d", randomPort), "", "", nil, &Options{ LoggingOptions: &LoggingOptions{ Logger: customLogger, Level: level.Trace.String(), diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go index 4266b145..461b2c16 100644 --- a/pkg/proxy/proxy.go +++ b/pkg/proxy/proxy.go @@ -24,6 +24,7 @@ import ( "github.com/saucelabs/forwarder/internal/credential" "github.com/saucelabs/forwarder/internal/logger" "github.com/saucelabs/forwarder/internal/pac" + "github.com/saucelabs/forwarder/internal/util" "github.com/saucelabs/forwarder/internal/validation" "github.com/saucelabs/randomness" "github.com/saucelabs/sypl" @@ -37,6 +38,8 @@ const ( DNSTimeout = 1 * time.Minute MaxRetry = 3 httpPort = 80 + proxyAuthHeader = "Proxy-Authorization" + authHeader = "Authorization" ) // Possible ways to run Forwarder. @@ -83,6 +86,7 @@ var ( ErrInvalidPACURI = customerror.NewInvalidError("PAC URI") ErrInvalidProxyParams = customerror.NewInvalidError("params") ErrInvalidUpstreamProxyURI = customerror.NewInvalidError("upstream proxy URI") + ErrInvalidSiteCredentials = customerror.NewInvalidError("invalid site credentials") ) // LoggingOptions defines logging options. @@ -135,6 +139,12 @@ type Options struct { // ProxyLocalhost if `true`, requests to `localhost`/`127.0.0.1` will be // forwarded to any upstream - if set. ProxyLocalhost bool + // SiteCredentials contains URLs with the credentials, for example: + // - https://usr1:pwd1@foo.bar:4443 + // - http://usr2:pwd2@bar.foo:8080 + // - usr3:pwd3@bar.foo:8080 + // Proxy will add basic auth headers for requests to these URLs. + SiteCredentials []string `json:"site_credentials" validate:"omitempty"` } // Default sets `Options` default values. @@ -204,35 +214,27 @@ type Proxy struct { // Credentials for proxies specified in PAC content. pacProxiesCredentials []string - // host:port credentials for passing basic authentication to requests - siteCredentials map[string]string - - // host (wildcard port) credentials for passing basic authentication to requests - siteCredentialsHost map[string]string - - // port (wildcard host) credentials for passing basic authentication to requests - siteCredentialsPort map[string]string - - // Global wildcard credentials for passing basic authentication to requests - siteCredentialsWildcard string + // credentials for passing basic authentication to requests + siteCredentialsMatcher siteCredentialsMatcher // Underlying proxy implementation. proxy *goproxy.ProxyHttpServer } +func basicAuth(userpwd string) string { + return base64.StdEncoding.EncodeToString([]byte(userpwd)) +} + // Sets the `Proxy-Authorization` header based on `uri` user info. func setProxyBasicAuthHeader(uri *url.URL, req *http.Request) { - encodedCredential := base64. - StdEncoding. - EncodeToString([]byte(uri.User.String())) - req.Header.Set( - "Proxy-Authorization", - fmt.Sprintf("Basic %s", encodedCredential), + proxyAuthHeader, + fmt.Sprintf("Basic %s", basicAuth(uri.User.String())), ) logger.Get().Debuglnf( - "Proxy-Authorization header set with %s:*** for url %s", + "%s header set with %s:*** for %s", + proxyAuthHeader, uri.User.Username(), req.URL.String(), ) @@ -340,7 +342,7 @@ func setupUpstreamProxyConnection(ctx *goproxy.ProxyCtx, uri *url.URL) { logger.Get().Tracelnf("Connection to the upstream proxy %s is set up", uri.Redacted()) } -// setupUpstreamProxyConnection dynamically forwards connections to an upstream +// setupPACUpstreamProxyConnection dynamically forwards connections to an upstream // proxy setup via PAC. func setupPACUpstreamProxyConnection(p *Proxy, ctx *goproxy.ProxyCtx) error { urlToFindProxyFor := ctx.Req.URL.String() @@ -391,16 +393,15 @@ func parseSiteCredentials(creds []string) (map[string]string, map[string]string, global := "" for _, credentialText := range creds { - // Parse the URL, adding fake schema since the url package expects it - uri, err := url.Parse("schema://" + credentialText) + uri, err := util.NormalizeURI(credentialText) if err != nil { - return nil, nil, nil, "", err + return nil, nil, nil, "", fmt.Errorf("%w: %s", ErrInvalidSiteCredentials, err) } // Get the base64 of the credentials pass, found := uri.User.Password() if !found { - return nil, nil, nil, "", fmt.Errorf("password not found in %s", credentialText) + return nil, nil, nil, "", fmt.Errorf("%w: password not found in %s", ErrInvalidSiteCredentials, credentialText) } basicAuth, err := credential.NewBasicAuth(uri.User.Username(), pass) @@ -412,7 +413,7 @@ func parseSiteCredentials(creds []string) (map[string]string, map[string]string, if uri.Hostname() == "*" && uri.Port() == "0" { if global != "" { - return nil, nil, nil, "", fmt.Errorf("multiple credentials for global wildcard") + return nil, nil, nil, "", fmt.Errorf("%w: multiple credentials for global wildcard", ErrInvalidSiteCredentials) } global = encoded @@ -423,7 +424,7 @@ func parseSiteCredentials(creds []string) (map[string]string, map[string]string, if uri.Hostname() == "*" { _, found = portMap[uri.Port()] if found { - return nil, nil, nil, "", fmt.Errorf("multiple credentials for wildcard host with port %s", uri.Port()) + return nil, nil, nil, "", fmt.Errorf("%w: multiple credentials for wildcard host with port %s", ErrInvalidSiteCredentials, uri.Port()) } portMap[uri.Port()] = encoded @@ -434,7 +435,7 @@ func parseSiteCredentials(creds []string) (map[string]string, map[string]string, if uri.Port() == "0" { _, found = hostMap[uri.Hostname()] if found { - return nil, nil, nil, "", fmt.Errorf("multiple credentials for wildcard port with host %s", uri.Hostname()) + return nil, nil, nil, "", fmt.Errorf("%w: multiple credentials for wildcard port with host %s", ErrInvalidSiteCredentials, uri.Hostname()) } hostMap[uri.Hostname()] = encoded @@ -445,7 +446,7 @@ func parseSiteCredentials(creds []string) (map[string]string, map[string]string, // No wildcards, add the host:port directly _, found = hostportMap[uri.Host] if found { - return nil, nil, nil, "", fmt.Errorf("multiple credentials for %s", uri.Host) + return nil, nil, nil, "", fmt.Errorf("%w: multiple credentials for %s", ErrInvalidSiteCredentials, uri.Host) } hostportMap[uri.Host] = encoded @@ -646,11 +647,17 @@ func (p *Proxy) setupProxyHandlers() { ) } - p.maybeAddAuthHeader(req) - return ctx.Req, nil }) + if p.siteCredentialsMatcher.isSet() { + p.proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { + p.maybeAddAuthHeader(req) + + return ctx.Req, nil + }) + } + p.proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { if resp != nil { logger.Get().Debuglnf("%s <- %s %v (%v bytes)", @@ -667,53 +674,20 @@ func (p *Proxy) setupProxyHandlers() { func (p *Proxy) maybeAddAuthHeader(req *http.Request) { hostport := req.Host - var requestPort string - if req.URL.Port() == "" { // When the destination URL doesn't contain an explicit port, Go http-parsed // URL Port() returns an empty string. if req.URL.Scheme == "http" { - requestPort = fmt.Sprintf("%d", httpPort) hostport = fmt.Sprintf("%s:%d", req.Host, httpPort) } else { logger.Get().Warnlnf("Failed to determine port for %s.", req.URL.Redacted()) } - } else { - requestPort = req.URL.Port() - } - - /* Priority is exact match, then host, then port, then global wildcard */ - - // Check the hostport table - if creds, found := p.siteCredentials[hostport]; found { - logger.Get().Tracelnf("Adding an auth header for %s", hostport) - req.Header.Set("Authorization", fmt.Sprintf("Basic %s", creds)) - - return } - // Host wildcard - check the port only - if creds, found := p.siteCredentialsPort[requestPort]; found { - logger.Get().Tracelnf("Adding an auth header for host wildcard and port match %s", requestPort) - req.Header.Set("Authorization", fmt.Sprintf("Basic %s", creds)) + creds := p.siteCredentialsMatcher.match(hostport) - return - } - - // Port wildcard - check the host only - if creds, found := p.siteCredentialsHost[req.URL.Hostname()]; found { - logger.Get().Tracelnf("Adding an auth header for port wildcard and host match %s", req.URL.Hostname()) - req.Header.Set("Authorization", fmt.Sprintf("Basic %s", creds)) - - return - } - - // Global wildcard was set - if p.siteCredentialsWildcard != "" { - logger.Get().Tracelnf("Adding an auth header for global wildcard") - req.Header.Set("Authorization", fmt.Sprintf("Basic %s", p.siteCredentialsWildcard)) - - return + if creds != "" { + req.Header.Set(authHeader, fmt.Sprintf("Basic %s", creds)) } } @@ -727,7 +701,6 @@ func New( localProxyURI string, upstreamProxyURI string, pacURI string, pacProxiesCredentials []string, - siteCredentials []string, options *Options, ) (*Proxy, error) { // Instantiate validator. @@ -755,27 +728,38 @@ func New( return nil, err } + siteCredentials := options.SiteCredentials + siteCredentialsFromEnv := loadSiteCredentialsFromEnvVar("FORWARDER_SITE_CREDENTIALS") + + if len(siteCredentials) == 0 && siteCredentialsFromEnv != nil { + siteCredentials = siteCredentialsFromEnv + } + // Parse site credential list into map of host:port -> base64 encoded credentials. hostportMap, hostMap, portMap, global, err := parseSiteCredentials(siteCredentials) if err != nil { return nil, err } - p := &Proxy{ - LocalProxyURI: localProxyURI, - Mode: Direct, - Options: finalOptions, - PACURI: pacURI, - State: Initializing, - UpstreamProxyURI: upstreamProxyURI, - pacProxiesCredentials: pacProxiesCredentials, - mutex: &sync.RWMutex{}, + credsMatcher := siteCredentialsMatcher{ siteCredentials: hostportMap, siteCredentialsHost: hostMap, siteCredentialsPort: portMap, siteCredentialsWildcard: global, } + p := &Proxy{ + LocalProxyURI: localProxyURI, + Mode: Direct, + Options: finalOptions, + PACURI: pacURI, + State: Initializing, + UpstreamProxyURI: upstreamProxyURI, + pacProxiesCredentials: pacProxiesCredentials, + mutex: &sync.RWMutex{}, + siteCredentialsMatcher: credsMatcher, + } + if err := validation.Get().Struct(p); err != nil { return nil, customerror.Wrap(ErrInvalidProxyParams, err) } diff --git a/pkg/proxy/proxy_test.go b/pkg/proxy/proxy_test.go index c36fc2bd..a8a12676 100644 --- a/pkg/proxy/proxy_test.go +++ b/pkg/proxy/proxy_test.go @@ -169,9 +169,13 @@ func TestParseSiteCredentials(t *testing.T) { global string err bool }{ - "invalid with schema": { - in: []string{"https://user:pass@abc"}, - err: true, + "valid with schema": { + in: []string{"https://user:pass@abc"}, + hostport: map[string]string{ + "abc": "dXNlcjpwYXNz", + }, + host: map[string]string{}, + port: map[string]string{}, }, "empty user": { in: []string{":pass@abc"}, @@ -555,13 +559,12 @@ func TestNew(t *testing.T) { // PAC proxies credentials in standard URI format. tt.args.pacProxiesCredentials, - // site credentials in standard URI format. - siteCredentials, - // Logging settings. &Options{ DNSURIs: dnsURIs, LoggingOptions: loggingOptions, + // site credentials in standard URI format. + SiteCredentials: siteCredentials, }, ) if err != nil { @@ -632,9 +635,6 @@ func TestNew(t *testing.T) { // PAC proxies credentials in standard URI format. nil, - // site credentials in standard URI format. - nil, - // Logging settings. &Options{ LoggingOptions: loggingOptions, @@ -718,7 +718,7 @@ func BenchmarkNew(b *testing.B) { localProxyURI := URIBuilder(defaultProxyHostname, r.MustGenerate(), "", "") - proxy, err := New(localProxyURI.String(), "", "", nil, nil, + proxy, err := New(localProxyURI.String(), "", "", nil, &Options{ LoggingOptions: loggingOptions, }) diff --git a/pkg/proxy/site_credentials.go b/pkg/proxy/site_credentials.go new file mode 100644 index 00000000..ce65dd62 --- /dev/null +++ b/pkg/proxy/site_credentials.go @@ -0,0 +1,81 @@ +// Copyright 2021 The forwarder Authors. All rights reserved. +// Use of this source code is governed by a MIT +// license that can be found in the LICENSE file. + +package proxy + +import ( + "strings" + + "github.com/saucelabs/forwarder/internal/logger" +) + +type siteCredentialsMatcher struct { + // host:port credentials for passing basic authentication to requests + siteCredentials map[string]string + + // host (wildcard port) credentials for passing basic authentication to requests + siteCredentialsHost map[string]string + + // port (wildcard host) credentials for passing basic authentication to requests + siteCredentialsPort map[string]string + + // Global wildcard credentials for passing basic authentication to requests + siteCredentialsWildcard string +} + +// match matches a `hostport` to one of the configured credentials. +// Priority is exact match, then host, then port, then global wildcard. +func (matcher siteCredentialsMatcher) match(hostport string) string { + if creds, found := matcher.siteCredentials[hostport]; found { + logger.Get().Tracelnf("Found an auth for %s", hostport) + + return creds + } + + // hostport parameter is expected to contain host:port. + partsLen := 2 + + parts := strings.SplitN(hostport, ":", partsLen) + + if len(parts) != partsLen { + logger.Get().Warnlnf("Unexpected host:port parameter: %s; will not match host or port wildcards", hostport) + + return "" + } + + host, port := parts[0], parts[1] + + // Host wildcard - check the port only. + if creds, found := matcher.siteCredentialsPort[port]; found { + logger.Get().Tracelnf("Found an auth for host wildcard and port match %s", port) + + return creds + } + + // Port wildcard - check the host only. + if creds, found := matcher.siteCredentialsHost[host]; found { + logger.Get().Tracelnf("Found an auth header for port wildcard and host match %s", host) + + return creds + } + + // Log whether the global wildcard is set. + // This is a very esoteric use case. It's only added to support a legacy implementation. + if matcher.siteCredentialsWildcard != "" { + logger.Get().Traceln("Found an auth for global wildcard") + } + + return matcher.siteCredentialsWildcard +} + +func (matcher siteCredentialsMatcher) isSet() bool { + if len(matcher.siteCredentials) > 0 || + len(matcher.siteCredentialsPort) > 0 || + len(matcher.siteCredentialsHost) > 0 || + matcher.siteCredentialsWildcard != "" { + return true + } + + return false +} diff --git a/pkg/proxy/site_credentials_test.go b/pkg/proxy/site_credentials_test.go new file mode 100644 index 00000000..b2f3d786 --- /dev/null +++ b/pkg/proxy/site_credentials_test.go @@ -0,0 +1,98 @@ +// Copyright 2021 The forwarder Authors. All rights reserved. +// Use of this source code is governed by a MIT +// license that can be found in the LICENSE file. + +package proxy + +import ( + "testing" + + "github.com/saucelabs/forwarder/internal/logger" + "github.com/stretchr/testify/assert" +) + +func TestSiteCredentialsMatcher(t *testing.T) { + tests := map[string]struct { + hostport string + hostPortMap map[string]string + portMap map[string]string + hostMap map[string]string + global string + isSet bool + expected string + }{ + "matcher is not initialized": { + hostPortMap: map[string]string{}, + expected: "", + hostport: "abc:80", + portMap: map[string]string{}, + hostMap: map[string]string{}, + isSet: false, + global: "", + }, + "matches hostport": { + hostPortMap: map[string]string{"abc:80": "user:pass"}, + expected: "user:pass", + hostport: "abc:80", + portMap: map[string]string{"*:80": "foo"}, + hostMap: map[string]string{"abc:0": "bar"}, + isSet: true, + global: "baz", + }, + "matches host wildcard": { + hostPortMap: map[string]string{"qux:80": "foo"}, + expected: "user:pass", + hostport: "abc:80", + portMap: map[string]string{"80": "user:pass"}, + hostMap: map[string]string{"abc": "bar"}, + isSet: true, + global: "baz", + }, + "matches port wildcard": { + hostPortMap: map[string]string{"qux:80": "foo"}, + expected: "user:pass", + hostport: "abc:80", + portMap: map[string]string{"90": "bar"}, + hostMap: map[string]string{"abc": "user:pass"}, + isSet: true, + global: "baz", + }, + "matches global wildcard": { + hostPortMap: map[string]string{"qux:80": "foo"}, + expected: "user:pass", + hostport: "abc:80", + portMap: map[string]string{"90": "bar"}, + hostMap: map[string]string{"qux": "baz"}, + isSet: true, + global: "user:pass", + }, + "no match": { + hostPortMap: map[string]string{"qux:80": "foo"}, + expected: "", + hostport: "foobar:8080", + portMap: map[string]string{"80": "bar"}, + hostMap: map[string]string{"qux": "baz"}, + isSet: true, + global: "", + }, + } + + logger.Setup(&LoggingOptions{ + Level: defaultProxyLoggingLevel, + }, + ) + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + matcher := siteCredentialsMatcher{ + siteCredentials: tc.hostPortMap, + siteCredentialsHost: tc.hostMap, + siteCredentialsPort: tc.portMap, + siteCredentialsWildcard: tc.global, + } + assert.Equalf(t, tc.isSet, matcher.isSet(), "Unexpected isSet: %v", matcher) + creds := matcher.match(tc.hostport) + assert.Equalf(t, tc.expected, creds, "Unexpected result: %v", creds) + }) + } +} diff --git a/pkg/proxy/utils.go b/pkg/proxy/utils.go index f0c158bd..05232fa8 100644 --- a/pkg/proxy/utils.go +++ b/pkg/proxy/utils.go @@ -16,7 +16,7 @@ import ( var ErrFailedToCopyOptions = customerror.NewFailedToError("deepCopy options") -// Loads, validate credential from env var, and set URI's user. +// Load credentials from the env var, validate and set the URL's user:pwd. func loadCredentialFromEnvVar(envVar string, uri *url.URL) error { credentialFromEnvVar := os.Getenv(envVar) @@ -35,6 +35,17 @@ func loadCredentialFromEnvVar(envVar string, uri *url.URL) error { return nil } +// Load URLs and their basic auth from the env var. +func loadSiteCredentialsFromEnvVar(envVar string) []string { + basicAuthURLstr := os.Getenv(envVar) + + if basicAuthURLstr == "" { + return nil + } + + return strings.Split(basicAuthURLstr, ",") +} + // Copy from `source` to `target`. // // Basic deep copy implementation. From 2b6e33415408c23dd2d279f6fedfc3ce2f47f58c Mon Sep 17 00:00:00 2001 From: Dan Slov Date: Mon, 11 Jul 2022 20:11:20 -0700 Subject: [PATCH 09/10] Handle https as a known port --- pkg/proxy/proxy.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go index 461b2c16..b9a21ba1 100644 --- a/pkg/proxy/proxy.go +++ b/pkg/proxy/proxy.go @@ -38,6 +38,7 @@ const ( DNSTimeout = 1 * time.Minute MaxRetry = 3 httpPort = 80 + httpsPort = 443 proxyAuthHeader = "Proxy-Authorization" authHeader = "Authorization" ) @@ -677,9 +678,12 @@ func (p *Proxy) maybeAddAuthHeader(req *http.Request) { if req.URL.Port() == "" { // When the destination URL doesn't contain an explicit port, Go http-parsed // URL Port() returns an empty string. - if req.URL.Scheme == "http" { + switch req.URL.Scheme { + case "http": hostport = fmt.Sprintf("%s:%d", req.Host, httpPort) - } else { + case "https": + hostport = fmt.Sprintf("%s:%d", req.Host, httpsPort) + default: logger.Get().Warnlnf("Failed to determine port for %s.", req.URL.Redacted()) } } From 25ff65b991e6d125552c6ba481c7afcfae9479fb Mon Sep 17 00:00:00 2001 From: Dan Slov Date: Tue, 12 Jul 2022 03:26:27 -0700 Subject: [PATCH 10/10] Adding release notes --- CHANGELOG.md | 4 ++++ README.md | 6 ++++-- pkg/proxy/doc.go | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09efb7e7..ea171dd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Load config from Config file, and from env vars. Use viper for that - Automatically alocates a random port, if the specified one is in-use +## [0.3.0] - 2022-07-12 +## Changed +- Added support for setting basic auth header via API, `--site-credentials` flag, or an env var + ## [0.2.0] - 2022-05-30 ## Changed - Upgraded goproxy library to the latest master diff --git a/README.md b/README.md index 563473da..f3ece1b7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # forwarder -`forwarder` provides a simple forward 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`. +`forwarder` provides a simple forward 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 `FORWARDER_LOCALPROXY_AUTH`. For remote proxy credential, set `FORWARDER_UPSTREAMPROXY_AUTH`. ## Install diff --git a/pkg/proxy/doc.go b/pkg/proxy/doc.go index 99c16739..26c1374d 100644 --- a/pkg/proxy/doc.go +++ b/pkg/proxy/doc.go @@ -6,6 +6,6 @@ // HTTP basic authentication. // 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 parent proxy credential, set `PROXY_PARENT_CREDENTIAL`. +// environment variables. For local proxy credential, set `FORWARDER_LOCALPROXY_AUTH`. +// For parent proxy credential, set `FORWARDER_UPSTREAMPROXY_AUTH`. package proxy