git.iko.soy/typeable/oauth2-proxy
on 8cbf921 help sign in
390 lines 11 kB history raw
1package validation
3import (
4 "crypto"
5 "errors"
6 "io/ioutil"
7 "net/url"
8 "os"
9 "strings"
10 "testing"
11 "time"
13 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
14 "github.com/stretchr/testify/assert"
17const (
18 cookieSecret = "secretthirtytwobytes+abcdefghijk"
19 clientID = "bazquux"
20 clientSecret = "xyzzyplugh"
23func testOptions() *options.Options {
24 o := options.NewOptions()
25 o.UpstreamServers = append(o.UpstreamServers, options.Upstream{
26 ID: "upstream",
27 Path: "/",
28 URI: "http://127.0.0.1:8080/",
29 })
30 o.Cookie.Secret = cookieSecret
31 o.ClientID = clientID
32 o.ClientSecret = clientSecret
33 o.EmailDomains = []string{"*"}
34 return o
37func errorMsg(msgs []string) string {
38 result := make([]string, 0)
39 result = append(result, "invalid configuration:")
40 result = append(result, msgs...)
41 return strings.Join(result, "\n ")
44func TestNewOptions(t *testing.T) {
45 o := options.NewOptions()
46 o.EmailDomains = []string{"*"}
47 err := Validate(o)
48 assert.NotEqual(t, nil, err)
50 expected := errorMsg([]string{
51 "missing setting: cookie-secret",
52 "missing setting: client-id",
53 "missing setting: client-secret or client-secret-file"})
54 assert.Equal(t, expected, err.Error())
57func TestClientSecretFileOptionFails(t *testing.T) {
58 o := options.NewOptions()
59 o.Cookie.Secret = cookieSecret
60 o.ClientID = clientID
61 o.ClientSecretFile = clientSecret
62 o.EmailDomains = []string{"*"}
63 err := Validate(o)
64 assert.NotEqual(t, nil, err)
66 p := o.GetProvider().Data()
67 assert.Equal(t, clientSecret, p.ClientSecretFile)
68 assert.Equal(t, "", p.ClientSecret)
70 s, err := p.GetClientSecret()
71 assert.NotEqual(t, nil, err)
72 assert.Equal(t, "", s)
75func TestClientSecretFileOption(t *testing.T) {
76 var err error
77 f, err := ioutil.TempFile("", "client_secret_temp_file_")
78 if err != nil {
79 t.Fatalf("failed to create temp file: %v", err)
80 }
81 f.WriteString("testcase")
82 if err := f.Close(); err != nil {
83 t.Fatalf("failed to close temp file: %v", err)
84 }
85 clientSecretFileName := f.Name()
86 defer os.Remove(clientSecretFileName)
88 o := options.NewOptions()
89 o.Cookie.Secret = cookieSecret
90 o.ClientID = clientID
91 o.ClientSecretFile = clientSecretFileName
92 o.EmailDomains = []string{"*"}
93 err = Validate(o)
94 assert.Equal(t, nil, err)
96 p := o.GetProvider().Data()
97 assert.Equal(t, clientSecretFileName, p.ClientSecretFile)
98 assert.Equal(t, "", p.ClientSecret)
100 s, err := p.GetClientSecret()
101 assert.Equal(t, nil, err)
102 assert.Equal(t, "testcase", s)
105func TestGoogleGroupOptions(t *testing.T) {
106 o := testOptions()
107 o.GoogleGroups = []string{"googlegroup"}
108 err := Validate(o)
109 assert.NotEqual(t, nil, err)
111 expected := errorMsg([]string{
112 "missing setting: google-admin-email",
113 "missing setting: google-service-account-json"})
114 assert.Equal(t, expected, err.Error())
117func TestGoogleGroupInvalidFile(t *testing.T) {
118 o := testOptions()
119 o.GoogleGroups = []string{"test_group"}
120 o.GoogleAdminEmail = "admin@example.com"
121 o.GoogleServiceAccountJSON = "file_doesnt_exist.json"
122 err := Validate(o)
123 assert.NotEqual(t, nil, err)
125 expected := errorMsg([]string{
126 "invalid Google credentials file: file_doesnt_exist.json",
127 })
128 assert.Equal(t, expected, err.Error())
131func TestInitializedOptions(t *testing.T) {
132 o := testOptions()
133 assert.Equal(t, nil, Validate(o))
136// Note that it's not worth testing nonparseable URLs, since url.Parse()
137// seems to parse damn near anything.
138func TestRedirectURL(t *testing.T) {
139 o := testOptions()
140 o.RawRedirectURL = "https://myhost.com/oauth2/callback"
141 assert.Equal(t, nil, Validate(o))
142 expected := &url.URL{
143 Scheme: "https", Host: "myhost.com", Path: "/oauth2/callback"}
144 assert.Equal(t, expected, o.GetRedirectURL())
147func TestCompiledRegex(t *testing.T) {
148 o := testOptions()
149 regexps := []string{"/foo/.*", "/ba[rz]/quux"}
150 o.SkipAuthRegex = regexps
151 assert.Equal(t, nil, Validate(o))
152 actual := make([]string, 0)
153 for _, regex := range o.GetCompiledRegex() {
154 actual = append(actual, regex.String())
155 }
156 assert.Equal(t, regexps, actual)
159func TestCompiledRegexError(t *testing.T) {
160 o := testOptions()
161 o.SkipAuthRegex = []string{"(foobaz", "barquux)"}
162 err := Validate(o)
163 assert.NotEqual(t, nil, err)
165 expected := errorMsg([]string{
166 "error compiling regex=\"(foobaz\" error parsing regexp: " +
167 "missing closing ): `(foobaz`",
168 "error compiling regex=\"barquux)\" error parsing regexp: " +
169 "unexpected ): `barquux)`"})
170 assert.Equal(t, expected, err.Error())
172 o.SkipAuthRegex = []string{"foobaz", "barquux)"}
173 err = Validate(o)
174 assert.NotEqual(t, nil, err)
176 expected = errorMsg([]string{
177 "error compiling regex=\"barquux)\" error parsing regexp: " +
178 "unexpected ): `barquux)`"})
179 assert.Equal(t, expected, err.Error())
182func TestDefaultProviderApiSettings(t *testing.T) {
183 o := testOptions()
184 assert.Equal(t, nil, Validate(o))
185 p := o.GetProvider().Data()
186 assert.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline",
187 p.LoginURL.String())
188 assert.Equal(t, "https://www.googleapis.com/oauth2/v3/token",
189 p.RedeemURL.String())
190 assert.Equal(t, "", p.ProfileURL.String())
191 assert.Equal(t, "profile email", p.Scope)
194func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) {
195 o := testOptions()
196 assert.Equal(t, nil, Validate(o))
198 assert.Equal(t, false, o.PassAccessToken)
199 o.PassAccessToken = true
200 o.Cookie.Secret = "cookie of invalid length-"
201 assert.NotEqual(t, nil, Validate(o))
203 o.PassAccessToken = false
204 o.Cookie.Refresh = time.Duration(24) * time.Hour
205 assert.NotEqual(t, nil, Validate(o))
207 o.Cookie.Secret = "16 bytes AES-128"
208 assert.Equal(t, nil, Validate(o))
210 o.Cookie.Secret = "24 byte secret AES-192--"
211 assert.Equal(t, nil, Validate(o))
213 o.Cookie.Secret = "32 byte secret for AES-256------"
214 assert.Equal(t, nil, Validate(o))
217func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
218 o := testOptions()
219 assert.Equal(t, nil, Validate(o))
221 o.Cookie.Secret = "0123456789abcdef"
222 o.Cookie.Refresh = o.Cookie.Expire
223 assert.NotEqual(t, nil, Validate(o))
225 o.Cookie.Refresh -= time.Duration(1)
226 assert.Equal(t, nil, Validate(o))
229func TestBase64CookieSecret(t *testing.T) {
230 o := testOptions()
231 assert.Equal(t, nil, Validate(o))
233 // 32 byte, base64 (urlsafe) encoded key
234 o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ="
235 assert.Equal(t, nil, Validate(o))
237 // 32 byte, base64 (urlsafe) encoded key, w/o padding
238 o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ"
239 assert.Equal(t, nil, Validate(o))
241 // 24 byte, base64 (urlsafe) encoded key
242 o.Cookie.Secret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3"
243 assert.Equal(t, nil, Validate(o))
245 // 16 byte, base64 (urlsafe) encoded key
246 o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA=="
247 assert.Equal(t, nil, Validate(o))
249 // 16 byte, base64 (urlsafe) encoded key, w/o padding
250 o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA"
251 assert.Equal(t, nil, Validate(o))
254func TestValidateSignatureKey(t *testing.T) {
255 o := testOptions()
256 o.SignatureKey = "sha1:secret"
257 assert.Equal(t, nil, Validate(o))
258 assert.Equal(t, o.GetSignatureData().Hash, crypto.SHA1)
259 assert.Equal(t, o.GetSignatureData().Key, "secret")
262func TestValidateSignatureKeyInvalidSpec(t *testing.T) {
263 o := testOptions()
264 o.SignatureKey = "invalid spec"
265 err := Validate(o)
266 assert.Equal(t, err.Error(), "invalid configuration:\n"+
267 " invalid signature hash:key spec: "+o.SignatureKey)
270func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) {
271 o := testOptions()
272 o.SignatureKey = "unsupported:default secret"
273 err := Validate(o)
274 assert.Equal(t, err.Error(), "invalid configuration:\n"+
275 " unsupported signature hash algorithm: "+o.SignatureKey)
278func TestSkipOIDCDiscovery(t *testing.T) {
279 o := testOptions()
280 o.ProviderType = "oidc"
281 o.OIDCIssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/"
282 o.SkipOIDCDiscovery = true
284 err := Validate(o)
285 assert.Equal(t, "invalid configuration:\n"+
286 " missing setting: login-url\n missing setting: redeem-url\n missing setting: oidc-jwks-url", err.Error())
288 o.LoginURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in"
289 o.RedeemURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_sign_in"
290 o.OIDCJwksURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys"
292 assert.Equal(t, nil, Validate(o))
295func TestGCPHealthcheck(t *testing.T) {
296 o := testOptions()
297 o.GCPHealthChecks = true
298 assert.Equal(t, nil, Validate(o))
301func TestRealClientIPHeader(t *testing.T) {
302 // Ensure nil if ReverseProxy not set.
303 o := testOptions()
304 o.RealClientIPHeader = "X-Real-IP"
305 assert.Equal(t, nil, Validate(o))
306 assert.Nil(t, o.GetRealClientIPParser())
308 // Ensure simple use case works.
309 o = testOptions()
310 o.ReverseProxy = true
311 o.RealClientIPHeader = "X-Forwarded-For"
312 assert.Equal(t, nil, Validate(o))
313 assert.NotNil(t, o.GetRealClientIPParser())
315 // Ensure unknown header format process an error.
316 o = testOptions()
317 o.ReverseProxy = true
318 o.RealClientIPHeader = "Forwarded"
319 err := Validate(o)
320 assert.NotEqual(t, nil, err)
321 expected := errorMsg([]string{
322 "real_client_ip_header (Forwarded) not accepted parameter value: the http header key (Forwarded) is either invalid or unsupported",
323 })
324 assert.Equal(t, expected, err.Error())
325 assert.Nil(t, o.GetRealClientIPParser())
327 // Ensure invalid header format produces an error.
328 o = testOptions()
329 o.ReverseProxy = true
330 o.RealClientIPHeader = "!934invalidheader-23:"
331 err = Validate(o)
332 assert.NotEqual(t, nil, err)
333 expected = errorMsg([]string{
334 "real_client_ip_header (!934invalidheader-23:) not accepted parameter value: the http header key (!934invalidheader-23:) is either invalid or unsupported",
335 })
336 assert.Equal(t, expected, err.Error())
337 assert.Nil(t, o.GetRealClientIPParser())
340func TestIPCIDRSetOption(t *testing.T) {
341 tests := []struct {
342 name string
343 trustedIPs []string
344 err error
345 }{
346 {
347 "TestSomeIPs",
348 []string{"127.0.0.1", "10.32.0.1/32", "43.36.201.0/24", "::1", "2a12:105:ee7:9234:0:0:0:0/64"},
349 nil,
350 }, {
351 "TestOverlappingIPs",
352 []string{"135.180.78.199", "135.180.78.199/32", "d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4", "d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4/128"},
353 nil,
354 }, {
355 "TestInvalidIPs",
356 []string{"[::1]", "alkwlkbn/32"},
357 errors.New(
358 "invalid configuration:\n" +
359 " trusted_ips[0] ([::1]) could not be recognized\n" +
360 " trusted_ips[1] (alkwlkbn/32) could not be recognized",
361 ),
362 },
363 }
365 for _, tt := range tests {
366 t.Run(tt.name, func(t *testing.T) {
367 o := testOptions()
368 o.TrustedIPs = tt.trustedIPs
369 err := Validate(o)
370 if tt.err == nil {
371 assert.Nil(t, err)
372 } else {
373 assert.Equal(t, tt.err.Error(), err.Error())
374 }
375 })
376 }
379func TestProviderCAFilesError(t *testing.T) {
380 file, err := ioutil.TempFile("", "absent.*.crt")
381 assert.NoError(t, err)
382 assert.NoError(t, file.Close())
383 assert.NoError(t, os.Remove(file.Name()))
385 o := testOptions()
386 o.ProviderCAFiles = append(o.ProviderCAFiles, file.Name())
387 err = Validate(o)
388 assert.Error(t, err)
389 assert.Contains(t, err.Error(), "unable to load provider CA file(s)")