Support HTTP method based allowlists
This commit is contained in:
		
							parent
							
								
									fcb83c48f4
								
							
						
					
					
						commit
						183cb124a4
					
				|  | @ -7,6 +7,9 @@ | ||||||
| - [#575](https://github.com/oauth2-proxy/oauth2-proxy/pull/575) Sessions from v5.1.1 or earlier will no longer validate since they were not signed with SHA1. | - [#575](https://github.com/oauth2-proxy/oauth2-proxy/pull/575) Sessions from v5.1.1 or earlier will no longer validate since they were not signed with SHA1. | ||||||
|   - Sessions from v6.0.0 or later had a graceful conversion to SHA256 that resulted in no reauthentication |   - Sessions from v6.0.0 or later had a graceful conversion to SHA256 that resulted in no reauthentication | ||||||
|   - Upgrading from v5.1.1 or earlier will result in a reauthentication |   - Upgrading from v5.1.1 or earlier will result in a reauthentication | ||||||
|  | - [#789](https://github.com/oauth2-proxy/oauth2-proxy/pull/789) `--skip-auth-route` is (almost) backwards compatible with `--skip-auth-regex` | ||||||
|  |   - We are marking `--skip-auth-regex` as DEPRECATED and will remove it in the next major version. | ||||||
|  |   - If your regex contains an `=` and you want it for all methods, you will need to add a leading `=` (this is the area where `--skip-auth-regex` doesn't port perfectly) | ||||||
| - [#616](https://github.com/oauth2-proxy/oauth2-proxy/pull/616) Ensure you have configured oauth2-proxy to use the `groups` scope. The user may be logged out initially as they may not currently have the `groups` claim however after going back through login process wil be authenticated. | - [#616](https://github.com/oauth2-proxy/oauth2-proxy/pull/616) Ensure you have configured oauth2-proxy to use the `groups` scope. The user may be logged out initially as they may not currently have the `groups` claim however after going back through login process wil be authenticated. | ||||||
| 
 | 
 | ||||||
| ## Breaking Changes | ## Breaking Changes | ||||||
|  | @ -23,6 +26,7 @@ | ||||||
| ## Changes since v6.1.1 | ## Changes since v6.1.1 | ||||||
| 
 | 
 | ||||||
| - [#753](https://github.com/oauth2-proxy/oauth2-proxy/pull/753) Pass resource parameter in login url (@codablock) | - [#753](https://github.com/oauth2-proxy/oauth2-proxy/pull/753) Pass resource parameter in login url (@codablock) | ||||||
|  | - [#789](https://github.com/oauth2-proxy/oauth2-proxy/pull/789) Add `--skip-auth-route` configuration option for `METHOD=pathRegex` based allowlists (@NickMeves) | ||||||
| - [#575](https://github.com/oauth2-proxy/oauth2-proxy/pull/575) Stop accepting legacy SHA1 signed cookies (@NickMeves) | - [#575](https://github.com/oauth2-proxy/oauth2-proxy/pull/575) Stop accepting legacy SHA1 signed cookies (@NickMeves) | ||||||
| - [#722](https://github.com/oauth2-proxy/oauth2-proxy/pull/722) Validate Redis configuration options at startup (@NickMeves) | - [#722](https://github.com/oauth2-proxy/oauth2-proxy/pull/722) Validate Redis configuration options at startup (@NickMeves) | ||||||
| - [#791](https://github.com/oauth2-proxy/oauth2-proxy/pull/791) Remove GetPreferredUsername method from provider interface (@NickMeves) | - [#791](https://github.com/oauth2-proxy/oauth2-proxy/pull/791) Remove GetPreferredUsername method from provider interface (@NickMeves) | ||||||
|  |  | ||||||
|  | @ -119,8 +119,9 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example | ||||||
| | `--signature-key` | string | GAP-Signature request signature key (algorithm:secretkey) | | | | `--signature-key` | string | GAP-Signature request signature key (algorithm:secretkey) | | | ||||||
| | `--silence-ping-logging` | bool | disable logging of requests to ping endpoint | false | | | `--silence-ping-logging` | bool | disable logging of requests to ping endpoint | false | | ||||||
| | `--skip-auth-preflight` | bool | will skip authentication for OPTIONS requests | false | | | `--skip-auth-preflight` | bool | will skip authentication for OPTIONS requests | false | | ||||||
| | `--skip-auth-regex` | string | bypass authentication for requests paths that match (may be given multiple times) | | | | `--skip-auth-regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times) | | | ||||||
| | `--skip-auth-strip-headers` | bool | strips `X-Forwarded-*` style authentication headers & `Authorization` header if they would be set by oauth2-proxy for request paths in `--skip-auth-regex` | false | | | `--skip-auth-route` | string \| list | bypass authentication for requests that match the method & path. Format: method=path_regex OR path_regex alone for all methods | | | ||||||
|  | | `--skip-auth-strip-headers` | bool | strips `X-Forwarded-*` style authentication headers & `Authorization` header if they would be set by oauth2-proxy for allowlisted requests (`--skip-auth-route`, `--skip-auth-regex`, `--skip-auth-preflight`, `--trusted-ip`) | false | | ||||||
| | `--skip-jwt-bearer-tokens` | bool | will skip requests that have verified JWT bearer tokens | false | | | `--skip-jwt-bearer-tokens` | bool | will skip requests that have verified JWT bearer tokens | false | | ||||||
| | `--skip-oidc-discovery` | bool | bypass OIDC endpoint discovery. `--login-url`, `--redeem-url` and `--oidc-jwks-url` must be configured in this case | false | | | `--skip-oidc-discovery` | bool | bypass OIDC endpoint discovery. `--login-url`, `--redeem-url` and `--oidc-jwks-url` must be configured in this case | false | | ||||||
| | `--skip-provider-button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false | | | `--skip-provider-button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false | | ||||||
|  |  | ||||||
|  | @ -48,6 +48,12 @@ var ( | ||||||
| 	invalidRedirectRegex = regexp.MustCompile(`[/\\](?:[\s\v]*|\.{1,2})[/\\]`) | 	invalidRedirectRegex = regexp.MustCompile(`[/\\](?:[\s\v]*|\.{1,2})[/\\]`) | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // allowedRoute manages method + path based allowlists
 | ||||||
|  | type allowedRoute struct { | ||||||
|  | 	method    string | ||||||
|  | 	pathRegex *regexp.Regexp | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // OAuthProxy is the main authentication proxy
 | // OAuthProxy is the main authentication proxy
 | ||||||
| type OAuthProxy struct { | type OAuthProxy struct { | ||||||
| 	CookieSeed     string | 	CookieSeed     string | ||||||
|  | @ -70,6 +76,7 @@ type OAuthProxy struct { | ||||||
| 	AuthOnlyPath      string | 	AuthOnlyPath      string | ||||||
| 	UserInfoPath      string | 	UserInfoPath      string | ||||||
| 
 | 
 | ||||||
|  | 	allowedRoutes           []*allowedRoute | ||||||
| 	redirectURL             *url.URL // the url to receive requests at
 | 	redirectURL             *url.URL // the url to receive requests at
 | ||||||
| 	whitelistDomains        []string | 	whitelistDomains        []string | ||||||
| 	provider                providers.Provider | 	provider                providers.Provider | ||||||
|  | @ -90,13 +97,11 @@ type OAuthProxy struct { | ||||||
| 	SetAuthorization        bool | 	SetAuthorization        bool | ||||||
| 	PassAuthorization       bool | 	PassAuthorization       bool | ||||||
| 	PreferEmailToUser       bool | 	PreferEmailToUser       bool | ||||||
| 	skipAuthRegex           []string |  | ||||||
| 	skipAuthPreflight       bool | 	skipAuthPreflight       bool | ||||||
| 	skipAuthStripHeaders    bool | 	skipAuthStripHeaders    bool | ||||||
| 	skipJwtBearerTokens     bool | 	skipJwtBearerTokens     bool | ||||||
| 	mainJwtBearerVerifier   *oidc.IDTokenVerifier | 	mainJwtBearerVerifier   *oidc.IDTokenVerifier | ||||||
| 	extraJwtBearerVerifiers []*oidc.IDTokenVerifier | 	extraJwtBearerVerifiers []*oidc.IDTokenVerifier | ||||||
| 	compiledRegex           []*regexp.Regexp |  | ||||||
| 	templates               *template.Template | 	templates               *template.Template | ||||||
| 	realClientIPParser      ipapi.RealClientIPParser | 	realClientIPParser      ipapi.RealClientIPParser | ||||||
| 	trustedIPs              *ip.NetSet | 	trustedIPs              *ip.NetSet | ||||||
|  | @ -121,10 +126,6 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr | ||||||
| 		return nil, fmt.Errorf("error initialising upstream proxy: %v", err) | 		return nil, fmt.Errorf("error initialising upstream proxy: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, u := range opts.GetCompiledRegex() { |  | ||||||
| 		logger.Printf("compiled skip-auth-regex => %q", u) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if opts.SkipJwtBearerTokens { | 	if opts.SkipJwtBearerTokens { | ||||||
| 		logger.Printf("Skipping JWT tokens from configured OIDC issuer: %q", opts.OIDCIssuerURL) | 		logger.Printf("Skipping JWT tokens from configured OIDC issuer: %q", opts.OIDCIssuerURL) | ||||||
| 		for _, issuer := range opts.ExtraJwtIssuers { | 		for _, issuer := range opts.ExtraJwtIssuers { | ||||||
|  | @ -163,6 +164,11 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	allowedRoutes, err := buildRoutesAllowlist(opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	sessionChain := buildSessionChain(opts, sessionStore, basicAuthValidator) | 	sessionChain := buildSessionChain(opts, sessionStore, basicAuthValidator) | ||||||
| 
 | 
 | ||||||
| 	return &OAuthProxy{ | 	return &OAuthProxy{ | ||||||
|  | @ -192,14 +198,13 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr | ||||||
| 		sessionStore:            sessionStore, | 		sessionStore:            sessionStore, | ||||||
| 		serveMux:                upstreamProxy, | 		serveMux:                upstreamProxy, | ||||||
| 		redirectURL:             redirectURL, | 		redirectURL:             redirectURL, | ||||||
|  | 		allowedRoutes:           allowedRoutes, | ||||||
| 		whitelistDomains:        opts.WhitelistDomains, | 		whitelistDomains:        opts.WhitelistDomains, | ||||||
| 		skipAuthRegex:           opts.SkipAuthRegex, |  | ||||||
| 		skipAuthPreflight:       opts.SkipAuthPreflight, | 		skipAuthPreflight:       opts.SkipAuthPreflight, | ||||||
| 		skipAuthStripHeaders:    opts.SkipAuthStripHeaders, | 		skipAuthStripHeaders:    opts.SkipAuthStripHeaders, | ||||||
| 		skipJwtBearerTokens:     opts.SkipJwtBearerTokens, | 		skipJwtBearerTokens:     opts.SkipJwtBearerTokens, | ||||||
| 		mainJwtBearerVerifier:   opts.GetOIDCVerifier(), | 		mainJwtBearerVerifier:   opts.GetOIDCVerifier(), | ||||||
| 		extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(), | 		extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(), | ||||||
| 		compiledRegex:           opts.GetCompiledRegex(), |  | ||||||
| 		realClientIPParser:      opts.GetRealClientIPParser(), | 		realClientIPParser:      opts.GetRealClientIPParser(), | ||||||
| 		SetXAuthRequest:         opts.SetXAuthRequest, | 		SetXAuthRequest:         opts.SetXAuthRequest, | ||||||
| 		PassBasicAuth:           opts.PassBasicAuth, | 		PassBasicAuth:           opts.PassBasicAuth, | ||||||
|  | @ -277,6 +282,51 @@ func buildSignInMessage(opts *options.Options) string { | ||||||
| 	return msg | 	return msg | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // buildRoutesAllowlist builds an []allowedRoute  list from either the legacy
 | ||||||
|  | // SkipAuthRegex option (paths only support) or newer SkipAuthRoutes option
 | ||||||
|  | // (method=path support)
 | ||||||
|  | func buildRoutesAllowlist(opts *options.Options) ([]*allowedRoute, error) { | ||||||
|  | 	var routes []*allowedRoute | ||||||
|  | 
 | ||||||
|  | 	for _, path := range opts.SkipAuthRegex { | ||||||
|  | 		compiledRegex, err := regexp.Compile(path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		routes = append(routes, &allowedRoute{ | ||||||
|  | 			method:    "", | ||||||
|  | 			pathRegex: compiledRegex, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, methodPath := range opts.SkipAuthRoutes { | ||||||
|  | 		var ( | ||||||
|  | 			method string | ||||||
|  | 			path   string | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 		parts := strings.Split(methodPath, "=") | ||||||
|  | 		if len(parts) == 1 { | ||||||
|  | 			method = "" | ||||||
|  | 			path = parts[0] | ||||||
|  | 		} else { | ||||||
|  | 			method = strings.ToUpper(parts[0]) | ||||||
|  | 			path = strings.Join(parts[1:], "=") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		compiledRegex, err := regexp.Compile(path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		routes = append(routes, &allowedRoute{ | ||||||
|  | 			method:    method, | ||||||
|  | 			pathRegex: compiledRegex, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return routes, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
 | // GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
 | ||||||
| // redirect clients to once authenticated
 | // redirect clients to once authenticated
 | ||||||
| func (p *OAuthProxy) GetRedirectURI(host string) string { | func (p *OAuthProxy) GetRedirectURI(host string) string { | ||||||
|  | @ -584,16 +634,16 @@ func (p *OAuthProxy) IsValidRedirect(redirect string) bool { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // IsWhitelistedRequest is used to check if auth should be skipped for this request
 | // IsAllowedRequest is used to check if auth should be skipped for this request
 | ||||||
| func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) bool { | func (p *OAuthProxy) IsAllowedRequest(req *http.Request) bool { | ||||||
| 	isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS" | 	isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS" | ||||||
| 	return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path) || p.IsTrustedIP(req) | 	return isPreflightRequestAllowed || p.isAllowedRoute(req) || p.IsTrustedIP(req) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // IsWhitelistedPath is used to check if the request path is allowed without auth
 | // IsAllowedRoute is used to check if the request method & path is allowed without auth
 | ||||||
| func (p *OAuthProxy) IsWhitelistedPath(path string) bool { | func (p *OAuthProxy) isAllowedRoute(req *http.Request) bool { | ||||||
| 	for _, u := range p.compiledRegex { | 	for _, route := range p.allowedRoutes { | ||||||
| 		if u.MatchString(path) { | 		if (route.method == "" || req.Method == route.method) && route.pathRegex.MatchString(req.URL.Path) { | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -643,7 +693,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { | ||||||
| 	switch path := req.URL.Path; { | 	switch path := req.URL.Path; { | ||||||
| 	case path == p.RobotsPath: | 	case path == p.RobotsPath: | ||||||
| 		p.RobotsTxt(rw) | 		p.RobotsTxt(rw) | ||||||
| 	case p.IsWhitelistedRequest(req): | 	case p.IsAllowedRequest(req): | ||||||
| 		p.SkipAuthProxy(rw, req) | 		p.SkipAuthProxy(rw, req) | ||||||
| 	case path == p.SignInPath: | 	case path == p.SignInPath: | ||||||
| 		p.SignIn(rw, req) | 		p.SignIn(rw, req) | ||||||
|  | @ -831,7 +881,7 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) | ||||||
| 	rw.WriteHeader(http.StatusAccepted) | 	rw.WriteHeader(http.StatusAccepted) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SkipAuthProxy proxies whitelisted requests and skips authentication
 | // SkipAuthProxy proxies allowlisted requests and skips authentication
 | ||||||
| func (p *OAuthProxy) SkipAuthProxy(rw http.ResponseWriter, req *http.Request) { | func (p *OAuthProxy) SkipAuthProxy(rw http.ResponseWriter, req *http.Request) { | ||||||
| 	if p.skipAuthStripHeaders { | 	if p.skipAuthStripHeaders { | ||||||
| 		p.stripAuthHeaders(req) | 		p.stripAuthHeaders(req) | ||||||
|  | @ -1026,7 +1076,7 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // stripAuthHeaders removes Auth headers for whitelisted routes from skipAuthRegex
 | // stripAuthHeaders removes Auth headers for allowlisted routes from skipAuthRegex
 | ||||||
| func (p *OAuthProxy) stripAuthHeaders(req *http.Request) { | func (p *OAuthProxy) stripAuthHeaders(req *http.Request) { | ||||||
| 	if p.PassBasicAuth { | 	if p.PassBasicAuth { | ||||||
| 		req.Header.Del("X-Forwarded-User") | 		req.Header.Del("X-Forwarded-User") | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ package options | ||||||
| import ( | import ( | ||||||
| 	"crypto" | 	"crypto" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"regexp" |  | ||||||
| 
 | 
 | ||||||
| 	oidc "github.com/coreos/go-oidc" | 	oidc "github.com/coreos/go-oidc" | ||||||
| 	ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip" | 	ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip" | ||||||
|  | @ -67,6 +66,7 @@ type Options struct { | ||||||
| 	UpstreamServers Upstreams `cfg:",internal"` | 	UpstreamServers Upstreams `cfg:",internal"` | ||||||
| 
 | 
 | ||||||
| 	SkipAuthRegex         []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"` | 	SkipAuthRegex         []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"` | ||||||
|  | 	SkipAuthRoutes        []string `flag:"skip-auth-route" cfg:"skip_auth_routes"` | ||||||
| 	SkipAuthStripHeaders  bool     `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"` | 	SkipAuthStripHeaders  bool     `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"` | ||||||
| 	SkipJwtBearerTokens   bool     `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"` | 	SkipJwtBearerTokens   bool     `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"` | ||||||
| 	ExtraJwtIssuers       []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"` | 	ExtraJwtIssuers       []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"` | ||||||
|  | @ -114,7 +114,6 @@ type Options struct { | ||||||
| 
 | 
 | ||||||
| 	// internal values that are set after config validation
 | 	// internal values that are set after config validation
 | ||||||
| 	redirectURL        *url.URL | 	redirectURL        *url.URL | ||||||
| 	compiledRegex      []*regexp.Regexp |  | ||||||
| 	provider           providers.Provider | 	provider           providers.Provider | ||||||
| 	signatureData      *SignatureData | 	signatureData      *SignatureData | ||||||
| 	oidcVerifier       *oidc.IDTokenVerifier | 	oidcVerifier       *oidc.IDTokenVerifier | ||||||
|  | @ -124,7 +123,6 @@ type Options struct { | ||||||
| 
 | 
 | ||||||
| // Options for Getting internal values
 | // Options for Getting internal values
 | ||||||
| func (o *Options) GetRedirectURL() *url.URL                        { return o.redirectURL } | func (o *Options) GetRedirectURL() *url.URL                        { return o.redirectURL } | ||||||
| func (o *Options) GetCompiledRegex() []*regexp.Regexp              { return o.compiledRegex } |  | ||||||
| func (o *Options) GetProvider() providers.Provider                 { return o.provider } | func (o *Options) GetProvider() providers.Provider                 { return o.provider } | ||||||
| func (o *Options) GetSignatureData() *SignatureData                { return o.signatureData } | func (o *Options) GetSignatureData() *SignatureData                { return o.signatureData } | ||||||
| func (o *Options) GetOIDCVerifier() *oidc.IDTokenVerifier          { return o.oidcVerifier } | func (o *Options) GetOIDCVerifier() *oidc.IDTokenVerifier          { return o.oidcVerifier } | ||||||
|  | @ -133,7 +131,6 @@ func (o *Options) GetRealClientIPParser() ipapi.RealClientIPParser { return o.re | ||||||
| 
 | 
 | ||||||
| // Options for Setting internal values
 | // Options for Setting internal values
 | ||||||
| func (o *Options) SetRedirectURL(s *url.URL)                        { o.redirectURL = s } | func (o *Options) SetRedirectURL(s *url.URL)                        { o.redirectURL = s } | ||||||
| func (o *Options) SetCompiledRegex(s []*regexp.Regexp)              { o.compiledRegex = s } |  | ||||||
| func (o *Options) SetProvider(s providers.Provider)                 { o.provider = s } | func (o *Options) SetProvider(s providers.Provider)                 { o.provider = s } | ||||||
| func (o *Options) SetSignatureData(s *SignatureData)                { o.signatureData = s } | func (o *Options) SetSignatureData(s *SignatureData)                { o.signatureData = s } | ||||||
| func (o *Options) SetOIDCVerifier(s *oidc.IDTokenVerifier)          { o.oidcVerifier = s } | func (o *Options) SetOIDCVerifier(s *oidc.IDTokenVerifier)          { o.oidcVerifier = s } | ||||||
|  | @ -195,8 +192,9 @@ func NewFlagSet() *pflag.FlagSet { | ||||||
| 	flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header") | 	flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header") | ||||||
| 	flagSet.Bool("pass-authorization-header", false, "pass the Authorization 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.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.StringSlice("skip-auth-regex", []string{}, "(DEPRECATED for --skip-auth-route) bypass authentication for requests path's that match (may be given multiple times)") | ||||||
| 	flagSet.Bool("skip-auth-strip-headers", false, "strips X-Forwarded-* style authentication headers & Authorization header if they would be set by oauth2-proxy for request paths in --skip-auth-regex") | 	flagSet.StringSlice("skip-auth-route", []string{}, "bypass authentication for requests that match the method & path. Format: method=path_regex OR path_regex alone for all methods") | ||||||
|  | 	flagSet.Bool("skip-auth-strip-headers", false, "strips `X-Forwarded-*` style authentication headers & `Authorization` header if they would be set by oauth2-proxy for allowlisted requests (`--skip-auth-route`, `--skip-auth-regex`, `--skip-auth-preflight`, `--trusted-ip`)") | ||||||
| 	flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start") | 	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("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-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers") | ||||||
|  |  | ||||||
|  | @ -0,0 +1,70 @@ | ||||||
|  | package validation | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/ip" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func validateAllowlists(o *options.Options) []string { | ||||||
|  | 	msgs := []string{} | ||||||
|  | 
 | ||||||
|  | 	msgs = append(msgs, validateRoutes(o)...) | ||||||
|  | 	msgs = append(msgs, validateRegexes(o)...) | ||||||
|  | 	msgs = append(msgs, validateTrustedIPs(o)...) | ||||||
|  | 
 | ||||||
|  | 	if len(o.TrustedIPs) > 0 && o.ReverseProxy { | ||||||
|  | 		_, err := fmt.Fprintln(os.Stderr, "WARNING: mixing --trusted-ip with --reverse-proxy is a potential security vulnerability. An attacker can inject a trusted IP into an X-Real-IP or X-Forwarded-For header if they aren't properly protected outside of oauth2-proxy") | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return msgs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // validateRoutes validates method=path routes passed with options.SkipAuthRoutes
 | ||||||
|  | func validateRoutes(o *options.Options) []string { | ||||||
|  | 	msgs := []string{} | ||||||
|  | 	for _, route := range o.SkipAuthRoutes { | ||||||
|  | 		var regex string | ||||||
|  | 		parts := strings.Split(route, "=") | ||||||
|  | 		if len(parts) == 1 { | ||||||
|  | 			regex = parts[0] | ||||||
|  | 		} else { | ||||||
|  | 			regex = strings.Join(parts[1:], "=") | ||||||
|  | 		} | ||||||
|  | 		_, err := regexp.Compile(regex) | ||||||
|  | 		if err != nil { | ||||||
|  | 			msgs = append(msgs, fmt.Sprintf("error compiling regex /%s/: %v", regex, err)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return msgs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // validateRegex validates regex paths passed with options.SkipAuthRegex
 | ||||||
|  | func validateRegexes(o *options.Options) []string { | ||||||
|  | 	msgs := []string{} | ||||||
|  | 	for _, regex := range o.SkipAuthRegex { | ||||||
|  | 		_, err := regexp.Compile(regex) | ||||||
|  | 		if err != nil { | ||||||
|  | 			msgs = append(msgs, fmt.Sprintf("error compiling regex /%s/: %v", regex, err)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return msgs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // validateTrustedIPs validates IP/CIDRs for IP based allowlists
 | ||||||
|  | func validateTrustedIPs(o *options.Options) []string { | ||||||
|  | 	msgs := []string{} | ||||||
|  | 	for i, ipStr := range o.TrustedIPs { | ||||||
|  | 		if nil == ip.ParseIPNet(ipStr) { | ||||||
|  | 			msgs = append(msgs, fmt.Sprintf("trusted_ips[%d] (%s) could not be recognized", i, ipStr)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return msgs | ||||||
|  | } | ||||||
|  | @ -0,0 +1,149 @@ | ||||||
|  | package validation | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func Test_validateAllowlists(t *testing.T) { | ||||||
|  | 	opts := &options.Options{ | ||||||
|  | 		SkipAuthRoutes: []string{ | ||||||
|  | 			"POST=/foo/bar", | ||||||
|  | 			"PUT=^/foo/bar$", | ||||||
|  | 		}, | ||||||
|  | 		SkipAuthRegex: []string{"/foo/baz"}, | ||||||
|  | 		TrustedIPs: []string{ | ||||||
|  | 			"10.32.0.1/32", | ||||||
|  | 			"43.36.201.0/24", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	assert.Equal(t, []string{}, validateAllowlists(opts)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Test_validateRoutes(t *testing.T) { | ||||||
|  | 	testCases := map[string]struct { | ||||||
|  | 		Regexes  []string | ||||||
|  | 		Expected []string | ||||||
|  | 	}{ | ||||||
|  | 		"Valid regex routes": { | ||||||
|  | 			Regexes: []string{ | ||||||
|  | 				"/foo", | ||||||
|  | 				"POST=/foo/bar", | ||||||
|  | 				"PUT=^/foo/bar$", | ||||||
|  | 				"DELETE=/crazy/(?:regex)?/[^/]+/stuff$", | ||||||
|  | 			}, | ||||||
|  | 			Expected: []string{}, | ||||||
|  | 		}, | ||||||
|  | 		"Bad regexes do not compile": { | ||||||
|  | 			Regexes: []string{ | ||||||
|  | 				"POST=/(foo", | ||||||
|  | 				"OPTIONS=/foo/bar)", | ||||||
|  | 				"GET=^]/foo/bar[$", | ||||||
|  | 				"GET=^]/foo/bar[$", | ||||||
|  | 			}, | ||||||
|  | 			Expected: []string{ | ||||||
|  | 				"error compiling regex //(foo/: error parsing regexp: missing closing ): `/(foo`", | ||||||
|  | 				"error compiling regex //foo/bar)/: error parsing regexp: unexpected ): `/foo/bar)`", | ||||||
|  | 				"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`", | ||||||
|  | 				"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for testName, tc := range testCases { | ||||||
|  | 		t.Run(testName, func(t *testing.T) { | ||||||
|  | 			opts := &options.Options{ | ||||||
|  | 				SkipAuthRoutes: tc.Regexes, | ||||||
|  | 			} | ||||||
|  | 			msgs := validateRoutes(opts) | ||||||
|  | 			assert.Equal(t, tc.Expected, msgs) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Test_validateRegexes(t *testing.T) { | ||||||
|  | 	testCases := map[string]struct { | ||||||
|  | 		Regexes  []string | ||||||
|  | 		Expected []string | ||||||
|  | 	}{ | ||||||
|  | 		"Valid regex routes": { | ||||||
|  | 			Regexes: []string{ | ||||||
|  | 				"/foo", | ||||||
|  | 				"/foo/bar", | ||||||
|  | 				"^/foo/bar$", | ||||||
|  | 				"/crazy/(?:regex)?/[^/]+/stuff$", | ||||||
|  | 			}, | ||||||
|  | 			Expected: []string{}, | ||||||
|  | 		}, | ||||||
|  | 		"Bad regexes do not compile": { | ||||||
|  | 			Regexes: []string{ | ||||||
|  | 				"/(foo", | ||||||
|  | 				"/foo/bar)", | ||||||
|  | 				"^]/foo/bar[$", | ||||||
|  | 				"^]/foo/bar[$", | ||||||
|  | 			}, | ||||||
|  | 			Expected: []string{ | ||||||
|  | 				"error compiling regex //(foo/: error parsing regexp: missing closing ): `/(foo`", | ||||||
|  | 				"error compiling regex //foo/bar)/: error parsing regexp: unexpected ): `/foo/bar)`", | ||||||
|  | 				"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`", | ||||||
|  | 				"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for testName, tc := range testCases { | ||||||
|  | 		t.Run(testName, func(t *testing.T) { | ||||||
|  | 			opts := &options.Options{ | ||||||
|  | 				SkipAuthRegex: tc.Regexes, | ||||||
|  | 			} | ||||||
|  | 			msgs := validateRegexes(opts) | ||||||
|  | 			assert.Equal(t, tc.Expected, msgs) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Test_validateTrustedIPs(t *testing.T) { | ||||||
|  | 	testCases := map[string]struct { | ||||||
|  | 		TrustedIPs []string | ||||||
|  | 		Expected   []string | ||||||
|  | 	}{ | ||||||
|  | 		"Non-overlapping valid IPs": { | ||||||
|  | 			TrustedIPs: []string{ | ||||||
|  | 				"127.0.0.1", | ||||||
|  | 				"10.32.0.1/32", | ||||||
|  | 				"43.36.201.0/24", | ||||||
|  | 				"::1", | ||||||
|  | 				"2a12:105:ee7:9234:0:0:0:0/64", | ||||||
|  | 			}, | ||||||
|  | 			Expected: []string{}, | ||||||
|  | 		}, | ||||||
|  | 		"Overlapping valid IPs": { | ||||||
|  | 			TrustedIPs: []string{ | ||||||
|  | 				"135.180.78.199", | ||||||
|  | 				"135.180.78.199/32", | ||||||
|  | 				"d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4", | ||||||
|  | 				"d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4/128", | ||||||
|  | 			}, | ||||||
|  | 			Expected: []string{}, | ||||||
|  | 		}, | ||||||
|  | 		"Invalid IPs": { | ||||||
|  | 			TrustedIPs: []string{"[::1]", "alkwlkbn/32"}, | ||||||
|  | 			Expected: []string{ | ||||||
|  | 				"trusted_ips[0] ([::1]) could not be recognized", | ||||||
|  | 				"trusted_ips[1] (alkwlkbn/32) could not be recognized", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for testName, tc := range testCases { | ||||||
|  | 		t.Run(testName, func(t *testing.T) { | ||||||
|  | 			opts := &options.Options{ | ||||||
|  | 				TrustedIPs: tc.TrustedIPs, | ||||||
|  | 			} | ||||||
|  | 			msgs := validateTrustedIPs(opts) | ||||||
|  | 			assert.Equal(t, tc.Expected, msgs) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -9,7 +9,6 @@ import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| 	"regexp" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/coreos/go-oidc" | 	"github.com/coreos/go-oidc" | ||||||
|  | @ -184,15 +183,6 @@ func Validate(o *options.Options) error { | ||||||
| 	o.SetRedirectURL(redirectURL) | 	o.SetRedirectURL(redirectURL) | ||||||
| 
 | 
 | ||||||
| 	msgs = append(msgs, validateUpstreams(o.UpstreamServers)...) | 	msgs = append(msgs, validateUpstreams(o.UpstreamServers)...) | ||||||
| 
 |  | ||||||
| 	for _, u := range o.SkipAuthRegex { |  | ||||||
| 		compiledRegex, err := regexp.Compile(u) |  | ||||||
| 		if err != nil { |  | ||||||
| 			msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err)) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		o.SetCompiledRegex(append(o.GetCompiledRegex(), compiledRegex)) |  | ||||||
| 	} |  | ||||||
| 	msgs = parseProviderInfo(o, msgs) | 	msgs = parseProviderInfo(o, msgs) | ||||||
| 
 | 
 | ||||||
| 	if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" { | 	if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" { | ||||||
|  | @ -223,18 +213,8 @@ func Validate(o *options.Options) error { | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(o.TrustedIPs) > 0 && o.ReverseProxy { | 	// Do this after ReverseProxy validation for TrustedIP coordinated checks
 | ||||||
| 		_, err := fmt.Fprintln(os.Stderr, "WARNING: trusting of IPs with --reverse-proxy poses risks if a header spoofing attack is possible.") | 	msgs = append(msgs, validateAllowlists(o)...) | ||||||
| 		if err != nil { |  | ||||||
| 			panic(err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i, ipStr := range o.TrustedIPs { |  | ||||||
| 		if nil == ip.ParseIPNet(ipStr) { |  | ||||||
| 			msgs = append(msgs, fmt.Sprintf("trusted_ips[%d] (%s) could not be recognized", i, ipStr)) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	if len(msgs) != 0 { | 	if len(msgs) != 0 { | ||||||
| 		return fmt.Errorf("invalid configuration:\n  %s", | 		return fmt.Errorf("invalid configuration:\n  %s", | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ package validation | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto" | 	"crypto" | ||||||
| 	"errors" |  | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
|  | @ -78,12 +77,19 @@ func TestClientSecretFileOption(t *testing.T) { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("failed to create temp file: %v", err) | 		t.Fatalf("failed to create temp file: %v", err) | ||||||
| 	} | 	} | ||||||
| 	f.WriteString("testcase") | 	_, err = f.WriteString("testcase") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("failed to write to temp file: %v", err) | ||||||
|  | 	} | ||||||
| 	if err := f.Close(); err != nil { | 	if err := f.Close(); err != nil { | ||||||
| 		t.Fatalf("failed to close temp file: %v", err) | 		t.Fatalf("failed to close temp file: %v", err) | ||||||
| 	} | 	} | ||||||
| 	clientSecretFileName := f.Name() | 	clientSecretFileName := f.Name() | ||||||
| 	defer os.Remove(clientSecretFileName) | 	defer func(t *testing.T) { | ||||||
|  | 		if err := os.Remove(clientSecretFileName); err != nil { | ||||||
|  | 			t.Fatalf("failed to delete temp file: %v", err) | ||||||
|  | 		} | ||||||
|  | 	}(t) | ||||||
| 
 | 
 | ||||||
| 	o := options.NewOptions() | 	o := options.NewOptions() | ||||||
| 	o.Cookie.Secret = cookieSecret | 	o.Cookie.Secret = cookieSecret | ||||||
|  | @ -144,41 +150,6 @@ func TestRedirectURL(t *testing.T) { | ||||||
| 	assert.Equal(t, expected, o.GetRedirectURL()) | 	assert.Equal(t, expected, o.GetRedirectURL()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestCompiledRegex(t *testing.T) { |  | ||||||
| 	o := testOptions() |  | ||||||
| 	regexps := []string{"/foo/.*", "/ba[rz]/quux"} |  | ||||||
| 	o.SkipAuthRegex = regexps |  | ||||||
| 	assert.Equal(t, nil, Validate(o)) |  | ||||||
| 	actual := make([]string, 0) |  | ||||||
| 	for _, regex := range o.GetCompiledRegex() { |  | ||||||
| 		actual = append(actual, regex.String()) |  | ||||||
| 	} |  | ||||||
| 	assert.Equal(t, regexps, actual) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestCompiledRegexError(t *testing.T) { |  | ||||||
| 	o := testOptions() |  | ||||||
| 	o.SkipAuthRegex = []string{"(foobaz", "barquux)"} |  | ||||||
| 	err := Validate(o) |  | ||||||
| 	assert.NotEqual(t, nil, err) |  | ||||||
| 
 |  | ||||||
| 	expected := errorMsg([]string{ |  | ||||||
| 		"error compiling regex=\"(foobaz\" error parsing regexp: " + |  | ||||||
| 			"missing closing ): `(foobaz`", |  | ||||||
| 		"error compiling regex=\"barquux)\" error parsing regexp: " + |  | ||||||
| 			"unexpected ): `barquux)`"}) |  | ||||||
| 	assert.Equal(t, expected, err.Error()) |  | ||||||
| 
 |  | ||||||
| 	o.SkipAuthRegex = []string{"foobaz", "barquux)"} |  | ||||||
| 	err = Validate(o) |  | ||||||
| 	assert.NotEqual(t, nil, err) |  | ||||||
| 
 |  | ||||||
| 	expected = errorMsg([]string{ |  | ||||||
| 		"error compiling regex=\"barquux)\" error parsing regexp: " + |  | ||||||
| 			"unexpected ): `barquux)`"}) |  | ||||||
| 	assert.Equal(t, expected, err.Error()) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestDefaultProviderApiSettings(t *testing.T) { | func TestDefaultProviderApiSettings(t *testing.T) { | ||||||
| 	o := testOptions() | 	o := testOptions() | ||||||
| 	assert.Equal(t, nil, Validate(o)) | 	assert.Equal(t, nil, Validate(o)) | ||||||
|  | @ -337,45 +308,6 @@ func TestRealClientIPHeader(t *testing.T) { | ||||||
| 	assert.Nil(t, o.GetRealClientIPParser()) | 	assert.Nil(t, o.GetRealClientIPParser()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestIPCIDRSetOption(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		name       string |  | ||||||
| 		trustedIPs []string |  | ||||||
| 		err        error |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			"TestSomeIPs", |  | ||||||
| 			[]string{"127.0.0.1", "10.32.0.1/32", "43.36.201.0/24", "::1", "2a12:105:ee7:9234:0:0:0:0/64"}, |  | ||||||
| 			nil, |  | ||||||
| 		}, { |  | ||||||
| 			"TestOverlappingIPs", |  | ||||||
| 			[]string{"135.180.78.199", "135.180.78.199/32", "d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4", "d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4/128"}, |  | ||||||
| 			nil, |  | ||||||
| 		}, { |  | ||||||
| 			"TestInvalidIPs", |  | ||||||
| 			[]string{"[::1]", "alkwlkbn/32"}, |  | ||||||
| 			errors.New( |  | ||||||
| 				"invalid configuration:\n" + |  | ||||||
| 					"  trusted_ips[0] ([::1]) could not be recognized\n" + |  | ||||||
| 					"  trusted_ips[1] (alkwlkbn/32) could not be recognized", |  | ||||||
| 			), |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.name, func(t *testing.T) { |  | ||||||
| 			o := testOptions() |  | ||||||
| 			o.TrustedIPs = tt.trustedIPs |  | ||||||
| 			err := Validate(o) |  | ||||||
| 			if tt.err == nil { |  | ||||||
| 				assert.Nil(t, err) |  | ||||||
| 			} else { |  | ||||||
| 				assert.Equal(t, tt.err.Error(), err.Error()) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestProviderCAFilesError(t *testing.T) { | func TestProviderCAFilesError(t *testing.T) { | ||||||
| 	file, err := ioutil.TempFile("", "absent.*.crt") | 	file, err := ioutil.TempFile("", "absent.*.crt") | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue