mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-04-25 12:24:41 +02:00
This also removes the check for the decoded from the valid secret size check. The code was unreachable because encryption.SecretBytes will only return the decoded secret if it was the right length after decoding.
367 lines
10 KiB
Go
367 lines
10 KiB
Go
package validation
|
|
|
|
import (
|
|
"crypto"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const (
|
|
cookieSecret = "secretthirtytwobytes+abcdefghijk"
|
|
clientID = "bazquux"
|
|
clientSecret = "xyzzyplugh"
|
|
)
|
|
|
|
func testOptions() *options.Options {
|
|
o := options.NewOptions()
|
|
o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
|
|
o.Cookie.Secret = cookieSecret
|
|
o.ClientID = clientID
|
|
o.ClientSecret = clientSecret
|
|
o.EmailDomains = []string{"*"}
|
|
return o
|
|
}
|
|
|
|
func errorMsg(msgs []string) string {
|
|
result := make([]string, 0)
|
|
result = append(result, "invalid configuration:")
|
|
result = append(result, msgs...)
|
|
return strings.Join(result, "\n ")
|
|
}
|
|
|
|
func TestNewOptions(t *testing.T) {
|
|
o := options.NewOptions()
|
|
o.EmailDomains = []string{"*"}
|
|
err := Validate(o)
|
|
assert.NotEqual(t, nil, err)
|
|
|
|
expected := errorMsg([]string{
|
|
"missing setting: cookie-secret",
|
|
"missing setting: client-id",
|
|
"missing setting: client-secret or client-secret-file"})
|
|
assert.Equal(t, expected, err.Error())
|
|
}
|
|
|
|
func TestClientSecretFileOptionFails(t *testing.T) {
|
|
o := options.NewOptions()
|
|
o.Cookie.Secret = cookieSecret
|
|
o.ClientID = clientID
|
|
o.ClientSecretFile = clientSecret
|
|
o.EmailDomains = []string{"*"}
|
|
err := Validate(o)
|
|
assert.NotEqual(t, nil, err)
|
|
|
|
p := o.GetProvider().Data()
|
|
assert.Equal(t, clientSecret, p.ClientSecretFile)
|
|
assert.Equal(t, "", p.ClientSecret)
|
|
|
|
s, err := p.GetClientSecret()
|
|
assert.NotEqual(t, nil, err)
|
|
assert.Equal(t, "", s)
|
|
}
|
|
|
|
func TestClientSecretFileOption(t *testing.T) {
|
|
var err error
|
|
f, err := ioutil.TempFile("", "client_secret_temp_file_")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp file: %v", err)
|
|
}
|
|
f.WriteString("testcase")
|
|
if err := f.Close(); err != nil {
|
|
t.Fatalf("failed to close temp file: %v", err)
|
|
}
|
|
clientSecretFileName := f.Name()
|
|
defer os.Remove(clientSecretFileName)
|
|
|
|
o := options.NewOptions()
|
|
o.Cookie.Secret = cookieSecret
|
|
o.ClientID = clientID
|
|
o.ClientSecretFile = clientSecretFileName
|
|
o.EmailDomains = []string{"*"}
|
|
err = Validate(o)
|
|
assert.Equal(t, nil, err)
|
|
|
|
p := o.GetProvider().Data()
|
|
assert.Equal(t, clientSecretFileName, p.ClientSecretFile)
|
|
assert.Equal(t, "", p.ClientSecret)
|
|
|
|
s, err := p.GetClientSecret()
|
|
assert.Equal(t, nil, err)
|
|
assert.Equal(t, "testcase", s)
|
|
}
|
|
|
|
func TestGoogleGroupOptions(t *testing.T) {
|
|
o := testOptions()
|
|
o.GoogleGroups = []string{"googlegroup"}
|
|
err := Validate(o)
|
|
assert.NotEqual(t, nil, err)
|
|
|
|
expected := errorMsg([]string{
|
|
"missing setting: google-admin-email",
|
|
"missing setting: google-service-account-json"})
|
|
assert.Equal(t, expected, err.Error())
|
|
}
|
|
|
|
func TestGoogleGroupInvalidFile(t *testing.T) {
|
|
o := testOptions()
|
|
o.GoogleGroups = []string{"test_group"}
|
|
o.GoogleAdminEmail = "admin@example.com"
|
|
o.GoogleServiceAccountJSON = "file_doesnt_exist.json"
|
|
err := Validate(o)
|
|
assert.NotEqual(t, nil, err)
|
|
|
|
expected := errorMsg([]string{
|
|
"invalid Google credentials file: file_doesnt_exist.json",
|
|
})
|
|
assert.Equal(t, expected, err.Error())
|
|
}
|
|
|
|
func TestInitializedOptions(t *testing.T) {
|
|
o := testOptions()
|
|
assert.Equal(t, nil, Validate(o))
|
|
}
|
|
|
|
// Note that it's not worth testing nonparseable URLs, since url.Parse()
|
|
// seems to parse damn near anything.
|
|
func TestRedirectURL(t *testing.T) {
|
|
o := testOptions()
|
|
o.RawRedirectURL = "https://myhost.com/oauth2/callback"
|
|
assert.Equal(t, nil, Validate(o))
|
|
expected := &url.URL{
|
|
Scheme: "https", Host: "myhost.com", Path: "/oauth2/callback"}
|
|
assert.Equal(t, expected, o.GetRedirectURL())
|
|
}
|
|
|
|
func TestProxyURLs(t *testing.T) {
|
|
o := testOptions()
|
|
o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081")
|
|
assert.Equal(t, nil, Validate(o))
|
|
expected := []*url.URL{
|
|
{Scheme: "http", Host: "127.0.0.1:8080", Path: "/"},
|
|
// note the '/' was added
|
|
{Scheme: "http", Host: "127.0.0.1:8081", Path: "/"},
|
|
}
|
|
assert.Equal(t, expected, o.GetProxyURLs())
|
|
}
|
|
|
|
func TestProxyURLsError(t *testing.T) {
|
|
o := testOptions()
|
|
o.Upstreams = append(o.Upstreams, "127.0.0.1:8081")
|
|
err := Validate(o)
|
|
assert.NotEqual(t, nil, err)
|
|
assert.Contains(t, err.Error(), "error parsing upstream")
|
|
}
|
|
|
|
func TestCompiledRegex(t *testing.T) {
|
|
o := testOptions()
|
|
regexps := []string{"/foo/.*", "/ba[rz]/quux"}
|
|
o.SkipAuthRegex = regexps
|
|
assert.Equal(t, nil, Validate(o))
|
|
actual := make([]string, 0)
|
|
for _, regex := range o.GetCompiledRegex() {
|
|
actual = append(actual, regex.String())
|
|
}
|
|
assert.Equal(t, regexps, actual)
|
|
}
|
|
|
|
func TestCompiledRegexError(t *testing.T) {
|
|
o := testOptions()
|
|
o.SkipAuthRegex = []string{"(foobaz", "barquux)"}
|
|
err := Validate(o)
|
|
assert.NotEqual(t, nil, err)
|
|
|
|
expected := errorMsg([]string{
|
|
"error compiling regex=\"(foobaz\" error parsing regexp: " +
|
|
"missing closing ): `(foobaz`",
|
|
"error compiling regex=\"barquux)\" error parsing regexp: " +
|
|
"unexpected ): `barquux)`"})
|
|
assert.Equal(t, expected, err.Error())
|
|
|
|
o.SkipAuthRegex = []string{"foobaz", "barquux)"}
|
|
err = Validate(o)
|
|
assert.NotEqual(t, nil, err)
|
|
|
|
expected = errorMsg([]string{
|
|
"error compiling regex=\"barquux)\" error parsing regexp: " +
|
|
"unexpected ): `barquux)`"})
|
|
assert.Equal(t, expected, err.Error())
|
|
}
|
|
|
|
func TestDefaultProviderApiSettings(t *testing.T) {
|
|
o := testOptions()
|
|
assert.Equal(t, nil, Validate(o))
|
|
p := o.GetProvider().Data()
|
|
assert.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline",
|
|
p.LoginURL.String())
|
|
assert.Equal(t, "https://www.googleapis.com/oauth2/v3/token",
|
|
p.RedeemURL.String())
|
|
assert.Equal(t, "", p.ProfileURL.String())
|
|
assert.Equal(t, "profile email", p.Scope)
|
|
}
|
|
|
|
func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) {
|
|
o := testOptions()
|
|
assert.Equal(t, nil, Validate(o))
|
|
|
|
assert.Equal(t, false, o.PassAccessToken)
|
|
o.PassAccessToken = true
|
|
o.Cookie.Secret = "cookie of invalid length-"
|
|
assert.NotEqual(t, nil, Validate(o))
|
|
|
|
o.PassAccessToken = false
|
|
o.Cookie.Refresh = time.Duration(24) * time.Hour
|
|
assert.NotEqual(t, nil, Validate(o))
|
|
|
|
o.Cookie.Secret = "16 bytes AES-128"
|
|
assert.Equal(t, nil, Validate(o))
|
|
|
|
o.Cookie.Secret = "24 byte secret AES-192--"
|
|
assert.Equal(t, nil, Validate(o))
|
|
|
|
o.Cookie.Secret = "32 byte secret for AES-256------"
|
|
assert.Equal(t, nil, Validate(o))
|
|
}
|
|
|
|
func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
|
|
o := testOptions()
|
|
assert.Equal(t, nil, Validate(o))
|
|
|
|
o.Cookie.Secret = "0123456789abcdef"
|
|
o.Cookie.Refresh = o.Cookie.Expire
|
|
assert.NotEqual(t, nil, Validate(o))
|
|
|
|
o.Cookie.Refresh -= time.Duration(1)
|
|
assert.Equal(t, nil, Validate(o))
|
|
}
|
|
|
|
func TestBase64CookieSecret(t *testing.T) {
|
|
o := testOptions()
|
|
assert.Equal(t, nil, Validate(o))
|
|
|
|
// 32 byte, base64 (urlsafe) encoded key
|
|
o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ="
|
|
assert.Equal(t, nil, Validate(o))
|
|
|
|
// 32 byte, base64 (urlsafe) encoded key, w/o padding
|
|
o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ"
|
|
assert.Equal(t, nil, Validate(o))
|
|
|
|
// 24 byte, base64 (urlsafe) encoded key
|
|
o.Cookie.Secret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3"
|
|
assert.Equal(t, nil, Validate(o))
|
|
|
|
// 16 byte, base64 (urlsafe) encoded key
|
|
o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA=="
|
|
assert.Equal(t, nil, Validate(o))
|
|
|
|
// 16 byte, base64 (urlsafe) encoded key, w/o padding
|
|
o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA"
|
|
assert.Equal(t, nil, Validate(o))
|
|
}
|
|
|
|
func TestValidateSignatureKey(t *testing.T) {
|
|
o := testOptions()
|
|
o.SignatureKey = "sha1:secret"
|
|
assert.Equal(t, nil, Validate(o))
|
|
assert.Equal(t, o.GetSignatureData().Hash, crypto.SHA1)
|
|
assert.Equal(t, o.GetSignatureData().Key, "secret")
|
|
}
|
|
|
|
func TestValidateSignatureKeyInvalidSpec(t *testing.T) {
|
|
o := testOptions()
|
|
o.SignatureKey = "invalid spec"
|
|
err := Validate(o)
|
|
assert.Equal(t, err.Error(), "invalid configuration:\n"+
|
|
" invalid signature hash:key spec: "+o.SignatureKey)
|
|
}
|
|
|
|
func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) {
|
|
o := testOptions()
|
|
o.SignatureKey = "unsupported:default secret"
|
|
err := Validate(o)
|
|
assert.Equal(t, err.Error(), "invalid configuration:\n"+
|
|
" unsupported signature hash algorithm: "+o.SignatureKey)
|
|
}
|
|
|
|
func TestSkipOIDCDiscovery(t *testing.T) {
|
|
o := testOptions()
|
|
o.ProviderType = "oidc"
|
|
o.OIDCIssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/"
|
|
o.SkipOIDCDiscovery = true
|
|
|
|
err := Validate(o)
|
|
assert.Equal(t, "invalid configuration:\n"+
|
|
" missing setting: login-url\n missing setting: redeem-url\n missing setting: oidc-jwks-url", err.Error())
|
|
|
|
o.LoginURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in"
|
|
o.RedeemURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_sign_in"
|
|
o.OIDCJwksURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys"
|
|
|
|
assert.Equal(t, nil, Validate(o))
|
|
}
|
|
|
|
func TestGCPHealthcheck(t *testing.T) {
|
|
o := testOptions()
|
|
o.GCPHealthChecks = true
|
|
assert.Equal(t, nil, Validate(o))
|
|
}
|
|
|
|
func TestRealClientIPHeader(t *testing.T) {
|
|
// Ensure nil if ReverseProxy not set.
|
|
o := testOptions()
|
|
o.RealClientIPHeader = "X-Real-IP"
|
|
assert.Equal(t, nil, Validate(o))
|
|
assert.Nil(t, o.GetRealClientIPParser())
|
|
|
|
// Ensure simple use case works.
|
|
o = testOptions()
|
|
o.ReverseProxy = true
|
|
o.RealClientIPHeader = "X-Forwarded-For"
|
|
assert.Equal(t, nil, Validate(o))
|
|
assert.NotNil(t, o.GetRealClientIPParser())
|
|
|
|
// Ensure unknown header format process an error.
|
|
o = testOptions()
|
|
o.ReverseProxy = true
|
|
o.RealClientIPHeader = "Forwarded"
|
|
err := Validate(o)
|
|
assert.NotEqual(t, nil, err)
|
|
expected := errorMsg([]string{
|
|
"real_client_ip_header (Forwarded) not accepted parameter value: the http header key (Forwarded) is either invalid or unsupported",
|
|
})
|
|
assert.Equal(t, expected, err.Error())
|
|
assert.Nil(t, o.GetRealClientIPParser())
|
|
|
|
// Ensure invalid header format produces an error.
|
|
o = testOptions()
|
|
o.ReverseProxy = true
|
|
o.RealClientIPHeader = "!934invalidheader-23:"
|
|
err = Validate(o)
|
|
assert.NotEqual(t, nil, err)
|
|
expected = errorMsg([]string{
|
|
"real_client_ip_header (!934invalidheader-23:) not accepted parameter value: the http header key (!934invalidheader-23:) is either invalid or unsupported",
|
|
})
|
|
assert.Equal(t, expected, err.Error())
|
|
assert.Nil(t, o.GetRealClientIPParser())
|
|
}
|
|
|
|
func TestProviderCAFilesError(t *testing.T) {
|
|
file, err := ioutil.TempFile("", "absent.*.crt")
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, file.Close())
|
|
assert.NoError(t, os.Remove(file.Name()))
|
|
|
|
o := testOptions()
|
|
o.ProviderCAFiles = append(o.ProviderCAFiles, file.Name())
|
|
err = Validate(o)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unable to load provider CA file(s)")
|
|
}
|