mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2026-10-17 20:17:03 +02:00
Move Options and Validation to package
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RealClientIPParser is an interface for a getting the client's real IP to be used for logging.
|
||||
type RealClientIPParser interface {
|
||||
GetRealClientIP(http.Header) (net.IP, error)
|
||||
}
|
||||
|
||||
func GetRealClientIPParser(headerKey string) (RealClientIPParser, error) {
|
||||
headerKey = http.CanonicalHeaderKey(headerKey)
|
||||
|
||||
switch headerKey {
|
||||
case http.CanonicalHeaderKey("X-Forwarded-For"), http.CanonicalHeaderKey("X-Real-IP"), http.CanonicalHeaderKey("X-ProxyUser-IP"):
|
||||
return &xForwardedForClientIPParser{header: headerKey}, nil
|
||||
}
|
||||
|
||||
// TODO: implement the more standardized but more complex `Forwarded` header.
|
||||
return nil, fmt.Errorf("the http header key (%s) is either invalid or unsupported", headerKey)
|
||||
}
|
||||
|
||||
type xForwardedForClientIPParser struct {
|
||||
header string
|
||||
}
|
||||
|
||||
// GetRealClientIP obtain the IP address of the end-user (not proxy).
|
||||
// Parses headers sharing the format as specified by:
|
||||
// * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For.
|
||||
// Returns the `<client>` portion specified in the above document.
|
||||
// Additionally, is capable of parsing IPs with the port included, for v4 in the format "<ip>:<port>" and for v6 in the
|
||||
// format "[<ip>]:<port>". With-port and without-port formats are seamlessly supported concurrently.
|
||||
func (p xForwardedForClientIPParser) GetRealClientIP(h http.Header) (net.IP, error) {
|
||||
var ipStr string
|
||||
if realIP := h.Get(p.header); realIP != "" {
|
||||
ipStr = realIP
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Each successive proxy may append itself, comma separated, to the end of the X-Forwarded-for header.
|
||||
// Select only the first IP listed, as it is the client IP recorded by the first proxy.
|
||||
if commaIndex := strings.IndexRune(ipStr, ','); commaIndex != -1 {
|
||||
ipStr = ipStr[:commaIndex]
|
||||
}
|
||||
ipStr = strings.TrimSpace(ipStr)
|
||||
|
||||
if ipHost, _, err := net.SplitHostPort(ipStr); err == nil {
|
||||
ipStr = ipHost
|
||||
}
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("unable to parse ip (%s) from %s header", ipStr, http.CanonicalHeaderKey(p.header))
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
// getRemoteIP obtains the IP of the low-level connected network host
|
||||
func getRemoteIP(req *http.Request) (net.IP, error) {
|
||||
if ipStr, _, err := net.SplitHostPort(req.RemoteAddr); err != nil {
|
||||
return nil, fmt.Errorf("unable to get ip and port from http.RemoteAddr (%s)", req.RemoteAddr)
|
||||
} else if ip := net.ParseIP(ipStr); ip != nil {
|
||||
return ip, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to parse ip (%s)", ipStr)
|
||||
}
|
||||
}
|
||||
|
||||
// GetClientString obtains the human readable string of the remote IP and optionally the real client IP if available
|
||||
func GetClientString(p RealClientIPParser, req *http.Request, full bool) (s string) {
|
||||
var realClientIPStr string
|
||||
if p != nil {
|
||||
if realClientIP, err := p.GetRealClientIP(req.Header); err == nil && realClientIP != nil {
|
||||
realClientIPStr = realClientIP.String()
|
||||
}
|
||||
}
|
||||
|
||||
var remoteIPStr string
|
||||
if remoteIP, err := getRemoteIP(req); err == nil {
|
||||
remoteIPStr = remoteIP.String()
|
||||
}
|
||||
|
||||
if !full && realClientIPStr != "" {
|
||||
return realClientIPStr
|
||||
}
|
||||
if full && realClientIPStr != "" {
|
||||
return fmt.Sprintf("%s (%s)", remoteIPStr, realClientIPStr)
|
||||
}
|
||||
return remoteIPStr
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetRealClientIPParser(t *testing.T) {
|
||||
forwardedForType := reflect.TypeOf((*xForwardedForClientIPParser)(nil))
|
||||
|
||||
tests := []struct {
|
||||
header string
|
||||
errString string
|
||||
parserType reflect.Type
|
||||
}{
|
||||
{"X-Forwarded-For", "", forwardedForType},
|
||||
{"X-REAL-IP", "", forwardedForType},
|
||||
{"x-proxyuser-ip", "", forwardedForType},
|
||||
{"", "the http header key () is either invalid or unsupported", nil},
|
||||
{"Forwarded", "the http header key (Forwarded) is either invalid or unsupported", nil},
|
||||
{"2#* @##$$:kd", "the http header key (2#* @##$$:kd) is either invalid or unsupported", nil},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
p, err := GetRealClientIPParser(test.header)
|
||||
|
||||
if test.errString == "" {
|
||||
assert.Nil(t, err)
|
||||
} else {
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, test.errString, err.Error())
|
||||
}
|
||||
|
||||
if test.parserType == nil {
|
||||
assert.Nil(t, p)
|
||||
} else {
|
||||
assert.NotNil(t, p)
|
||||
assert.Equal(t, test.parserType, reflect.TypeOf(p))
|
||||
}
|
||||
|
||||
if xp, ok := p.(*xForwardedForClientIPParser); ok {
|
||||
assert.Equal(t, http.CanonicalHeaderKey(test.header), xp.header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestXForwardedForClientIPParser(t *testing.T) {
|
||||
p := &xForwardedForClientIPParser{header: http.CanonicalHeaderKey("X-Forwarded-For")}
|
||||
|
||||
tests := []struct {
|
||||
headerValue string
|
||||
errString string
|
||||
expectedIP net.IP
|
||||
}{
|
||||
{"", "", nil},
|
||||
{"1.2.3.4", "", net.ParseIP("1.2.3.4")},
|
||||
{"10::23", "", net.ParseIP("10::23")},
|
||||
{"::1", "", net.ParseIP("::1")},
|
||||
{"[::1]:1234", "", net.ParseIP("::1")},
|
||||
{"10.0.10.11:1234", "", net.ParseIP("10.0.10.11")},
|
||||
{"192.168.10.50, 10.0.0.1, 1.2.3.4", "", net.ParseIP("192.168.10.50")},
|
||||
{"nil", "unable to parse ip (nil) from X-Forwarded-For header", nil},
|
||||
{"10000.10000.10000.10000", "unable to parse ip (10000.10000.10000.10000) from X-Forwarded-For header", nil},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
h := http.Header{}
|
||||
h.Add("X-Forwarded-For", test.headerValue)
|
||||
|
||||
ip, err := p.GetRealClientIP(h)
|
||||
|
||||
if test.errString == "" {
|
||||
assert.Nil(t, err)
|
||||
} else {
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, test.errString, err.Error())
|
||||
}
|
||||
|
||||
if test.expectedIP == nil {
|
||||
assert.Nil(t, ip)
|
||||
} else {
|
||||
assert.NotNil(t, ip)
|
||||
assert.Equal(t, test.expectedIP, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestXForwardedForClientIPParserIgnoresOthers(t *testing.T) {
|
||||
p := &xForwardedForClientIPParser{header: http.CanonicalHeaderKey("X-Forwarded-For")}
|
||||
|
||||
h := http.Header{}
|
||||
expectedIPString := "192.168.10.50"
|
||||
h.Add("X-Real-IP", "10.0.0.1")
|
||||
h.Add("X-ProxyUser-IP", "10.0.0.1")
|
||||
h.Add("X-Forwarded-For", expectedIPString)
|
||||
ip, err := p.GetRealClientIP(h)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, ip)
|
||||
assert.Equal(t, ip, net.ParseIP(expectedIPString))
|
||||
}
|
||||
|
||||
func TestGetRemoteIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
remoteAddr string
|
||||
errString string
|
||||
expectedIP net.IP
|
||||
}{
|
||||
{"", "unable to get ip and port from http.RemoteAddr ()", nil},
|
||||
{"nil", "unable to get ip and port from http.RemoteAddr (nil)", nil},
|
||||
{"235.28.129.186", "unable to get ip and port from http.RemoteAddr (235.28.129.186)", nil},
|
||||
{"90::45", "unable to get ip and port from http.RemoteAddr (90::45)", nil},
|
||||
{"192.168.73.165:14976, 10.4.201.15:18453", "unable to get ip and port from http.RemoteAddr (192.168.73.165:14976, 10.4.201.15:18453)", nil},
|
||||
{"10000.10000.10000.10000:8080", "unable to parse ip (10000.10000.10000.10000)", nil},
|
||||
{"[::1]:48290", "", net.ParseIP("::1")},
|
||||
{"10.254.244.165:62750", "", net.ParseIP("10.254.244.165")},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
req := &http.Request{RemoteAddr: test.remoteAddr}
|
||||
|
||||
ip, err := getRemoteIP(req)
|
||||
|
||||
if test.errString == "" {
|
||||
assert.Nil(t, err)
|
||||
} else {
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, test.errString, err.Error())
|
||||
}
|
||||
|
||||
if test.expectedIP == nil {
|
||||
assert.Nil(t, ip)
|
||||
} else {
|
||||
assert.NotNil(t, ip)
|
||||
assert.Equal(t, test.expectedIP, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClientString(t *testing.T) {
|
||||
p := &xForwardedForClientIPParser{header: http.CanonicalHeaderKey("X-Forwarded-For")}
|
||||
|
||||
tests := []struct {
|
||||
parser RealClientIPParser
|
||||
remoteAddr string
|
||||
headerValue string
|
||||
expectedClient string
|
||||
expectedClientFull string
|
||||
}{
|
||||
// Should fail quietly, only printing warnings to the log
|
||||
{nil, "", "", "", ""},
|
||||
{p, "127.0.0.1:11950", "", "127.0.0.1", "127.0.0.1"},
|
||||
{p, "[::1]:28660", "99.103.56.12", "99.103.56.12", "::1 (99.103.56.12)"},
|
||||
{nil, "10.254.244.165:62750", "", "10.254.244.165", "10.254.244.165"},
|
||||
// Parser is nil, the contents of X-Forwarded-For should be ignored in all cases.
|
||||
{nil, "[2001:470:26:307:a5a1:1177:2ae3:e9c3]:48290", "127.0.0.1", "2001:470:26:307:a5a1:1177:2ae3:e9c3", "2001:470:26:307:a5a1:1177:2ae3:e9c3"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
h := http.Header{}
|
||||
h.Add("X-Forwarded-For", test.headerValue)
|
||||
req := &http.Request{
|
||||
Header: h,
|
||||
RemoteAddr: test.remoteAddr,
|
||||
}
|
||||
|
||||
client := GetClientString(test.parser, req, false)
|
||||
assert.Equal(t, test.expectedClient, client)
|
||||
|
||||
clientFull := GetClientString(test.parser, req, true)
|
||||
assert.Equal(t, test.expectedClientFull, clientFull)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/logging"
|
||||
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/providers"
|
||||
)
|
||||
|
||||
// SignatureData holds hmacauth signature hash and key
|
||||
type SignatureData struct {
|
||||
Hash crypto.Hash
|
||||
Key string
|
||||
}
|
||||
|
||||
// Options holds Configuration Options that can be set by Command Line Flag,
|
||||
// or Config File
|
||||
type Options struct {
|
||||
ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix" env:"OAUTH2_PROXY_PROXY_PREFIX"`
|
||||
PingPath string `flag:"ping-path" cfg:"ping_path" env:"OAUTH2_PROXY_PING_PATH"`
|
||||
ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets" env:"OAUTH2_PROXY_PROXY_WEBSOCKETS"`
|
||||
HTTPAddress string `flag:"http-address" cfg:"http_address" env:"OAUTH2_PROXY_HTTP_ADDRESS"`
|
||||
HTTPSAddress string `flag:"https-address" cfg:"https_address" env:"OAUTH2_PROXY_HTTPS_ADDRESS"`
|
||||
ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy" env:"OAUTH2_PROXY_REVERSE_PROXY"`
|
||||
RealClientIPHeader string `flag:"real-client-ip-header" cfg:"real_client_ip_header" env:"OAUTH2_PROXY_REAL_CLIENT_IP_HEADER"`
|
||||
ForceHTTPS bool `flag:"force-https" cfg:"force_https" env:"OAUTH2_PROXY_FORCE_HTTPS"`
|
||||
RawRedirectURL string `flag:"redirect-url" cfg:"redirect_url" env:"OAUTH2_PROXY_REDIRECT_URL"`
|
||||
ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
|
||||
ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
|
||||
ClientSecretFile string `flag:"client-secret-file" cfg:"client_secret_file" env:"OAUTH2_PROXY_CLIENT_SECRET_FILE"`
|
||||
TLSCertFile string `flag:"tls-cert-file" cfg:"tls_cert_file" env:"OAUTH2_PROXY_TLS_CERT_FILE"`
|
||||
TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"`
|
||||
|
||||
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"`
|
||||
KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group" env:"OAUTH2_PROXY_KEYCLOAK_GROUP"`
|
||||
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT"`
|
||||
BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team" env:"OAUTH2_PROXY_BITBUCKET_TEAM"`
|
||||
BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository" env:"OAUTH2_PROXY_BITBUCKET_REPOSITORY"`
|
||||
EmailDomains []string `flag:"email-domain" cfg:"email_domains" env:"OAUTH2_PROXY_EMAIL_DOMAINS"`
|
||||
WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS"`
|
||||
GitHubOrg string `flag:"github-org" cfg:"github_org" env:"OAUTH2_PROXY_GITHUB_ORG"`
|
||||
GitHubTeam string `flag:"github-team" cfg:"github_team" env:"OAUTH2_PROXY_GITHUB_TEAM"`
|
||||
GitHubRepo string `flag:"github-repo" cfg:"github_repo" env:"OAUTH2_PROXY_GITHUB_REPO"`
|
||||
GitHubToken string `flag:"github-token" cfg:"github_token" env:"OAUTH2_PROXY_GITHUB_TOKEN"`
|
||||
GitLabGroup string `flag:"gitlab-group" cfg:"gitlab_group" env:"OAUTH2_PROXY_GITLAB_GROUP"`
|
||||
GoogleGroups []string `flag:"google-group" cfg:"google_group" env:"OAUTH2_PROXY_GOOGLE_GROUPS"`
|
||||
GoogleAdminEmail string `flag:"google-admin-email" cfg:"google_admin_email" env:"OAUTH2_PROXY_GOOGLE_ADMIN_EMAIL"`
|
||||
GoogleServiceAccountJSON string `flag:"google-service-account-json" cfg:"google_service_account_json" env:"OAUTH2_PROXY_GOOGLE_SERVICE_ACCOUNT_JSON"`
|
||||
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file" env:"OAUTH2_PROXY_HTPASSWD_FILE"`
|
||||
DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form" env:"OAUTH2_PROXY_DISPLAY_HTPASSWD_FORM"`
|
||||
CustomTemplatesDir string `flag:"custom-templates-dir" cfg:"custom_templates_dir" env:"OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR"`
|
||||
Banner string `flag:"banner" cfg:"banner" env:"OAUTH2_PROXY_BANNER"`
|
||||
Footer string `flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER"`
|
||||
|
||||
Cookie CookieOptions `cfg:",squash"`
|
||||
Session SessionOptions `cfg:",squash"`
|
||||
|
||||
Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
|
||||
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
|
||||
SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens" env:"OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS"`
|
||||
ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers" env:"OAUTH2_PROXY_EXTRA_JWT_ISSUERS"`
|
||||
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"`
|
||||
SetBasicAuth bool `flag:"set-basic-auth" cfg:"set_basic_auth" env:"OAUTH2_PROXY_SET_BASIC_AUTH"`
|
||||
PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user" env:"OAUTH2_PROXY_PREFER_EMAIL_TO_USER"`
|
||||
BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"`
|
||||
PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"`
|
||||
PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header" env:"OAUTH2_PROXY_PASS_HOST_HEADER"`
|
||||
SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button" env:"OAUTH2_PROXY_SKIP_PROVIDER_BUTTON"`
|
||||
PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers" env:"OAUTH2_PROXY_PASS_USER_HEADERS"`
|
||||
SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY"`
|
||||
SSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_UPSTREAM_INSECURE_SKIP_VERIFY"`
|
||||
SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest" env:"OAUTH2_PROXY_SET_XAUTHREQUEST"`
|
||||
SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header" env:"OAUTH2_PROXY_SET_AUTHORIZATION_HEADER"`
|
||||
PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header" env:"OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER"`
|
||||
SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight" env:"OAUTH2_PROXY_SKIP_AUTH_PREFLIGHT"`
|
||||
FlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval" env:"OAUTH2_PROXY_FLUSH_INTERVAL"`
|
||||
|
||||
// These options allow for other providers besides Google, with
|
||||
// potential overrides.
|
||||
ProviderType string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"`
|
||||
ProviderName string `flag:"provider-display-name" cfg:"provider_display_name" env:"OAUTH2_PROXY_PROVIDER_DISPLAY_NAME"`
|
||||
OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"`
|
||||
InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL"`
|
||||
InsecureOIDCSkipIssuerVerification bool `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification" env:"OAUTH2_PROXY_INSECURE_OIDC_SKIP_ISSUER_VERIFICATION"`
|
||||
SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_PROXY_SKIP_OIDC_DISCOVERY"`
|
||||
OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_PROXY_OIDC_JWKS_URL"`
|
||||
LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"`
|
||||
RedeemURL string `flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL"`
|
||||
ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"`
|
||||
ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"`
|
||||
ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"`
|
||||
Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"`
|
||||
Prompt string `flag:"prompt" cfg:"prompt" env:"OAUTH2_PROXY_PROMPT"`
|
||||
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` // Deprecated by OIDC 1.0
|
||||
UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim" env:"OAUTH2_PROXY_USER_ID_CLAIM"`
|
||||
|
||||
// Configuration values for logging
|
||||
LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_PROXY_LOGGING_FILENAME"`
|
||||
LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_PROXY_LOGGING_MAX_SIZE"`
|
||||
LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_PROXY_LOGGING_MAX_AGE"`
|
||||
LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_PROXY_LOGGING_MAX_BACKUPS"`
|
||||
LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_PROXY_LOGGING_LOCAL_TIME"`
|
||||
LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_PROXY_LOGGING_COMPRESS"`
|
||||
StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_PROXY_STANDARD_LOGGING"`
|
||||
StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"`
|
||||
RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"`
|
||||
RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"`
|
||||
ExcludeLoggingPaths string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATHS"`
|
||||
SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"`
|
||||
AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_PROXY_LOGGING_AUTH_LOGGING"`
|
||||
AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_PROXY_AUTH_LOGGING_FORMAT"`
|
||||
SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
|
||||
AcrValues string `flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES"`
|
||||
JWTKey string `flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY"`
|
||||
JWTKeyFile string `flag:"jwt-key-file" cfg:"jwt_key_file" env:"OAUTH2_PROXY_JWT_KEY_FILE"`
|
||||
PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url" env:"OAUTH2_PROXY_PUBJWK_URL"`
|
||||
GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS"`
|
||||
|
||||
// internal values that are set after config validation
|
||||
redirectURL *url.URL
|
||||
proxyURLs []*url.URL
|
||||
compiledRegex []*regexp.Regexp
|
||||
provider providers.Provider
|
||||
sessionStore sessionsapi.SessionStore
|
||||
signatureData *SignatureData
|
||||
oidcVerifier *oidc.IDTokenVerifier
|
||||
jwtBearerVerifiers []*oidc.IDTokenVerifier
|
||||
realClientIPParser logging.RealClientIPParser
|
||||
}
|
||||
|
||||
// Options for Getting internal values
|
||||
func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
|
||||
func (o *Options) GetProxyURLs() []*url.URL { return o.proxyURLs }
|
||||
func (o *Options) GetCompiledRegex() []*regexp.Regexp { return o.compiledRegex }
|
||||
func (o *Options) GetProvider() providers.Provider { return o.provider }
|
||||
func (o *Options) GetSessionStore() sessionsapi.SessionStore { return o.sessionStore }
|
||||
func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
|
||||
func (o *Options) GetOIDCVerifier() *oidc.IDTokenVerifier { return o.oidcVerifier }
|
||||
func (o *Options) GetJWTBearerVerifiers() []*oidc.IDTokenVerifier { return o.jwtBearerVerifiers }
|
||||
func (o *Options) GetRealClientIPParser() logging.RealClientIPParser { return o.realClientIPParser }
|
||||
|
||||
// Options for Setting internal values
|
||||
func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
|
||||
func (o *Options) SetProxyURLs(s []*url.URL) { o.proxyURLs = s }
|
||||
func (o *Options) SetCompiledRegex(s []*regexp.Regexp) { o.compiledRegex = s }
|
||||
func (o *Options) SetProvider(s providers.Provider) { o.provider = s }
|
||||
func (o *Options) SetSessionStore(s sessionsapi.SessionStore) { o.sessionStore = s }
|
||||
func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
|
||||
func (o *Options) SetOIDCVerifier(s *oidc.IDTokenVerifier) { o.oidcVerifier = s }
|
||||
func (o *Options) SetJWTBearerVerifiers(s []*oidc.IDTokenVerifier) { o.jwtBearerVerifiers = s }
|
||||
func (o *Options) SetRealClientIPParser(s logging.RealClientIPParser) { o.realClientIPParser = s }
|
||||
|
||||
// NewOptions constructs a new Options with defaulted values
|
||||
func NewOptions() *Options {
|
||||
return &Options{
|
||||
ProxyPrefix: "/oauth2",
|
||||
PingPath: "/ping",
|
||||
ProxyWebSockets: true,
|
||||
HTTPAddress: "127.0.0.1:4180",
|
||||
HTTPSAddress: ":443",
|
||||
RealClientIPHeader: "X-Real-IP",
|
||||
ForceHTTPS: false,
|
||||
DisplayHtpasswdForm: true,
|
||||
Cookie: CookieOptions{
|
||||
Name: "_oauth2_proxy",
|
||||
Secure: true,
|
||||
HTTPOnly: true,
|
||||
Expire: time.Duration(168) * time.Hour,
|
||||
Refresh: time.Duration(0),
|
||||
},
|
||||
Session: SessionOptions{
|
||||
Type: "cookie",
|
||||
},
|
||||
SetXAuthRequest: false,
|
||||
SkipAuthPreflight: false,
|
||||
PassBasicAuth: true,
|
||||
SetBasicAuth: false,
|
||||
PassUserHeaders: true,
|
||||
PassAccessToken: false,
|
||||
PassHostHeader: true,
|
||||
SetAuthorization: false,
|
||||
PassAuthorization: false,
|
||||
PreferEmailToUser: false,
|
||||
Prompt: "", // Change to "login" when ApprovalPrompt officially deprecated
|
||||
ApprovalPrompt: "force",
|
||||
UserIDClaim: "email",
|
||||
InsecureOIDCAllowUnverifiedEmail: false,
|
||||
SkipOIDCDiscovery: false,
|
||||
LoggingFilename: "",
|
||||
LoggingMaxSize: 100,
|
||||
LoggingMaxAge: 7,
|
||||
LoggingMaxBackups: 0,
|
||||
LoggingLocalTime: true,
|
||||
LoggingCompress: false,
|
||||
ExcludeLoggingPaths: "",
|
||||
SilencePingLogging: false,
|
||||
StandardLogging: true,
|
||||
StandardLoggingFormat: logger.DefaultStandardLoggingFormat,
|
||||
RequestLogging: true,
|
||||
RequestLoggingFormat: logger.DefaultRequestLoggingFormat,
|
||||
AuthLogging: true,
|
||||
AuthLoggingFormat: logger.DefaultAuthLoggingFormat,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/mbland/hmacauth"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/logging"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/providers"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
// Validate checks that required options are set and validates those that they
|
||||
// are of the correct format
|
||||
func Validate(o *options.Options) error {
|
||||
if o.SSLInsecureSkipVerify {
|
||||
// TODO: Accept a certificate bundle.
|
||||
insecureTransport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
http.DefaultClient = &http.Client{Transport: insecureTransport}
|
||||
}
|
||||
|
||||
msgs := make([]string, 0)
|
||||
if o.Cookie.Secret == "" {
|
||||
msgs = append(msgs, "missing setting: cookie-secret")
|
||||
}
|
||||
if o.ClientID == "" {
|
||||
msgs = append(msgs, "missing setting: client-id")
|
||||
}
|
||||
// login.gov uses a signed JWT to authenticate, not a client-secret
|
||||
if o.ProviderType != "login.gov" {
|
||||
if o.ClientSecret == "" && o.ClientSecretFile == "" {
|
||||
msgs = append(msgs, "missing setting: client-secret or client-secret-file")
|
||||
}
|
||||
if o.ClientSecret == "" && o.ClientSecretFile != "" {
|
||||
_, err := ioutil.ReadFile(o.ClientSecretFile)
|
||||
if err != nil {
|
||||
msgs = append(msgs, "could not read client secret file: "+o.ClientSecretFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
if o.AuthenticatedEmailsFile == "" && len(o.EmailDomains) == 0 && o.HtpasswdFile == "" {
|
||||
msgs = append(msgs, "missing setting for email validation: email-domain or authenticated-emails-file required."+
|
||||
"\n use email-domain=* to authorize all email addresses")
|
||||
}
|
||||
|
||||
if o.SetBasicAuth && o.SetAuthorization {
|
||||
msgs = append(msgs, "mutually exclusive: set-basic-auth and set-authorization-header can not both be true")
|
||||
}
|
||||
|
||||
if o.OIDCIssuerURL != "" {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if o.InsecureOIDCSkipIssuerVerification && !o.SkipOIDCDiscovery {
|
||||
// go-oidc doesn't let us pass bypass the issuer check this in the oidc.NewProvider call
|
||||
// (which uses discovery to get the URLs), so we'll do a quick check ourselves and if
|
||||
// we get the URLs, we'll just use the non-discovery path.
|
||||
|
||||
logger.Printf("Performing OIDC Discovery...")
|
||||
|
||||
if req, err := http.NewRequest("GET", strings.TrimSuffix(o.OIDCIssuerURL, "/")+"/.well-known/openid-configuration", nil); err == nil {
|
||||
if body, err := requests.Request(req); err == nil {
|
||||
|
||||
// Prefer manually configured URLs. It's a bit unclear
|
||||
// why you'd be doing discovery and also providing the URLs
|
||||
// explicitly though...
|
||||
if o.LoginURL == "" {
|
||||
o.LoginURL = body.Get("authorization_endpoint").MustString()
|
||||
}
|
||||
|
||||
if o.RedeemURL == "" {
|
||||
o.RedeemURL = body.Get("token_endpoint").MustString()
|
||||
}
|
||||
|
||||
if o.OIDCJwksURL == "" {
|
||||
o.OIDCJwksURL = body.Get("jwks_uri").MustString()
|
||||
}
|
||||
|
||||
if o.ProfileURL == "" {
|
||||
o.ProfileURL = body.Get("userinfo_endpoint").MustString()
|
||||
}
|
||||
|
||||
o.SkipOIDCDiscovery = true
|
||||
} else {
|
||||
logger.Printf("error: failed to discover OIDC configuration: %v", err)
|
||||
}
|
||||
} else {
|
||||
logger.Printf("error: failed parsing OIDC discovery URL: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a manual IDTokenVerifier from issuer URL & JWKS URI
|
||||
// instead of metadata discovery if we enable -skip-oidc-discovery.
|
||||
// In this case we need to make sure the required endpoints for
|
||||
// the provider are configured.
|
||||
if o.SkipOIDCDiscovery {
|
||||
if o.LoginURL == "" {
|
||||
msgs = append(msgs, "missing setting: login-url")
|
||||
}
|
||||
if o.RedeemURL == "" {
|
||||
msgs = append(msgs, "missing setting: redeem-url")
|
||||
}
|
||||
if o.OIDCJwksURL == "" {
|
||||
msgs = append(msgs, "missing setting: oidc-jwks-url")
|
||||
}
|
||||
keySet := oidc.NewRemoteKeySet(ctx, o.OIDCJwksURL)
|
||||
o.SetOIDCVerifier(oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{
|
||||
ClientID: o.ClientID,
|
||||
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
|
||||
}))
|
||||
} else {
|
||||
// Configure discoverable provider data.
|
||||
provider, err := oidc.NewProvider(ctx, o.OIDCIssuerURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.SetOIDCVerifier(provider.Verifier(&oidc.Config{
|
||||
ClientID: o.ClientID,
|
||||
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
|
||||
}))
|
||||
|
||||
o.LoginURL = provider.Endpoint().AuthURL
|
||||
o.RedeemURL = provider.Endpoint().TokenURL
|
||||
}
|
||||
if o.Scope == "" {
|
||||
o.Scope = "openid email profile"
|
||||
}
|
||||
}
|
||||
|
||||
if o.PreferEmailToUser && !o.PassBasicAuth && !o.PassUserHeaders {
|
||||
msgs = append(msgs, "PreferEmailToUser should only be used with PassBasicAuth or PassUserHeaders")
|
||||
}
|
||||
|
||||
if o.SkipJwtBearerTokens {
|
||||
// If we are using an oidc provider, go ahead and add that provider to the list
|
||||
if o.GetOIDCVerifier() != nil {
|
||||
o.SetJWTBearerVerifiers(append(o.GetJWTBearerVerifiers(), o.GetOIDCVerifier()))
|
||||
}
|
||||
// Configure extra issuers
|
||||
if len(o.ExtraJwtIssuers) > 0 {
|
||||
var jwtIssuers []jwtIssuer
|
||||
jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs)
|
||||
for _, jwtIssuer := range jwtIssuers {
|
||||
verifier, err := newVerifierFromJwtIssuer(jwtIssuer)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
|
||||
}
|
||||
o.SetJWTBearerVerifiers(append(o.GetJWTBearerVerifiers(), verifier))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var redirectURL *url.URL
|
||||
redirectURL, msgs = parseURL(o.RawRedirectURL, "redirect", msgs)
|
||||
o.SetRedirectURL(redirectURL)
|
||||
|
||||
for _, u := range o.Upstreams {
|
||||
upstreamURL, err := url.Parse(u)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("error parsing upstream: %s", err))
|
||||
} else {
|
||||
if upstreamURL.Path == "" {
|
||||
upstreamURL.Path = "/"
|
||||
}
|
||||
o.SetProxyURLs(append(o.GetProxyURLs(), upstreamURL))
|
||||
}
|
||||
}
|
||||
|
||||
for _, u := range o.SkipAuthRegex {
|
||||
compiledRegex, err := regexp.Compile(u)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err))
|
||||
continue
|
||||
}
|
||||
o.SetCompiledRegex(append(o.GetCompiledRegex(), compiledRegex))
|
||||
}
|
||||
msgs = parseProviderInfo(o, msgs)
|
||||
|
||||
var cipher *encryption.Cipher
|
||||
if o.PassAccessToken || o.SetAuthorization || o.PassAuthorization || (o.Cookie.Refresh != time.Duration(0)) {
|
||||
validCookieSecretSize := false
|
||||
for _, i := range []int{16, 24, 32} {
|
||||
if len(encryption.SecretBytes(o.Cookie.Secret)) == i {
|
||||
validCookieSecretSize = true
|
||||
}
|
||||
}
|
||||
var decoded bool
|
||||
if string(encryption.SecretBytes(o.Cookie.Secret)) != o.Cookie.Secret {
|
||||
decoded = true
|
||||
}
|
||||
if !validCookieSecretSize {
|
||||
var suffix string
|
||||
if decoded {
|
||||
suffix = fmt.Sprintf(" note: cookie secret was base64 decoded from %q", o.Cookie.Secret)
|
||||
}
|
||||
msgs = append(msgs, fmt.Sprintf(
|
||||
"cookie_secret must be 16, 24, or 32 bytes "+
|
||||
"to create an AES cipher when "+
|
||||
"pass_access_token == true or "+
|
||||
"cookie_refresh != 0, but is %d bytes.%s",
|
||||
len(encryption.SecretBytes(o.Cookie.Secret)), suffix))
|
||||
} else {
|
||||
var err error
|
||||
cipher, err = encryption.NewCipher(encryption.SecretBytes(o.Cookie.Secret))
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o.Session.Cipher = cipher
|
||||
sessionStore, err := sessions.NewSessionStore(&o.Session, &o.Cookie)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err))
|
||||
} else {
|
||||
o.SetSessionStore(sessionStore)
|
||||
}
|
||||
|
||||
if o.Cookie.Refresh >= o.Cookie.Expire {
|
||||
msgs = append(msgs, fmt.Sprintf(
|
||||
"cookie_refresh (%s) must be less than "+
|
||||
"cookie_expire (%s)",
|
||||
o.Cookie.Refresh.String(),
|
||||
o.Cookie.Expire.String()))
|
||||
}
|
||||
|
||||
if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
|
||||
if len(o.GoogleGroups) < 1 {
|
||||
msgs = append(msgs, "missing setting: google-group")
|
||||
}
|
||||
if o.GoogleAdminEmail == "" {
|
||||
msgs = append(msgs, "missing setting: google-admin-email")
|
||||
}
|
||||
if o.GoogleServiceAccountJSON == "" {
|
||||
msgs = append(msgs, "missing setting: google-service-account-json")
|
||||
}
|
||||
}
|
||||
|
||||
switch o.Cookie.SameSite {
|
||||
case "", "none", "lax", "strict":
|
||||
default:
|
||||
msgs = append(msgs, fmt.Sprintf("cookie_samesite (%s) must be one of ['', 'lax', 'strict', 'none']", o.Cookie.SameSite))
|
||||
}
|
||||
|
||||
// Sort cookie domains by length, so that we try longer (and more specific)
|
||||
// domains first
|
||||
sort.Slice(o.Cookie.Domains, func(i, j int) bool {
|
||||
return len(o.Cookie.Domains[i]) > len(o.Cookie.Domains[j])
|
||||
})
|
||||
|
||||
msgs = parseSignatureKey(o, msgs)
|
||||
msgs = validateCookieName(o, msgs)
|
||||
msgs = setupLogger(o, msgs)
|
||||
|
||||
if o.ReverseProxy {
|
||||
parser, err := logging.GetRealClientIPParser(o.RealClientIPHeader)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("real_client_ip_header (%s) not accepted parameter value: %v", o.RealClientIPHeader, err))
|
||||
}
|
||||
o.SetRealClientIPParser(parser)
|
||||
}
|
||||
|
||||
if len(msgs) != 0 {
|
||||
return fmt.Errorf("invalid configuration:\n %s",
|
||||
strings.Join(msgs, "\n "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseProviderInfo(o *options.Options, msgs []string) []string {
|
||||
p := &providers.ProviderData{
|
||||
Scope: o.Scope,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
ClientSecretFile: o.ClientSecretFile,
|
||||
Prompt: o.Prompt,
|
||||
ApprovalPrompt: o.ApprovalPrompt,
|
||||
AcrValues: o.AcrValues,
|
||||
}
|
||||
p.LoginURL, msgs = parseURL(o.LoginURL, "login", msgs)
|
||||
p.RedeemURL, msgs = parseURL(o.RedeemURL, "redeem", msgs)
|
||||
p.ProfileURL, msgs = parseURL(o.ProfileURL, "profile", msgs)
|
||||
p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs)
|
||||
p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
|
||||
|
||||
o.SetProvider(providers.New(o.ProviderType, p))
|
||||
switch p := o.GetProvider().(type) {
|
||||
case *providers.AzureProvider:
|
||||
p.Configure(o.AzureTenant)
|
||||
case *providers.GitHubProvider:
|
||||
p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
|
||||
p.SetRepo(o.GitHubRepo, o.GitHubToken)
|
||||
case *providers.KeycloakProvider:
|
||||
p.SetGroup(o.KeycloakGroup)
|
||||
case *providers.GoogleProvider:
|
||||
if o.GoogleServiceAccountJSON != "" {
|
||||
file, err := os.Open(o.GoogleServiceAccountJSON)
|
||||
if err != nil {
|
||||
msgs = append(msgs, "invalid Google credentials file: "+o.GoogleServiceAccountJSON)
|
||||
} else {
|
||||
p.SetGroupRestriction(o.GoogleGroups, o.GoogleAdminEmail, file)
|
||||
}
|
||||
}
|
||||
case *providers.BitbucketProvider:
|
||||
p.SetTeam(o.BitbucketTeam)
|
||||
p.SetRepository(o.BitbucketRepository)
|
||||
case *providers.OIDCProvider:
|
||||
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
|
||||
p.UserIDClaim = o.UserIDClaim
|
||||
if o.GetOIDCVerifier() == nil {
|
||||
msgs = append(msgs, "oidc provider requires an oidc issuer URL")
|
||||
} else {
|
||||
p.Verifier = o.GetOIDCVerifier()
|
||||
}
|
||||
case *providers.GitLabProvider:
|
||||
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
|
||||
p.Group = o.GitLabGroup
|
||||
p.EmailDomains = o.EmailDomains
|
||||
|
||||
if o.GetOIDCVerifier() != nil {
|
||||
p.Verifier = o.GetOIDCVerifier()
|
||||
} else {
|
||||
// Initialize with default verifier for gitlab.com
|
||||
ctx := context.Background()
|
||||
|
||||
provider, err := oidc.NewProvider(ctx, "https://gitlab.com")
|
||||
if err != nil {
|
||||
msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
|
||||
} else {
|
||||
p.Verifier = provider.Verifier(&oidc.Config{
|
||||
ClientID: o.ClientID,
|
||||
})
|
||||
|
||||
p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
|
||||
p.RedeemURL, msgs = parseURL(provider.Endpoint().TokenURL, "redeem", msgs)
|
||||
}
|
||||
}
|
||||
case *providers.LoginGovProvider:
|
||||
p.PubJWKURL, msgs = parseURL(o.PubJWKURL, "pubjwk", msgs)
|
||||
|
||||
// JWT key can be supplied via env variable or file in the filesystem, but not both.
|
||||
switch {
|
||||
case o.JWTKey != "" && o.JWTKeyFile != "":
|
||||
msgs = append(msgs, "cannot set both jwt-key and jwt-key-file options")
|
||||
case o.JWTKey == "" && o.JWTKeyFile == "":
|
||||
msgs = append(msgs, "login.gov provider requires a private key for signing JWTs")
|
||||
case o.JWTKey != "":
|
||||
// The JWT Key is in the commandline argument
|
||||
signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(o.JWTKey))
|
||||
if err != nil {
|
||||
msgs = append(msgs, "could not parse RSA Private Key PEM")
|
||||
} else {
|
||||
p.JWTKey = signKey
|
||||
}
|
||||
case o.JWTKeyFile != "":
|
||||
// The JWT key is in the filesystem
|
||||
keyData, err := ioutil.ReadFile(o.JWTKeyFile)
|
||||
if err != nil {
|
||||
msgs = append(msgs, "could not read key file: "+o.JWTKeyFile)
|
||||
}
|
||||
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
|
||||
if err != nil {
|
||||
msgs = append(msgs, "could not parse private key from PEM file:"+o.JWTKeyFile)
|
||||
} else {
|
||||
p.JWTKey = signKey
|
||||
}
|
||||
}
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
func parseSignatureKey(o *options.Options, msgs []string) []string {
|
||||
if o.SignatureKey == "" {
|
||||
return msgs
|
||||
}
|
||||
|
||||
components := strings.Split(o.SignatureKey, ":")
|
||||
if len(components) != 2 {
|
||||
return append(msgs, "invalid signature hash:key spec: "+
|
||||
o.SignatureKey)
|
||||
}
|
||||
|
||||
algorithm, secretKey := components[0], components[1]
|
||||
var hash crypto.Hash
|
||||
var err error
|
||||
if hash, err = hmacauth.DigestNameToCryptoHash(algorithm); err != nil {
|
||||
return append(msgs, "unsupported signature hash algorithm: "+
|
||||
o.SignatureKey)
|
||||
}
|
||||
o.SetSignatureData(&options.SignatureData{Hash: hash, Key: secretKey})
|
||||
return msgs
|
||||
}
|
||||
|
||||
// parseJwtIssuers takes in an array of strings in the form of issuer=audience
|
||||
// and parses to an array of jwtIssuer structs.
|
||||
func parseJwtIssuers(issuers []string, msgs []string) ([]jwtIssuer, []string) {
|
||||
parsedIssuers := make([]jwtIssuer, 0, len(issuers))
|
||||
for _, jwtVerifier := range issuers {
|
||||
components := strings.Split(jwtVerifier, "=")
|
||||
if len(components) < 2 {
|
||||
msgs = append(msgs, fmt.Sprintf("invalid jwt verifier uri=audience spec: %s", jwtVerifier))
|
||||
continue
|
||||
}
|
||||
uri, audience := components[0], strings.Join(components[1:], "=")
|
||||
parsedIssuers = append(parsedIssuers, jwtIssuer{issuerURI: uri, audience: audience})
|
||||
}
|
||||
return parsedIssuers, msgs
|
||||
}
|
||||
|
||||
// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
|
||||
// a verifier for that issuer.
|
||||
func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error) {
|
||||
config := &oidc.Config{
|
||||
ClientID: jwtIssuer.audience,
|
||||
}
|
||||
// Try as an OpenID Connect Provider first
|
||||
var verifier *oidc.IDTokenVerifier
|
||||
provider, err := oidc.NewProvider(context.Background(), jwtIssuer.issuerURI)
|
||||
if err != nil {
|
||||
// Try as JWKS URI
|
||||
jwksURI := strings.TrimSuffix(jwtIssuer.issuerURI, "/") + "/.well-known/jwks.json"
|
||||
_, err := http.NewRequest("GET", jwksURI, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verifier = oidc.NewVerifier(jwtIssuer.issuerURI, oidc.NewRemoteKeySet(context.Background(), jwksURI), config)
|
||||
} else {
|
||||
verifier = provider.Verifier(config)
|
||||
}
|
||||
return verifier, nil
|
||||
}
|
||||
|
||||
func validateCookieName(o *options.Options, msgs []string) []string {
|
||||
cookie := &http.Cookie{Name: o.Cookie.Name}
|
||||
if cookie.String() == "" {
|
||||
return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.Cookie.Name))
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
func setupLogger(o *options.Options, msgs []string) []string {
|
||||
// Setup the log file
|
||||
if len(o.LoggingFilename) > 0 {
|
||||
// Validate that the file/dir can be written
|
||||
file, err := os.OpenFile(o.LoggingFilename, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
return append(msgs, "unable to write to log file: "+o.LoggingFilename)
|
||||
}
|
||||
}
|
||||
file.Close()
|
||||
|
||||
logger.Printf("Redirecting logging to file: %s", o.LoggingFilename)
|
||||
|
||||
logWriter := &lumberjack.Logger{
|
||||
Filename: o.LoggingFilename,
|
||||
MaxSize: o.LoggingMaxSize, // megabytes
|
||||
MaxAge: o.LoggingMaxAge, // days
|
||||
MaxBackups: o.LoggingMaxBackups,
|
||||
LocalTime: o.LoggingLocalTime,
|
||||
Compress: o.LoggingCompress,
|
||||
}
|
||||
|
||||
logger.SetOutput(logWriter)
|
||||
}
|
||||
|
||||
// Supply a sanity warning to the logger if all logging is disabled
|
||||
if !o.StandardLogging && !o.AuthLogging && !o.RequestLogging {
|
||||
logger.Print("Warning: Logging disabled. No further logs will be shown.")
|
||||
}
|
||||
|
||||
// Pass configuration values to the standard logger
|
||||
logger.SetStandardEnabled(o.StandardLogging)
|
||||
logger.SetAuthEnabled(o.AuthLogging)
|
||||
logger.SetReqEnabled(o.RequestLogging)
|
||||
logger.SetStandardTemplate(o.StandardLoggingFormat)
|
||||
logger.SetAuthTemplate(o.AuthLoggingFormat)
|
||||
logger.SetReqTemplate(o.RequestLoggingFormat)
|
||||
logger.SetGetClientFunc(func(r *http.Request) string {
|
||||
return logging.GetClientString(o.GetRealClientIPParser(), r, false)
|
||||
})
|
||||
|
||||
excludePaths := make([]string, 0)
|
||||
excludePaths = append(excludePaths, strings.Split(o.ExcludeLoggingPaths, ",")...)
|
||||
if o.SilencePingLogging {
|
||||
excludePaths = append(excludePaths, o.PingPath)
|
||||
}
|
||||
|
||||
logger.SetExcludePaths(excludePaths)
|
||||
|
||||
if !o.LoggingLocalTime {
|
||||
logger.SetFlags(logger.Flags() | logger.LUTC)
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
|
||||
type jwtIssuer struct {
|
||||
issuerURI string
|
||||
audience string
|
||||
}
|
||||
|
||||
func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) {
|
||||
parsed, err := url.Parse(toParse)
|
||||
if err != nil {
|
||||
return nil, append(msgs, fmt.Sprintf(
|
||||
"error parsing %s-url=%q %s", urltype, toParse, err))
|
||||
}
|
||||
return parsed, msgs
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
cookieSecret = "foobar"
|
||||
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 TestValidateCookie(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.Cookie.Name = "_valid_cookie_name"
|
||||
assert.Equal(t, nil, Validate(o))
|
||||
}
|
||||
|
||||
func TestValidateCookieBadName(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.Cookie.Name = "_bad_cookie_name{}"
|
||||
err := Validate(o)
|
||||
assert.Equal(t, err.Error(), "invalid configuration:\n"+
|
||||
fmt.Sprintf(" invalid cookie name: %q", o.Cookie.Name))
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user