Skip to content

Commit 6dbe910

Browse files
committed
test coverage
1 parent 44278f8 commit 6dbe910

File tree

7 files changed

+145
-30
lines changed

7 files changed

+145
-30
lines changed

command/run/run.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func (c *command) runE(cmd *cobra.Command, _ []string) (cmdErr error) {
131131
// Configure Kerberos as first because HTTP Transport
132132
// for various forwarder elements may require reaching hosts behind
133133
// Kerberos authenticated upstream proxy
134-
var kerberosAdapter *forwarder.KerberosAdapter = nil
134+
var kerberosAdapter forwarder.KerberosAdapter = nil
135135

136136
// use separate flag for determining if Kerberos is enabled
137137
// than presence of config file, this may change in the future
@@ -340,7 +340,7 @@ func (c *command) configureHeadersModifiers() {
340340
}
341341

342342
// configure upstream proxy transport - connect headers and/or Kerberos auth
343-
func (c *command) configureTransportProxy(tr *http.Transport, kerberosAdapter *forwarder.KerberosAdapter) {
343+
func (c *command) configureTransportProxy(tr *http.Transport, kerberosAdapter forwarder.KerberosAdapter) {
344344
headersToAllocate := len(c.connectHeaders)
345345

346346
if c.kerberosConfig.AuthUpstreamProxy {

dialvia/http_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,50 @@ func TestHTTPProxyDialerDialContext(t *testing.T) {
8888
}
8989
})
9090

91+
t.Run("CONNECT headers from GetProxyConnectHeaders", func(t *testing.T) {
92+
93+
d.GetProxyConnectHeader = func(_ context.Context, proxyURL *url.URL, _ string) (http.Header, error) {
94+
authHeader := make(http.Header, 1)
95+
authHeader.Set("Proxy-Authorization", "TEST-PROXY-AUTHORIZATION")
96+
return authHeader, nil
97+
98+
}
99+
100+
errCh := make(chan error, 1)
101+
go func() {
102+
errCh <- serveOne(l, func(conn net.Conn) error {
103+
pbr := bufio.NewReader(conn)
104+
req, err := http.ReadRequest(pbr)
105+
if err != nil {
106+
return err
107+
}
108+
109+
if req.Method != http.MethodConnect {
110+
return fmt.Errorf("HTTP CONNECT method expected")
111+
}
112+
113+
if req.Header.Get("Proxy-Authorization") != "TEST-PROXY-AUTHORIZATION" {
114+
return fmt.Errorf("Proxy-Authorization header expected but not present")
115+
}
116+
117+
return proxyutil.NewResponse(404, nil, req).Write(conn)
118+
})
119+
}()
120+
121+
conn, err := d.DialContext(ctx, "tcp", "foobar.com:80")
122+
if err == nil {
123+
t.Fatal("err is nil")
124+
}
125+
t.Log(err)
126+
if conn != nil {
127+
t.Fatal("conn is not nil")
128+
}
129+
130+
if err := <-errCh; err != nil {
131+
t.Fatal(err)
132+
}
133+
})
134+
91135
t.Run("response with data", func(t *testing.T) {
92136
errCh := make(chan error, 1)
93137
go func() {

docs/content/cli/forwarder_run.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ List of hosts for which Kerberos (SPNEGO) authorization tokens will be injected
694694
* Value Format: `<value>` (you can use empty command line switch to enable)
695695
* Default Value: `false`
696696

697-
Authenticate to a configured upstream proxy with Kerberos (using `Proxy-Authorization` HTTP header in CONNECT requests). Please note that if forwarder configuration results in multiple proxies available (like PAC for example), forwarder will try to authenticate to each one of them.
697+
Authenticate to a configured upstream proxy with Kerberos (using `Proxy-Authorization` HTTP header). Please note that if forwarder configuration results in multiple proxies available (like PAC for example), forwarder will try to authenticate to each one of them.
698698

699699

700700
### --kerberos-run-diagnostics {#kerberos-run-diagnostics}
@@ -704,7 +704,7 @@ Authenticate to a configured upstream proxy with Kerberos (using `Proxy-Authoriz
704704
* Default Value: `false`
705705

706706

707-
Running forwarder with `--kerberos-run-diagnostics` switch will print debugging information about Kerberos connection and an error when there are discrepancies between supported encryption types and keytab entry:
707+
Running forwarder with `--kerberos-run-diagnostics` switch will print debugging information about Kerberos connection or known configuration erros - for example an error when there are discrepancies between supported encryption types and keytab entry:
708708

709709
```
710710
msg="fatal error exiting" error="kerberos configuration potential problems: default_tkt_enctypes specifies 17 but this enctype is not available in the client's keytab\ndefault_tkt_enctypes specifies 23 but this enctype is not available in the client's keytab\npreferred_preauth_types specifies 17 but this enctype is not available in the client's keytab\npreferred_preauth_types specifies 15 but this enctype is not available in the client's keytab\npreferred_preauth_types specifies 14 but this enctype is not available in the client's keytab"

http_proxy.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ type HTTPProxy struct {
155155
proxy *martian.Proxy
156156
mitmCACert *x509.Certificate
157157
proxyFunc ProxyFunc
158-
kerberosAdapter *KerberosAdapter
158+
kerberosAdapter KerberosAdapter
159159
localhost []string
160160

161161
tlsConfig *tls.Config
@@ -165,7 +165,7 @@ type HTTPProxy struct {
165165
// NewHTTPProxy creates a new HTTP proxy.
166166
// It is the caller's responsibility to call Close on the returned server.
167167
func NewHTTPProxy(cfg *HTTPProxyConfig, pr PACResolver, cm *CredentialsMatcher, rt http.RoundTripper, log log.StructuredLogger,
168-
kerberosAdapter *KerberosAdapter,
168+
kerberosAdapter KerberosAdapter,
169169
) (*HTTPProxy, error) {
170170
hp, err := newHTTPProxy(cfg, pr, cm, rt, log, kerberosAdapter)
171171
if err != nil {
@@ -205,7 +205,7 @@ func NewHTTPProxy(cfg *HTTPProxyConfig, pr PACResolver, cm *CredentialsMatcher,
205205
}
206206

207207
// NewHTTPProxyHandler is like NewHTTPProxy but returns http.Handler instead of *HTTPProxy.
208-
func NewHTTPProxyHandler(cfg *HTTPProxyConfig, pr PACResolver, cm *CredentialsMatcher, rt http.RoundTripper, log log.StructuredLogger, kerberosAdapter *KerberosAdapter) (http.Handler, error) {
208+
func NewHTTPProxyHandler(cfg *HTTPProxyConfig, pr PACResolver, cm *CredentialsMatcher, rt http.RoundTripper, log log.StructuredLogger, kerberosAdapter KerberosAdapter) (http.Handler, error) {
209209
hp, err := newHTTPProxy(cfg, pr, cm, rt, log, kerberosAdapter)
210210
if err != nil {
211211
return nil, err
@@ -214,7 +214,7 @@ func NewHTTPProxyHandler(cfg *HTTPProxyConfig, pr PACResolver, cm *CredentialsMa
214214
return hp.handler(), nil
215215
}
216216

217-
func newHTTPProxy(cfg *HTTPProxyConfig, pr PACResolver, cm *CredentialsMatcher, rt http.RoundTripper, log log.StructuredLogger, kerberosAdapter *KerberosAdapter) (*HTTPProxy, error) {
217+
func newHTTPProxy(cfg *HTTPProxyConfig, pr PACResolver, cm *CredentialsMatcher, rt http.RoundTripper, log log.StructuredLogger, kerberosAdapter KerberosAdapter) (*HTTPProxy, error) {
218218
if err := cfg.Validate(); err != nil {
219219
return nil, err
220220
}
@@ -353,7 +353,7 @@ func (hp *HTTPProxy) upstreamProxyURL() *url.URL {
353353
// to auth upstream proxy and clear existing auth data
354354
// so http.RoundTripper would not try to add custom Authorization header
355355

356-
if hp.kerberosAdapter != nil && hp.kerberosAdapter.configuration.AuthUpstreamProxy {
356+
if hp.kerberosAdapter != nil && hp.kerberosAdapter.GetConfig().AuthUpstreamProxy {
357357

358358
proxyURL.User = nil
359359
return proxyURL
@@ -385,7 +385,7 @@ func (hp *HTTPProxy) pacProxy(r *http.Request) (*url.URL, error) {
385385
// to auth upstream proxy and clear existing auth data
386386
// so http.RoundTripper would not try to add custom Authorization header
387387

388-
if hp.kerberosAdapter != nil && hp.kerberosAdapter.configuration.AuthUpstreamProxy {
388+
if hp.kerberosAdapter != nil && hp.kerberosAdapter.GetConfig().AuthUpstreamProxy {
389389

390390
proxyURL.User = nil
391391
return proxyURL, nil
@@ -426,7 +426,7 @@ func (hp *HTTPProxy) middlewareStack() (martian.RequestResponseModifier, *martia
426426
stack.AddRequestModifier(hp.injectKerberosSPNEGOAuthentication())
427427
}
428428

429-
if hp.kerberosAdapter != nil && hp.kerberosAdapter.configuration.AuthUpstreamProxy && hp.proxyFunc != nil {
429+
if hp.kerberosAdapter != nil && hp.kerberosAdapter.GetConfig().AuthUpstreamProxy && hp.proxyFunc != nil {
430430
stack.AddRequestModifier(hp.injectKerberosUpstreamProxyAuthorizationHeader())
431431
}
432432

@@ -487,7 +487,7 @@ func (hp *HTTPProxy) injectKerberosSPNEGOAuthentication() martian.RequestModifie
487487

488488
return martian.RequestModifierFunc(func(req *http.Request) error {
489489
// TODO: use a map or something faster than array lookup
490-
if slices.Contains(hp.kerberosAdapter.configuration.KerberosEnabledHosts, strings.ToLower(req.URL.Hostname())) {
490+
if slices.Contains(hp.kerberosAdapter.GetConfig().KerberosEnabledHosts, strings.ToLower(req.URL.Hostname())) {
491491
spn, err := hp.kerberosAdapter.GetSPNForHost(req.URL.Hostname())
492492
if err != nil {
493493
return err

http_proxy_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"testing"
1919

2020
"github.com/saucelabs/forwarder/log/slog"
21+
"github.com/stretchr/testify/assert"
2122
"golang.org/x/net/http2"
2223
)
2324

@@ -199,3 +200,43 @@ func TestErrorResponse(t *testing.T) {
199200
}
200201
})
201202
}
203+
204+
type KerberosAdapterMock struct {
205+
KDCConnected bool
206+
}
207+
208+
func (a *KerberosAdapterMock) ConnectToKDC() error {
209+
a.KDCConnected = true
210+
return nil
211+
}
212+
213+
func (a *KerberosAdapterMock) GetConfig() *KerberosConfig {
214+
return DefaultKerberosConfig()
215+
}
216+
217+
func (a *KerberosAdapterMock) GetSPNForHost(hostname string) (string, error) {
218+
return hostname, nil
219+
}
220+
func (a *KerberosAdapterMock) GetSPNEGOHeaderValue(spn string) (string, error) {
221+
return "TEST-TOKEN", nil
222+
}
223+
224+
func (a *KerberosAdapterMock) GetProxyAuthHeader(_ context.Context, proxyURL *url.URL, _ string) (http.Header, error) {
225+
return nil, nil
226+
}
227+
228+
func TestKerberosAuth(t *testing.T) {
229+
cfg := DefaultHTTPProxyConfig()
230+
cfg.ProxyLocalhost = AllowProxyLocalhost
231+
232+
kerberosAdapter := KerberosAdapterMock{}
233+
234+
_, err := NewHTTPProxyHandler(cfg, nil, nil, nil, slog.Default(), &kerberosAdapter)
235+
if err != nil {
236+
t.Fatal(err)
237+
}
238+
239+
t.Run("KDC connected", func(t *testing.T) {
240+
assert.True(t, kerberosAdapter.KDCConnected)
241+
})
242+
}

kerberos.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,27 @@ type KerberosConfig struct {
3434
KerberosEnabledHosts []string
3535
}
3636

37+
type KerberosAdapter interface {
38+
ConnectToKDC() error
39+
GetSPNForHost(hostname string) (string, error)
40+
GetSPNEGOHeaderValue(spn string) (string, error)
41+
GetConfig() *KerberosConfig
42+
GetProxyAuthHeader(_ context.Context, proxyURL *url.URL, _ string) (http.Header, error)
43+
}
44+
3745
func DefaultKerberosConfig() *KerberosConfig {
3846
return &KerberosConfig{
3947
// default zero values are fine
4048
}
4149
}
4250

43-
type KerberosAdapter struct {
51+
type KerberosClient struct {
4452
configuration KerberosConfig
4553
krb5client client.Client
4654
log log.StructuredLogger
4755
}
4856

49-
func NewKerberosAdapter(cnf KerberosConfig, log log.StructuredLogger) (*KerberosAdapter, error) {
57+
func NewKerberosAdapter(cnf KerberosConfig, log log.StructuredLogger) (*KerberosClient, error) {
5058
// technically this should not happen as adapter should not be initialized without
5159
// proper config present, but better safe than sorry
5260
if cnf.CfgFilePath == "" {
@@ -57,6 +65,13 @@ func NewKerberosAdapter(cnf KerberosConfig, log log.StructuredLogger) (*Kerberos
5765
return nil, fmt.Errorf("kerberos keytab file not specified")
5866
}
5967

68+
if cnf.UserName == "" {
69+
return nil, fmt.Errorf("kerberos username not specified")
70+
}
71+
if cnf.UserRealm == "" {
72+
return nil, fmt.Errorf("kerberos user realm not specified")
73+
}
74+
6075
krb5Config, err := config.Load(cnf.CfgFilePath)
6176
if err != nil {
6277
return nil, fmt.Errorf("error loading kerberos config file %s: %w", cnf.CfgFilePath, err)
@@ -67,19 +82,16 @@ func NewKerberosAdapter(cnf KerberosConfig, log log.StructuredLogger) (*Kerberos
6782
return nil, fmt.Errorf("error loading kerberos keytab file %s: %w", cnf.KeyTabFilePath, err)
6883
}
6984

70-
if cnf.UserName == "" {
71-
return nil, fmt.Errorf("kerberos username not specified")
72-
}
73-
if cnf.UserRealm == "" {
74-
return nil, fmt.Errorf("kerberos user realm not specified")
75-
}
76-
7785
krb5Client := client.NewWithKeytab(cnf.UserName, cnf.UserRealm, krb5Keytab, krb5Config)
7886

79-
return &KerberosAdapter{configuration: cnf, krb5client: *krb5Client, log: log}, nil
87+
return &KerberosClient{configuration: cnf, krb5client: *krb5Client, log: log}, nil
88+
}
89+
90+
func (a *KerberosClient) GetConfig() *KerberosConfig {
91+
return &a.configuration
8092
}
8193

82-
func (a *KerberosAdapter) ConnectToKDC() error {
94+
func (a *KerberosClient) ConnectToKDC() error {
8395
a.log.Debug("Logging to KDC server")
8496
loginErr := a.krb5client.Login()
8597
if loginErr != nil && !a.configuration.RunDiagnostics {
@@ -113,14 +125,14 @@ func (a *KerberosAdapter) ConnectToKDC() error {
113125
return nil
114126
}
115127

116-
func (a *KerberosAdapter) GetSPNForHost(hostname string) (string, error) {
128+
func (a *KerberosClient) GetSPNForHost(hostname string) (string, error) {
117129
// static for now but in the future we may want to do DNS queries for CNAME
118130
return "HTTP/" + hostname, nil
119131
}
120132

121133
// GetSPNEGOHeaderValue accepts SPN service name and returns header value that should
122134
// be put inside Authorization or Proxy-Authorization header.
123-
func (a *KerberosAdapter) GetSPNEGOHeaderValue(spn string) (string, error) {
135+
func (a *KerberosClient) GetSPNEGOHeaderValue(spn string) (string, error) {
124136
a.log.Debug("Generating SPNEGO header value for SPN: ", spn)
125137

126138
cli := spnego.SPNEGOClient(&a.krb5client, spn)
@@ -141,7 +153,7 @@ func (a *KerberosAdapter) GetSPNEGOHeaderValue(spn string) (string, error) {
141153
return "Negotiate " + base64.StdEncoding.EncodeToString(nb), nil
142154
}
143155

144-
func (a *KerberosAdapter) GetProxyAuthHeader(_ context.Context, proxyURL *url.URL, _ string) (http.Header, error) {
156+
func (a *KerberosClient) GetProxyAuthHeader(_ context.Context, proxyURL *url.URL, _ string) (http.Header, error) {
145157
spn := "HTTP/" + proxyURL.Hostname()
146158

147159
SPNEGOHeaderValue, err := a.GetSPNEGOHeaderValue(spn)

kerberos_test.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,32 @@ import (
1010
"testing"
1111

1212
"github.com/saucelabs/forwarder/log/slog"
13+
"github.com/stretchr/testify/assert"
1314
)
1415

1516
func TestKerberosAdapterFailsWithoutConfig(t *testing.T) {
1617
cnf := KerberosConfig{}
1718

1819
_, err := NewKerberosAdapter(cnf, slog.Default())
19-
// TODO: match expected error message
20-
if err != nil {
21-
t.Fatal(err)
22-
}
20+
assert.NotNil(t, err)
21+
assert.ErrorContains(t, err, "kerberos config file (krb5.conf) not specified")
22+
23+
cnf.CfgFilePath = "/tmp/test.cfg"
24+
25+
_, err = NewKerberosAdapter(cnf, slog.Default())
26+
assert.NotNil(t, err)
27+
assert.ErrorContains(t, err, "kerberos keytab file not specified")
28+
29+
cnf.KeyTabFilePath = "/tmp/keytab"
30+
31+
_, err = NewKerberosAdapter(cnf, slog.Default())
32+
assert.NotNil(t, err)
33+
assert.ErrorContains(t, err, "kerberos username not specified")
34+
35+
cnf.UserName = "user1"
36+
37+
_, err = NewKerberosAdapter(cnf, slog.Default())
38+
assert.NotNil(t, err)
39+
assert.ErrorContains(t, err, "kerberos user realm not specified")
40+
2341
}

0 commit comments

Comments
 (0)