You've already forked oauth2-proxy
mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-07-15 01:44:22 +02:00
Merge pull request #826 from oauth2-proxy/header-middlewares
Integrate new header injectors into project
This commit is contained in:
@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
## Changes since v6.1.1
|
## Changes since v6.1.1
|
||||||
|
|
||||||
|
- [#826](https://github.com/oauth2-proxy/oauth2-proxy/pull/826) Integrate new header injectors into project (@JoelSpeed)
|
||||||
- [#898](https://github.com/oauth2-proxy/oauth2-proxy/pull/898) Migrate documentation to Docusaurus (@JoelSpeed)
|
- [#898](https://github.com/oauth2-proxy/oauth2-proxy/pull/898) Migrate documentation to Docusaurus (@JoelSpeed)
|
||||||
- [#754](https://github.com/oauth2-proxy/oauth2-proxy/pull/754) Azure token refresh (@codablock)
|
- [#754](https://github.com/oauth2-proxy/oauth2-proxy/pull/754) Azure token refresh (@codablock)
|
||||||
- [#825](https://github.com/oauth2-proxy/oauth2-proxy/pull/825) Fix code coverage reporting on GitHub actions(@JoelSpeed)
|
- [#825](https://github.com/oauth2-proxy/oauth2-proxy/pull/825) Fix code coverage reporting on GitHub actions(@JoelSpeed)
|
||||||
|
30
main.go
30
main.go
@ -3,17 +3,14 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/justinas/alice"
|
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
|
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -63,33 +60,8 @@ func main() {
|
|||||||
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
chain := alice.New()
|
|
||||||
|
|
||||||
if opts.ForceHTTPS {
|
|
||||||
_, httpsPort, err := net.SplitHostPort(opts.HTTPSAddress)
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatalf("FATAL: invalid HTTPS address %q: %v", opts.HTTPAddress, err)
|
|
||||||
}
|
|
||||||
chain = chain.Append(middleware.NewRedirectToHTTPS(httpsPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
healthCheckPaths := []string{opts.PingPath}
|
|
||||||
healthCheckUserAgents := []string{opts.PingUserAgent}
|
|
||||||
if opts.GCPHealthChecks {
|
|
||||||
healthCheckPaths = append(healthCheckPaths, "/liveness_check", "/readiness_check")
|
|
||||||
healthCheckUserAgents = append(healthCheckUserAgents, "GoogleHC/1.0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// To silence logging of health checks, register the health check handler before
|
|
||||||
// the logging handler
|
|
||||||
if opts.Logging.SilencePing {
|
|
||||||
chain = chain.Append(middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents), LoggingHandler)
|
|
||||||
} else {
|
|
||||||
chain = chain.Append(LoggingHandler, middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents))
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
Handler: chain.Then(oauthproxy),
|
Handler: oauthproxy,
|
||||||
Opts: opts,
|
Opts: opts,
|
||||||
stop: make(chan struct{}, 1),
|
stop: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
|
226
oauthproxy.go
226
oauthproxy.go
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
b64 "encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -98,7 +97,6 @@ type OAuthProxy struct {
|
|||||||
PassAuthorization bool
|
PassAuthorization bool
|
||||||
PreferEmailToUser bool
|
PreferEmailToUser bool
|
||||||
skipAuthPreflight bool
|
skipAuthPreflight bool
|
||||||
skipAuthStripHeaders bool
|
|
||||||
skipJwtBearerTokens bool
|
skipJwtBearerTokens bool
|
||||||
mainJwtBearerVerifier *oidc.IDTokenVerifier
|
mainJwtBearerVerifier *oidc.IDTokenVerifier
|
||||||
extraJwtBearerVerifiers []*oidc.IDTokenVerifier
|
extraJwtBearerVerifiers []*oidc.IDTokenVerifier
|
||||||
@ -110,6 +108,8 @@ type OAuthProxy struct {
|
|||||||
AllowedGroups []string
|
AllowedGroups []string
|
||||||
|
|
||||||
sessionChain alice.Chain
|
sessionChain alice.Chain
|
||||||
|
headersChain alice.Chain
|
||||||
|
preAuthChain alice.Chain
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
|
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
|
||||||
@ -169,7 +169,15 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preAuthChain, err := buildPreAuthChain(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not build pre-auth chain: %v", err)
|
||||||
|
}
|
||||||
sessionChain := buildSessionChain(opts, sessionStore, basicAuthValidator)
|
sessionChain := buildSessionChain(opts, sessionStore, basicAuthValidator)
|
||||||
|
headersChain, err := buildHeadersChain(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not build headers chain: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &OAuthProxy{
|
return &OAuthProxy{
|
||||||
CookieName: opts.Cookie.Name,
|
CookieName: opts.Cookie.Name,
|
||||||
@ -201,20 +209,10 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||||||
allowedRoutes: allowedRoutes,
|
allowedRoutes: allowedRoutes,
|
||||||
whitelistDomains: opts.WhitelistDomains,
|
whitelistDomains: opts.WhitelistDomains,
|
||||||
skipAuthPreflight: opts.SkipAuthPreflight,
|
skipAuthPreflight: opts.SkipAuthPreflight,
|
||||||
skipAuthStripHeaders: opts.SkipAuthStripHeaders,
|
|
||||||
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
|
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
|
||||||
mainJwtBearerVerifier: opts.GetOIDCVerifier(),
|
mainJwtBearerVerifier: opts.GetOIDCVerifier(),
|
||||||
extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
|
extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
|
||||||
realClientIPParser: opts.GetRealClientIPParser(),
|
realClientIPParser: opts.GetRealClientIPParser(),
|
||||||
SetXAuthRequest: opts.SetXAuthRequest,
|
|
||||||
PassBasicAuth: opts.PassBasicAuth,
|
|
||||||
SetBasicAuth: opts.SetBasicAuth,
|
|
||||||
PassUserHeaders: opts.PassUserHeaders,
|
|
||||||
BasicAuthPassword: opts.BasicAuthPassword,
|
|
||||||
PassAccessToken: opts.PassAccessToken,
|
|
||||||
SetAuthorization: opts.SetAuthorization,
|
|
||||||
PassAuthorization: opts.PassAuthorization,
|
|
||||||
PreferEmailToUser: opts.PreferEmailToUser,
|
|
||||||
SkipProviderButton: opts.SkipProviderButton,
|
SkipProviderButton: opts.SkipProviderButton,
|
||||||
templates: templates,
|
templates: templates,
|
||||||
trustedIPs: trustedIPs,
|
trustedIPs: trustedIPs,
|
||||||
@ -226,12 +224,46 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||||||
basicAuthValidator: basicAuthValidator,
|
basicAuthValidator: basicAuthValidator,
|
||||||
displayHtpasswdForm: basicAuthValidator != nil,
|
displayHtpasswdForm: basicAuthValidator != nil,
|
||||||
sessionChain: sessionChain,
|
sessionChain: sessionChain,
|
||||||
|
headersChain: headersChain,
|
||||||
|
preAuthChain: preAuthChain,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionStore, validator basic.Validator) alice.Chain {
|
// buildPreAuthChain constructs a chain that should process every request before
|
||||||
|
// the OAuth2 Proxy authentication logic kicks in.
|
||||||
|
// For example forcing HTTPS or health checks.
|
||||||
|
func buildPreAuthChain(opts *options.Options) (alice.Chain, error) {
|
||||||
chain := alice.New(middleware.NewScope())
|
chain := alice.New(middleware.NewScope())
|
||||||
|
|
||||||
|
if opts.ForceHTTPS {
|
||||||
|
_, httpsPort, err := net.SplitHostPort(opts.HTTPSAddress)
|
||||||
|
if err != nil {
|
||||||
|
return alice.Chain{}, fmt.Errorf("invalid HTTPS address %q: %v", opts.HTTPAddress, err)
|
||||||
|
}
|
||||||
|
chain = chain.Append(middleware.NewRedirectToHTTPS(httpsPort))
|
||||||
|
}
|
||||||
|
|
||||||
|
healthCheckPaths := []string{opts.PingPath}
|
||||||
|
healthCheckUserAgents := []string{opts.PingUserAgent}
|
||||||
|
if opts.GCPHealthChecks {
|
||||||
|
healthCheckPaths = append(healthCheckPaths, "/liveness_check", "/readiness_check")
|
||||||
|
healthCheckUserAgents = append(healthCheckUserAgents, "GoogleHC/1.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// To silence logging of health checks, register the health check handler before
|
||||||
|
// the logging handler
|
||||||
|
if opts.Logging.SilencePing {
|
||||||
|
chain = chain.Append(middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents), LoggingHandler)
|
||||||
|
} else {
|
||||||
|
chain = chain.Append(LoggingHandler, middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents))
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionStore, validator basic.Validator) alice.Chain {
|
||||||
|
chain := alice.New()
|
||||||
|
|
||||||
if opts.SkipJwtBearerTokens {
|
if opts.SkipJwtBearerTokens {
|
||||||
sessionLoaders := []middlewareapi.TokenToSessionLoader{}
|
sessionLoaders := []middlewareapi.TokenToSessionLoader{}
|
||||||
if opts.GetOIDCVerifier() != nil {
|
if opts.GetOIDCVerifier() != nil {
|
||||||
@ -264,6 +296,20 @@ func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionSt
|
|||||||
return chain
|
return chain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildHeadersChain(opts *options.Options) (alice.Chain, error) {
|
||||||
|
requestInjector, err := middleware.NewRequestHeaderInjector(opts.InjectRequestHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return alice.Chain{}, fmt.Errorf("error constructing request header injector: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responseInjector, err := middleware.NewResponseHeaderInjector(opts.InjectResponseHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return alice.Chain{}, fmt.Errorf("error constructing request header injector: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return alice.New(requestInjector, responseInjector), nil
|
||||||
|
}
|
||||||
|
|
||||||
func buildSignInMessage(opts *options.Options) string {
|
func buildSignInMessage(opts *options.Options) string {
|
||||||
var msg string
|
var msg string
|
||||||
if len(opts.Banner) >= 1 {
|
if len(opts.Banner) >= 1 {
|
||||||
@ -685,6 +731,10 @@ func (p *OAuthProxy) IsTrustedIP(req *http.Request) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
p.preAuthChain.Then(http.HandlerFunc(p.serveHTTP)).ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OAuthProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
if req.URL.Path != p.AuthOnlyPath && strings.HasPrefix(req.URL.Path, p.ProxyPrefix) {
|
if req.URL.Path != p.AuthOnlyPath && strings.HasPrefix(req.URL.Path, p.ProxyPrefix) {
|
||||||
prepareNoCache(rw)
|
prepareNoCache(rw)
|
||||||
}
|
}
|
||||||
@ -884,15 +934,14 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
|
|||||||
|
|
||||||
// we are authenticated
|
// we are authenticated
|
||||||
p.addHeadersForProxying(rw, req, session)
|
p.addHeadersForProxying(rw, req, session)
|
||||||
|
p.headersChain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
rw.WriteHeader(http.StatusAccepted)
|
rw.WriteHeader(http.StatusAccepted)
|
||||||
|
})).ServeHTTP(rw, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SkipAuthProxy proxies allowlisted 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 {
|
p.headersChain.Then(p.serveMux).ServeHTTP(rw, req)
|
||||||
p.stripAuthHeaders(req)
|
|
||||||
}
|
|
||||||
p.serveMux.ServeHTTP(rw, req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy proxies the user request if the user is authenticated else it prompts
|
// Proxy proxies the user request if the user is authenticated else it prompts
|
||||||
@ -903,8 +952,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
|||||||
case nil:
|
case nil:
|
||||||
// we are authenticated
|
// we are authenticated
|
||||||
p.addHeadersForProxying(rw, req, session)
|
p.addHeadersForProxying(rw, req, session)
|
||||||
p.serveMux.ServeHTTP(rw, req)
|
p.headersChain.Then(p.serveMux).ServeHTTP(rw, req)
|
||||||
|
|
||||||
case ErrNeedsLogin:
|
case ErrNeedsLogin:
|
||||||
// we need to send the user to a login screen
|
// we need to send the user to a login screen
|
||||||
if isAjax(req) {
|
if isAjax(req) {
|
||||||
@ -961,120 +1009,6 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
|
|||||||
|
|
||||||
// addHeadersForProxying adds the appropriate headers the request / response for proxying
|
// addHeadersForProxying adds the appropriate headers the request / response for proxying
|
||||||
func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) {
|
func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) {
|
||||||
if p.PassBasicAuth {
|
|
||||||
if p.PreferEmailToUser && session.Email != "" {
|
|
||||||
req.SetBasicAuth(session.Email, p.BasicAuthPassword)
|
|
||||||
req.Header["X-Forwarded-User"] = []string{session.Email}
|
|
||||||
req.Header.Del("X-Forwarded-Email")
|
|
||||||
} else {
|
|
||||||
req.SetBasicAuth(session.User, p.BasicAuthPassword)
|
|
||||||
req.Header["X-Forwarded-User"] = []string{session.User}
|
|
||||||
if session.Email != "" {
|
|
||||||
req.Header["X-Forwarded-Email"] = []string{session.Email}
|
|
||||||
} else {
|
|
||||||
req.Header.Del("X-Forwarded-Email")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if session.PreferredUsername != "" {
|
|
||||||
req.Header["X-Forwarded-Preferred-Username"] = []string{session.PreferredUsername}
|
|
||||||
} else {
|
|
||||||
req.Header.Del("X-Forwarded-Preferred-Username")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.PassUserHeaders {
|
|
||||||
if p.PreferEmailToUser && session.Email != "" {
|
|
||||||
req.Header["X-Forwarded-User"] = []string{session.Email}
|
|
||||||
req.Header.Del("X-Forwarded-Email")
|
|
||||||
} else {
|
|
||||||
req.Header["X-Forwarded-User"] = []string{session.User}
|
|
||||||
if session.Email != "" {
|
|
||||||
req.Header["X-Forwarded-Email"] = []string{session.Email}
|
|
||||||
} else {
|
|
||||||
req.Header.Del("X-Forwarded-Email")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if session.PreferredUsername != "" {
|
|
||||||
req.Header["X-Forwarded-Preferred-Username"] = []string{session.PreferredUsername}
|
|
||||||
} else {
|
|
||||||
req.Header.Del("X-Forwarded-Preferred-Username")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(session.Groups) > 0 {
|
|
||||||
for _, group := range session.Groups {
|
|
||||||
req.Header.Add("X-Forwarded-Groups", group)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
req.Header.Del("X-Forwarded-Groups")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.SetXAuthRequest {
|
|
||||||
rw.Header().Set("X-Auth-Request-User", session.User)
|
|
||||||
if session.Email != "" {
|
|
||||||
rw.Header().Set("X-Auth-Request-Email", session.Email)
|
|
||||||
} else {
|
|
||||||
rw.Header().Del("X-Auth-Request-Email")
|
|
||||||
}
|
|
||||||
if session.PreferredUsername != "" {
|
|
||||||
rw.Header().Set("X-Auth-Request-Preferred-Username", session.PreferredUsername)
|
|
||||||
} else {
|
|
||||||
rw.Header().Del("X-Auth-Request-Preferred-Username")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.PassAccessToken {
|
|
||||||
if session.AccessToken != "" {
|
|
||||||
rw.Header().Set("X-Auth-Request-Access-Token", session.AccessToken)
|
|
||||||
} else {
|
|
||||||
rw.Header().Del("X-Auth-Request-Access-Token")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(session.Groups) > 0 {
|
|
||||||
for _, group := range session.Groups {
|
|
||||||
rw.Header().Add("X-Auth-Request-Groups", group)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rw.Header().Del("X-Auth-Request-Groups")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.PassAccessToken {
|
|
||||||
if session.AccessToken != "" {
|
|
||||||
req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken}
|
|
||||||
} else {
|
|
||||||
req.Header.Del("X-Forwarded-Access-Token")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.PassAuthorization {
|
|
||||||
if session.IDToken != "" {
|
|
||||||
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", session.IDToken)}
|
|
||||||
} else {
|
|
||||||
req.Header.Del("Authorization")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.SetBasicAuth {
|
|
||||||
switch {
|
|
||||||
case p.PreferEmailToUser && session.Email != "":
|
|
||||||
authVal := b64.StdEncoding.EncodeToString([]byte(session.Email + ":" + p.BasicAuthPassword))
|
|
||||||
rw.Header().Set("Authorization", "Basic "+authVal)
|
|
||||||
case session.User != "":
|
|
||||||
authVal := b64.StdEncoding.EncodeToString([]byte(session.User + ":" + p.BasicAuthPassword))
|
|
||||||
rw.Header().Set("Authorization", "Basic "+authVal)
|
|
||||||
default:
|
|
||||||
rw.Header().Del("Authorization")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.SetAuthorization {
|
|
||||||
if session.IDToken != "" {
|
|
||||||
rw.Header().Set("Authorization", fmt.Sprintf("Bearer %s", session.IDToken))
|
|
||||||
} else {
|
|
||||||
rw.Header().Del("Authorization")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if session.Email == "" {
|
if session.Email == "" {
|
||||||
rw.Header().Set("GAP-Auth", session.User)
|
rw.Header().Set("GAP-Auth", session.User)
|
||||||
} else {
|
} else {
|
||||||
@ -1082,32 +1016,6 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stripAuthHeaders removes Auth headers for allowlisted routes from skipAuthRegex
|
|
||||||
func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
|
|
||||||
if p.PassBasicAuth {
|
|
||||||
req.Header.Del("X-Forwarded-User")
|
|
||||||
req.Header.Del("X-Forwarded-Groups")
|
|
||||||
req.Header.Del("X-Forwarded-Email")
|
|
||||||
req.Header.Del("X-Forwarded-Preferred-Username")
|
|
||||||
req.Header.Del("Authorization")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.PassUserHeaders {
|
|
||||||
req.Header.Del("X-Forwarded-User")
|
|
||||||
req.Header.Del("X-Forwarded-Groups")
|
|
||||||
req.Header.Del("X-Forwarded-Email")
|
|
||||||
req.Header.Del("X-Forwarded-Preferred-Username")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.PassAccessToken {
|
|
||||||
req.Header.Del("X-Forwarded-Access-Token")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.PassAuthorization {
|
|
||||||
req.Header.Del("Authorization")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isAjax checks if a request is an ajax request
|
// isAjax checks if a request is an ajax request
|
||||||
func isAjax(req *http.Request) bool {
|
func isAjax(req *http.Request) bool {
|
||||||
acceptValues := req.Header.Values("Accept")
|
acceptValues := req.Header.Values("Accept")
|
||||||
|
@ -495,6 +495,8 @@ func TestBasicAuthPassword(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
basicAuthPassword := "This is a secure password"
|
||||||
opts := baseTestOptions()
|
opts := baseTestOptions()
|
||||||
opts.UpstreamServers = options.Upstreams{
|
opts.UpstreamServers = options.Upstreams{
|
||||||
{
|
{
|
||||||
@ -505,11 +507,22 @@ func TestBasicAuthPassword(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts.Cookie.Secure = false
|
opts.Cookie.Secure = false
|
||||||
opts.PassBasicAuth = true
|
opts.InjectRequestHeaders = []options.Header{
|
||||||
opts.SetBasicAuth = true
|
{
|
||||||
opts.PassUserHeaders = true
|
Name: "Authorization",
|
||||||
opts.PreferEmailToUser = true
|
Values: []options.HeaderValue{
|
||||||
opts.BasicAuthPassword = "This is a secure password"
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
BasicAuthPassword: &options.SecretSource{
|
||||||
|
Value: []byte(base64.StdEncoding.EncodeToString([]byte(basicAuthPassword))),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
err := validation.Validate(opts)
|
err := validation.Validate(opts)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
@ -524,148 +537,44 @@ func TestBasicAuthPassword(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the required session
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
|
req, _ := http.NewRequest("GET", "/", nil)
|
||||||
req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
|
err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
|
||||||
proxy.ServeHTTP(rw, req)
|
Email: emailAddress,
|
||||||
if rw.Code >= 400 {
|
|
||||||
t.Fatalf("expected 3xx got %d", rw.Code)
|
|
||||||
}
|
|
||||||
cookie := rw.Header().Values("Set-Cookie")[1]
|
|
||||||
|
|
||||||
cookieName := proxy.CookieName
|
|
||||||
var value string
|
|
||||||
keyPrefix := cookieName + "="
|
|
||||||
|
|
||||||
for _, field := range strings.Split(cookie, "; ") {
|
|
||||||
value = strings.TrimPrefix(field, keyPrefix)
|
|
||||||
if value != field {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
value = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
|
|
||||||
req.AddCookie(&http.Cookie{
|
|
||||||
Name: cookieName,
|
|
||||||
Value: value,
|
|
||||||
Path: "/",
|
|
||||||
Expires: time.Now().Add(time.Duration(24)),
|
|
||||||
HttpOnly: true,
|
|
||||||
})
|
})
|
||||||
req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Extract the cookie value to inject into the test request
|
||||||
|
cookie := rw.Header().Values("Set-Cookie")[0]
|
||||||
|
|
||||||
|
req, _ = http.NewRequest("GET", "/", nil)
|
||||||
|
req.Header.Set("Cookie", cookie)
|
||||||
rw = httptest.NewRecorder()
|
rw = httptest.NewRecorder()
|
||||||
proxy.ServeHTTP(rw, req)
|
proxy.ServeHTTP(rw, req)
|
||||||
|
|
||||||
// The username in the basic auth credentials is expected to be equal to the email address from the
|
// The username in the basic auth credentials is expected to be equal to the email address from the
|
||||||
// auth response, so we use the same variable here.
|
// auth response, so we use the same variable here.
|
||||||
expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
|
expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+basicAuthPassword))
|
||||||
assert.Equal(t, expectedHeader, rw.Body.String())
|
assert.Equal(t, expectedHeader, rw.Body.String())
|
||||||
providerServer.Close()
|
providerServer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBasicAuthWithEmail(t *testing.T) {
|
|
||||||
opts := baseTestOptions()
|
|
||||||
opts.PassBasicAuth = true
|
|
||||||
opts.PassUserHeaders = false
|
|
||||||
opts.PreferEmailToUser = false
|
|
||||||
opts.BasicAuthPassword = "This is a secure password"
|
|
||||||
err := validation.Validate(opts)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
const emailAddress = "john.doe@example.com"
|
|
||||||
const userName = "9fcab5c9b889a557"
|
|
||||||
|
|
||||||
// The username in the basic auth credentials is expected to be equal to the email address from the
|
|
||||||
expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
|
|
||||||
expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
|
|
||||||
|
|
||||||
created := time.Now()
|
|
||||||
session := &sessions.SessionState{
|
|
||||||
User: userName,
|
|
||||||
Email: emailAddress,
|
|
||||||
AccessToken: "oauth_token",
|
|
||||||
CreatedAt: &created,
|
|
||||||
}
|
|
||||||
{
|
|
||||||
rw := httptest.NewRecorder()
|
|
||||||
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
|
|
||||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
|
||||||
return email == emailAddress
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
proxy.addHeadersForProxying(rw, req, session)
|
|
||||||
assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
|
|
||||||
assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.PreferEmailToUser = true
|
|
||||||
{
|
|
||||||
rw := httptest.NewRecorder()
|
|
||||||
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
|
|
||||||
|
|
||||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
|
||||||
return email == emailAddress
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
proxy.addHeadersForProxying(rw, req, session)
|
|
||||||
assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
|
|
||||||
assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPassUserHeadersWithEmail(t *testing.T) {
|
|
||||||
opts := baseTestOptions()
|
|
||||||
err := validation.Validate(opts)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
const emailAddress = "john.doe@example.com"
|
|
||||||
const userName = "9fcab5c9b889a557"
|
|
||||||
|
|
||||||
created := time.Now()
|
|
||||||
session := &sessions.SessionState{
|
|
||||||
User: userName,
|
|
||||||
Email: emailAddress,
|
|
||||||
AccessToken: "oauth_token",
|
|
||||||
CreatedAt: &created,
|
|
||||||
}
|
|
||||||
{
|
|
||||||
rw := httptest.NewRecorder()
|
|
||||||
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
|
|
||||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
|
||||||
return email == emailAddress
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
proxy.addHeadersForProxying(rw, req, session)
|
|
||||||
assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.PreferEmailToUser = true
|
|
||||||
{
|
|
||||||
rw := httptest.NewRecorder()
|
|
||||||
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
|
|
||||||
|
|
||||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
|
||||||
return email == emailAddress
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
proxy.addHeadersForProxying(rw, req, session)
|
|
||||||
assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPassGroupsHeadersWithGroups(t *testing.T) {
|
func TestPassGroupsHeadersWithGroups(t *testing.T) {
|
||||||
opts := baseTestOptions()
|
opts := baseTestOptions()
|
||||||
|
opts.InjectRequestHeaders = []options.Header{
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Groups",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "groups",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
err := validation.Validate(opts)
|
err := validation.Validate(opts)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
@ -681,162 +590,28 @@ func TestPassGroupsHeadersWithGroups(t *testing.T) {
|
|||||||
AccessToken: "oauth_token",
|
AccessToken: "oauth_token",
|
||||||
CreatedAt: &created,
|
CreatedAt: &created,
|
||||||
}
|
}
|
||||||
{
|
|
||||||
rw := httptest.NewRecorder()
|
|
||||||
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
|
|
||||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
||||||
return email == emailAddress
|
return email == emailAddress
|
||||||
})
|
})
|
||||||
if err != nil {
|
assert.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
// Save the required session
|
||||||
proxy.addHeadersForProxying(rw, req, session)
|
rw := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
err = proxy.sessionStore.Save(rw, req, session)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Extract the cookie value to inject into the test request
|
||||||
|
cookie := rw.Header().Values("Set-Cookie")[0]
|
||||||
|
|
||||||
|
req, _ = http.NewRequest("GET", "/", nil)
|
||||||
|
req.Header.Set("Cookie", cookie)
|
||||||
|
rw = httptest.NewRecorder()
|
||||||
|
proxy.ServeHTTP(rw, req)
|
||||||
|
|
||||||
assert.Equal(t, groups, req.Header["X-Forwarded-Groups"])
|
assert.Equal(t, groups, req.Header["X-Forwarded-Groups"])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func TestStripAuthHeaders(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
SkipAuthStripHeaders bool
|
|
||||||
PassBasicAuth bool
|
|
||||||
PassUserHeaders bool
|
|
||||||
PassAccessToken bool
|
|
||||||
PassAuthorization bool
|
|
||||||
StrippedHeaders map[string]bool
|
|
||||||
}{
|
|
||||||
"Default options": {
|
|
||||||
SkipAuthStripHeaders: true,
|
|
||||||
PassBasicAuth: true,
|
|
||||||
PassUserHeaders: true,
|
|
||||||
PassAccessToken: false,
|
|
||||||
PassAuthorization: false,
|
|
||||||
StrippedHeaders: map[string]bool{
|
|
||||||
"X-Forwarded-User": true,
|
|
||||||
"X-Forwared-Groups": true,
|
|
||||||
"X-Forwarded-Email": true,
|
|
||||||
"X-Forwarded-Preferred-Username": true,
|
|
||||||
"X-Forwarded-Access-Token": false,
|
|
||||||
"Authorization": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Pass access token": {
|
|
||||||
SkipAuthStripHeaders: true,
|
|
||||||
PassBasicAuth: true,
|
|
||||||
PassUserHeaders: true,
|
|
||||||
PassAccessToken: true,
|
|
||||||
PassAuthorization: false,
|
|
||||||
StrippedHeaders: map[string]bool{
|
|
||||||
"X-Forwarded-User": true,
|
|
||||||
"X-Forwared-Groups": true,
|
|
||||||
"X-Forwarded-Email": true,
|
|
||||||
"X-Forwarded-Preferred-Username": true,
|
|
||||||
"X-Forwarded-Access-Token": true,
|
|
||||||
"Authorization": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Nothing setting Authorization": {
|
|
||||||
SkipAuthStripHeaders: true,
|
|
||||||
PassBasicAuth: false,
|
|
||||||
PassUserHeaders: true,
|
|
||||||
PassAccessToken: true,
|
|
||||||
PassAuthorization: false,
|
|
||||||
StrippedHeaders: map[string]bool{
|
|
||||||
"X-Forwarded-User": true,
|
|
||||||
"X-Forwared-Groups": true,
|
|
||||||
"X-Forwarded-Email": true,
|
|
||||||
"X-Forwarded-Preferred-Username": true,
|
|
||||||
"X-Forwarded-Access-Token": true,
|
|
||||||
"Authorization": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Only Authorization header modified": {
|
|
||||||
SkipAuthStripHeaders: true,
|
|
||||||
PassBasicAuth: false,
|
|
||||||
PassUserHeaders: false,
|
|
||||||
PassAccessToken: false,
|
|
||||||
PassAuthorization: true,
|
|
||||||
StrippedHeaders: map[string]bool{
|
|
||||||
"X-Forwarded-User": false,
|
|
||||||
"X-Forwared-Groups": false,
|
|
||||||
"X-Forwarded-Email": false,
|
|
||||||
"X-Forwarded-Preferred-Username": false,
|
|
||||||
"X-Forwarded-Access-Token": false,
|
|
||||||
"Authorization": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Don't strip any headers (default options)": {
|
|
||||||
SkipAuthStripHeaders: false,
|
|
||||||
PassBasicAuth: true,
|
|
||||||
PassUserHeaders: true,
|
|
||||||
PassAccessToken: false,
|
|
||||||
PassAuthorization: false,
|
|
||||||
StrippedHeaders: map[string]bool{
|
|
||||||
"X-Forwarded-User": false,
|
|
||||||
"X-Forwared-Groups": false,
|
|
||||||
"X-Forwarded-Email": false,
|
|
||||||
"X-Forwarded-Preferred-Username": false,
|
|
||||||
"X-Forwarded-Access-Token": false,
|
|
||||||
"Authorization": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Don't strip any headers (custom options)": {
|
|
||||||
SkipAuthStripHeaders: false,
|
|
||||||
PassBasicAuth: true,
|
|
||||||
PassUserHeaders: true,
|
|
||||||
PassAccessToken: true,
|
|
||||||
PassAuthorization: false,
|
|
||||||
StrippedHeaders: map[string]bool{
|
|
||||||
"X-Forwarded-User": false,
|
|
||||||
"X-Forwared-Groups": false,
|
|
||||||
"X-Forwarded-Email": false,
|
|
||||||
"X-Forwarded-Preferred-Username": false,
|
|
||||||
"X-Forwarded-Access-Token": false,
|
|
||||||
"Authorization": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
initialHeaders := map[string]string{
|
|
||||||
"X-Forwarded-User": "9fcab5c9b889a557",
|
|
||||||
"X-Forwarded-Email": "john.doe@example.com",
|
|
||||||
"X-Forwarded-Groups": "a,b,c",
|
|
||||||
"X-Forwarded-Preferred-Username": "john.doe",
|
|
||||||
"X-Forwarded-Access-Token": "AccessToken",
|
|
||||||
"Authorization": "bearer IDToken",
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
opts := baseTestOptions()
|
|
||||||
opts.SkipAuthStripHeaders = tc.SkipAuthStripHeaders
|
|
||||||
opts.PassBasicAuth = tc.PassBasicAuth
|
|
||||||
opts.PassUserHeaders = tc.PassUserHeaders
|
|
||||||
opts.PassAccessToken = tc.PassAccessToken
|
|
||||||
opts.PassAuthorization = tc.PassAuthorization
|
|
||||||
err := validation.Validate(opts)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", fmt.Sprintf("%s/testCase", opts.ProxyPrefix), nil)
|
|
||||||
for header, val := range initialHeaders {
|
|
||||||
req.Header.Set(header, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
|
|
||||||
assert.NoError(t, err)
|
|
||||||
if proxy.skipAuthStripHeaders {
|
|
||||||
proxy.stripAuthHeaders(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
for header, stripped := range tc.StrippedHeaders {
|
|
||||||
if stripped {
|
|
||||||
assert.Equal(t, req.Header.Get(header), "")
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, req.Header.Get(header), initialHeaders[header])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PassAccessTokenTest struct {
|
type PassAccessTokenTest struct {
|
||||||
providerServer *httptest.Server
|
providerServer *httptest.Server
|
||||||
@ -884,7 +659,21 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTe
|
|||||||
}
|
}
|
||||||
|
|
||||||
patt.opts.Cookie.Secure = false
|
patt.opts.Cookie.Secure = false
|
||||||
patt.opts.PassAccessToken = opts.PassAccessToken
|
if opts.PassAccessToken {
|
||||||
|
patt.opts.InjectRequestHeaders = []options.Header{
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Access-Token",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "access_token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := validation.Validate(patt.opts)
|
err := validation.Validate(patt.opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -1442,7 +1231,48 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
|
|||||||
var pcTest ProcessCookieTest
|
var pcTest ProcessCookieTest
|
||||||
|
|
||||||
pcTest.opts = baseTestOptions()
|
pcTest.opts = baseTestOptions()
|
||||||
pcTest.opts.SetXAuthRequest = true
|
pcTest.opts.InjectResponseHeaders = []options.Header{
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-User",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-Email",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-Groups",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "groups",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Preferred-Username",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "preferred_username",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
pcTest.opts.AllowedGroups = []string{"oauth_groups"}
|
pcTest.opts.AllowedGroups = []string{"oauth_groups"}
|
||||||
err := validation.Validate(pcTest.opts)
|
err := validation.Validate(pcTest.opts)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -1480,8 +1310,62 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
|
|||||||
var pcTest ProcessCookieTest
|
var pcTest ProcessCookieTest
|
||||||
|
|
||||||
pcTest.opts = baseTestOptions()
|
pcTest.opts = baseTestOptions()
|
||||||
pcTest.opts.SetXAuthRequest = true
|
pcTest.opts.InjectResponseHeaders = []options.Header{
|
||||||
pcTest.opts.SetBasicAuth = true
|
{
|
||||||
|
Name: "X-Auth-Request-User",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-Email",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-Groups",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "groups",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Preferred-Username",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "preferred_username",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Authorization",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
BasicAuthPassword: &options.SecretSource{
|
||||||
|
Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
err := validation.Validate(pcTest.opts)
|
err := validation.Validate(pcTest.opts)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
@ -1511,7 +1395,7 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
|
assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
|
||||||
assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
|
assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
|
||||||
assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
|
assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
|
||||||
expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
|
expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:This is a secure password"))
|
||||||
assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
|
assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1519,8 +1403,48 @@ func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
|
|||||||
var pcTest ProcessCookieTest
|
var pcTest ProcessCookieTest
|
||||||
|
|
||||||
pcTest.opts = baseTestOptions()
|
pcTest.opts = baseTestOptions()
|
||||||
pcTest.opts.SetXAuthRequest = true
|
pcTest.opts.InjectResponseHeaders = []options.Header{
|
||||||
pcTest.opts.SetBasicAuth = false
|
{
|
||||||
|
Name: "X-Auth-Request-User",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-Email",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-Groups",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "groups",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Preferred-Username",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "preferred_username",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
err := validation.Validate(pcTest.opts)
|
err := validation.Validate(pcTest.opts)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
@ -1985,9 +1909,74 @@ func TestGetJwtSession(t *testing.T) {
|
|||||||
&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
|
&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
|
||||||
|
|
||||||
test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
|
test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
|
||||||
opts.PassAuthorization = true
|
opts.InjectRequestHeaders = []options.Header{
|
||||||
opts.SetAuthorization = true
|
{
|
||||||
opts.SetXAuthRequest = true
|
Name: "Authorization",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "id_token",
|
||||||
|
Prefix: "Bearer ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-User",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Email",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.InjectResponseHeaders = []options.Header{
|
||||||
|
{
|
||||||
|
Name: "Authorization",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "id_token",
|
||||||
|
Prefix: "Bearer ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-User",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-Email",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
opts.SkipJwtBearerTokens = true
|
opts.SkipJwtBearerTokens = true
|
||||||
opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
|
opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
|
||||||
})
|
})
|
||||||
@ -2004,15 +1993,6 @@ func TestGetJwtSession(t *testing.T) {
|
|||||||
"Authorization": {authHeader},
|
"Authorization": {authHeader},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bearer
|
|
||||||
expires := time.Unix(1912151821, 0)
|
|
||||||
session, err := test.proxy.getAuthenticatedSession(test.rw, test.req)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, session.User, "1234567890")
|
|
||||||
assert.Equal(t, session.Email, "john@example.com")
|
|
||||||
assert.Equal(t, session.ExpiresOn, &expires)
|
|
||||||
assert.Equal(t, session.IDToken, goodJwt)
|
|
||||||
|
|
||||||
test.proxy.ServeHTTP(test.rw, test.req)
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
if test.rw.Code >= 400 {
|
if test.rw.Code >= 400 {
|
||||||
t.Fatalf("expected 3xx got %d", test.rw.Code)
|
t.Fatalf("expected 3xx got %d", test.rw.Code)
|
||||||
@ -2140,6 +2120,43 @@ func baseTestOptions() *options.Options {
|
|||||||
opts.ClientID = clientID
|
opts.ClientID = clientID
|
||||||
opts.ClientSecret = clientSecret
|
opts.ClientSecret = clientSecret
|
||||||
opts.EmailDomains = []string{"*"}
|
opts.EmailDomains = []string{"*"}
|
||||||
|
|
||||||
|
// Default injected headers for legacy configuration
|
||||||
|
opts.InjectRequestHeaders = []options.Header{
|
||||||
|
{
|
||||||
|
Name: "Authorization",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
BasicAuthPassword: &options.SecretSource{
|
||||||
|
Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-User",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Email",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package options
|
package options
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -15,6 +16,9 @@ type LegacyOptions struct {
|
|||||||
// Legacy options related to upstream servers
|
// Legacy options related to upstream servers
|
||||||
LegacyUpstreams LegacyUpstreams `cfg:",squash"`
|
LegacyUpstreams LegacyUpstreams `cfg:",squash"`
|
||||||
|
|
||||||
|
// Legacy options for injecting request/response headers
|
||||||
|
LegacyHeaders LegacyHeaders `cfg:",squash"`
|
||||||
|
|
||||||
Options Options `cfg:",squash"`
|
Options Options `cfg:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,6 +30,11 @@ func NewLegacyOptions() *LegacyOptions {
|
|||||||
FlushInterval: time.Duration(1) * time.Second,
|
FlushInterval: time.Duration(1) * time.Second,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
LegacyHeaders: LegacyHeaders{
|
||||||
|
PassBasicAuth: true,
|
||||||
|
PassUserHeaders: true,
|
||||||
|
},
|
||||||
|
|
||||||
Options: *NewOptions(),
|
Options: *NewOptions(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,6 +46,7 @@ func (l *LegacyOptions) ToOptions() (*Options, error) {
|
|||||||
}
|
}
|
||||||
l.Options.UpstreamServers = upstreams
|
l.Options.UpstreamServers = upstreams
|
||||||
|
|
||||||
|
l.Options.InjectRequestHeaders, l.Options.InjectResponseHeaders = l.LegacyHeaders.convert()
|
||||||
return &l.Options, nil
|
return &l.Options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,3 +129,254 @@ func (l *LegacyUpstreams) convert() (Upstreams, error) {
|
|||||||
|
|
||||||
return upstreams, nil
|
return upstreams, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LegacyHeaders struct {
|
||||||
|
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
|
||||||
|
PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
|
||||||
|
PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers"`
|
||||||
|
PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header"`
|
||||||
|
|
||||||
|
SetBasicAuth bool `flag:"set-basic-auth" cfg:"set_basic_auth"`
|
||||||
|
SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest"`
|
||||||
|
SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header"`
|
||||||
|
|
||||||
|
PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user"`
|
||||||
|
BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
|
||||||
|
SkipAuthStripHeaders bool `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func legacyHeadersFlagSet() *pflag.FlagSet {
|
||||||
|
flagSet := pflag.NewFlagSet("headers", pflag.ExitOnError)
|
||||||
|
|
||||||
|
flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
|
||||||
|
flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
|
||||||
|
flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
|
||||||
|
flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream")
|
||||||
|
|
||||||
|
flagSet.Bool("set-basic-auth", false, "set HTTP Basic Auth information in response (useful in Nginx auth_request mode)")
|
||||||
|
flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email 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.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.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
|
||||||
|
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")
|
||||||
|
|
||||||
|
return flagSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert takes the legacy request/response headers and converts them to
|
||||||
|
// the new format for InjectRequestHeaders and InjectResponseHeaders
|
||||||
|
func (l *LegacyHeaders) convert() ([]Header, []Header) {
|
||||||
|
return l.getRequestHeaders(), l.getResponseHeaders()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LegacyHeaders) getRequestHeaders() []Header {
|
||||||
|
requestHeaders := []Header{}
|
||||||
|
|
||||||
|
if l.PassBasicAuth && l.BasicAuthPassword != "" {
|
||||||
|
requestHeaders = append(requestHeaders, getBasicAuthHeader(l.PreferEmailToUser, l.BasicAuthPassword))
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the old implementation, PassUserHeaders is a subset of PassBasicAuth
|
||||||
|
if l.PassBasicAuth || l.PassUserHeaders {
|
||||||
|
requestHeaders = append(requestHeaders, getPassUserHeaders(l.PreferEmailToUser)...)
|
||||||
|
requestHeaders = append(requestHeaders, getPreferredUsernameHeader())
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.PassAccessToken {
|
||||||
|
requestHeaders = append(requestHeaders, getPassAccessTokenHeader())
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.PassAuthorization {
|
||||||
|
requestHeaders = append(requestHeaders, getAuthorizationHeader())
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range requestHeaders {
|
||||||
|
requestHeaders[i].PreserveRequestValue = !l.SkipAuthStripHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LegacyHeaders) getResponseHeaders() []Header {
|
||||||
|
responseHeaders := []Header{}
|
||||||
|
|
||||||
|
if l.SetXAuthRequest {
|
||||||
|
responseHeaders = append(responseHeaders, getXAuthRequestHeaders(l.PassAccessToken)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.SetBasicAuth {
|
||||||
|
responseHeaders = append(responseHeaders, getBasicAuthHeader(l.PreferEmailToUser, l.BasicAuthPassword))
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.SetAuthorization {
|
||||||
|
responseHeaders = append(responseHeaders, getAuthorizationHeader())
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header {
|
||||||
|
claim := "user"
|
||||||
|
if preferEmailToUser {
|
||||||
|
claim = "email"
|
||||||
|
}
|
||||||
|
|
||||||
|
return Header{
|
||||||
|
Name: "Authorization",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: claim,
|
||||||
|
BasicAuthPassword: &SecretSource{
|
||||||
|
Value: []byte(base64.StdEncoding.EncodeToString([]byte(basicAuthPassword))),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPassUserHeaders(preferEmailToUser bool) []Header {
|
||||||
|
headers := []Header{
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Groups",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "groups",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if preferEmailToUser {
|
||||||
|
return append(headers,
|
||||||
|
Header{
|
||||||
|
Name: "X-Forwarded-User",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(headers,
|
||||||
|
Header{
|
||||||
|
Name: "X-Forwarded-User",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Header{
|
||||||
|
Name: "X-Forwarded-Email",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPassAccessTokenHeader() Header {
|
||||||
|
return Header{
|
||||||
|
Name: "X-Forwarded-Access-Token",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "access_token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuthorizationHeader() Header {
|
||||||
|
return Header{
|
||||||
|
Name: "Authorization",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "id_token",
|
||||||
|
Prefix: "Bearer ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPreferredUsernameHeader() Header {
|
||||||
|
return Header{
|
||||||
|
Name: "X-Forwarded-Preferred-Username",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "preferred_username",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getXAuthRequestHeaders(passAccessToken bool) []Header {
|
||||||
|
headers := []Header{
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-User",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-Email",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getPreferredUsernameHeader(),
|
||||||
|
{
|
||||||
|
Name: "X-Auth-Request-Groups",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "groups",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if passAccessToken {
|
||||||
|
headers = append(headers, Header{
|
||||||
|
Name: "X-Auth-Request-Access-Token",
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "access_token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package options
|
package options
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
@ -57,6 +58,55 @@ var _ = Describe("Legacy Options", func() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts.InjectRequestHeaders = []Header{
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Groups",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "groups",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-User",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Email",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "X-Forwarded-Preferred-Username",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "preferred_username",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.InjectResponseHeaders = []Header{}
|
||||||
|
|
||||||
converted, err := legacyOpts.ToOptions()
|
converted, err := legacyOpts.ToOptions()
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(converted).To(Equal(opts))
|
Expect(converted).To(Equal(opts))
|
||||||
@ -210,4 +260,425 @@ var _ = Describe("Legacy Options", func() {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("Legacy Headers", func() {
|
||||||
|
const basicAuthSecret = "super-secret-password"
|
||||||
|
|
||||||
|
type legacyHeadersTableInput struct {
|
||||||
|
legacyHeaders *LegacyHeaders
|
||||||
|
expectedRequestHeaders []Header
|
||||||
|
expectedResponseHeaders []Header
|
||||||
|
}
|
||||||
|
|
||||||
|
withPreserveRequestValue := func(h Header, preserve bool) Header {
|
||||||
|
h.PreserveRequestValue = preserve
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
xForwardedUser := Header{
|
||||||
|
Name: "X-Forwarded-User",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xForwardedEmail := Header{
|
||||||
|
Name: "X-Forwarded-Email",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xForwardedGroups := Header{
|
||||||
|
Name: "X-Forwarded-Groups",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "groups",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xForwardedPreferredUsername := Header{
|
||||||
|
Name: "X-Forwarded-Preferred-Username",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "preferred_username",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
basicAuthHeader := Header{
|
||||||
|
Name: "Authorization",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
BasicAuthPassword: &SecretSource{
|
||||||
|
Value: []byte(base64.StdEncoding.EncodeToString([]byte(basicAuthSecret))),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xForwardedUserWithEmail := Header{
|
||||||
|
Name: "X-Forwarded-User",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
basicAuthHeaderWithEmail := Header{
|
||||||
|
Name: "Authorization",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
BasicAuthPassword: &SecretSource{
|
||||||
|
Value: []byte(base64.StdEncoding.EncodeToString([]byte(basicAuthSecret))),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xAuthRequestUser := Header{
|
||||||
|
Name: "X-Auth-Request-User",
|
||||||
|
PreserveRequestValue: false,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xAuthRequestEmail := Header{
|
||||||
|
Name: "X-Auth-Request-Email",
|
||||||
|
PreserveRequestValue: false,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xAuthRequestGroups := Header{
|
||||||
|
Name: "X-Auth-Request-Groups",
|
||||||
|
PreserveRequestValue: false,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "groups",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xForwardedAccessToken := Header{
|
||||||
|
Name: "X-Forwarded-Access-Token",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "access_token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
xAuthRequestAccessToken := Header{
|
||||||
|
Name: "X-Auth-Request-Access-Token",
|
||||||
|
PreserveRequestValue: false,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "access_token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizationHeader := Header{
|
||||||
|
Name: "Authorization",
|
||||||
|
PreserveRequestValue: true,
|
||||||
|
Values: []HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &ClaimSource{
|
||||||
|
Claim: "id_token",
|
||||||
|
Prefix: "Bearer ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
DescribeTable("should convert to injectRequestHeaders",
|
||||||
|
func(in legacyHeadersTableInput) {
|
||||||
|
requestHeaders, responseHeaders := in.legacyHeaders.convert()
|
||||||
|
Expect(requestHeaders).To(ConsistOf(in.expectedRequestHeaders))
|
||||||
|
Expect(responseHeaders).To(ConsistOf(in.expectedResponseHeaders))
|
||||||
|
},
|
||||||
|
Entry("with all header options off", legacyHeadersTableInput{
|
||||||
|
legacyHeaders: &LegacyHeaders{
|
||||||
|
PassBasicAuth: false,
|
||||||
|
PassAccessToken: false,
|
||||||
|
PassUserHeaders: false,
|
||||||
|
PassAuthorization: false,
|
||||||
|
|
||||||
|
SetBasicAuth: false,
|
||||||
|
SetXAuthRequest: false,
|
||||||
|
SetAuthorization: false,
|
||||||
|
|
||||||
|
PreferEmailToUser: false,
|
||||||
|
BasicAuthPassword: "",
|
||||||
|
SkipAuthStripHeaders: false,
|
||||||
|
},
|
||||||
|
expectedRequestHeaders: []Header{},
|
||||||
|
expectedResponseHeaders: []Header{},
|
||||||
|
}),
|
||||||
|
Entry("with basic auth enabled", legacyHeadersTableInput{
|
||||||
|
legacyHeaders: &LegacyHeaders{
|
||||||
|
PassBasicAuth: true,
|
||||||
|
PassAccessToken: false,
|
||||||
|
PassUserHeaders: false,
|
||||||
|
PassAuthorization: false,
|
||||||
|
|
||||||
|
SetBasicAuth: true,
|
||||||
|
SetXAuthRequest: false,
|
||||||
|
SetAuthorization: false,
|
||||||
|
|
||||||
|
PreferEmailToUser: false,
|
||||||
|
BasicAuthPassword: basicAuthSecret,
|
||||||
|
SkipAuthStripHeaders: false,
|
||||||
|
},
|
||||||
|
expectedRequestHeaders: []Header{
|
||||||
|
xForwardedUser,
|
||||||
|
xForwardedEmail,
|
||||||
|
xForwardedGroups,
|
||||||
|
xForwardedPreferredUsername,
|
||||||
|
basicAuthHeader,
|
||||||
|
},
|
||||||
|
expectedResponseHeaders: []Header{
|
||||||
|
withPreserveRequestValue(basicAuthHeader, false),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with basic auth enabled and skipAuthStripHeaders", legacyHeadersTableInput{
|
||||||
|
legacyHeaders: &LegacyHeaders{
|
||||||
|
PassBasicAuth: true,
|
||||||
|
PassAccessToken: false,
|
||||||
|
PassUserHeaders: false,
|
||||||
|
PassAuthorization: false,
|
||||||
|
|
||||||
|
SetBasicAuth: true,
|
||||||
|
SetXAuthRequest: false,
|
||||||
|
SetAuthorization: false,
|
||||||
|
|
||||||
|
PreferEmailToUser: false,
|
||||||
|
BasicAuthPassword: basicAuthSecret,
|
||||||
|
SkipAuthStripHeaders: true,
|
||||||
|
},
|
||||||
|
expectedRequestHeaders: []Header{
|
||||||
|
withPreserveRequestValue(xForwardedUser, false),
|
||||||
|
withPreserveRequestValue(xForwardedEmail, false),
|
||||||
|
withPreserveRequestValue(xForwardedGroups, false),
|
||||||
|
withPreserveRequestValue(xForwardedPreferredUsername, false),
|
||||||
|
withPreserveRequestValue(basicAuthHeader, false),
|
||||||
|
},
|
||||||
|
expectedResponseHeaders: []Header{
|
||||||
|
withPreserveRequestValue(basicAuthHeader, false),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with basic auth enabled and preferEmailToUser", legacyHeadersTableInput{
|
||||||
|
legacyHeaders: &LegacyHeaders{
|
||||||
|
PassBasicAuth: true,
|
||||||
|
PassAccessToken: false,
|
||||||
|
PassUserHeaders: false,
|
||||||
|
PassAuthorization: false,
|
||||||
|
|
||||||
|
SetBasicAuth: true,
|
||||||
|
SetXAuthRequest: false,
|
||||||
|
SetAuthorization: false,
|
||||||
|
|
||||||
|
PreferEmailToUser: true,
|
||||||
|
BasicAuthPassword: basicAuthSecret,
|
||||||
|
SkipAuthStripHeaders: false,
|
||||||
|
},
|
||||||
|
expectedRequestHeaders: []Header{
|
||||||
|
xForwardedUserWithEmail,
|
||||||
|
xForwardedGroups,
|
||||||
|
xForwardedPreferredUsername,
|
||||||
|
basicAuthHeaderWithEmail,
|
||||||
|
},
|
||||||
|
expectedResponseHeaders: []Header{
|
||||||
|
withPreserveRequestValue(basicAuthHeaderWithEmail, false),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with basic auth enabled and passUserHeaders", legacyHeadersTableInput{
|
||||||
|
legacyHeaders: &LegacyHeaders{
|
||||||
|
PassBasicAuth: true,
|
||||||
|
PassAccessToken: false,
|
||||||
|
PassUserHeaders: true,
|
||||||
|
PassAuthorization: false,
|
||||||
|
|
||||||
|
SetBasicAuth: true,
|
||||||
|
SetXAuthRequest: false,
|
||||||
|
SetAuthorization: false,
|
||||||
|
|
||||||
|
PreferEmailToUser: false,
|
||||||
|
BasicAuthPassword: basicAuthSecret,
|
||||||
|
SkipAuthStripHeaders: false,
|
||||||
|
},
|
||||||
|
expectedRequestHeaders: []Header{
|
||||||
|
xForwardedUser,
|
||||||
|
xForwardedEmail,
|
||||||
|
xForwardedGroups,
|
||||||
|
xForwardedPreferredUsername,
|
||||||
|
basicAuthHeader,
|
||||||
|
},
|
||||||
|
expectedResponseHeaders: []Header{
|
||||||
|
withPreserveRequestValue(basicAuthHeader, false),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with passUserHeaders", legacyHeadersTableInput{
|
||||||
|
legacyHeaders: &LegacyHeaders{
|
||||||
|
PassBasicAuth: false,
|
||||||
|
PassAccessToken: false,
|
||||||
|
PassUserHeaders: true,
|
||||||
|
PassAuthorization: false,
|
||||||
|
|
||||||
|
SetBasicAuth: false,
|
||||||
|
SetXAuthRequest: false,
|
||||||
|
SetAuthorization: false,
|
||||||
|
|
||||||
|
PreferEmailToUser: false,
|
||||||
|
BasicAuthPassword: "",
|
||||||
|
SkipAuthStripHeaders: false,
|
||||||
|
},
|
||||||
|
expectedRequestHeaders: []Header{
|
||||||
|
xForwardedUser,
|
||||||
|
xForwardedEmail,
|
||||||
|
xForwardedGroups,
|
||||||
|
xForwardedPreferredUsername,
|
||||||
|
},
|
||||||
|
expectedResponseHeaders: []Header{},
|
||||||
|
}),
|
||||||
|
Entry("with setXAuthRequest", legacyHeadersTableInput{
|
||||||
|
legacyHeaders: &LegacyHeaders{
|
||||||
|
PassBasicAuth: false,
|
||||||
|
PassAccessToken: false,
|
||||||
|
PassUserHeaders: false,
|
||||||
|
PassAuthorization: false,
|
||||||
|
|
||||||
|
SetBasicAuth: false,
|
||||||
|
SetXAuthRequest: true,
|
||||||
|
SetAuthorization: false,
|
||||||
|
|
||||||
|
PreferEmailToUser: false,
|
||||||
|
BasicAuthPassword: "",
|
||||||
|
SkipAuthStripHeaders: false,
|
||||||
|
},
|
||||||
|
expectedRequestHeaders: []Header{},
|
||||||
|
expectedResponseHeaders: []Header{
|
||||||
|
xAuthRequestUser,
|
||||||
|
xAuthRequestEmail,
|
||||||
|
xAuthRequestGroups,
|
||||||
|
withPreserveRequestValue(xForwardedPreferredUsername, false),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with passAccessToken", legacyHeadersTableInput{
|
||||||
|
legacyHeaders: &LegacyHeaders{
|
||||||
|
PassBasicAuth: false,
|
||||||
|
PassAccessToken: true,
|
||||||
|
PassUserHeaders: false,
|
||||||
|
PassAuthorization: false,
|
||||||
|
|
||||||
|
SetBasicAuth: false,
|
||||||
|
SetXAuthRequest: false,
|
||||||
|
SetAuthorization: false,
|
||||||
|
|
||||||
|
PreferEmailToUser: false,
|
||||||
|
BasicAuthPassword: "",
|
||||||
|
SkipAuthStripHeaders: false,
|
||||||
|
},
|
||||||
|
expectedRequestHeaders: []Header{
|
||||||
|
xForwardedAccessToken,
|
||||||
|
},
|
||||||
|
expectedResponseHeaders: []Header{},
|
||||||
|
}),
|
||||||
|
Entry("with passAcessToken and setXAuthRequest", legacyHeadersTableInput{
|
||||||
|
legacyHeaders: &LegacyHeaders{
|
||||||
|
PassBasicAuth: false,
|
||||||
|
PassAccessToken: true,
|
||||||
|
PassUserHeaders: false,
|
||||||
|
PassAuthorization: false,
|
||||||
|
|
||||||
|
SetBasicAuth: false,
|
||||||
|
SetXAuthRequest: true,
|
||||||
|
SetAuthorization: false,
|
||||||
|
|
||||||
|
PreferEmailToUser: false,
|
||||||
|
BasicAuthPassword: "",
|
||||||
|
SkipAuthStripHeaders: false,
|
||||||
|
},
|
||||||
|
expectedRequestHeaders: []Header{
|
||||||
|
xForwardedAccessToken,
|
||||||
|
},
|
||||||
|
expectedResponseHeaders: []Header{
|
||||||
|
xAuthRequestUser,
|
||||||
|
xAuthRequestEmail,
|
||||||
|
xAuthRequestGroups,
|
||||||
|
withPreserveRequestValue(xForwardedPreferredUsername, false),
|
||||||
|
xAuthRequestAccessToken,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with authorization headers", legacyHeadersTableInput{
|
||||||
|
legacyHeaders: &LegacyHeaders{
|
||||||
|
PassBasicAuth: false,
|
||||||
|
PassAccessToken: false,
|
||||||
|
PassUserHeaders: false,
|
||||||
|
PassAuthorization: true,
|
||||||
|
|
||||||
|
SetBasicAuth: false,
|
||||||
|
SetXAuthRequest: false,
|
||||||
|
SetAuthorization: true,
|
||||||
|
|
||||||
|
PreferEmailToUser: false,
|
||||||
|
BasicAuthPassword: "",
|
||||||
|
SkipAuthStripHeaders: false,
|
||||||
|
},
|
||||||
|
expectedRequestHeaders: []Header{
|
||||||
|
authorizationHeader,
|
||||||
|
},
|
||||||
|
expectedResponseHeaders: []Header{
|
||||||
|
withPreserveRequestValue(authorizationHeader, false),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -65,22 +65,15 @@ type Options struct {
|
|||||||
// TODO(JoelSpeed): Rename when legacy config is removed
|
// TODO(JoelSpeed): Rename when legacy config is removed
|
||||||
UpstreamServers Upstreams `cfg:",internal"`
|
UpstreamServers Upstreams `cfg:",internal"`
|
||||||
|
|
||||||
|
InjectRequestHeaders []Header `cfg:",internal"`
|
||||||
|
InjectResponseHeaders []Header `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"`
|
SkipAuthRoutes []string `flag:"skip-auth-route" cfg:"skip_auth_routes"`
|
||||||
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"`
|
||||||
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"`
|
|
||||||
SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button"`
|
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"`
|
SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_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"`
|
SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
|
||||||
|
|
||||||
// These options allow for other providers besides Google, with
|
// These options allow for other providers besides Google, with
|
||||||
@ -151,15 +144,7 @@ func NewOptions() *Options {
|
|||||||
Cookie: cookieDefaults(),
|
Cookie: cookieDefaults(),
|
||||||
Session: sessionOptionsDefaults(),
|
Session: sessionOptionsDefaults(),
|
||||||
AzureTenant: "common",
|
AzureTenant: "common",
|
||||||
SetXAuthRequest: false,
|
|
||||||
SkipAuthPreflight: false,
|
SkipAuthPreflight: false,
|
||||||
PassBasicAuth: true,
|
|
||||||
SetBasicAuth: false,
|
|
||||||
PassUserHeaders: true,
|
|
||||||
PassAccessToken: false,
|
|
||||||
SetAuthorization: false,
|
|
||||||
PassAuthorization: false,
|
|
||||||
PreferEmailToUser: false,
|
|
||||||
Prompt: "", // Change to "login" when ApprovalPrompt officially deprecated
|
Prompt: "", // Change to "login" when ApprovalPrompt officially deprecated
|
||||||
ApprovalPrompt: "force",
|
ApprovalPrompt: "force",
|
||||||
UserIDClaim: "email",
|
UserIDClaim: "email",
|
||||||
@ -183,18 +168,8 @@ func NewFlagSet() *pflag.FlagSet {
|
|||||||
flagSet.String("tls-cert-file", "", "path to certificate file")
|
flagSet.String("tls-cert-file", "", "path to certificate file")
|
||||||
flagSet.String("tls-key-file", "", "path to private key 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.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.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-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{}, "(DEPRECATED for --skip-auth-route) 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.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.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")
|
||||||
@ -272,6 +247,7 @@ func NewFlagSet() *pflag.FlagSet {
|
|||||||
flagSet.AddFlagSet(cookieFlagSet())
|
flagSet.AddFlagSet(cookieFlagSet())
|
||||||
flagSet.AddFlagSet(loggingFlagSet())
|
flagSet.AddFlagSet(loggingFlagSet())
|
||||||
flagSet.AddFlagSet(legacyUpstreamsFlagSet())
|
flagSet.AddFlagSet(legacyUpstreamsFlagSet())
|
||||||
|
flagSet.AddFlagSet(legacyHeadersFlagSet())
|
||||||
|
|
||||||
return flagSet
|
return flagSet
|
||||||
}
|
}
|
||||||
|
46
pkg/validation/common.go
Normal file
46
pkg/validation/common.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
const multipleValuesForSecretSource = "multiple values specified for secret source: specify either value, fromEnv of fromFile"
|
||||||
|
|
||||||
|
func validateSecretSource(source options.SecretSource) string {
|
||||||
|
switch {
|
||||||
|
case len(source.Value) > 0 && source.FromEnv == "" && source.FromFile == "":
|
||||||
|
return validateSecretSourceValue(source.Value)
|
||||||
|
case len(source.Value) == 0 && source.FromEnv != "" && source.FromFile == "":
|
||||||
|
return validateSecretSourceEnv(source.FromEnv)
|
||||||
|
case len(source.Value) == 0 && source.FromEnv == "" && source.FromFile != "":
|
||||||
|
return validateSecretSourceFile(source.FromFile)
|
||||||
|
default:
|
||||||
|
return multipleValuesForSecretSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSecretSourceValue(value []byte) string {
|
||||||
|
dst := make([]byte, len(value))
|
||||||
|
if _, err := base64.StdEncoding.Decode(dst, value); err != nil {
|
||||||
|
return fmt.Sprintf("error decoding secret value: %v", err)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSecretSourceEnv(key string) string {
|
||||||
|
if value := os.Getenv(key); value == "" {
|
||||||
|
return fmt.Sprintf("error loading secret from environent: no value for for key %q", key)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSecretSourceFile(path string) string {
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
return fmt.Sprintf("error loadig secret from file: %v", err)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
138
pkg/validation/common_test.go
Normal file
138
pkg/validation/common_test.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/ginkgo/extensions/table"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Common", func() {
|
||||||
|
var validSecretSourceValue []byte
|
||||||
|
const validSecretSourceEnv = "OAUTH2_PROXY_TEST_SECRET_SOURCE_ENV"
|
||||||
|
var validSecretSourceFile string
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
validSecretSourceValue = []byte(base64.StdEncoding.EncodeToString([]byte("This is a secret source value")))
|
||||||
|
Expect(os.Setenv(validSecretSourceEnv, "This is a secret source env")).To(Succeed())
|
||||||
|
tmp, err := ioutil.TempFile("", "oauth2-proxy-secret-source-test")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
defer tmp.Close()
|
||||||
|
|
||||||
|
_, err = tmp.Write([]byte("This is a secret source file"))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
validSecretSourceFile = tmp.Name()
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(os.Unsetenv(validSecretSourceEnv)).To(Succeed())
|
||||||
|
Expect(os.Remove(validSecretSourceFile)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
type validateSecretSourceTableInput struct {
|
||||||
|
source func() options.SecretSource
|
||||||
|
expectedMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
DescribeTable("validateSecretSource should",
|
||||||
|
func(in validateSecretSourceTableInput) {
|
||||||
|
Expect(validateSecretSource(in.source())).To(Equal(in.expectedMsg))
|
||||||
|
},
|
||||||
|
Entry("with no entries", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{}
|
||||||
|
},
|
||||||
|
expectedMsg: multipleValuesForSecretSource,
|
||||||
|
}),
|
||||||
|
Entry("with a Value and FromEnv", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{
|
||||||
|
Value: validSecretSourceValue,
|
||||||
|
FromEnv: validSecretSourceEnv,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectedMsg: multipleValuesForSecretSource,
|
||||||
|
}),
|
||||||
|
Entry("with a Value and FromFile", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{
|
||||||
|
Value: validSecretSourceValue,
|
||||||
|
FromFile: validSecretSourceFile,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectedMsg: multipleValuesForSecretSource,
|
||||||
|
}),
|
||||||
|
Entry("with FromEnv and FromFile", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{
|
||||||
|
FromEnv: validSecretSourceEnv,
|
||||||
|
FromFile: validSecretSourceFile,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectedMsg: multipleValuesForSecretSource,
|
||||||
|
}),
|
||||||
|
Entry("with a Value, FromEnv and FromFile", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{
|
||||||
|
Value: validSecretSourceValue,
|
||||||
|
FromEnv: validSecretSourceEnv,
|
||||||
|
FromFile: validSecretSourceFile,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectedMsg: multipleValuesForSecretSource,
|
||||||
|
}),
|
||||||
|
Entry("with a valid Value", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{
|
||||||
|
Value: validSecretSourceValue,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectedMsg: "",
|
||||||
|
}),
|
||||||
|
Entry("with a valid FromEnv", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{
|
||||||
|
FromEnv: validSecretSourceEnv,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectedMsg: "",
|
||||||
|
}),
|
||||||
|
Entry("with a valid FromFile", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{
|
||||||
|
FromFile: validSecretSourceFile,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectedMsg: "",
|
||||||
|
}),
|
||||||
|
Entry("with an invalid Value", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{
|
||||||
|
Value: []byte("Invalid Base64 Value"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectedMsg: "error decoding secret value: illegal base64 data at input byte 7",
|
||||||
|
}),
|
||||||
|
Entry("with an invalid FromEnv", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{
|
||||||
|
FromEnv: "INVALID_ENV",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectedMsg: "error loading secret from environent: no value for for key \"INVALID_ENV\"",
|
||||||
|
}),
|
||||||
|
Entry("with an invalid FromFile", validateSecretSourceTableInput{
|
||||||
|
source: func() options.SecretSource {
|
||||||
|
return options.SecretSource{
|
||||||
|
FromFile: "invalidFile",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectedMsg: "error loadig secret from file: stat invalidFile: no such file or directory",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
63
pkg/validation/header.go
Normal file
63
pkg/validation/header.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateHeaders(headers []options.Header) []string {
|
||||||
|
msgs := []string{}
|
||||||
|
names := make(map[string]struct{})
|
||||||
|
|
||||||
|
for _, header := range headers {
|
||||||
|
msgs = append(msgs, validateHeader(header, names)...)
|
||||||
|
}
|
||||||
|
return msgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHeader(header options.Header, names map[string]struct{}) []string {
|
||||||
|
msgs := []string{}
|
||||||
|
|
||||||
|
if header.Name == "" {
|
||||||
|
msgs = append(msgs, "header has empty name: names are required for all headers")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := names[header.Name]; ok {
|
||||||
|
msgs = append(msgs, fmt.Sprintf("multiple headers found with name %q: header names must be unique", header.Name))
|
||||||
|
}
|
||||||
|
names[header.Name] = struct{}{}
|
||||||
|
|
||||||
|
for _, value := range header.Values {
|
||||||
|
msgs = append(msgs,
|
||||||
|
prefixValues(fmt.Sprintf("invalid header %q: invalid values: ", header.Name),
|
||||||
|
validateHeaderValue(header.Name, value)...,
|
||||||
|
)...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return msgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHeaderValue(name string, value options.HeaderValue) []string {
|
||||||
|
switch {
|
||||||
|
case value.SecretSource != nil && value.ClaimSource == nil:
|
||||||
|
return []string{validateSecretSource(*value.SecretSource)}
|
||||||
|
case value.SecretSource == nil && value.ClaimSource != nil:
|
||||||
|
return validateHeaderValueClaimSource(*value.ClaimSource)
|
||||||
|
default:
|
||||||
|
return []string{"header value has multiple entries: only one entry per value is allowed"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHeaderValueClaimSource(claim options.ClaimSource) []string {
|
||||||
|
msgs := []string{}
|
||||||
|
|
||||||
|
if claim.Claim == "" {
|
||||||
|
msgs = append(msgs, "claim should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if claim.BasicAuthPassword != nil {
|
||||||
|
msgs = append(msgs, prefixValues("invalid basicAuthPassword: ", validateSecretSource(*claim.BasicAuthPassword))...)
|
||||||
|
}
|
||||||
|
return msgs
|
||||||
|
}
|
164
pkg/validation/header_test.go
Normal file
164
pkg/validation/header_test.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/ginkgo/extensions/table"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Headers", func() {
|
||||||
|
type validateHeaderTableInput struct {
|
||||||
|
headers []options.Header
|
||||||
|
expectedMsgs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
validHeader1 := options.Header{
|
||||||
|
Name: "X-Email",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
validHeader2 := options.Header{
|
||||||
|
Name: "X-Forwarded-Auth",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
SecretSource: &options.SecretSource{
|
||||||
|
Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
validHeader3 := options.Header{
|
||||||
|
Name: "Authorization",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "email",
|
||||||
|
BasicAuthPassword: &options.SecretSource{
|
||||||
|
Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
DescribeTable("validateHeaders",
|
||||||
|
func(in validateHeaderTableInput) {
|
||||||
|
Expect(validateHeaders(in.headers)).To(ConsistOf(in.expectedMsgs))
|
||||||
|
},
|
||||||
|
Entry("with no headers", validateHeaderTableInput{
|
||||||
|
headers: []options.Header{},
|
||||||
|
expectedMsgs: []string{},
|
||||||
|
}),
|
||||||
|
Entry("with valid headers", validateHeaderTableInput{
|
||||||
|
headers: []options.Header{
|
||||||
|
validHeader1,
|
||||||
|
validHeader2,
|
||||||
|
validHeader3,
|
||||||
|
},
|
||||||
|
expectedMsgs: []string{},
|
||||||
|
}),
|
||||||
|
Entry("with multiple headers with the same name", validateHeaderTableInput{
|
||||||
|
headers: []options.Header{
|
||||||
|
validHeader1,
|
||||||
|
validHeader1,
|
||||||
|
validHeader2,
|
||||||
|
validHeader2,
|
||||||
|
},
|
||||||
|
expectedMsgs: []string{
|
||||||
|
"multiple headers found with name \"X-Email\": header names must be unique",
|
||||||
|
"multiple headers found with name \"X-Forwarded-Auth\": header names must be unique",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with an unamed header", validateHeaderTableInput{
|
||||||
|
headers: []options.Header{
|
||||||
|
{},
|
||||||
|
validHeader2,
|
||||||
|
},
|
||||||
|
expectedMsgs: []string{
|
||||||
|
"header has empty name: names are required for all headers",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with a header which has a claim and secret source", validateHeaderTableInput{
|
||||||
|
headers: []options.Header{
|
||||||
|
{
|
||||||
|
Name: "With-Claim-And-Secret",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{},
|
||||||
|
SecretSource: &options.SecretSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validHeader1,
|
||||||
|
},
|
||||||
|
expectedMsgs: []string{
|
||||||
|
"invalid header \"With-Claim-And-Secret\": invalid values: header value has multiple entries: only one entry per value is allowed",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with a header which has a claim without a claim", validateHeaderTableInput{
|
||||||
|
headers: []options.Header{
|
||||||
|
{
|
||||||
|
Name: "Without-Claim",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Prefix: "prefix",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validHeader3,
|
||||||
|
},
|
||||||
|
expectedMsgs: []string{
|
||||||
|
"invalid header \"Without-Claim\": invalid values: claim should not be empty",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with a header with invalid secret source", validateHeaderTableInput{
|
||||||
|
headers: []options.Header{
|
||||||
|
{
|
||||||
|
Name: "With-Invalid-Secret",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
SecretSource: &options.SecretSource{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validHeader1,
|
||||||
|
},
|
||||||
|
expectedMsgs: []string{
|
||||||
|
"invalid header \"With-Invalid-Secret\": invalid values: multiple values specified for secret source: specify either value, fromEnv of fromFile",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Entry("with a header with invalid basicAuthPassword source", validateHeaderTableInput{
|
||||||
|
headers: []options.Header{
|
||||||
|
{
|
||||||
|
Name: "With-Invalid-Basic-Auth",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "user",
|
||||||
|
BasicAuthPassword: &options.SecretSource{
|
||||||
|
Value: []byte("secret"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validHeader1,
|
||||||
|
},
|
||||||
|
expectedMsgs: []string{
|
||||||
|
"invalid header \"With-Invalid-Basic-Auth\": invalid values: invalid basicAuthPassword: error decoding secret value: illegal base64 data at input byte 4",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
@ -28,6 +28,8 @@ func Validate(o *options.Options) error {
|
|||||||
msgs := validateCookie(o.Cookie)
|
msgs := validateCookie(o.Cookie)
|
||||||
msgs = append(msgs, validateSessionCookieMinimal(o)...)
|
msgs = append(msgs, validateSessionCookieMinimal(o)...)
|
||||||
msgs = append(msgs, validateRedisSessionStore(o)...)
|
msgs = append(msgs, validateRedisSessionStore(o)...)
|
||||||
|
msgs = append(msgs, prefixValues("injectRequestHeaders: ", validateHeaders(o.InjectRequestHeaders)...)...)
|
||||||
|
msgs = append(msgs, prefixValues("injectRespeonseHeaders: ", validateHeaders(o.InjectRequestHeaders)...)...)
|
||||||
|
|
||||||
if o.SSLInsecureSkipVerify {
|
if o.SSLInsecureSkipVerify {
|
||||||
// InsecureSkipVerify is a configurable option we allow
|
// InsecureSkipVerify is a configurable option we allow
|
||||||
@ -71,10 +73,6 @@ func Validate(o *options.Options) error {
|
|||||||
"\n use email-domain=* to authorize all email addresses")
|
"\n use email-domain=* to authorize all email addresses")
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.SetBasicAuth && o.SetAuthorization {
|
|
||||||
msgs = append(msgs, "mutually exclusive: set-basic-auth and set-authorization-header can not both be true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.OIDCIssuerURL != "" {
|
if o.OIDCIssuerURL != "" {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -159,10 +157,6 @@ func Validate(o *options.Options) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.PreferEmailToUser && !o.PassBasicAuth && !o.PassUserHeaders {
|
|
||||||
msgs = append(msgs, "PreferEmailToUser should only be used with PassBasicAuth or PassUserHeaders")
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.SkipJwtBearerTokens {
|
if o.SkipJwtBearerTokens {
|
||||||
// Configure extra issuers
|
// Configure extra issuers
|
||||||
if len(o.ExtraJwtIssuers) > 0 {
|
if len(o.ExtraJwtIssuers) > 0 {
|
||||||
|
@ -162,29 +162,6 @@ func TestDefaultProviderApiSettings(t *testing.T) {
|
|||||||
assert.Equal(t, "profile email", p.Scope)
|
assert.Equal(t, "profile email", p.Scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) {
|
|
||||||
o := testOptions()
|
|
||||||
assert.Equal(t, nil, Validate(o))
|
|
||||||
|
|
||||||
assert.Equal(t, false, o.PassAccessToken)
|
|
||||||
o.PassAccessToken = true
|
|
||||||
o.Cookie.Secret = "cookie of invalid length-"
|
|
||||||
assert.NotEqual(t, nil, Validate(o))
|
|
||||||
|
|
||||||
o.PassAccessToken = false
|
|
||||||
o.Cookie.Refresh = time.Duration(24) * time.Hour
|
|
||||||
assert.NotEqual(t, nil, Validate(o))
|
|
||||||
|
|
||||||
o.Cookie.Secret = "16 bytes AES-128"
|
|
||||||
assert.Equal(t, nil, Validate(o))
|
|
||||||
|
|
||||||
o.Cookie.Secret = "24 byte secret AES-192--"
|
|
||||||
assert.Equal(t, nil, Validate(o))
|
|
||||||
|
|
||||||
o.Cookie.Secret = "32 byte secret for AES-256------"
|
|
||||||
assert.Equal(t, nil, Validate(o))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
|
func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
|
||||||
o := testOptions()
|
o := testOptions()
|
||||||
assert.Equal(t, nil, Validate(o))
|
assert.Equal(t, nil, Validate(o))
|
||||||
|
@ -16,18 +16,21 @@ func validateSessionCookieMinimal(o *options.Options) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
msgs := []string{}
|
msgs := []string{}
|
||||||
if o.PassAuthorization {
|
for _, header := range append(o.InjectRequestHeaders, o.InjectResponseHeaders...) {
|
||||||
|
for _, value := range header.Values {
|
||||||
|
if value.ClaimSource != nil {
|
||||||
|
if value.ClaimSource.Claim == "access_token" {
|
||||||
msgs = append(msgs,
|
msgs = append(msgs,
|
||||||
"pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set")
|
fmt.Sprintf("access_token claim for header %q requires oauth tokens in sessions. session_cookie_minimal cannot be set", header.Name))
|
||||||
}
|
}
|
||||||
if o.SetAuthorization {
|
if value.ClaimSource.Claim == "id_token" {
|
||||||
msgs = append(msgs,
|
msgs = append(msgs,
|
||||||
"set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set")
|
fmt.Sprintf("id_token claim for header %q requires oauth tokens in sessions. session_cookie_minimal cannot be set", header.Name))
|
||||||
}
|
}
|
||||||
if o.PassAccessToken {
|
|
||||||
msgs = append(msgs,
|
|
||||||
"pass_access_token requires oauth tokens in sessions. session_cookie_minimal cannot be set")
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if o.Cookie.Refresh != time.Duration(0) {
|
if o.Cookie.Refresh != time.Duration(0) {
|
||||||
msgs = append(msgs,
|
msgs = append(msgs,
|
||||||
"cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set")
|
"cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set")
|
||||||
|
@ -13,9 +13,8 @@ import (
|
|||||||
|
|
||||||
var _ = Describe("Sessions", func() {
|
var _ = Describe("Sessions", func() {
|
||||||
const (
|
const (
|
||||||
passAuthorizationMsg = "pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
idTokenConflictMsg = "id_token claim for header \"X-ID-Token\" requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
||||||
setAuthorizationMsg = "set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
accessTokenConflictMsg = "access_token claim for header \"X-Access-Token\" requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
||||||
passAccessTokenMsg = "pass_access_token requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
|
||||||
cookieRefreshMsg = "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
cookieRefreshMsg = "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,14 +37,25 @@ var _ = Describe("Sessions", func() {
|
|||||||
},
|
},
|
||||||
errStrings: []string{},
|
errStrings: []string{},
|
||||||
}),
|
}),
|
||||||
Entry("No minimal cookie session & passAuthorization", &cookieMinimalTableInput{
|
Entry("No minimal cookie session & request header has access_token claim", &cookieMinimalTableInput{
|
||||||
opts: &options.Options{
|
opts: &options.Options{
|
||||||
Session: options.SessionOptions{
|
Session: options.SessionOptions{
|
||||||
Cookie: options.CookieStoreOptions{
|
Cookie: options.CookieStoreOptions{
|
||||||
Minimal: false,
|
Minimal: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassAuthorization: true,
|
InjectRequestHeaders: []options.Header{
|
||||||
|
{
|
||||||
|
Name: "X-Access-Token",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "access_token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
errStrings: []string{},
|
errStrings: []string{},
|
||||||
}),
|
}),
|
||||||
@ -59,38 +69,71 @@ var _ = Describe("Sessions", func() {
|
|||||||
},
|
},
|
||||||
errStrings: []string{},
|
errStrings: []string{},
|
||||||
}),
|
}),
|
||||||
Entry("PassAuthorization conflict", &cookieMinimalTableInput{
|
Entry("Request Header id_token conflict", &cookieMinimalTableInput{
|
||||||
opts: &options.Options{
|
opts: &options.Options{
|
||||||
Session: options.SessionOptions{
|
Session: options.SessionOptions{
|
||||||
Cookie: options.CookieStoreOptions{
|
Cookie: options.CookieStoreOptions{
|
||||||
Minimal: true,
|
Minimal: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassAuthorization: true,
|
InjectRequestHeaders: []options.Header{
|
||||||
|
{
|
||||||
|
Name: "X-ID-Token",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "id_token",
|
||||||
},
|
},
|
||||||
errStrings: []string{passAuthorizationMsg},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errStrings: []string{idTokenConflictMsg},
|
||||||
}),
|
}),
|
||||||
Entry("SetAuthorization conflict", &cookieMinimalTableInput{
|
Entry("Response Header id_token conflict", &cookieMinimalTableInput{
|
||||||
opts: &options.Options{
|
opts: &options.Options{
|
||||||
Session: options.SessionOptions{
|
Session: options.SessionOptions{
|
||||||
Cookie: options.CookieStoreOptions{
|
Cookie: options.CookieStoreOptions{
|
||||||
Minimal: true,
|
Minimal: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
SetAuthorization: true,
|
InjectResponseHeaders: []options.Header{
|
||||||
|
{
|
||||||
|
Name: "X-ID-Token",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "id_token",
|
||||||
},
|
},
|
||||||
errStrings: []string{setAuthorizationMsg},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errStrings: []string{idTokenConflictMsg},
|
||||||
}),
|
}),
|
||||||
Entry("PassAccessToken conflict", &cookieMinimalTableInput{
|
Entry("Request Header access_token conflict", &cookieMinimalTableInput{
|
||||||
opts: &options.Options{
|
opts: &options.Options{
|
||||||
Session: options.SessionOptions{
|
Session: options.SessionOptions{
|
||||||
Cookie: options.CookieStoreOptions{
|
Cookie: options.CookieStoreOptions{
|
||||||
Minimal: true,
|
Minimal: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassAccessToken: true,
|
InjectRequestHeaders: []options.Header{
|
||||||
|
{
|
||||||
|
Name: "X-Access-Token",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "access_token",
|
||||||
},
|
},
|
||||||
errStrings: []string{passAccessTokenMsg},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errStrings: []string{accessTokenConflictMsg},
|
||||||
}),
|
}),
|
||||||
Entry("CookieRefresh conflict", &cookieMinimalTableInput{
|
Entry("CookieRefresh conflict", &cookieMinimalTableInput{
|
||||||
opts: &options.Options{
|
opts: &options.Options{
|
||||||
@ -112,10 +155,32 @@ var _ = Describe("Sessions", func() {
|
|||||||
Minimal: true,
|
Minimal: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassAuthorization: true,
|
InjectResponseHeaders: []options.Header{
|
||||||
PassAccessToken: true,
|
{
|
||||||
|
Name: "X-ID-Token",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "id_token",
|
||||||
},
|
},
|
||||||
errStrings: []string{passAuthorizationMsg, passAccessTokenMsg},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
InjectRequestHeaders: []options.Header{
|
||||||
|
{
|
||||||
|
Name: "X-Access-Token",
|
||||||
|
Values: []options.HeaderValue{
|
||||||
|
{
|
||||||
|
ClaimSource: &options.ClaimSource{
|
||||||
|
Claim: "access_token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errStrings: []string{idTokenConflictMsg, accessTokenConflictMsg},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
11
pkg/validation/utils.go
Normal file
11
pkg/validation/utils.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
func prefixValues(prefix string, values ...string) []string {
|
||||||
|
msgs := []string{}
|
||||||
|
for _, value := range values {
|
||||||
|
if value != "" {
|
||||||
|
msgs = append(msgs, prefix+value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msgs
|
||||||
|
}
|
Reference in New Issue
Block a user