Merge pull request #489 from oauth2-proxy/move-options
Move Options and Validation to packages
This commit is contained in:
		
						commit
						236c7fa60e
					
				
							
								
								
									
										5
									
								
								http.go
								
								
								
								
							
							
						
						
									
										5
									
								
								http.go
								
								
								
								
							|  | @ -9,13 +9,14 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Server represents an HTTP server
 | // Server represents an HTTP server
 | ||||||
| type Server struct { | type Server struct { | ||||||
| 	Handler http.Handler | 	Handler http.Handler | ||||||
| 	Opts    *Options | 	Opts    *options.Options | ||||||
| 	stop    chan struct{} // channel for waiting shutdown
 | 	stop    chan struct{} // channel for waiting shutdown
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -167,7 +168,7 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { | ||||||
| 	return tc, nil | 	return tc, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func redirectToHTTPS(opts *Options, h http.Handler) http.Handler { | func redirectToHTTPS(opts *options.Options, h http.Handler) http.Handler { | ||||||
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
| 		proto := r.Header.Get("X-Forwarded-Proto") | 		proto := r.Header.Get("X-Forwarded-Proto") | ||||||
| 		if opts.ForceHTTPS && (r.TLS == nil || (proto != "" && strings.ToLower(proto) != "https")) { | 		if opts.ForceHTTPS && (r.TLS == nil || (proto != "" && strings.ToLower(proto) != "https")) { | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -110,7 +111,7 @@ func TestGCPHealthcheckNotIngressPut(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestRedirectToHTTPSTrue(t *testing.T) { | func TestRedirectToHTTPSTrue(t *testing.T) { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.ForceHTTPS = true | 	opts.ForceHTTPS = true | ||||||
| 	handler := func(w http.ResponseWriter, req *http.Request) { | 	handler := func(w http.ResponseWriter, req *http.Request) { | ||||||
| 		w.Write([]byte("test")) | 		w.Write([]byte("test")) | ||||||
|  | @ -125,7 +126,7 @@ func TestRedirectToHTTPSTrue(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestRedirectToHTTPSFalse(t *testing.T) { | func TestRedirectToHTTPSFalse(t *testing.T) { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	handler := func(w http.ResponseWriter, req *http.Request) { | 	handler := func(w http.ResponseWriter, req *http.Request) { | ||||||
| 		w.Write([]byte("test")) | 		w.Write([]byte("test")) | ||||||
| 	} | 	} | ||||||
|  | @ -139,7 +140,7 @@ func TestRedirectToHTTPSFalse(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestRedirectNotWhenHTTPS(t *testing.T) { | func TestRedirectNotWhenHTTPS(t *testing.T) { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.ForceHTTPS = true | 	opts.ForceHTTPS = true | ||||||
| 	handler := func(w http.ResponseWriter, req *http.Request) { | 	handler := func(w http.ResponseWriter, req *http.Request) { | ||||||
| 		w.Write([]byte("test")) | 		w.Write([]byte("test")) | ||||||
|  | @ -160,7 +161,7 @@ func TestRedirectNotWhenHTTPS(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestGracefulShutdown(t *testing.T) { | func TestGracefulShutdown(t *testing.T) { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	stop := make(chan struct{}, 1) | 	stop := make(chan struct{}, 1) | ||||||
| 	srv := Server{Handler: http.DefaultServeMux, Opts: opts, stop: stop} | 	srv := Server{Handler: http.DefaultServeMux, Opts: opts, stop: stop} | ||||||
| 	var wg sync.WaitGroup | 	var wg sync.WaitGroup | ||||||
|  |  | ||||||
							
								
								
									
										126
									
								
								main.go
								
								
								
								
							
							
						
						
									
										126
									
								
								main.go
								
								
								
								
							|  | @ -13,134 +13,16 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | ||||||
| 	"github.com/spf13/pflag" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/validation" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
| 	logger.SetFlags(logger.Lshortfile) | 	logger.SetFlags(logger.Lshortfile) | ||||||
| 	flagSet := pflag.NewFlagSet("oauth2-proxy", pflag.ExitOnError) | 	flagSet := options.NewFlagSet() | ||||||
| 
 | 
 | ||||||
| 	config := flagSet.String("config", "", "path to config file") | 	config := flagSet.String("config", "", "path to config file") | ||||||
| 	showVersion := flagSet.Bool("version", false, "print version string") | 	showVersion := flagSet.Bool("version", false, "print version string") | ||||||
| 
 | 
 | ||||||
| 	flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients") |  | ||||||
| 	flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients") |  | ||||||
| 	flagSet.Bool("reverse-proxy", false, "are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted") |  | ||||||
| 	flagSet.String("real-client-ip-header", "X-Real-IP", "Header used to determine the real IP of the client (one of: X-Forwarded-For, X-Real-IP, or X-ProxyUser-IP)") |  | ||||||
| 	flagSet.Bool("force-https", false, "force HTTPS redirect for HTTP requests") |  | ||||||
| 	flagSet.String("tls-cert-file", "", "path to certificate file") |  | ||||||
| 	flagSet.String("tls-key-file", "", "path to private key file") |  | ||||||
| 	flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"") |  | ||||||
| 	flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)") |  | ||||||
| 	flagSet.StringSlice("upstream", []string{}, "the http url(s) of the upstream endpoint, file:// paths for static files or static://<status_code> for static response. Routing is based on the path") |  | ||||||
| 	flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream") |  | ||||||
| 	flagSet.Bool("set-basic-auth", false, "set HTTP Basic Auth information in response (useful in Nginx auth_request mode)") |  | ||||||
| 	flagSet.Bool("prefer-email-to-user", false, "Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. Used in conjunction with -pass-basic-auth and -pass-user-headers") |  | ||||||
| 	flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream") |  | ||||||
| 	flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header") |  | ||||||
| 	flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header") |  | ||||||
| 	flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream") |  | ||||||
| 	flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream") |  | ||||||
| 	flagSet.Bool("set-authorization-header", false, "set Authorization response headers (useful in Nginx auth_request mode)") |  | ||||||
| 	flagSet.StringSlice("skip-auth-regex", []string{}, "bypass authentication for requests path's that match (may be given multiple times)") |  | ||||||
| 	flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start") |  | ||||||
| 	flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests") |  | ||||||
| 	flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers") |  | ||||||
| 	flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams") |  | ||||||
| 	flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses") |  | ||||||
| 	flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)") |  | ||||||
| 	flagSet.StringSlice("extra-jwt-issuers", []string{}, "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)") |  | ||||||
| 
 |  | ||||||
| 	flagSet.StringSlice("email-domain", []string{}, "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") |  | ||||||
| 	flagSet.StringSlice("whitelist-domain", []string{}, "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)") |  | ||||||
| 	flagSet.String("keycloak-group", "", "restrict login to members of this group.") |  | ||||||
| 	flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.") |  | ||||||
| 	flagSet.String("bitbucket-team", "", "restrict logins to members of this team") |  | ||||||
| 	flagSet.String("bitbucket-repository", "", "restrict logins to user with access to this repository") |  | ||||||
| 	flagSet.String("github-org", "", "restrict logins to members of this organisation") |  | ||||||
| 	flagSet.String("github-team", "", "restrict logins to members of this team") |  | ||||||
| 	flagSet.String("github-repo", "", "restrict logins to collaborators of this repository") |  | ||||||
| 	flagSet.String("github-token", "", "the token to use when verifying repository collaborators (must have push access to the repository)") |  | ||||||
| 	flagSet.String("gitlab-group", "", "restrict logins to members of this group") |  | ||||||
| 	flagSet.StringSlice("google-group", []string{}, "restrict logins to members of this google group (may be given multiple times).") |  | ||||||
| 	flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls") |  | ||||||
| 	flagSet.String("google-service-account-json", "", "the path to the service account json credentials") |  | ||||||
| 	flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"") |  | ||||||
| 	flagSet.String("client-secret", "", "the OAuth Client Secret") |  | ||||||
| 	flagSet.String("client-secret-file", "", "the file with OAuth Client Secret") |  | ||||||
| 	flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)") |  | ||||||
| 	flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption or \"htpasswd -B\" for bcrypt encryption") |  | ||||||
| 	flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided") |  | ||||||
| 	flagSet.String("custom-templates-dir", "", "path to custom html templates") |  | ||||||
| 	flagSet.String("banner", "", "custom banner string. Use \"-\" to disable default banner.") |  | ||||||
| 	flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.") |  | ||||||
| 	flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)") |  | ||||||
| 	flagSet.String("ping-path", "/ping", "the ping endpoint that can be used for basic health checks") |  | ||||||
| 	flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying") |  | ||||||
| 
 |  | ||||||
| 	flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates") |  | ||||||
| 	flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)") |  | ||||||
| 	flagSet.StringSlice("cookie-domain", []string{}, "Optional cookie domains to force cookies to (ie: `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match).") |  | ||||||
| 	flagSet.String("cookie-path", "/", "an optional cookie path to force cookies to (ie: /poc/)*") |  | ||||||
| 	flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie") |  | ||||||
| 	flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable") |  | ||||||
| 	flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag") |  | ||||||
| 	flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") |  | ||||||
| 	flagSet.String("cookie-samesite", "", "set SameSite cookie attribute (ie: \"lax\", \"strict\", \"none\", or \"\"). ") |  | ||||||
| 
 |  | ||||||
| 	flagSet.String("session-store-type", "cookie", "the session storage provider to use") |  | ||||||
| 	flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])") |  | ||||||
| 	flagSet.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature") |  | ||||||
| 	flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel") |  | ||||||
| 	flagSet.String("redis-ca-path", "", "Redis custom CA path") |  | ||||||
| 	flagSet.Bool("redis-insecure-skip-tls-verify", false, "Use insecure TLS connection to redis") |  | ||||||
| 	flagSet.StringSlice("redis-sentinel-connection-urls", []string{}, "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel") |  | ||||||
| 	flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature") |  | ||||||
| 	flagSet.StringSlice("redis-cluster-connection-urls", []string{}, "List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-cluster") |  | ||||||
| 
 |  | ||||||
| 	flagSet.String("logging-filename", "", "File to log requests to, empty for stdout") |  | ||||||
| 	flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation") |  | ||||||
| 	flagSet.Int("logging-max-age", 7, "Maximum number of days to retain old log files") |  | ||||||
| 	flagSet.Int("logging-max-backups", 0, "Maximum number of old log files to retain; 0 to disable") |  | ||||||
| 	flagSet.Bool("logging-local-time", true, "If the time in log files and backup filenames are local or UTC time") |  | ||||||
| 	flagSet.Bool("logging-compress", false, "Should rotated log files be compressed using gzip") |  | ||||||
| 
 |  | ||||||
| 	flagSet.Bool("standard-logging", true, "Log standard runtime information") |  | ||||||
| 	flagSet.String("standard-logging-format", logger.DefaultStandardLoggingFormat, "Template for standard log lines") |  | ||||||
| 
 |  | ||||||
| 	flagSet.Bool("request-logging", true, "Log HTTP requests") |  | ||||||
| 	flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines") |  | ||||||
| 	flagSet.String("exclude-logging-paths", "", "Exclude logging requests to paths (eg: '/path1,/path2,/path3')") |  | ||||||
| 	flagSet.Bool("silence-ping-logging", false, "Disable logging of requests to ping endpoint") |  | ||||||
| 
 |  | ||||||
| 	flagSet.Bool("auth-logging", true, "Log authentication attempts") |  | ||||||
| 	flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines") |  | ||||||
| 
 |  | ||||||
| 	flagSet.String("provider", "google", "OAuth provider") |  | ||||||
| 	flagSet.String("provider-display-name", "", "Provider display name") |  | ||||||
| 	flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)") |  | ||||||
| 	flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified") |  | ||||||
| 	flagSet.Bool("insecure-oidc-skip-issuer-verification", false, "Do not verify if issuer matches OIDC discovery URL") |  | ||||||
| 	flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints") |  | ||||||
| 	flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)") |  | ||||||
| 	flagSet.String("login-url", "", "Authentication endpoint") |  | ||||||
| 	flagSet.String("redeem-url", "", "Token redemption endpoint") |  | ||||||
| 	flagSet.String("profile-url", "", "Profile access endpoint") |  | ||||||
| 	flagSet.String("resource", "", "The resource that is protected (Azure AD only)") |  | ||||||
| 	flagSet.String("validate-url", "", "Access token validation endpoint") |  | ||||||
| 	flagSet.String("scope", "", "OAuth scope specification") |  | ||||||
| 	flagSet.String("prompt", "", "OIDC prompt") |  | ||||||
| 	flagSet.String("approval-prompt", "force", "OAuth approval_prompt") |  | ||||||
| 
 |  | ||||||
| 	flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)") |  | ||||||
| 	flagSet.String("acr-values", "", "acr values string:  optional") |  | ||||||
| 	flagSet.String("jwt-key", "", "private key in PEM format used to sign JWT, so that you can say something like -jwt-key=\"${OAUTH2_PROXY_JWT_KEY}\": required by login.gov") |  | ||||||
| 	flagSet.String("jwt-key-file", "", "path to the private key file in PEM format used to sign the JWT so that you can say something like -jwt-key-file=/etc/ssl/private/jwt_signing_key.pem: required by login.gov") |  | ||||||
| 	flagSet.String("pubjwk-url", "", "JWK pubkey access endpoint: required by login.gov") |  | ||||||
| 	flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints") |  | ||||||
| 
 |  | ||||||
| 	flagSet.String("user-id-claim", "email", "which claim contains the user ID") |  | ||||||
| 
 |  | ||||||
| 	flagSet.Parse(os.Args[1:]) | 	flagSet.Parse(os.Args[1:]) | ||||||
| 
 | 
 | ||||||
| 	if *showVersion { | 	if *showVersion { | ||||||
|  | @ -148,14 +30,14 @@ func main() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	err := options.Load(*config, flagSet, opts) | 	err := options.Load(*config, flagSet, opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logger.Printf("ERROR: Failed to load config: %v", err) | 		logger.Printf("ERROR: Failed to load config: %v", err) | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = opts.Validate() | 	err = validation.Validate(opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logger.Printf("%s", err) | 		logger.Printf("%s", err) | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
|  |  | ||||||
|  | @ -19,9 +19,12 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/coreos/go-oidc" | 	"github.com/coreos/go-oidc" | ||||||
| 	"github.com/mbland/hmacauth" | 	"github.com/mbland/hmacauth" | ||||||
|  | 	ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||||
| 	sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" | 	sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/cookies" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/cookies" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/ip" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/providers" | 	"github.com/oauth2-proxy/oauth2-proxy/providers" | ||||||
| 	"github.com/yhat/wsutil" | 	"github.com/yhat/wsutil" | ||||||
|  | @ -112,7 +115,7 @@ type OAuthProxy struct { | ||||||
| 	jwtBearerVerifiers   []*oidc.IDTokenVerifier | 	jwtBearerVerifiers   []*oidc.IDTokenVerifier | ||||||
| 	compiledRegex        []*regexp.Regexp | 	compiledRegex        []*regexp.Regexp | ||||||
| 	templates            *template.Template | 	templates            *template.Template | ||||||
| 	realClientIPParser   realClientIPParser | 	realClientIPParser   ipapi.RealClientIPParser | ||||||
| 	Banner               string | 	Banner               string | ||||||
| 	Footer               string | 	Footer               string | ||||||
| } | } | ||||||
|  | @ -143,7 +146,7 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||||
| 
 | 
 | ||||||
| // NewReverseProxy creates a new reverse proxy for proxying requests to upstream
 | // NewReverseProxy creates a new reverse proxy for proxying requests to upstream
 | ||||||
| // servers
 | // servers
 | ||||||
| func NewReverseProxy(target *url.URL, opts *Options) (proxy *httputil.ReverseProxy) { | func NewReverseProxy(target *url.URL, opts *options.Options) (proxy *httputil.ReverseProxy) { | ||||||
| 	proxy = httputil.NewSingleHostReverseProxy(target) | 	proxy = httputil.NewSingleHostReverseProxy(target) | ||||||
| 	proxy.FlushInterval = opts.FlushInterval | 	proxy.FlushInterval = opts.FlushInterval | ||||||
| 	if opts.SSLUpstreamInsecureSkipVerify { | 	if opts.SSLUpstreamInsecureSkipVerify { | ||||||
|  | @ -181,7 +184,7 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url
 | // NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url
 | ||||||
| func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) http.Handler { | func NewWebSocketOrRestReverseProxy(u *url.URL, opts *options.Options, auth hmacauth.HmacAuth) http.Handler { | ||||||
| 	u.Path = "" | 	u.Path = "" | ||||||
| 	proxy := NewReverseProxy(u, opts) | 	proxy := NewReverseProxy(u, opts) | ||||||
| 	if !opts.PassHostHeader { | 	if !opts.PassHostHeader { | ||||||
|  | @ -209,14 +212,14 @@ func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.Hma | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewOAuthProxy creates a new instance of OAuthProxy from the options provided
 | // NewOAuthProxy creates a new instance of OAuthProxy from the options provided
 | ||||||
| func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { | func NewOAuthProxy(opts *options.Options, validator func(string) bool) *OAuthProxy { | ||||||
| 	serveMux := http.NewServeMux() | 	serveMux := http.NewServeMux() | ||||||
| 	var auth hmacauth.HmacAuth | 	var auth hmacauth.HmacAuth | ||||||
| 	if sigData := opts.signatureData; sigData != nil { | 	if sigData := opts.GetSignatureData(); sigData != nil { | ||||||
| 		auth = hmacauth.NewHmacAuth(sigData.hash, []byte(sigData.key), | 		auth = hmacauth.NewHmacAuth(sigData.Hash, []byte(sigData.Key), | ||||||
| 			SignatureHeader, SignatureHeaders) | 			SignatureHeader, SignatureHeaders) | ||||||
| 	} | 	} | ||||||
| 	for _, u := range opts.proxyURLs { | 	for _, u := range opts.GetProxyURLs() { | ||||||
| 		path := u.Path | 		path := u.Path | ||||||
| 		host := u.Host | 		host := u.Host | ||||||
| 		switch u.Scheme { | 		switch u.Scheme { | ||||||
|  | @ -252,7 +255,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { | ||||||
| 			panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme)) | 			panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme)) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	for _, u := range opts.compiledRegex { | 	for _, u := range opts.GetCompiledRegex() { | ||||||
| 		logger.Printf("compiled skip-auth-regex => %q", u) | 		logger.Printf("compiled skip-auth-regex => %q", u) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -262,12 +265,12 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { | ||||||
| 			logger.Printf("Skipping JWT tokens from extra JWT issuer: %q", issuer) | 			logger.Printf("Skipping JWT tokens from extra JWT issuer: %q", issuer) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	redirectURL := opts.redirectURL | 	redirectURL := opts.GetRedirectURL() | ||||||
| 	if redirectURL.Path == "" { | 	if redirectURL.Path == "" { | ||||||
| 		redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix) | 		redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID) | 	logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.GetProvider().Data().ProviderName, opts.ClientID) | ||||||
| 	refresh := "disabled" | 	refresh := "disabled" | ||||||
| 	if opts.Cookie.Refresh != time.Duration(0) { | 	if opts.Cookie.Refresh != time.Duration(0) { | ||||||
| 		refresh = fmt.Sprintf("after %s", opts.Cookie.Refresh) | 		refresh = fmt.Sprintf("after %s", opts.Cookie.Refresh) | ||||||
|  | @ -298,18 +301,18 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { | ||||||
| 		UserInfoPath:      fmt.Sprintf("%s/userinfo", opts.ProxyPrefix), | 		UserInfoPath:      fmt.Sprintf("%s/userinfo", opts.ProxyPrefix), | ||||||
| 
 | 
 | ||||||
| 		ProxyPrefix:          opts.ProxyPrefix, | 		ProxyPrefix:          opts.ProxyPrefix, | ||||||
| 		provider:             opts.provider, | 		provider:             opts.GetProvider(), | ||||||
| 		providerNameOverride: opts.ProviderName, | 		providerNameOverride: opts.ProviderName, | ||||||
| 		sessionStore:         opts.sessionStore, | 		sessionStore:         opts.GetSessionStore(), | ||||||
| 		serveMux:             serveMux, | 		serveMux:             serveMux, | ||||||
| 		redirectURL:          redirectURL, | 		redirectURL:          redirectURL, | ||||||
| 		whitelistDomains:     opts.WhitelistDomains, | 		whitelistDomains:     opts.WhitelistDomains, | ||||||
| 		skipAuthRegex:        opts.SkipAuthRegex, | 		skipAuthRegex:        opts.SkipAuthRegex, | ||||||
| 		skipAuthPreflight:    opts.SkipAuthPreflight, | 		skipAuthPreflight:    opts.SkipAuthPreflight, | ||||||
| 		skipJwtBearerTokens:  opts.SkipJwtBearerTokens, | 		skipJwtBearerTokens:  opts.SkipJwtBearerTokens, | ||||||
| 		jwtBearerVerifiers:   opts.jwtBearerVerifiers, | 		jwtBearerVerifiers:   opts.GetJWTBearerVerifiers(), | ||||||
| 		compiledRegex:        opts.compiledRegex, | 		compiledRegex:        opts.GetCompiledRegex(), | ||||||
| 		realClientIPParser:   opts.realClientIPParser, | 		realClientIPParser:   opts.GetRealClientIPParser(), | ||||||
| 		SetXAuthRequest:      opts.SetXAuthRequest, | 		SetXAuthRequest:      opts.SetXAuthRequest, | ||||||
| 		PassBasicAuth:        opts.PassBasicAuth, | 		PassBasicAuth:        opts.PassBasicAuth, | ||||||
| 		SetBasicAuth:         opts.SetBasicAuth, | 		SetBasicAuth:         opts.SetBasicAuth, | ||||||
|  | @ -760,7 +763,7 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) { | ||||||
| // OAuthCallback is the OAuth2 authentication flow callback that finishes the
 | // OAuthCallback is the OAuth2 authentication flow callback that finishes the
 | ||||||
| // OAuth2 authentication flow
 | // OAuth2 authentication flow
 | ||||||
| func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { | func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { | ||||||
| 	remoteAddr := getClientString(p.realClientIPParser, req, true) | 	remoteAddr := ip.GetClientString(p.realClientIPParser, req, true) | ||||||
| 
 | 
 | ||||||
| 	// finish the oauth cycle
 | 	// finish the oauth cycle
 | ||||||
| 	err := req.ParseForm() | 	err := req.ParseForm() | ||||||
|  | @ -888,7 +891,7 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	remoteAddr := getClientString(p.realClientIPParser, req, true) | 	remoteAddr := ip.GetClientString(p.realClientIPParser, req, true) | ||||||
| 	if session == nil { | 	if session == nil { | ||||||
| 		session, err = p.LoadCookiedSession(req) | 		session, err = p.LoadCookiedSession(req) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  |  | ||||||
|  | @ -18,9 +18,11 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/coreos/go-oidc" | 	"github.com/coreos/go-oidc" | ||||||
| 	"github.com/mbland/hmacauth" | 	"github.com/mbland/hmacauth" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/validation" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/providers" | 	"github.com/oauth2-proxy/oauth2-proxy/providers" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"github.com/stretchr/testify/require" | 	"github.com/stretchr/testify/require" | ||||||
|  | @ -71,7 +73,7 @@ func TestWebSocketProxy(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	backendURL, _ := url.Parse(backend.URL) | 	backendURL, _ := url.Parse(backend.URL) | ||||||
| 
 | 
 | ||||||
| 	options := NewOptions() | 	options := options.NewOptions() | ||||||
| 	var auth hmacauth.HmacAuth | 	var auth hmacauth.HmacAuth | ||||||
| 	options.PassHostHeader = true | 	options.PassHostHeader = true | ||||||
| 	proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, options, auth) | 	proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, options, auth) | ||||||
|  | @ -121,7 +123,7 @@ func TestNewReverseProxy(t *testing.T) { | ||||||
| 	backendHost := net.JoinHostPort(backendHostname, backendPort) | 	backendHost := net.JoinHostPort(backendHostname, backendPort) | ||||||
| 	proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/") | 	proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/") | ||||||
| 
 | 
 | ||||||
| 	proxyHandler := NewReverseProxy(proxyURL, &Options{FlushInterval: time.Second}) | 	proxyHandler := NewReverseProxy(proxyURL, &options.Options{FlushInterval: time.Second}) | ||||||
| 	setProxyUpstreamHostHeader(proxyHandler, proxyURL) | 	setProxyUpstreamHostHeader(proxyHandler, proxyURL) | ||||||
| 	frontend := httptest.NewServer(proxyHandler) | 	frontend := httptest.NewServer(proxyHandler) | ||||||
| 	defer frontend.Close() | 	defer frontend.Close() | ||||||
|  | @ -143,7 +145,7 @@ func TestEncodedSlashes(t *testing.T) { | ||||||
| 	defer backend.Close() | 	defer backend.Close() | ||||||
| 
 | 
 | ||||||
| 	b, _ := url.Parse(backend.URL) | 	b, _ := url.Parse(backend.URL) | ||||||
| 	proxyHandler := NewReverseProxy(b, &Options{FlushInterval: time.Second}) | 	proxyHandler := NewReverseProxy(b, &options.Options{FlushInterval: time.Second}) | ||||||
| 	setProxyDirector(proxyHandler) | 	setProxyDirector(proxyHandler) | ||||||
| 	frontend := httptest.NewServer(proxyHandler) | 	frontend := httptest.NewServer(proxyHandler) | ||||||
| 	defer frontend.Close() | 	defer frontend.Close() | ||||||
|  | @ -161,11 +163,11 @@ func TestEncodedSlashes(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestRobotsTxt(t *testing.T) { | func TestRobotsTxt(t *testing.T) { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.ClientID = "asdlkjx" | 	opts.ClientID = "asdlkjx" | ||||||
| 	opts.ClientSecret = "alkgks" | 	opts.ClientSecret = "alkgks" | ||||||
| 	opts.Cookie.Secret = "asdkugkj" | 	opts.Cookie.Secret = "asdkugkj" | ||||||
| 	opts.Validate() | 	validation.Validate(opts) | ||||||
| 
 | 
 | ||||||
| 	proxy := NewOAuthProxy(opts, func(string) bool { return true }) | 	proxy := NewOAuthProxy(opts, func(string) bool { return true }) | ||||||
| 	rw := httptest.NewRecorder() | 	rw := httptest.NewRecorder() | ||||||
|  | @ -176,7 +178,7 @@ func TestRobotsTxt(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestIsValidRedirect(t *testing.T) { | func TestIsValidRedirect(t *testing.T) { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.ClientID = "skdlfj" | 	opts.ClientID = "skdlfj" | ||||||
| 	opts.ClientSecret = "fgkdsgj" | 	opts.ClientSecret = "fgkdsgj" | ||||||
| 	opts.Cookie.Secret = "ljgiogbj" | 	opts.Cookie.Secret = "ljgiogbj" | ||||||
|  | @ -189,7 +191,7 @@ func TestIsValidRedirect(t *testing.T) { | ||||||
| 		"anyport.bar:*", | 		"anyport.bar:*", | ||||||
| 		".sub.anyport.bar:*", | 		".sub.anyport.bar:*", | ||||||
| 	} | 	} | ||||||
| 	opts.Validate() | 	validation.Validate(opts) | ||||||
| 
 | 
 | ||||||
| 	proxy := NewOAuthProxy(opts, func(string) bool { return true }) | 	proxy := NewOAuthProxy(opts, func(string) bool { return true }) | ||||||
| 
 | 
 | ||||||
|  | @ -451,7 +453,7 @@ func TestBasicAuthPassword(t *testing.T) { | ||||||
| 		w.WriteHeader(200) | 		w.WriteHeader(200) | ||||||
| 		w.Write([]byte(payload)) | 		w.Write([]byte(payload)) | ||||||
| 	})) | 	})) | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.Upstreams = append(opts.Upstreams, providerServer.URL) | 	opts.Upstreams = append(opts.Upstreams, providerServer.URL) | ||||||
| 	// The CookieSecret must be 32 bytes in order to create the AES
 | 	// The CookieSecret must be 32 bytes in order to create the AES
 | ||||||
| 	// cipher.
 | 	// cipher.
 | ||||||
|  | @ -464,12 +466,12 @@ func TestBasicAuthPassword(t *testing.T) { | ||||||
| 	opts.PassUserHeaders = true | 	opts.PassUserHeaders = true | ||||||
| 	opts.PreferEmailToUser = true | 	opts.PreferEmailToUser = true | ||||||
| 	opts.BasicAuthPassword = "This is a secure password" | 	opts.BasicAuthPassword = "This is a secure password" | ||||||
| 	opts.Validate() | 	validation.Validate(opts) | ||||||
| 
 | 
 | ||||||
| 	providerURL, _ := url.Parse(providerServer.URL) | 	providerURL, _ := url.Parse(providerServer.URL) | ||||||
| 	const emailAddress = "john.doe@example.com" | 	const emailAddress = "john.doe@example.com" | ||||||
| 
 | 
 | ||||||
| 	opts.provider = NewTestProvider(providerURL, emailAddress) | 	opts.SetProvider(NewTestProvider(providerURL, emailAddress)) | ||||||
| 	proxy := NewOAuthProxy(opts, func(email string) bool { | 	proxy := NewOAuthProxy(opts, func(email string) bool { | ||||||
| 		return email == emailAddress | 		return email == emailAddress | ||||||
| 	}) | 	}) | ||||||
|  | @ -518,12 +520,12 @@ func TestBasicAuthPassword(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestBasicAuthWithEmail(t *testing.T) { | func TestBasicAuthWithEmail(t *testing.T) { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.PassBasicAuth = true | 	opts.PassBasicAuth = true | ||||||
| 	opts.PassUserHeaders = false | 	opts.PassUserHeaders = false | ||||||
| 	opts.PreferEmailToUser = false | 	opts.PreferEmailToUser = false | ||||||
| 	opts.BasicAuthPassword = "This is a secure password" | 	opts.BasicAuthPassword = "This is a secure password" | ||||||
| 	opts.Validate() | 	validation.Validate(opts) | ||||||
| 
 | 
 | ||||||
| 	const emailAddress = "john.doe@example.com" | 	const emailAddress = "john.doe@example.com" | ||||||
| 	const userName = "9fcab5c9b889a557" | 	const userName = "9fcab5c9b889a557" | ||||||
|  | @ -564,11 +566,11 @@ func TestBasicAuthWithEmail(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestPassUserHeadersWithEmail(t *testing.T) { | func TestPassUserHeadersWithEmail(t *testing.T) { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.PassBasicAuth = false | 	opts.PassBasicAuth = false | ||||||
| 	opts.PassUserHeaders = true | 	opts.PassUserHeaders = true | ||||||
| 	opts.PreferEmailToUser = false | 	opts.PreferEmailToUser = false | ||||||
| 	opts.Validate() | 	validation.Validate(opts) | ||||||
| 
 | 
 | ||||||
| 	const emailAddress = "john.doe@example.com" | 	const emailAddress = "john.doe@example.com" | ||||||
| 	const userName = "9fcab5c9b889a557" | 	const userName = "9fcab5c9b889a557" | ||||||
|  | @ -605,7 +607,7 @@ func TestPassUserHeadersWithEmail(t *testing.T) { | ||||||
| type PassAccessTokenTest struct { | type PassAccessTokenTest struct { | ||||||
| 	providerServer *httptest.Server | 	providerServer *httptest.Server | ||||||
| 	proxy          *OAuthProxy | 	proxy          *OAuthProxy | ||||||
| 	opts           *Options | 	opts           *options.Options | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type PassAccessTokenTestOptions struct { | type PassAccessTokenTestOptions struct { | ||||||
|  | @ -632,7 +634,7 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes | ||||||
| 			w.Write([]byte(payload)) | 			w.Write([]byte(payload)) | ||||||
| 		})) | 		})) | ||||||
| 
 | 
 | ||||||
| 	t.opts = NewOptions() | 	t.opts = options.NewOptions() | ||||||
| 	t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL) | 	t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL) | ||||||
| 	if opts.ProxyUpstream != "" { | 	if opts.ProxyUpstream != "" { | ||||||
| 		t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream) | 		t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream) | ||||||
|  | @ -644,12 +646,12 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes | ||||||
| 	t.opts.ClientSecret = "gfjgojl" | 	t.opts.ClientSecret = "gfjgojl" | ||||||
| 	t.opts.Cookie.Secure = false | 	t.opts.Cookie.Secure = false | ||||||
| 	t.opts.PassAccessToken = opts.PassAccessToken | 	t.opts.PassAccessToken = opts.PassAccessToken | ||||||
| 	t.opts.Validate() | 	validation.Validate(t.opts) | ||||||
| 
 | 
 | ||||||
| 	providerURL, _ := url.Parse(t.providerServer.URL) | 	providerURL, _ := url.Parse(t.providerServer.URL) | ||||||
| 	const emailAddress = "michael.bland@gsa.gov" | 	const emailAddress = "michael.bland@gsa.gov" | ||||||
| 
 | 
 | ||||||
| 	t.opts.provider = NewTestProvider(providerURL, emailAddress) | 	t.opts.SetProvider(NewTestProvider(providerURL, emailAddress)) | ||||||
| 	t.proxy = NewOAuthProxy(t.opts, func(email string) bool { | 	t.proxy = NewOAuthProxy(t.opts, func(email string) bool { | ||||||
| 		return email == emailAddress | 		return email == emailAddress | ||||||
| 	}) | 	}) | ||||||
|  | @ -779,7 +781,7 @@ func TestDoNotForwardAccessTokenUpstream(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type SignInPageTest struct { | type SignInPageTest struct { | ||||||
| 	opts                 *Options | 	opts                 *options.Options | ||||||
| 	proxy                *OAuthProxy | 	proxy                *OAuthProxy | ||||||
| 	signInRegexp         *regexp.Regexp | 	signInRegexp         *regexp.Regexp | ||||||
| 	signInProviderRegexp *regexp.Regexp | 	signInProviderRegexp *regexp.Regexp | ||||||
|  | @ -791,12 +793,12 @@ const signInSkipProvider = `>Found<` | ||||||
| func NewSignInPageTest(skipProvider bool) *SignInPageTest { | func NewSignInPageTest(skipProvider bool) *SignInPageTest { | ||||||
| 	var sipTest SignInPageTest | 	var sipTest SignInPageTest | ||||||
| 
 | 
 | ||||||
| 	sipTest.opts = NewOptions() | 	sipTest.opts = options.NewOptions() | ||||||
| 	sipTest.opts.Cookie.Secret = "adklsj2" | 	sipTest.opts.Cookie.Secret = "adklsj2" | ||||||
| 	sipTest.opts.ClientID = "lkdgj" | 	sipTest.opts.ClientID = "lkdgj" | ||||||
| 	sipTest.opts.ClientSecret = "sgiufgoi" | 	sipTest.opts.ClientSecret = "sgiufgoi" | ||||||
| 	sipTest.opts.SkipProviderButton = skipProvider | 	sipTest.opts.SkipProviderButton = skipProvider | ||||||
| 	sipTest.opts.Validate() | 	validation.Validate(sipTest.opts) | ||||||
| 
 | 
 | ||||||
| 	sipTest.proxy = NewOAuthProxy(sipTest.opts, func(email string) bool { | 	sipTest.proxy = NewOAuthProxy(sipTest.opts, func(email string) bool { | ||||||
| 		return true | 		return true | ||||||
|  | @ -876,7 +878,7 @@ func TestSignInPageSkipProviderDirect(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type ProcessCookieTest struct { | type ProcessCookieTest struct { | ||||||
| 	opts         *Options | 	opts         *options.Options | ||||||
| 	proxy        *OAuthProxy | 	proxy        *OAuthProxy | ||||||
| 	rw           *httptest.ResponseRecorder | 	rw           *httptest.ResponseRecorder | ||||||
| 	req          *http.Request | 	req          *http.Request | ||||||
|  | @ -887,12 +889,12 @@ type ProcessCookieTestOpts struct { | ||||||
| 	providerValidateCookieResponse bool | 	providerValidateCookieResponse bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type OptionsModifier func(*Options) | type OptionsModifier func(*options.Options) | ||||||
| 
 | 
 | ||||||
| func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest { | func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest { | ||||||
| 	var pcTest ProcessCookieTest | 	var pcTest ProcessCookieTest | ||||||
| 
 | 
 | ||||||
| 	pcTest.opts = NewOptions() | 	pcTest.opts = options.NewOptions() | ||||||
| 	for _, modifier := range modifiers { | 	for _, modifier := range modifiers { | ||||||
| 		modifier(pcTest.opts) | 		modifier(pcTest.opts) | ||||||
| 	} | 	} | ||||||
|  | @ -902,7 +904,7 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi | ||||||
| 	// First, set the CookieRefresh option so proxy.AesCipher is created,
 | 	// First, set the CookieRefresh option so proxy.AesCipher is created,
 | ||||||
| 	// needed to encrypt the access_token.
 | 	// needed to encrypt the access_token.
 | ||||||
| 	pcTest.opts.Cookie.Refresh = time.Hour | 	pcTest.opts.Cookie.Refresh = time.Hour | ||||||
| 	pcTest.opts.Validate() | 	validation.Validate(pcTest.opts) | ||||||
| 
 | 
 | ||||||
| 	pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool { | 	pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool { | ||||||
| 		return pcTest.validateUser | 		return pcTest.validateUser | ||||||
|  | @ -971,7 +973,7 @@ func TestProcessCookieNoCookieError(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestProcessCookieRefreshNotSet(t *testing.T) { | func TestProcessCookieRefreshNotSet(t *testing.T) { | ||||||
| 	pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) { | 	pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) { | ||||||
| 		opts.Cookie.Expire = time.Duration(23) * time.Hour | 		opts.Cookie.Expire = time.Duration(23) * time.Hour | ||||||
| 	}) | 	}) | ||||||
| 	reference := time.Now().Add(time.Duration(-2) * time.Hour) | 	reference := time.Now().Add(time.Duration(-2) * time.Hour) | ||||||
|  | @ -988,7 +990,7 @@ func TestProcessCookieRefreshNotSet(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestProcessCookieFailIfCookieExpired(t *testing.T) { | func TestProcessCookieFailIfCookieExpired(t *testing.T) { | ||||||
| 	pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) { | 	pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) { | ||||||
| 		opts.Cookie.Expire = time.Duration(24) * time.Hour | 		opts.Cookie.Expire = time.Duration(24) * time.Hour | ||||||
| 	}) | 	}) | ||||||
| 	reference := time.Now().Add(time.Duration(25) * time.Hour * -1) | 	reference := time.Now().Add(time.Duration(25) * time.Hour * -1) | ||||||
|  | @ -1003,7 +1005,7 @@ func TestProcessCookieFailIfCookieExpired(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) { | func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) { | ||||||
| 	pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) { | 	pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) { | ||||||
| 		opts.Cookie.Expire = time.Duration(24) * time.Hour | 		opts.Cookie.Expire = time.Duration(24) * time.Hour | ||||||
| 	}) | 	}) | ||||||
| 	reference := time.Now().Add(time.Duration(25) * time.Hour * -1) | 	reference := time.Now().Add(time.Duration(25) * time.Hour * -1) | ||||||
|  | @ -1073,7 +1075,7 @@ func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) { | func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) { | ||||||
| 	test := NewAuthOnlyEndpointTest(func(opts *Options) { | 	test := NewAuthOnlyEndpointTest(func(opts *options.Options) { | ||||||
| 		opts.Cookie.Expire = time.Duration(24) * time.Hour | 		opts.Cookie.Expire = time.Duration(24) * time.Hour | ||||||
| 	}) | 	}) | ||||||
| 	reference := time.Now().Add(time.Duration(25) * time.Hour * -1) | 	reference := time.Now().Add(time.Duration(25) * time.Hour * -1) | ||||||
|  | @ -1103,9 +1105,9 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) { | ||||||
| func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) { | func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) { | ||||||
| 	var pcTest ProcessCookieTest | 	var pcTest ProcessCookieTest | ||||||
| 
 | 
 | ||||||
| 	pcTest.opts = NewOptions() | 	pcTest.opts = options.NewOptions() | ||||||
| 	pcTest.opts.SetXAuthRequest = true | 	pcTest.opts.SetXAuthRequest = true | ||||||
| 	pcTest.opts.Validate() | 	validation.Validate(pcTest.opts) | ||||||
| 
 | 
 | ||||||
| 	pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool { | 	pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool { | ||||||
| 		return pcTest.validateUser | 		return pcTest.validateUser | ||||||
|  | @ -1133,10 +1135,10 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) { | ||||||
| func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) { | func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) { | ||||||
| 	var pcTest ProcessCookieTest | 	var pcTest ProcessCookieTest | ||||||
| 
 | 
 | ||||||
| 	pcTest.opts = NewOptions() | 	pcTest.opts = options.NewOptions() | ||||||
| 	pcTest.opts.SetXAuthRequest = true | 	pcTest.opts.SetXAuthRequest = true | ||||||
| 	pcTest.opts.SetBasicAuth = true | 	pcTest.opts.SetBasicAuth = true | ||||||
| 	pcTest.opts.Validate() | 	validation.Validate(pcTest.opts) | ||||||
| 
 | 
 | ||||||
| 	pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool { | 	pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool { | ||||||
| 		return pcTest.validateUser | 		return pcTest.validateUser | ||||||
|  | @ -1166,10 +1168,10 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) { | ||||||
| func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) { | func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) { | ||||||
| 	var pcTest ProcessCookieTest | 	var pcTest ProcessCookieTest | ||||||
| 
 | 
 | ||||||
| 	pcTest.opts = NewOptions() | 	pcTest.opts = options.NewOptions() | ||||||
| 	pcTest.opts.SetXAuthRequest = true | 	pcTest.opts.SetXAuthRequest = true | ||||||
| 	pcTest.opts.SetBasicAuth = false | 	pcTest.opts.SetBasicAuth = false | ||||||
| 	pcTest.opts.Validate() | 	validation.Validate(pcTest.opts) | ||||||
| 
 | 
 | ||||||
| 	pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool { | 	pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool { | ||||||
| 		return pcTest.validateUser | 		return pcTest.validateUser | ||||||
|  | @ -1202,16 +1204,16 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) { | ||||||
| 	})) | 	})) | ||||||
| 	defer upstream.Close() | 	defer upstream.Close() | ||||||
| 
 | 
 | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.Upstreams = append(opts.Upstreams, upstream.URL) | 	opts.Upstreams = append(opts.Upstreams, upstream.URL) | ||||||
| 	opts.ClientID = "aljsal" | 	opts.ClientID = "aljsal" | ||||||
| 	opts.ClientSecret = "jglkfsdgj" | 	opts.ClientSecret = "jglkfsdgj" | ||||||
| 	opts.Cookie.Secret = "dkfjgdls" | 	opts.Cookie.Secret = "dkfjgdls" | ||||||
| 	opts.SkipAuthPreflight = true | 	opts.SkipAuthPreflight = true | ||||||
| 	opts.Validate() | 	validation.Validate(opts) | ||||||
| 
 | 
 | ||||||
| 	upstreamURL, _ := url.Parse(upstream.URL) | 	upstreamURL, _ := url.Parse(upstream.URL) | ||||||
| 	opts.provider = NewTestProvider(upstreamURL, "") | 	opts.SetProvider(NewTestProvider(upstreamURL, "")) | ||||||
| 
 | 
 | ||||||
| 	proxy := NewOAuthProxy(opts, func(string) bool { return false }) | 	proxy := NewOAuthProxy(opts, func(string) bool { return false }) | ||||||
| 	rw := httptest.NewRecorder() | 	rw := httptest.NewRecorder() | ||||||
|  | @ -1242,7 +1244,7 @@ func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Req | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type SignatureTest struct { | type SignatureTest struct { | ||||||
| 	opts          *Options | 	opts          *options.Options | ||||||
| 	upstream      *httptest.Server | 	upstream      *httptest.Server | ||||||
| 	upstreamHost  string | 	upstreamHost  string | ||||||
| 	provider      *httptest.Server | 	provider      *httptest.Server | ||||||
|  | @ -1252,7 +1254,7 @@ type SignatureTest struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewSignatureTest() *SignatureTest { | func NewSignatureTest() *SignatureTest { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.Cookie.Secret = "cookie secret" | 	opts.Cookie.Secret = "cookie secret" | ||||||
| 	opts.ClientID = "client ID" | 	opts.ClientID = "client ID" | ||||||
| 	opts.ClientSecret = "client secret" | 	opts.ClientSecret = "client secret" | ||||||
|  | @ -1269,7 +1271,7 @@ func NewSignatureTest() *SignatureTest { | ||||||
| 	} | 	} | ||||||
| 	provider := httptest.NewServer(http.HandlerFunc(providerHandler)) | 	provider := httptest.NewServer(http.HandlerFunc(providerHandler)) | ||||||
| 	providerURL, _ := url.Parse(provider.URL) | 	providerURL, _ := url.Parse(provider.URL) | ||||||
| 	opts.provider = NewTestProvider(providerURL, "mbland@acm.org") | 	opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org")) | ||||||
| 
 | 
 | ||||||
| 	return &SignatureTest{ | 	return &SignatureTest{ | ||||||
| 		opts, | 		opts, | ||||||
|  | @ -1304,7 +1306,7 @@ func (fnc *fakeNetConn) Read(p []byte) (n int, err error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) { | func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) { | ||||||
| 	err := st.opts.Validate() | 	err := validation.Validate(st.opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		panic(err) | ||||||
| 	} | 	} | ||||||
|  | @ -1360,8 +1362,8 @@ func TestRequestSignaturePostRequest(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestGetRedirect(t *testing.T) { | func TestGetRedirect(t *testing.T) { | ||||||
| 	options := NewOptions() | 	options := options.NewOptions() | ||||||
| 	_ = options.Validate() | 	_ = validation.Validate(options) | ||||||
| 	require.NotEmpty(t, options.ProxyPrefix) | 	require.NotEmpty(t, options.ProxyPrefix) | ||||||
| 	proxy := NewOAuthProxy(options, func(s string) bool { return false }) | 	proxy := NewOAuthProxy(options, func(s string) bool { return false }) | ||||||
| 
 | 
 | ||||||
|  | @ -1393,17 +1395,17 @@ func TestGetRedirect(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type ajaxRequestTest struct { | type ajaxRequestTest struct { | ||||||
| 	opts  *Options | 	opts  *options.Options | ||||||
| 	proxy *OAuthProxy | 	proxy *OAuthProxy | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newAjaxRequestTest() *ajaxRequestTest { | func newAjaxRequestTest() *ajaxRequestTest { | ||||||
| 	test := &ajaxRequestTest{} | 	test := &ajaxRequestTest{} | ||||||
| 	test.opts = NewOptions() | 	test.opts = options.NewOptions() | ||||||
| 	test.opts.Cookie.Secret = "sdflsw" | 	test.opts.Cookie.Secret = "sdflsw" | ||||||
| 	test.opts.ClientID = "gkljfdl" | 	test.opts.ClientID = "gkljfdl" | ||||||
| 	test.opts.ClientSecret = "sdflkjs" | 	test.opts.ClientSecret = "sdflkjs" | ||||||
| 	test.opts.Validate() | 	validation.Validate(test.opts) | ||||||
| 	test.proxy = NewOAuthProxy(test.opts, func(email string) bool { | 	test.proxy = NewOAuthProxy(test.opts, func(email string) bool { | ||||||
| 		return true | 		return true | ||||||
| 	}) | 	}) | ||||||
|  | @ -1457,7 +1459,7 @@ func TestAjaxForbiddendRequest(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestClearSplitCookie(t *testing.T) { | func TestClearSplitCookie(t *testing.T) { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.Cookie.Name = "oauth2" | 	opts.Cookie.Name = "oauth2" | ||||||
| 	opts.Cookie.Domains = []string{"abc"} | 	opts.Cookie.Domains = []string{"abc"} | ||||||
| 	store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie) | 	store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie) | ||||||
|  | @ -1486,7 +1488,7 @@ func TestClearSplitCookie(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestClearSingleCookie(t *testing.T) { | func TestClearSingleCookie(t *testing.T) { | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.Cookie.Name = "oauth2" | 	opts.Cookie.Name = "oauth2" | ||||||
| 	opts.Cookie.Domains = []string{"abc"} | 	opts.Cookie.Domains = []string{"abc"} | ||||||
| 	store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie) | 	store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie) | ||||||
|  | @ -1542,12 +1544,12 @@ func TestGetJwtSession(t *testing.T) { | ||||||
| 	verifier := oidc.NewVerifier("https://issuer.example.com", keyset, | 	verifier := oidc.NewVerifier("https://issuer.example.com", keyset, | ||||||
| 		&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) | 		&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) | ||||||
| 
 | 
 | ||||||
| 	test := NewAuthOnlyEndpointTest(func(opts *Options) { | 	test := NewAuthOnlyEndpointTest(func(opts *options.Options) { | ||||||
| 		opts.PassAuthorization = true | 		opts.PassAuthorization = true | ||||||
| 		opts.SetAuthorization = true | 		opts.SetAuthorization = true | ||||||
| 		opts.SetXAuthRequest = true | 		opts.SetXAuthRequest = true | ||||||
| 		opts.SkipJwtBearerTokens = true | 		opts.SkipJwtBearerTokens = true | ||||||
| 		opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier) | 		opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier)) | ||||||
| 	}) | 	}) | ||||||
| 	tp, _ := test.proxy.provider.(*TestProvider) | 	tp, _ := test.proxy.provider.(*TestProvider) | ||||||
| 	tp.GroupValidator = func(s string) bool { | 	tp.GroupValidator = func(s string) bool { | ||||||
|  | @ -1666,10 +1668,10 @@ func Test_noCacheHeadersDoesNotExistsInResponseHeadersFromUpstream(t *testing.T) | ||||||
| 	})) | 	})) | ||||||
| 	t.Cleanup(upstream.Close) | 	t.Cleanup(upstream.Close) | ||||||
| 
 | 
 | ||||||
| 	opts := NewOptions() | 	opts := options.NewOptions() | ||||||
| 	opts.Upstreams = []string{upstream.URL} | 	opts.Upstreams = []string{upstream.URL} | ||||||
| 	opts.SkipAuthRegex = []string{".*"} | 	opts.SkipAuthRegex = []string{".*"} | ||||||
| 	_ = opts.Validate() | 	_ = validation.Validate(opts) | ||||||
| 	proxy := NewOAuthProxy(opts, func(email string) bool { | 	proxy := NewOAuthProxy(opts, func(email string) bool { | ||||||
| 		return true | 		return true | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | package ip | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net" | ||||||
|  | 	"net/http" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // 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) | ||||||
|  | } | ||||||
|  | @ -4,13 +4,13 @@ import "time" | ||||||
| 
 | 
 | ||||||
| // CookieOptions contains configuration options relating to Cookie configuration
 | // CookieOptions contains configuration options relating to Cookie configuration
 | ||||||
| type CookieOptions struct { | type CookieOptions struct { | ||||||
| 	Name     string        `flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME"` | 	Name     string        `flag:"cookie-name" cfg:"cookie_name"` | ||||||
| 	Secret   string        `flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET"` | 	Secret   string        `flag:"cookie-secret" cfg:"cookie_secret"` | ||||||
| 	Domains  []string      `flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN"` | 	Domains  []string      `flag:"cookie-domain" cfg:"cookie_domain"` | ||||||
| 	Path     string        `flag:"cookie-path" cfg:"cookie_path" env:"OAUTH2_PROXY_COOKIE_PATH"` | 	Path     string        `flag:"cookie-path" cfg:"cookie_path"` | ||||||
| 	Expire   time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE"` | 	Expire   time.Duration `flag:"cookie-expire" cfg:"cookie_expire"` | ||||||
| 	Refresh  time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"` | 	Refresh  time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh"` | ||||||
| 	Secure   bool          `flag:"cookie-secure" cfg:"cookie_secure" env:"OAUTH2_PROXY_COOKIE_SECURE"` | 	Secure   bool          `flag:"cookie-secure" cfg:"cookie_secure"` | ||||||
| 	HTTPOnly bool          `flag:"cookie-httponly" cfg:"cookie_httponly" env:"OAUTH2_PROXY_COOKIE_HTTPONLY"` | 	HTTPOnly bool          `flag:"cookie-httponly" cfg:"cookie_httponly"` | ||||||
| 	SameSite string        `flag:"cookie-samesite" cfg:"cookie_samesite" env:"OAUTH2_PROXY_COOKIE_SAMESITE"` | 	SameSite string        `flag:"cookie-samesite" cfg:"cookie_samesite"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -295,6 +295,11 @@ var _ = Describe("Load", func() { | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}), | 			}), | ||||||
|  | 			Entry("with an empty Options struct, should return default values", &testOptionsTableInput{ | ||||||
|  | 				flagSet:        NewFlagSet, | ||||||
|  | 				input:          &Options{}, | ||||||
|  | 				expectedOutput: NewOptions(), | ||||||
|  | 			}), | ||||||
| 		) | 		) | ||||||
| 	}) | 	}) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,340 @@ | ||||||
|  | package options | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto" | ||||||
|  | 	"net/url" | ||||||
|  | 	"regexp" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	oidc "github.com/coreos/go-oidc" | ||||||
|  | 	ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip" | ||||||
|  | 	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" | ||||||
|  | 	"github.com/spf13/pflag" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // 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"` | ||||||
|  | 	PingPath           string `flag:"ping-path" cfg:"ping_path"` | ||||||
|  | 	ProxyWebSockets    bool   `flag:"proxy-websockets" cfg:"proxy_websockets"` | ||||||
|  | 	HTTPAddress        string `flag:"http-address" cfg:"http_address"` | ||||||
|  | 	HTTPSAddress       string `flag:"https-address" cfg:"https_address"` | ||||||
|  | 	ReverseProxy       bool   `flag:"reverse-proxy" cfg:"reverse_proxy"` | ||||||
|  | 	RealClientIPHeader string `flag:"real-client-ip-header" cfg:"real_client_ip_header"` | ||||||
|  | 	ForceHTTPS         bool   `flag:"force-https" cfg:"force_https"` | ||||||
|  | 	RawRedirectURL     string `flag:"redirect-url" cfg:"redirect_url"` | ||||||
|  | 	ClientID           string `flag:"client-id" cfg:"client_id"` | ||||||
|  | 	ClientSecret       string `flag:"client-secret" cfg:"client_secret"` | ||||||
|  | 	ClientSecretFile   string `flag:"client-secret-file" cfg:"client_secret_file"` | ||||||
|  | 	TLSCertFile        string `flag:"tls-cert-file" cfg:"tls_cert_file"` | ||||||
|  | 	TLSKeyFile         string `flag:"tls-key-file" cfg:"tls_key_file"` | ||||||
|  | 
 | ||||||
|  | 	AuthenticatedEmailsFile  string   `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"` | ||||||
|  | 	KeycloakGroup            string   `flag:"keycloak-group" cfg:"keycloak_group"` | ||||||
|  | 	AzureTenant              string   `flag:"azure-tenant" cfg:"azure_tenant"` | ||||||
|  | 	BitbucketTeam            string   `flag:"bitbucket-team" cfg:"bitbucket_team"` | ||||||
|  | 	BitbucketRepository      string   `flag:"bitbucket-repository" cfg:"bitbucket_repository"` | ||||||
|  | 	EmailDomains             []string `flag:"email-domain" cfg:"email_domains"` | ||||||
|  | 	WhitelistDomains         []string `flag:"whitelist-domain" cfg:"whitelist_domains"` | ||||||
|  | 	GitHubOrg                string   `flag:"github-org" cfg:"github_org"` | ||||||
|  | 	GitHubTeam               string   `flag:"github-team" cfg:"github_team"` | ||||||
|  | 	GitHubRepo               string   `flag:"github-repo" cfg:"github_repo"` | ||||||
|  | 	GitHubToken              string   `flag:"github-token" cfg:"github_token"` | ||||||
|  | 	GitLabGroup              string   `flag:"gitlab-group" cfg:"gitlab_group"` | ||||||
|  | 	GoogleGroups             []string `flag:"google-group" cfg:"google_group"` | ||||||
|  | 	GoogleAdminEmail         string   `flag:"google-admin-email" cfg:"google_admin_email"` | ||||||
|  | 	GoogleServiceAccountJSON string   `flag:"google-service-account-json" cfg:"google_service_account_json"` | ||||||
|  | 	HtpasswdFile             string   `flag:"htpasswd-file" cfg:"htpasswd_file"` | ||||||
|  | 	DisplayHtpasswdForm      bool     `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"` | ||||||
|  | 	CustomTemplatesDir       string   `flag:"custom-templates-dir" cfg:"custom_templates_dir"` | ||||||
|  | 	Banner                   string   `flag:"banner" cfg:"banner"` | ||||||
|  | 	Footer                   string   `flag:"footer" cfg:"footer"` | ||||||
|  | 
 | ||||||
|  | 	Cookie  CookieOptions  `cfg:",squash"` | ||||||
|  | 	Session SessionOptions `cfg:",squash"` | ||||||
|  | 
 | ||||||
|  | 	Upstreams                     []string      `flag:"upstream" cfg:"upstreams"` | ||||||
|  | 	SkipAuthRegex                 []string      `flag:"skip-auth-regex" cfg:"skip_auth_regex"` | ||||||
|  | 	SkipJwtBearerTokens           bool          `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"` | ||||||
|  | 	ExtraJwtIssuers               []string      `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"` | ||||||
|  | 	PassBasicAuth                 bool          `flag:"pass-basic-auth" cfg:"pass_basic_auth"` | ||||||
|  | 	SetBasicAuth                  bool          `flag:"set-basic-auth" cfg:"set_basic_auth"` | ||||||
|  | 	PreferEmailToUser             bool          `flag:"prefer-email-to-user" cfg:"prefer_email_to_user"` | ||||||
|  | 	BasicAuthPassword             string        `flag:"basic-auth-password" cfg:"basic_auth_password"` | ||||||
|  | 	PassAccessToken               bool          `flag:"pass-access-token" cfg:"pass_access_token"` | ||||||
|  | 	PassHostHeader                bool          `flag:"pass-host-header" cfg:"pass_host_header"` | ||||||
|  | 	SkipProviderButton            bool          `flag:"skip-provider-button" cfg:"skip_provider_button"` | ||||||
|  | 	PassUserHeaders               bool          `flag:"pass-user-headers" cfg:"pass_user_headers"` | ||||||
|  | 	SSLInsecureSkipVerify         bool          `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"` | ||||||
|  | 	SSLUpstreamInsecureSkipVerify bool          `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify"` | ||||||
|  | 	SetXAuthRequest               bool          `flag:"set-xauthrequest" cfg:"set_xauthrequest"` | ||||||
|  | 	SetAuthorization              bool          `flag:"set-authorization-header" cfg:"set_authorization_header"` | ||||||
|  | 	PassAuthorization             bool          `flag:"pass-authorization-header" cfg:"pass_authorization_header"` | ||||||
|  | 	SkipAuthPreflight             bool          `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"` | ||||||
|  | 	FlushInterval                 time.Duration `flag:"flush-interval" cfg:"flush_interval"` | ||||||
|  | 
 | ||||||
|  | 	// These options allow for other providers besides Google, with
 | ||||||
|  | 	// potential overrides.
 | ||||||
|  | 	ProviderType                       string `flag:"provider" cfg:"provider"` | ||||||
|  | 	ProviderName                       string `flag:"provider-display-name" cfg:"provider_display_name"` | ||||||
|  | 	OIDCIssuerURL                      string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url"` | ||||||
|  | 	InsecureOIDCAllowUnverifiedEmail   bool   `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email"` | ||||||
|  | 	InsecureOIDCSkipIssuerVerification bool   `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification"` | ||||||
|  | 	SkipOIDCDiscovery                  bool   `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery"` | ||||||
|  | 	OIDCJwksURL                        string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url"` | ||||||
|  | 	LoginURL                           string `flag:"login-url" cfg:"login_url"` | ||||||
|  | 	RedeemURL                          string `flag:"redeem-url" cfg:"redeem_url"` | ||||||
|  | 	ProfileURL                         string `flag:"profile-url" cfg:"profile_url"` | ||||||
|  | 	ProtectedResource                  string `flag:"resource" cfg:"resource"` | ||||||
|  | 	ValidateURL                        string `flag:"validate-url" cfg:"validate_url"` | ||||||
|  | 	Scope                              string `flag:"scope" cfg:"scope"` | ||||||
|  | 	Prompt                             string `flag:"prompt" cfg:"prompt"` | ||||||
|  | 	ApprovalPrompt                     string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
 | ||||||
|  | 	UserIDClaim                        string `flag:"user-id-claim" cfg:"user_id_claim"` | ||||||
|  | 
 | ||||||
|  | 	// Configuration values for logging
 | ||||||
|  | 	LoggingFilename       string `flag:"logging-filename" cfg:"logging_filename"` | ||||||
|  | 	LoggingMaxSize        int    `flag:"logging-max-size" cfg:"logging_max_size"` | ||||||
|  | 	LoggingMaxAge         int    `flag:"logging-max-age" cfg:"logging_max_age"` | ||||||
|  | 	LoggingMaxBackups     int    `flag:"logging-max-backups" cfg:"logging_max_backups"` | ||||||
|  | 	LoggingLocalTime      bool   `flag:"logging-local-time" cfg:"logging_local_time"` | ||||||
|  | 	LoggingCompress       bool   `flag:"logging-compress" cfg:"logging_compress"` | ||||||
|  | 	StandardLogging       bool   `flag:"standard-logging" cfg:"standard_logging"` | ||||||
|  | 	StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format"` | ||||||
|  | 	RequestLogging        bool   `flag:"request-logging" cfg:"request_logging"` | ||||||
|  | 	RequestLoggingFormat  string `flag:"request-logging-format" cfg:"request_logging_format"` | ||||||
|  | 	ExcludeLoggingPaths   string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths"` | ||||||
|  | 	SilencePingLogging    bool   `flag:"silence-ping-logging" cfg:"silence_ping_logging"` | ||||||
|  | 	AuthLogging           bool   `flag:"auth-logging" cfg:"auth_logging"` | ||||||
|  | 	AuthLoggingFormat     string `flag:"auth-logging-format" cfg:"auth_logging_format"` | ||||||
|  | 	SignatureKey          string `flag:"signature-key" cfg:"signature_key"` | ||||||
|  | 	AcrValues             string `flag:"acr-values" cfg:"acr_values"` | ||||||
|  | 	JWTKey                string `flag:"jwt-key" cfg:"jwt_key"` | ||||||
|  | 	JWTKeyFile            string `flag:"jwt-key-file" cfg:"jwt_key_file"` | ||||||
|  | 	PubJWKURL             string `flag:"pubjwk-url" cfg:"pubjwk_url"` | ||||||
|  | 	GCPHealthChecks       bool   `flag:"gcp-healthchecks" cfg:"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 ipapi.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() ipapi.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 ipapi.RealClientIPParser) { o.realClientIPParser = s } | ||||||
|  | 
 | ||||||
|  | // NewOptions constructs a new Options with defaulted values
 | ||||||
|  | func NewOptions() *Options { | ||||||
|  | 	return &Options{ | ||||||
|  | 		ProxyPrefix:         "/oauth2", | ||||||
|  | 		ProviderType:        "google", | ||||||
|  | 		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), | ||||||
|  | 			Path:     "/", | ||||||
|  | 		}, | ||||||
|  | 		Session: SessionOptions{ | ||||||
|  | 			Type: "cookie", | ||||||
|  | 		}, | ||||||
|  | 		AzureTenant:                      "common", | ||||||
|  | 		SetXAuthRequest:                  false, | ||||||
|  | 		SkipAuthPreflight:                false, | ||||||
|  | 		FlushInterval:                    time.Duration(1) * time.Second, | ||||||
|  | 		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, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewFlagSet creates a new FlagSet with all of the flags required by Options
 | ||||||
|  | func NewFlagSet() *pflag.FlagSet { | ||||||
|  | 	flagSet := pflag.NewFlagSet("oauth2-proxy", pflag.ExitOnError) | ||||||
|  | 
 | ||||||
|  | 	flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients") | ||||||
|  | 	flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients") | ||||||
|  | 	flagSet.Bool("reverse-proxy", false, "are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted") | ||||||
|  | 	flagSet.String("real-client-ip-header", "X-Real-IP", "Header used to determine the real IP of the client (one of: X-Forwarded-For, X-Real-IP, or X-ProxyUser-IP)") | ||||||
|  | 	flagSet.Bool("force-https", false, "force HTTPS redirect for HTTP requests") | ||||||
|  | 	flagSet.String("tls-cert-file", "", "path to certificate file") | ||||||
|  | 	flagSet.String("tls-key-file", "", "path to private key file") | ||||||
|  | 	flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"") | ||||||
|  | 	flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)") | ||||||
|  | 	flagSet.StringSlice("upstream", []string{}, "the http url(s) of the upstream endpoint, file:// paths for static files or static://<status_code> for static response. Routing is based on the path") | ||||||
|  | 	flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream") | ||||||
|  | 	flagSet.Bool("set-basic-auth", false, "set HTTP Basic Auth information in response (useful in Nginx auth_request mode)") | ||||||
|  | 	flagSet.Bool("prefer-email-to-user", false, "Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. Used in conjunction with -pass-basic-auth and -pass-user-headers") | ||||||
|  | 	flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream") | ||||||
|  | 	flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header") | ||||||
|  | 	flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header") | ||||||
|  | 	flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream") | ||||||
|  | 	flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream") | ||||||
|  | 	flagSet.Bool("set-authorization-header", false, "set Authorization response headers (useful in Nginx auth_request mode)") | ||||||
|  | 	flagSet.StringSlice("skip-auth-regex", []string{}, "bypass authentication for requests path's that match (may be given multiple times)") | ||||||
|  | 	flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start") | ||||||
|  | 	flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests") | ||||||
|  | 	flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers") | ||||||
|  | 	flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams") | ||||||
|  | 	flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses") | ||||||
|  | 	flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)") | ||||||
|  | 	flagSet.StringSlice("extra-jwt-issuers", []string{}, "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)") | ||||||
|  | 
 | ||||||
|  | 	flagSet.StringSlice("email-domain", []string{}, "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") | ||||||
|  | 	flagSet.StringSlice("whitelist-domain", []string{}, "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)") | ||||||
|  | 	flagSet.String("keycloak-group", "", "restrict login to members of this group.") | ||||||
|  | 	flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.") | ||||||
|  | 	flagSet.String("bitbucket-team", "", "restrict logins to members of this team") | ||||||
|  | 	flagSet.String("bitbucket-repository", "", "restrict logins to user with access to this repository") | ||||||
|  | 	flagSet.String("github-org", "", "restrict logins to members of this organisation") | ||||||
|  | 	flagSet.String("github-team", "", "restrict logins to members of this team") | ||||||
|  | 	flagSet.String("github-repo", "", "restrict logins to collaborators of this repository") | ||||||
|  | 	flagSet.String("github-token", "", "the token to use when verifying repository collaborators (must have push access to the repository)") | ||||||
|  | 	flagSet.String("gitlab-group", "", "restrict logins to members of this group") | ||||||
|  | 	flagSet.StringSlice("google-group", []string{}, "restrict logins to members of this google group (may be given multiple times).") | ||||||
|  | 	flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls") | ||||||
|  | 	flagSet.String("google-service-account-json", "", "the path to the service account json credentials") | ||||||
|  | 	flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"") | ||||||
|  | 	flagSet.String("client-secret", "", "the OAuth Client Secret") | ||||||
|  | 	flagSet.String("client-secret-file", "", "the file with OAuth Client Secret") | ||||||
|  | 	flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)") | ||||||
|  | 	flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption or \"htpasswd -B\" for bcrypt encryption") | ||||||
|  | 	flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided") | ||||||
|  | 	flagSet.String("custom-templates-dir", "", "path to custom html templates") | ||||||
|  | 	flagSet.String("banner", "", "custom banner string. Use \"-\" to disable default banner.") | ||||||
|  | 	flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.") | ||||||
|  | 	flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)") | ||||||
|  | 	flagSet.String("ping-path", "/ping", "the ping endpoint that can be used for basic health checks") | ||||||
|  | 	flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying") | ||||||
|  | 
 | ||||||
|  | 	flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates") | ||||||
|  | 	flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)") | ||||||
|  | 	flagSet.StringSlice("cookie-domain", []string{}, "Optional cookie domains to force cookies to (ie: `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match).") | ||||||
|  | 	flagSet.String("cookie-path", "/", "an optional cookie path to force cookies to (ie: /poc/)*") | ||||||
|  | 	flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie") | ||||||
|  | 	flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable") | ||||||
|  | 	flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag") | ||||||
|  | 	flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") | ||||||
|  | 	flagSet.String("cookie-samesite", "", "set SameSite cookie attribute (ie: \"lax\", \"strict\", \"none\", or \"\"). ") | ||||||
|  | 
 | ||||||
|  | 	flagSet.String("session-store-type", "cookie", "the session storage provider to use") | ||||||
|  | 	flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])") | ||||||
|  | 	flagSet.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature") | ||||||
|  | 	flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel") | ||||||
|  | 	flagSet.String("redis-ca-path", "", "Redis custom CA path") | ||||||
|  | 	flagSet.Bool("redis-insecure-skip-tls-verify", false, "Use insecure TLS connection to redis") | ||||||
|  | 	flagSet.StringSlice("redis-sentinel-connection-urls", []string{}, "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel") | ||||||
|  | 	flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature") | ||||||
|  | 	flagSet.StringSlice("redis-cluster-connection-urls", []string{}, "List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-cluster") | ||||||
|  | 
 | ||||||
|  | 	flagSet.String("logging-filename", "", "File to log requests to, empty for stdout") | ||||||
|  | 	flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation") | ||||||
|  | 	flagSet.Int("logging-max-age", 7, "Maximum number of days to retain old log files") | ||||||
|  | 	flagSet.Int("logging-max-backups", 0, "Maximum number of old log files to retain; 0 to disable") | ||||||
|  | 	flagSet.Bool("logging-local-time", true, "If the time in log files and backup filenames are local or UTC time") | ||||||
|  | 	flagSet.Bool("logging-compress", false, "Should rotated log files be compressed using gzip") | ||||||
|  | 
 | ||||||
|  | 	flagSet.Bool("standard-logging", true, "Log standard runtime information") | ||||||
|  | 	flagSet.String("standard-logging-format", logger.DefaultStandardLoggingFormat, "Template for standard log lines") | ||||||
|  | 
 | ||||||
|  | 	flagSet.Bool("request-logging", true, "Log HTTP requests") | ||||||
|  | 	flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines") | ||||||
|  | 	flagSet.String("exclude-logging-paths", "", "Exclude logging requests to paths (eg: '/path1,/path2,/path3')") | ||||||
|  | 	flagSet.Bool("silence-ping-logging", false, "Disable logging of requests to ping endpoint") | ||||||
|  | 
 | ||||||
|  | 	flagSet.Bool("auth-logging", true, "Log authentication attempts") | ||||||
|  | 	flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines") | ||||||
|  | 
 | ||||||
|  | 	flagSet.String("provider", "google", "OAuth provider") | ||||||
|  | 	flagSet.String("provider-display-name", "", "Provider display name") | ||||||
|  | 	flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)") | ||||||
|  | 	flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified") | ||||||
|  | 	flagSet.Bool("insecure-oidc-skip-issuer-verification", false, "Do not verify if issuer matches OIDC discovery URL") | ||||||
|  | 	flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints") | ||||||
|  | 	flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)") | ||||||
|  | 	flagSet.String("login-url", "", "Authentication endpoint") | ||||||
|  | 	flagSet.String("redeem-url", "", "Token redemption endpoint") | ||||||
|  | 	flagSet.String("profile-url", "", "Profile access endpoint") | ||||||
|  | 	flagSet.String("resource", "", "The resource that is protected (Azure AD only)") | ||||||
|  | 	flagSet.String("validate-url", "", "Access token validation endpoint") | ||||||
|  | 	flagSet.String("scope", "", "OAuth scope specification") | ||||||
|  | 	flagSet.String("prompt", "", "OIDC prompt") | ||||||
|  | 	flagSet.String("approval-prompt", "force", "OAuth approval_prompt") | ||||||
|  | 
 | ||||||
|  | 	flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)") | ||||||
|  | 	flagSet.String("acr-values", "", "acr values string:  optional") | ||||||
|  | 	flagSet.String("jwt-key", "", "private key in PEM format used to sign JWT, so that you can say something like -jwt-key=\"${OAUTH2_PROXY_JWT_KEY}\": required by login.gov") | ||||||
|  | 	flagSet.String("jwt-key-file", "", "path to the private key file in PEM format used to sign the JWT so that you can say something like -jwt-key-file=/etc/ssl/private/jwt_signing_key.pem: required by login.gov") | ||||||
|  | 	flagSet.String("pubjwk-url", "", "JWK pubkey access endpoint: required by login.gov") | ||||||
|  | 	flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints") | ||||||
|  | 
 | ||||||
|  | 	flagSet.String("user-id-claim", "email", "which claim contains the user ID") | ||||||
|  | 
 | ||||||
|  | 	return flagSet | ||||||
|  | } | ||||||
|  | @ -4,7 +4,7 @@ import "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" | ||||||
| 
 | 
 | ||||||
| // SessionOptions contains configuration options for the SessionStore providers.
 | // SessionOptions contains configuration options for the SessionStore providers.
 | ||||||
| type SessionOptions struct { | type SessionOptions struct { | ||||||
| 	Type   string             `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"` | 	Type   string             `flag:"session-store-type" cfg:"session_store_type"` | ||||||
| 	Cipher *encryption.Cipher `cfg:",internal"` | 	Cipher *encryption.Cipher `cfg:",internal"` | ||||||
| 	Redis  RedisStoreOptions  `cfg:",squash"` | 	Redis  RedisStoreOptions  `cfg:",squash"` | ||||||
| } | } | ||||||
|  | @ -19,12 +19,12 @@ var RedisSessionStoreType = "redis" | ||||||
| 
 | 
 | ||||||
| // RedisStoreOptions contains configuration options for the RedisSessionStore.
 | // RedisStoreOptions contains configuration options for the RedisSessionStore.
 | ||||||
| type RedisStoreOptions struct { | type RedisStoreOptions struct { | ||||||
| 	ConnectionURL          string   `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"` | 	ConnectionURL          string   `flag:"redis-connection-url" cfg:"redis_connection_url"` | ||||||
| 	UseSentinel            bool     `flag:"redis-use-sentinel" cfg:"redis_use_sentinel" env:"OAUTH2_PROXY_REDIS_USE_SENTINEL"` | 	UseSentinel            bool     `flag:"redis-use-sentinel" cfg:"redis_use_sentinel"` | ||||||
| 	SentinelMasterName     string   `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name" env:"OAUTH2_PROXY_REDIS_SENTINEL_MASTER_NAME"` | 	SentinelMasterName     string   `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name"` | ||||||
| 	SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls" env:"OAUTH2_PROXY_REDIS_SENTINEL_CONNECTION_URLS"` | 	SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls"` | ||||||
| 	UseCluster             bool     `flag:"redis-use-cluster" cfg:"redis_use_cluster" env:"OAUTH2_PROXY_REDIS_USE_CLUSTER"` | 	UseCluster             bool     `flag:"redis-use-cluster" cfg:"redis_use_cluster"` | ||||||
| 	ClusterConnectionURLs  []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls" env:"OAUTH2_PROXY_REDIS_CLUSTER_CONNECTION_URLS"` | 	ClusterConnectionURLs  []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls"` | ||||||
| 	CAPath                 string   `flag:"redis-ca-path" cfg:"redis_ca_path" env:"OAUTH2_PROXY_REDIS_CA_PATH"` | 	CAPath                 string   `flag:"redis-ca-path" cfg:"redis_ca_path"` | ||||||
| 	InsecureSkipTLSVerify  bool     `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify" env:"OAUTH2_PROXY_REDIS_INSECURE_SKIP_TLS_VERIFY"` | 	InsecureSkipTLSVerify  bool     `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package main | package ip | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | @ -6,14 +6,10 @@ import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | 	ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type realClientIPParser interface { | func GetRealClientIPParser(headerKey string) (ipapi.RealClientIPParser, error) { | ||||||
| 	GetRealClientIP(http.Header) (net.IP, error) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func getRealClientIPParser(headerKey string) (realClientIPParser, error) { |  | ||||||
| 	headerKey = http.CanonicalHeaderKey(headerKey) | 	headerKey = http.CanonicalHeaderKey(headerKey) | ||||||
| 
 | 
 | ||||||
| 	switch headerKey { | 	switch headerKey { | ||||||
|  | @ -73,13 +69,11 @@ func getRemoteIP(req *http.Request) (net.IP, error) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // getClientString obtains the human readable string of the remote IP and optionally the real client IP if available
 | // 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) { | func GetClientString(p ipapi.RealClientIPParser, req *http.Request, full bool) (s string) { | ||||||
| 	var realClientIPStr string | 	var realClientIPStr string | ||||||
| 	if p != nil { | 	if p != nil { | ||||||
| 		if realClientIP, err := p.GetRealClientIP(req.Header); err != nil { | 		if realClientIP, err := p.GetRealClientIP(req.Header); err == nil && realClientIP != nil { | ||||||
| 			logger.Printf("Unable to get real client IP: %v", err) |  | ||||||
| 		} else if realClientIP != nil { |  | ||||||
| 			realClientIPStr = realClientIP.String() | 			realClientIPStr = realClientIP.String() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -87,9 +81,6 @@ func getClientString(p realClientIPParser, req *http.Request, full bool) (s stri | ||||||
| 	var remoteIPStr string | 	var remoteIPStr string | ||||||
| 	if remoteIP, err := getRemoteIP(req); err == nil { | 	if remoteIP, err := getRemoteIP(req); err == nil { | ||||||
| 		remoteIPStr = remoteIP.String() | 		remoteIPStr = remoteIP.String() | ||||||
| 	} else { |  | ||||||
| 		// Should not happen, if it does, likely a bug.
 |  | ||||||
| 		logger.Printf("Unable to get remote IP(?!?!): %v", err) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !full && realClientIPStr != "" { | 	if !full && realClientIPStr != "" { | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package main | package ip | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"net" | 	"net" | ||||||
|  | @ -6,6 +6,7 @@ import ( | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -26,7 +27,7 @@ func TestGetRealClientIPParser(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, test := range tests { | 	for _, test := range tests { | ||||||
| 		p, err := getRealClientIPParser(test.header) | 		p, err := GetRealClientIPParser(test.header) | ||||||
| 
 | 
 | ||||||
| 		if test.errString == "" { | 		if test.errString == "" { | ||||||
| 			assert.Nil(t, err) | 			assert.Nil(t, err) | ||||||
|  | @ -144,7 +145,7 @@ func TestGetClientString(t *testing.T) { | ||||||
| 	p := &xForwardedForClientIPParser{header: http.CanonicalHeaderKey("X-Forwarded-For")} | 	p := &xForwardedForClientIPParser{header: http.CanonicalHeaderKey("X-Forwarded-For")} | ||||||
| 
 | 
 | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		parser             realClientIPParser | 		parser             ipapi.RealClientIPParser | ||||||
| 		remoteAddr         string | 		remoteAddr         string | ||||||
| 		headerValue        string | 		headerValue        string | ||||||
| 		expectedClient     string | 		expectedClient     string | ||||||
|  | @ -167,10 +168,10 @@ func TestGetClientString(t *testing.T) { | ||||||
| 			RemoteAddr: test.remoteAddr, | 			RemoteAddr: test.remoteAddr, | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		client := getClientString(test.parser, req, false) | 		client := GetClientString(test.parser, req, false) | ||||||
| 		assert.Equal(t, test.expectedClient, client) | 		assert.Equal(t, test.expectedClient, client) | ||||||
| 
 | 
 | ||||||
| 		clientFull := getClientString(test.parser, req, true) | 		clientFull := GetClientString(test.parser, req, true) | ||||||
| 		assert.Equal(t, test.expectedClientFull, clientFull) | 		assert.Equal(t, test.expectedClientFull, clientFull) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package main | package validation | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | @ -14,12 +14,12 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	oidc "github.com/coreos/go-oidc" | 	"github.com/coreos/go-oidc" | ||||||
| 	"github.com/dgrijalva/jwt-go" | 	"github.com/dgrijalva/jwt-go" | ||||||
| 	"github.com/mbland/hmacauth" | 	"github.com/mbland/hmacauth" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||||
| 	sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" |  | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/ip" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions" | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions" | ||||||
|  | @ -27,197 +27,9 @@ import ( | ||||||
| 	"gopkg.in/natefinch/lumberjack.v2" | 	"gopkg.in/natefinch/lumberjack.v2" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // 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"` |  | ||||||
| 	RedirectURL        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  options.CookieOptions  `cfg:",squash"` |  | ||||||
| 	Session options.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.
 |  | ||||||
| 	Provider                           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 realClientIPParser |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SignatureData holds hmacauth signature hash and key
 |  | ||||||
| type SignatureData struct { |  | ||||||
| 	hash crypto.Hash |  | ||||||
| 	key  string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 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", |  | ||||||
| 		ForceHTTPS:          false, |  | ||||||
| 		DisplayHtpasswdForm: true, |  | ||||||
| 		Cookie: options.CookieOptions{ |  | ||||||
| 			Name:     "_oauth2_proxy", |  | ||||||
| 			Secure:   true, |  | ||||||
| 			HTTPOnly: true, |  | ||||||
| 			Expire:   time.Duration(168) * time.Hour, |  | ||||||
| 			Refresh:  time.Duration(0), |  | ||||||
| 		}, |  | ||||||
| 		Session: options.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, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 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 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Validate checks that required options are set and validates those that they
 | // Validate checks that required options are set and validates those that they
 | ||||||
| // are of the correct format
 | // are of the correct format
 | ||||||
| func (o *Options) Validate() error { | func Validate(o *options.Options) error { | ||||||
| 	if o.SSLInsecureSkipVerify { | 	if o.SSLInsecureSkipVerify { | ||||||
| 		// TODO: Accept a certificate bundle.
 | 		// TODO: Accept a certificate bundle.
 | ||||||
| 		insecureTransport := &http.Transport{ | 		insecureTransport := &http.Transport{ | ||||||
|  | @ -234,7 +46,7 @@ func (o *Options) Validate() error { | ||||||
| 		msgs = append(msgs, "missing setting: client-id") | 		msgs = append(msgs, "missing setting: client-id") | ||||||
| 	} | 	} | ||||||
| 	// login.gov uses a signed JWT to authenticate, not a client-secret
 | 	// login.gov uses a signed JWT to authenticate, not a client-secret
 | ||||||
| 	if o.Provider != "login.gov" { | 	if o.ProviderType != "login.gov" { | ||||||
| 		if o.ClientSecret == "" && o.ClientSecretFile == "" { | 		if o.ClientSecret == "" && o.ClientSecretFile == "" { | ||||||
| 			msgs = append(msgs, "missing setting: client-secret or client-secret-file") | 			msgs = append(msgs, "missing setting: client-secret or client-secret-file") | ||||||
| 		} | 		} | ||||||
|  | @ -311,20 +123,20 @@ func (o *Options) Validate() error { | ||||||
| 				msgs = append(msgs, "missing setting: oidc-jwks-url") | 				msgs = append(msgs, "missing setting: oidc-jwks-url") | ||||||
| 			} | 			} | ||||||
| 			keySet := oidc.NewRemoteKeySet(ctx, o.OIDCJwksURL) | 			keySet := oidc.NewRemoteKeySet(ctx, o.OIDCJwksURL) | ||||||
| 			o.oidcVerifier = oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{ | 			o.SetOIDCVerifier(oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{ | ||||||
| 				ClientID:        o.ClientID, | 				ClientID:        o.ClientID, | ||||||
| 				SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification, | 				SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification, | ||||||
| 			}) | 			})) | ||||||
| 		} else { | 		} else { | ||||||
| 			// Configure discoverable provider data.
 | 			// Configure discoverable provider data.
 | ||||||
| 			provider, err := oidc.NewProvider(ctx, o.OIDCIssuerURL) | 			provider, err := oidc.NewProvider(ctx, o.OIDCIssuerURL) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 			o.oidcVerifier = provider.Verifier(&oidc.Config{ | 			o.SetOIDCVerifier(provider.Verifier(&oidc.Config{ | ||||||
| 				ClientID:        o.ClientID, | 				ClientID:        o.ClientID, | ||||||
| 				SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification, | 				SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification, | ||||||
| 			}) | 			})) | ||||||
| 
 | 
 | ||||||
| 			o.LoginURL = provider.Endpoint().AuthURL | 			o.LoginURL = provider.Endpoint().AuthURL | ||||||
| 			o.RedeemURL = provider.Endpoint().TokenURL | 			o.RedeemURL = provider.Endpoint().TokenURL | ||||||
|  | @ -340,8 +152,8 @@ func (o *Options) Validate() error { | ||||||
| 
 | 
 | ||||||
| 	if o.SkipJwtBearerTokens { | 	if o.SkipJwtBearerTokens { | ||||||
| 		// If we are using an oidc provider, go ahead and add that provider to the list
 | 		// If we are using an oidc provider, go ahead and add that provider to the list
 | ||||||
| 		if o.oidcVerifier != nil { | 		if o.GetOIDCVerifier() != nil { | ||||||
| 			o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, o.oidcVerifier) | 			o.SetJWTBearerVerifiers(append(o.GetJWTBearerVerifiers(), o.GetOIDCVerifier())) | ||||||
| 		} | 		} | ||||||
| 		// Configure extra issuers
 | 		// Configure extra issuers
 | ||||||
| 		if len(o.ExtraJwtIssuers) > 0 { | 		if len(o.ExtraJwtIssuers) > 0 { | ||||||
|  | @ -352,12 +164,14 @@ func (o *Options) Validate() error { | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err)) | 					msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err)) | ||||||
| 				} | 				} | ||||||
| 				o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, verifier) | 				o.SetJWTBearerVerifiers(append(o.GetJWTBearerVerifiers(), verifier)) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs) | 	var redirectURL *url.URL | ||||||
|  | 	redirectURL, msgs = parseURL(o.RawRedirectURL, "redirect", msgs) | ||||||
|  | 	o.SetRedirectURL(redirectURL) | ||||||
| 
 | 
 | ||||||
| 	for _, u := range o.Upstreams { | 	for _, u := range o.Upstreams { | ||||||
| 		upstreamURL, err := url.Parse(u) | 		upstreamURL, err := url.Parse(u) | ||||||
|  | @ -367,7 +181,7 @@ func (o *Options) Validate() error { | ||||||
| 			if upstreamURL.Path == "" { | 			if upstreamURL.Path == "" { | ||||||
| 				upstreamURL.Path = "/" | 				upstreamURL.Path = "/" | ||||||
| 			} | 			} | ||||||
| 			o.proxyURLs = append(o.proxyURLs, upstreamURL) | 			o.SetProxyURLs(append(o.GetProxyURLs(), upstreamURL)) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -377,7 +191,7 @@ func (o *Options) Validate() error { | ||||||
| 			msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err)) | 			msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err)) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		o.compiledRegex = append(o.compiledRegex, compiledRegex) | 		o.SetCompiledRegex(append(o.GetCompiledRegex(), compiledRegex)) | ||||||
| 	} | 	} | ||||||
| 	msgs = parseProviderInfo(o, msgs) | 	msgs = parseProviderInfo(o, msgs) | ||||||
| 
 | 
 | ||||||
|  | @ -418,7 +232,7 @@ func (o *Options) Validate() error { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err)) | 		msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err)) | ||||||
| 	} else { | 	} else { | ||||||
| 		o.sessionStore = sessionStore | 		o.SetSessionStore(sessionStore) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if o.Cookie.Refresh >= o.Cookie.Expire { | 	if o.Cookie.Refresh >= o.Cookie.Expire { | ||||||
|  | @ -458,10 +272,11 @@ func (o *Options) Validate() error { | ||||||
| 	msgs = setupLogger(o, msgs) | 	msgs = setupLogger(o, msgs) | ||||||
| 
 | 
 | ||||||
| 	if o.ReverseProxy { | 	if o.ReverseProxy { | ||||||
| 		o.realClientIPParser, err = getRealClientIPParser(o.RealClientIPHeader) | 		parser, err := ip.GetRealClientIPParser(o.RealClientIPHeader) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			msgs = append(msgs, fmt.Sprintf("real_client_ip_header (%s) not accepted parameter value: %v", o.RealClientIPHeader, err)) | 			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 { | 	if len(msgs) != 0 { | ||||||
|  | @ -471,7 +286,7 @@ func (o *Options) Validate() error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func parseProviderInfo(o *Options, msgs []string) []string { | func parseProviderInfo(o *options.Options, msgs []string) []string { | ||||||
| 	p := &providers.ProviderData{ | 	p := &providers.ProviderData{ | ||||||
| 		Scope:            o.Scope, | 		Scope:            o.Scope, | ||||||
| 		ClientID:         o.ClientID, | 		ClientID:         o.ClientID, | ||||||
|  | @ -487,8 +302,8 @@ func parseProviderInfo(o *Options, msgs []string) []string { | ||||||
| 	p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs) | 	p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs) | ||||||
| 	p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs) | 	p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs) | ||||||
| 
 | 
 | ||||||
| 	o.provider = providers.New(o.Provider, p) | 	o.SetProvider(providers.New(o.ProviderType, p)) | ||||||
| 	switch p := o.provider.(type) { | 	switch p := o.GetProvider().(type) { | ||||||
| 	case *providers.AzureProvider: | 	case *providers.AzureProvider: | ||||||
| 		p.Configure(o.AzureTenant) | 		p.Configure(o.AzureTenant) | ||||||
| 	case *providers.GitHubProvider: | 	case *providers.GitHubProvider: | ||||||
|  | @ -511,18 +326,18 @@ func parseProviderInfo(o *Options, msgs []string) []string { | ||||||
| 	case *providers.OIDCProvider: | 	case *providers.OIDCProvider: | ||||||
| 		p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail | 		p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail | ||||||
| 		p.UserIDClaim = o.UserIDClaim | 		p.UserIDClaim = o.UserIDClaim | ||||||
| 		if o.oidcVerifier == nil { | 		if o.GetOIDCVerifier() == nil { | ||||||
| 			msgs = append(msgs, "oidc provider requires an oidc issuer URL") | 			msgs = append(msgs, "oidc provider requires an oidc issuer URL") | ||||||
| 		} else { | 		} else { | ||||||
| 			p.Verifier = o.oidcVerifier | 			p.Verifier = o.GetOIDCVerifier() | ||||||
| 		} | 		} | ||||||
| 	case *providers.GitLabProvider: | 	case *providers.GitLabProvider: | ||||||
| 		p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail | 		p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail | ||||||
| 		p.Group = o.GitLabGroup | 		p.Group = o.GitLabGroup | ||||||
| 		p.EmailDomains = o.EmailDomains | 		p.EmailDomains = o.EmailDomains | ||||||
| 
 | 
 | ||||||
| 		if o.oidcVerifier != nil { | 		if o.GetOIDCVerifier() != nil { | ||||||
| 			p.Verifier = o.oidcVerifier | 			p.Verifier = o.GetOIDCVerifier() | ||||||
| 		} else { | 		} else { | ||||||
| 			// Initialize with default verifier for gitlab.com
 | 			// Initialize with default verifier for gitlab.com
 | ||||||
| 			ctx := context.Background() | 			ctx := context.Background() | ||||||
|  | @ -573,7 +388,7 @@ func parseProviderInfo(o *Options, msgs []string) []string { | ||||||
| 	return msgs | 	return msgs | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func parseSignatureKey(o *Options, msgs []string) []string { | func parseSignatureKey(o *options.Options, msgs []string) []string { | ||||||
| 	if o.SignatureKey == "" { | 	if o.SignatureKey == "" { | ||||||
| 		return msgs | 		return msgs | ||||||
| 	} | 	} | ||||||
|  | @ -591,7 +406,7 @@ func parseSignatureKey(o *Options, msgs []string) []string { | ||||||
| 		return append(msgs, "unsupported signature hash algorithm: "+ | 		return append(msgs, "unsupported signature hash algorithm: "+ | ||||||
| 			o.SignatureKey) | 			o.SignatureKey) | ||||||
| 	} | 	} | ||||||
| 	o.signatureData = &SignatureData{hash: hash, key: secretKey} | 	o.SetSignatureData(&options.SignatureData{Hash: hash, Key: secretKey}) | ||||||
| 	return msgs | 	return msgs | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -634,7 +449,7 @@ func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error | ||||||
| 	return verifier, nil | 	return verifier, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func validateCookieName(o *Options, msgs []string) []string { | func validateCookieName(o *options.Options, msgs []string) []string { | ||||||
| 	cookie := &http.Cookie{Name: o.Cookie.Name} | 	cookie := &http.Cookie{Name: o.Cookie.Name} | ||||||
| 	if cookie.String() == "" { | 	if cookie.String() == "" { | ||||||
| 		return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.Cookie.Name)) | 		return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.Cookie.Name)) | ||||||
|  | @ -642,7 +457,7 @@ func validateCookieName(o *Options, msgs []string) []string { | ||||||
| 	return msgs | 	return msgs | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func setupLogger(o *Options, msgs []string) []string { | func setupLogger(o *options.Options, msgs []string) []string { | ||||||
| 	// Setup the log file
 | 	// Setup the log file
 | ||||||
| 	if len(o.LoggingFilename) > 0 { | 	if len(o.LoggingFilename) > 0 { | ||||||
| 		// Validate that the file/dir can be written
 | 		// Validate that the file/dir can be written
 | ||||||
|  | @ -681,7 +496,7 @@ func setupLogger(o *Options, msgs []string) []string { | ||||||
| 	logger.SetAuthTemplate(o.AuthLoggingFormat) | 	logger.SetAuthTemplate(o.AuthLoggingFormat) | ||||||
| 	logger.SetReqTemplate(o.RequestLoggingFormat) | 	logger.SetReqTemplate(o.RequestLoggingFormat) | ||||||
| 	logger.SetGetClientFunc(func(r *http.Request) string { | 	logger.SetGetClientFunc(func(r *http.Request) string { | ||||||
| 		return getClientString(o.realClientIPParser, r, false) | 		return ip.GetClientString(o.GetRealClientIPParser(), r, false) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	excludePaths := make([]string, 0) | 	excludePaths := make([]string, 0) | ||||||
|  | @ -698,3 +513,18 @@ func setupLogger(o *Options, msgs []string) []string { | ||||||
| 
 | 
 | ||||||
| 	return msgs | 	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 | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package main | package validation | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto" | 	"crypto" | ||||||
|  | @ -10,6 +10,7 @@ import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -19,8 +20,8 @@ const ( | ||||||
| 	clientSecret = "xyzzyplugh" | 	clientSecret = "xyzzyplugh" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func testOptions() *Options { | func testOptions() *options.Options { | ||||||
| 	o := NewOptions() | 	o := options.NewOptions() | ||||||
| 	o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/") | 	o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/") | ||||||
| 	o.Cookie.Secret = cookieSecret | 	o.Cookie.Secret = cookieSecret | ||||||
| 	o.ClientID = clientID | 	o.ClientID = clientID | ||||||
|  | @ -37,9 +38,9 @@ func errorMsg(msgs []string) string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestNewOptions(t *testing.T) { | func TestNewOptions(t *testing.T) { | ||||||
| 	o := NewOptions() | 	o := options.NewOptions() | ||||||
| 	o.EmailDomains = []string{"*"} | 	o.EmailDomains = []string{"*"} | ||||||
| 	err := o.Validate() | 	err := Validate(o) | ||||||
| 	assert.NotEqual(t, nil, err) | 	assert.NotEqual(t, nil, err) | ||||||
| 
 | 
 | ||||||
| 	expected := errorMsg([]string{ | 	expected := errorMsg([]string{ | ||||||
|  | @ -50,15 +51,15 @@ func TestNewOptions(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestClientSecretFileOptionFails(t *testing.T) { | func TestClientSecretFileOptionFails(t *testing.T) { | ||||||
| 	o := NewOptions() | 	o := options.NewOptions() | ||||||
| 	o.Cookie.Secret = cookieSecret | 	o.Cookie.Secret = cookieSecret | ||||||
| 	o.ClientID = clientID | 	o.ClientID = clientID | ||||||
| 	o.ClientSecretFile = clientSecret | 	o.ClientSecretFile = clientSecret | ||||||
| 	o.EmailDomains = []string{"*"} | 	o.EmailDomains = []string{"*"} | ||||||
| 	err := o.Validate() | 	err := Validate(o) | ||||||
| 	assert.NotEqual(t, nil, err) | 	assert.NotEqual(t, nil, err) | ||||||
| 
 | 
 | ||||||
| 	p := o.provider.Data() | 	p := o.GetProvider().Data() | ||||||
| 	assert.Equal(t, clientSecret, p.ClientSecretFile) | 	assert.Equal(t, clientSecret, p.ClientSecretFile) | ||||||
| 	assert.Equal(t, "", p.ClientSecret) | 	assert.Equal(t, "", p.ClientSecret) | ||||||
| 
 | 
 | ||||||
|  | @ -80,15 +81,15 @@ func TestClientSecretFileOption(t *testing.T) { | ||||||
| 	clientSecretFileName := f.Name() | 	clientSecretFileName := f.Name() | ||||||
| 	defer os.Remove(clientSecretFileName) | 	defer os.Remove(clientSecretFileName) | ||||||
| 
 | 
 | ||||||
| 	o := NewOptions() | 	o := options.NewOptions() | ||||||
| 	o.Cookie.Secret = cookieSecret | 	o.Cookie.Secret = cookieSecret | ||||||
| 	o.ClientID = clientID | 	o.ClientID = clientID | ||||||
| 	o.ClientSecretFile = clientSecretFileName | 	o.ClientSecretFile = clientSecretFileName | ||||||
| 	o.EmailDomains = []string{"*"} | 	o.EmailDomains = []string{"*"} | ||||||
| 	err = o.Validate() | 	err = Validate(o) | ||||||
| 	assert.Equal(t, nil, err) | 	assert.Equal(t, nil, err) | ||||||
| 
 | 
 | ||||||
| 	p := o.provider.Data() | 	p := o.GetProvider().Data() | ||||||
| 	assert.Equal(t, clientSecretFileName, p.ClientSecretFile) | 	assert.Equal(t, clientSecretFileName, p.ClientSecretFile) | ||||||
| 	assert.Equal(t, "", p.ClientSecret) | 	assert.Equal(t, "", p.ClientSecret) | ||||||
| 
 | 
 | ||||||
|  | @ -100,7 +101,7 @@ func TestClientSecretFileOption(t *testing.T) { | ||||||
| func TestGoogleGroupOptions(t *testing.T) { | func TestGoogleGroupOptions(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.GoogleGroups = []string{"googlegroup"} | 	o.GoogleGroups = []string{"googlegroup"} | ||||||
| 	err := o.Validate() | 	err := Validate(o) | ||||||
| 	assert.NotEqual(t, nil, err) | 	assert.NotEqual(t, nil, err) | ||||||
| 
 | 
 | ||||||
| 	expected := errorMsg([]string{ | 	expected := errorMsg([]string{ | ||||||
|  | @ -114,7 +115,7 @@ func TestGoogleGroupInvalidFile(t *testing.T) { | ||||||
| 	o.GoogleGroups = []string{"test_group"} | 	o.GoogleGroups = []string{"test_group"} | ||||||
| 	o.GoogleAdminEmail = "admin@example.com" | 	o.GoogleAdminEmail = "admin@example.com" | ||||||
| 	o.GoogleServiceAccountJSON = "file_doesnt_exist.json" | 	o.GoogleServiceAccountJSON = "file_doesnt_exist.json" | ||||||
| 	err := o.Validate() | 	err := Validate(o) | ||||||
| 	assert.NotEqual(t, nil, err) | 	assert.NotEqual(t, nil, err) | ||||||
| 
 | 
 | ||||||
| 	expected := errorMsg([]string{ | 	expected := errorMsg([]string{ | ||||||
|  | @ -125,36 +126,36 @@ func TestGoogleGroupInvalidFile(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestInitializedOptions(t *testing.T) { | func TestInitializedOptions(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Note that it's not worth testing nonparseable URLs, since url.Parse()
 | // Note that it's not worth testing nonparseable URLs, since url.Parse()
 | ||||||
| // seems to parse damn near anything.
 | // seems to parse damn near anything.
 | ||||||
| func TestRedirectURL(t *testing.T) { | func TestRedirectURL(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.RedirectURL = "https://myhost.com/oauth2/callback" | 	o.RawRedirectURL = "https://myhost.com/oauth2/callback" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 	expected := &url.URL{ | 	expected := &url.URL{ | ||||||
| 		Scheme: "https", Host: "myhost.com", Path: "/oauth2/callback"} | 		Scheme: "https", Host: "myhost.com", Path: "/oauth2/callback"} | ||||||
| 	assert.Equal(t, expected, o.redirectURL) | 	assert.Equal(t, expected, o.GetRedirectURL()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestProxyURLs(t *testing.T) { | func TestProxyURLs(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081") | 	o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081") | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 	expected := []*url.URL{ | 	expected := []*url.URL{ | ||||||
| 		{Scheme: "http", Host: "127.0.0.1:8080", Path: "/"}, | 		{Scheme: "http", Host: "127.0.0.1:8080", Path: "/"}, | ||||||
| 		// note the '/' was added
 | 		// note the '/' was added
 | ||||||
| 		{Scheme: "http", Host: "127.0.0.1:8081", Path: "/"}, | 		{Scheme: "http", Host: "127.0.0.1:8081", Path: "/"}, | ||||||
| 	} | 	} | ||||||
| 	assert.Equal(t, expected, o.proxyURLs) | 	assert.Equal(t, expected, o.GetProxyURLs()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestProxyURLsError(t *testing.T) { | func TestProxyURLsError(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.Upstreams = append(o.Upstreams, "127.0.0.1:8081") | 	o.Upstreams = append(o.Upstreams, "127.0.0.1:8081") | ||||||
| 	err := o.Validate() | 	err := Validate(o) | ||||||
| 	assert.NotEqual(t, nil, err) | 	assert.NotEqual(t, nil, err) | ||||||
| 	assert.Contains(t, err.Error(), "error parsing upstream") | 	assert.Contains(t, err.Error(), "error parsing upstream") | ||||||
| } | } | ||||||
|  | @ -163,9 +164,9 @@ func TestCompiledRegex(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	regexps := []string{"/foo/.*", "/ba[rz]/quux"} | 	regexps := []string{"/foo/.*", "/ba[rz]/quux"} | ||||||
| 	o.SkipAuthRegex = regexps | 	o.SkipAuthRegex = regexps | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 	actual := make([]string, 0) | 	actual := make([]string, 0) | ||||||
| 	for _, regex := range o.compiledRegex { | 	for _, regex := range o.GetCompiledRegex() { | ||||||
| 		actual = append(actual, regex.String()) | 		actual = append(actual, regex.String()) | ||||||
| 	} | 	} | ||||||
| 	assert.Equal(t, regexps, actual) | 	assert.Equal(t, regexps, actual) | ||||||
|  | @ -174,7 +175,7 @@ func TestCompiledRegex(t *testing.T) { | ||||||
| func TestCompiledRegexError(t *testing.T) { | func TestCompiledRegexError(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.SkipAuthRegex = []string{"(foobaz", "barquux)"} | 	o.SkipAuthRegex = []string{"(foobaz", "barquux)"} | ||||||
| 	err := o.Validate() | 	err := Validate(o) | ||||||
| 	assert.NotEqual(t, nil, err) | 	assert.NotEqual(t, nil, err) | ||||||
| 
 | 
 | ||||||
| 	expected := errorMsg([]string{ | 	expected := errorMsg([]string{ | ||||||
|  | @ -185,7 +186,7 @@ func TestCompiledRegexError(t *testing.T) { | ||||||
| 	assert.Equal(t, expected, err.Error()) | 	assert.Equal(t, expected, err.Error()) | ||||||
| 
 | 
 | ||||||
| 	o.SkipAuthRegex = []string{"foobaz", "barquux)"} | 	o.SkipAuthRegex = []string{"foobaz", "barquux)"} | ||||||
| 	err = o.Validate() | 	err = Validate(o) | ||||||
| 	assert.NotEqual(t, nil, err) | 	assert.NotEqual(t, nil, err) | ||||||
| 
 | 
 | ||||||
| 	expected = errorMsg([]string{ | 	expected = errorMsg([]string{ | ||||||
|  | @ -196,8 +197,8 @@ func TestCompiledRegexError(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestDefaultProviderApiSettings(t *testing.T) { | func TestDefaultProviderApiSettings(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 	p := o.provider.Data() | 	p := o.GetProvider().Data() | ||||||
| 	assert.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline", | 	assert.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline", | ||||||
| 		p.LoginURL.String()) | 		p.LoginURL.String()) | ||||||
| 	assert.Equal(t, "https://www.googleapis.com/oauth2/v3/token", | 	assert.Equal(t, "https://www.googleapis.com/oauth2/v3/token", | ||||||
|  | @ -208,76 +209,76 @@ func TestDefaultProviderApiSettings(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) { | func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	assert.Equal(t, false, o.PassAccessToken) | 	assert.Equal(t, false, o.PassAccessToken) | ||||||
| 	o.PassAccessToken = true | 	o.PassAccessToken = true | ||||||
| 	o.Cookie.Secret = "cookie of invalid length-" | 	o.Cookie.Secret = "cookie of invalid length-" | ||||||
| 	assert.NotEqual(t, nil, o.Validate()) | 	assert.NotEqual(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	o.PassAccessToken = false | 	o.PassAccessToken = false | ||||||
| 	o.Cookie.Refresh = time.Duration(24) * time.Hour | 	o.Cookie.Refresh = time.Duration(24) * time.Hour | ||||||
| 	assert.NotEqual(t, nil, o.Validate()) | 	assert.NotEqual(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	o.Cookie.Secret = "16 bytes AES-128" | 	o.Cookie.Secret = "16 bytes AES-128" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	o.Cookie.Secret = "24 byte secret AES-192--" | 	o.Cookie.Secret = "24 byte secret AES-192--" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	o.Cookie.Secret = "32 byte secret for AES-256------" | 	o.Cookie.Secret = "32 byte secret for AES-256------" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) { | func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	o.Cookie.Secret = "0123456789abcdef" | 	o.Cookie.Secret = "0123456789abcdef" | ||||||
| 	o.Cookie.Refresh = o.Cookie.Expire | 	o.Cookie.Refresh = o.Cookie.Expire | ||||||
| 	assert.NotEqual(t, nil, o.Validate()) | 	assert.NotEqual(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	o.Cookie.Refresh -= time.Duration(1) | 	o.Cookie.Refresh -= time.Duration(1) | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestBase64CookieSecret(t *testing.T) { | func TestBase64CookieSecret(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	// 32 byte, base64 (urlsafe) encoded key
 | 	// 32 byte, base64 (urlsafe) encoded key
 | ||||||
| 	o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ=" | 	o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ=" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	// 32 byte, base64 (urlsafe) encoded key, w/o padding
 | 	// 32 byte, base64 (urlsafe) encoded key, w/o padding
 | ||||||
| 	o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ" | 	o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	// 24 byte, base64 (urlsafe) encoded key
 | 	// 24 byte, base64 (urlsafe) encoded key
 | ||||||
| 	o.Cookie.Secret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3" | 	o.Cookie.Secret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	// 16 byte, base64 (urlsafe) encoded key
 | 	// 16 byte, base64 (urlsafe) encoded key
 | ||||||
| 	o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA==" | 	o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA==" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 
 | 
 | ||||||
| 	// 16 byte, base64 (urlsafe) encoded key, w/o padding
 | 	// 16 byte, base64 (urlsafe) encoded key, w/o padding
 | ||||||
| 	o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA" | 	o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestValidateSignatureKey(t *testing.T) { | func TestValidateSignatureKey(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.SignatureKey = "sha1:secret" | 	o.SignatureKey = "sha1:secret" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 	assert.Equal(t, o.signatureData.hash, crypto.SHA1) | 	assert.Equal(t, o.GetSignatureData().Hash, crypto.SHA1) | ||||||
| 	assert.Equal(t, o.signatureData.key, "secret") | 	assert.Equal(t, o.GetSignatureData().Key, "secret") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestValidateSignatureKeyInvalidSpec(t *testing.T) { | func TestValidateSignatureKeyInvalidSpec(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.SignatureKey = "invalid spec" | 	o.SignatureKey = "invalid spec" | ||||||
| 	err := o.Validate() | 	err := Validate(o) | ||||||
| 	assert.Equal(t, err.Error(), "invalid configuration:\n"+ | 	assert.Equal(t, err.Error(), "invalid configuration:\n"+ | ||||||
| 		"  invalid signature hash:key spec: "+o.SignatureKey) | 		"  invalid signature hash:key spec: "+o.SignatureKey) | ||||||
| } | } | ||||||
|  | @ -285,7 +286,7 @@ func TestValidateSignatureKeyInvalidSpec(t *testing.T) { | ||||||
| func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) { | func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.SignatureKey = "unsupported:default secret" | 	o.SignatureKey = "unsupported:default secret" | ||||||
| 	err := o.Validate() | 	err := Validate(o) | ||||||
| 	assert.Equal(t, err.Error(), "invalid configuration:\n"+ | 	assert.Equal(t, err.Error(), "invalid configuration:\n"+ | ||||||
| 		"  unsupported signature hash algorithm: "+o.SignatureKey) | 		"  unsupported signature hash algorithm: "+o.SignatureKey) | ||||||
| } | } | ||||||
|  | @ -293,24 +294,24 @@ func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) { | ||||||
| func TestValidateCookie(t *testing.T) { | func TestValidateCookie(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.Cookie.Name = "_valid_cookie_name" | 	o.Cookie.Name = "_valid_cookie_name" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestValidateCookieBadName(t *testing.T) { | func TestValidateCookieBadName(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.Cookie.Name = "_bad_cookie_name{}" | 	o.Cookie.Name = "_bad_cookie_name{}" | ||||||
| 	err := o.Validate() | 	err := Validate(o) | ||||||
| 	assert.Equal(t, err.Error(), "invalid configuration:\n"+ | 	assert.Equal(t, err.Error(), "invalid configuration:\n"+ | ||||||
| 		fmt.Sprintf("  invalid cookie name: %q", o.Cookie.Name)) | 		fmt.Sprintf("  invalid cookie name: %q", o.Cookie.Name)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestSkipOIDCDiscovery(t *testing.T) { | func TestSkipOIDCDiscovery(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.Provider = "oidc" | 	o.ProviderType = "oidc" | ||||||
| 	o.OIDCIssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/" | 	o.OIDCIssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/" | ||||||
| 	o.SkipOIDCDiscovery = true | 	o.SkipOIDCDiscovery = true | ||||||
| 
 | 
 | ||||||
| 	err := o.Validate() | 	err := Validate(o) | ||||||
| 	assert.Equal(t, "invalid configuration:\n"+ | 	assert.Equal(t, "invalid configuration:\n"+ | ||||||
| 		"  missing setting: login-url\n  missing setting: redeem-url\n  missing setting: oidc-jwks-url", err.Error()) | 		"  missing setting: login-url\n  missing setting: redeem-url\n  missing setting: oidc-jwks-url", err.Error()) | ||||||
| 
 | 
 | ||||||
|  | @ -318,54 +319,50 @@ func TestSkipOIDCDiscovery(t *testing.T) { | ||||||
| 	o.RedeemURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/token?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" | 	o.OIDCJwksURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys" | ||||||
| 
 | 
 | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestGCPHealthcheck(t *testing.T) { | func TestGCPHealthcheck(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	o.GCPHealthChecks = true | 	o.GCPHealthChecks = true | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestRealClientIPHeader(t *testing.T) { | func TestRealClientIPHeader(t *testing.T) { | ||||||
| 	var o *Options |  | ||||||
| 	var err error |  | ||||||
| 	var expected string |  | ||||||
| 
 |  | ||||||
| 	// Ensure nil if ReverseProxy not set.
 | 	// Ensure nil if ReverseProxy not set.
 | ||||||
| 	o = testOptions() | 	o := testOptions() | ||||||
| 	o.RealClientIPHeader = "X-Real-IP" | 	o.RealClientIPHeader = "X-Real-IP" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 	assert.Nil(t, o.realClientIPParser) | 	assert.Nil(t, o.GetRealClientIPParser()) | ||||||
| 
 | 
 | ||||||
| 	// Ensure simple use case works.
 | 	// Ensure simple use case works.
 | ||||||
| 	o = testOptions() | 	o = testOptions() | ||||||
| 	o.ReverseProxy = true | 	o.ReverseProxy = true | ||||||
| 	o.RealClientIPHeader = "X-Forwarded-For" | 	o.RealClientIPHeader = "X-Forwarded-For" | ||||||
| 	assert.Equal(t, nil, o.Validate()) | 	assert.Equal(t, nil, Validate(o)) | ||||||
| 	assert.NotNil(t, o.realClientIPParser) | 	assert.NotNil(t, o.GetRealClientIPParser()) | ||||||
| 
 | 
 | ||||||
| 	// Ensure unknown header format process an error.
 | 	// Ensure unknown header format process an error.
 | ||||||
| 	o = testOptions() | 	o = testOptions() | ||||||
| 	o.ReverseProxy = true | 	o.ReverseProxy = true | ||||||
| 	o.RealClientIPHeader = "Forwarded" | 	o.RealClientIPHeader = "Forwarded" | ||||||
| 	err = o.Validate() | 	err := Validate(o) | ||||||
| 	assert.NotEqual(t, nil, err) | 	assert.NotEqual(t, nil, err) | ||||||
| 	expected = errorMsg([]string{ | 	expected := errorMsg([]string{ | ||||||
| 		"real_client_ip_header (Forwarded) not accepted parameter value: the http header key (Forwarded) is either invalid or unsupported", | 		"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.Equal(t, expected, err.Error()) | ||||||
| 	assert.Nil(t, o.realClientIPParser) | 	assert.Nil(t, o.GetRealClientIPParser()) | ||||||
| 
 | 
 | ||||||
| 	// Ensure invalid header format produces an error.
 | 	// Ensure invalid header format produces an error.
 | ||||||
| 	o = testOptions() | 	o = testOptions() | ||||||
| 	o.ReverseProxy = true | 	o.ReverseProxy = true | ||||||
| 	o.RealClientIPHeader = "!934invalidheader-23:" | 	o.RealClientIPHeader = "!934invalidheader-23:" | ||||||
| 	err = o.Validate() | 	err = Validate(o) | ||||||
| 	assert.NotEqual(t, nil, err) | 	assert.NotEqual(t, nil, err) | ||||||
| 	expected = errorMsg([]string{ | 	expected = errorMsg([]string{ | ||||||
| 		"real_client_ip_header (!934invalidheader-23:) not accepted parameter value: the http header key (!934invalidheader-23:) is either invalid or unsupported", | 		"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.Equal(t, expected, err.Error()) | ||||||
| 	assert.Nil(t, o.realClientIPParser) | 	assert.Nil(t, o.GetRealClientIPParser()) | ||||||
| } | } | ||||||
		Loading…
	
		Reference in New Issue