1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2024-11-24 08:52:25 +02:00

Merge pull request #489 from oauth2-proxy/move-options

Move Options and Validation to packages
This commit is contained in:
Joel Speed 2020-05-23 15:59:29 +01:00 committed by GitHub
commit 236c7fa60e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 583 additions and 519 deletions

View File

@ -9,13 +9,14 @@ import (
"strings"
"time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
)
// Server represents an HTTP server
type Server struct {
Handler http.Handler
Opts *Options
Opts *options.Options
stop chan struct{} // channel for waiting shutdown
}
@ -167,7 +168,7 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
return tc, nil
}
func redirectToHTTPS(opts *Options, h http.Handler) http.Handler {
func redirectToHTTPS(opts *options.Options, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
proto := r.Header.Get("X-Forwarded-Proto")
if opts.ForceHTTPS && (r.TLS == nil || (proto != "" && strings.ToLower(proto) != "https")) {

View File

@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
"github.com/stretchr/testify/assert"
)
@ -110,7 +111,7 @@ func TestGCPHealthcheckNotIngressPut(t *testing.T) {
}
func TestRedirectToHTTPSTrue(t *testing.T) {
opts := NewOptions()
opts := options.NewOptions()
opts.ForceHTTPS = true
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
@ -125,7 +126,7 @@ func TestRedirectToHTTPSTrue(t *testing.T) {
}
func TestRedirectToHTTPSFalse(t *testing.T) {
opts := NewOptions()
opts := options.NewOptions()
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
@ -139,7 +140,7 @@ func TestRedirectToHTTPSFalse(t *testing.T) {
}
func TestRedirectNotWhenHTTPS(t *testing.T) {
opts := NewOptions()
opts := options.NewOptions()
opts.ForceHTTPS = true
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
@ -160,7 +161,7 @@ func TestRedirectNotWhenHTTPS(t *testing.T) {
}
func TestGracefulShutdown(t *testing.T) {
opts := NewOptions()
opts := options.NewOptions()
stop := make(chan struct{}, 1)
srv := Server{Handler: http.DefaultServeMux, Opts: opts, stop: stop}
var wg sync.WaitGroup

126
main.go
View File

@ -13,134 +13,16 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
"github.com/spf13/pflag"
"github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
)
func main() {
logger.SetFlags(logger.Lshortfile)
flagSet := pflag.NewFlagSet("oauth2-proxy", pflag.ExitOnError)
flagSet := options.NewFlagSet()
config := flagSet.String("config", "", "path to config file")
showVersion := flagSet.Bool("version", false, "print version string")
flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients")
flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients")
flagSet.Bool("reverse-proxy", false, "are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted")
flagSet.String("real-client-ip-header", "X-Real-IP", "Header used to determine the real IP of the client (one of: X-Forwarded-For, X-Real-IP, or X-ProxyUser-IP)")
flagSet.Bool("force-https", false, "force HTTPS redirect for HTTP requests")
flagSet.String("tls-cert-file", "", "path to certificate file")
flagSet.String("tls-key-file", "", "path to private key file")
flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)")
flagSet.StringSlice("upstream", []string{}, "the http url(s) of the upstream endpoint, file:// paths for static files or static://<status_code> for static response. Routing is based on the path")
flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
flagSet.Bool("set-basic-auth", false, "set HTTP Basic Auth information in response (useful in Nginx auth_request mode)")
flagSet.Bool("prefer-email-to-user", false, "Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. Used in conjunction with -pass-basic-auth and -pass-user-headers")
flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")
flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream")
flagSet.Bool("set-authorization-header", false, "set Authorization response headers (useful in Nginx auth_request mode)")
flagSet.StringSlice("skip-auth-regex", []string{}, "bypass authentication for requests path's that match (may be given multiple times)")
flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start")
flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests")
flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers")
flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams")
flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses")
flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)")
flagSet.StringSlice("extra-jwt-issuers", []string{}, "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)")
flagSet.StringSlice("email-domain", []string{}, "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email")
flagSet.StringSlice("whitelist-domain", []string{}, "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)")
flagSet.String("keycloak-group", "", "restrict login to members of this group.")
flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
flagSet.String("bitbucket-team", "", "restrict logins to members of this team")
flagSet.String("bitbucket-repository", "", "restrict logins to user with access to this repository")
flagSet.String("github-org", "", "restrict logins to members of this organisation")
flagSet.String("github-team", "", "restrict logins to members of this team")
flagSet.String("github-repo", "", "restrict logins to collaborators of this repository")
flagSet.String("github-token", "", "the token to use when verifying repository collaborators (must have push access to the repository)")
flagSet.String("gitlab-group", "", "restrict logins to members of this group")
flagSet.StringSlice("google-group", []string{}, "restrict logins to members of this google group (may be given multiple times).")
flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls")
flagSet.String("google-service-account-json", "", "the path to the service account json credentials")
flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
flagSet.String("client-secret", "", "the OAuth Client Secret")
flagSet.String("client-secret-file", "", "the file with OAuth Client Secret")
flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption or \"htpasswd -B\" for bcrypt encryption")
flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
flagSet.String("custom-templates-dir", "", "path to custom html templates")
flagSet.String("banner", "", "custom banner string. Use \"-\" to disable default banner.")
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
flagSet.String("ping-path", "/ping", "the ping endpoint that can be used for basic health checks")
flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)")
flagSet.StringSlice("cookie-domain", []string{}, "Optional cookie domains to force cookies to (ie: `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match).")
flagSet.String("cookie-path", "/", "an optional cookie path to force cookies to (ie: /poc/)*")
flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag")
flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
flagSet.String("cookie-samesite", "", "set SameSite cookie attribute (ie: \"lax\", \"strict\", \"none\", or \"\"). ")
flagSet.String("session-store-type", "cookie", "the session storage provider to use")
flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])")
flagSet.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature")
flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel")
flagSet.String("redis-ca-path", "", "Redis custom CA path")
flagSet.Bool("redis-insecure-skip-tls-verify", false, "Use insecure TLS connection to redis")
flagSet.StringSlice("redis-sentinel-connection-urls", []string{}, "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel")
flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature")
flagSet.StringSlice("redis-cluster-connection-urls", []string{}, "List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-cluster")
flagSet.String("logging-filename", "", "File to log requests to, empty for stdout")
flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation")
flagSet.Int("logging-max-age", 7, "Maximum number of days to retain old log files")
flagSet.Int("logging-max-backups", 0, "Maximum number of old log files to retain; 0 to disable")
flagSet.Bool("logging-local-time", true, "If the time in log files and backup filenames are local or UTC time")
flagSet.Bool("logging-compress", false, "Should rotated log files be compressed using gzip")
flagSet.Bool("standard-logging", true, "Log standard runtime information")
flagSet.String("standard-logging-format", logger.DefaultStandardLoggingFormat, "Template for standard log lines")
flagSet.Bool("request-logging", true, "Log HTTP requests")
flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines")
flagSet.String("exclude-logging-paths", "", "Exclude logging requests to paths (eg: '/path1,/path2,/path3')")
flagSet.Bool("silence-ping-logging", false, "Disable logging of requests to ping endpoint")
flagSet.Bool("auth-logging", true, "Log authentication attempts")
flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines")
flagSet.String("provider", "google", "OAuth provider")
flagSet.String("provider-display-name", "", "Provider display name")
flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified")
flagSet.Bool("insecure-oidc-skip-issuer-verification", false, "Do not verify if issuer matches OIDC discovery URL")
flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints")
flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)")
flagSet.String("login-url", "", "Authentication endpoint")
flagSet.String("redeem-url", "", "Token redemption endpoint")
flagSet.String("profile-url", "", "Profile access endpoint")
flagSet.String("resource", "", "The resource that is protected (Azure AD only)")
flagSet.String("validate-url", "", "Access token validation endpoint")
flagSet.String("scope", "", "OAuth scope specification")
flagSet.String("prompt", "", "OIDC prompt")
flagSet.String("approval-prompt", "force", "OAuth approval_prompt")
flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)")
flagSet.String("acr-values", "", "acr values string: optional")
flagSet.String("jwt-key", "", "private key in PEM format used to sign JWT, so that you can say something like -jwt-key=\"${OAUTH2_PROXY_JWT_KEY}\": required by login.gov")
flagSet.String("jwt-key-file", "", "path to the private key file in PEM format used to sign the JWT so that you can say something like -jwt-key-file=/etc/ssl/private/jwt_signing_key.pem: required by login.gov")
flagSet.String("pubjwk-url", "", "JWK pubkey access endpoint: required by login.gov")
flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints")
flagSet.String("user-id-claim", "email", "which claim contains the user ID")
flagSet.Parse(os.Args[1:])
if *showVersion {
@ -148,14 +30,14 @@ func main() {
return
}
opts := NewOptions()
opts := options.NewOptions()
err := options.Load(*config, flagSet, opts)
if err != nil {
logger.Printf("ERROR: Failed to load config: %v", err)
os.Exit(1)
}
err = opts.Validate()
err = validation.Validate(opts)
if err != nil {
logger.Printf("%s", err)
os.Exit(1)

View File

@ -19,9 +19,12 @@ import (
"github.com/coreos/go-oidc"
"github.com/mbland/hmacauth"
ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/cookies"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
"github.com/oauth2-proxy/oauth2-proxy/pkg/ip"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/providers"
"github.com/yhat/wsutil"
@ -112,7 +115,7 @@ type OAuthProxy struct {
jwtBearerVerifiers []*oidc.IDTokenVerifier
compiledRegex []*regexp.Regexp
templates *template.Template
realClientIPParser realClientIPParser
realClientIPParser ipapi.RealClientIPParser
Banner string
Footer string
}
@ -143,7 +146,7 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
// servers
func NewReverseProxy(target *url.URL, opts *Options) (proxy *httputil.ReverseProxy) {
func NewReverseProxy(target *url.URL, opts *options.Options) (proxy *httputil.ReverseProxy) {
proxy = httputil.NewSingleHostReverseProxy(target)
proxy.FlushInterval = opts.FlushInterval
if opts.SSLUpstreamInsecureSkipVerify {
@ -181,7 +184,7 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
}
// NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url
func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) http.Handler {
func NewWebSocketOrRestReverseProxy(u *url.URL, opts *options.Options, auth hmacauth.HmacAuth) http.Handler {
u.Path = ""
proxy := NewReverseProxy(u, opts)
if !opts.PassHostHeader {
@ -209,14 +212,14 @@ func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.Hma
}
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
func NewOAuthProxy(opts *options.Options, validator func(string) bool) *OAuthProxy {
serveMux := http.NewServeMux()
var auth hmacauth.HmacAuth
if sigData := opts.signatureData; sigData != nil {
auth = hmacauth.NewHmacAuth(sigData.hash, []byte(sigData.key),
if sigData := opts.GetSignatureData(); sigData != nil {
auth = hmacauth.NewHmacAuth(sigData.Hash, []byte(sigData.Key),
SignatureHeader, SignatureHeaders)
}
for _, u := range opts.proxyURLs {
for _, u := range opts.GetProxyURLs() {
path := u.Path
host := u.Host
switch u.Scheme {
@ -252,7 +255,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
}
}
for _, u := range opts.compiledRegex {
for _, u := range opts.GetCompiledRegex() {
logger.Printf("compiled skip-auth-regex => %q", u)
}
@ -262,12 +265,12 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
logger.Printf("Skipping JWT tokens from extra JWT issuer: %q", issuer)
}
}
redirectURL := opts.redirectURL
redirectURL := opts.GetRedirectURL()
if redirectURL.Path == "" {
redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
}
logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.GetProvider().Data().ProviderName, opts.ClientID)
refresh := "disabled"
if opts.Cookie.Refresh != time.Duration(0) {
refresh = fmt.Sprintf("after %s", opts.Cookie.Refresh)
@ -298,18 +301,18 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
UserInfoPath: fmt.Sprintf("%s/userinfo", opts.ProxyPrefix),
ProxyPrefix: opts.ProxyPrefix,
provider: opts.provider,
provider: opts.GetProvider(),
providerNameOverride: opts.ProviderName,
sessionStore: opts.sessionStore,
sessionStore: opts.GetSessionStore(),
serveMux: serveMux,
redirectURL: redirectURL,
whitelistDomains: opts.WhitelistDomains,
skipAuthRegex: opts.SkipAuthRegex,
skipAuthPreflight: opts.SkipAuthPreflight,
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
jwtBearerVerifiers: opts.jwtBearerVerifiers,
compiledRegex: opts.compiledRegex,
realClientIPParser: opts.realClientIPParser,
jwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
compiledRegex: opts.GetCompiledRegex(),
realClientIPParser: opts.GetRealClientIPParser(),
SetXAuthRequest: opts.SetXAuthRequest,
PassBasicAuth: opts.PassBasicAuth,
SetBasicAuth: opts.SetBasicAuth,
@ -760,7 +763,7 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
// OAuthCallback is the OAuth2 authentication flow callback that finishes the
// OAuth2 authentication flow
func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
remoteAddr := getClientString(p.realClientIPParser, req, true)
remoteAddr := ip.GetClientString(p.realClientIPParser, req, true)
// finish the oauth cycle
err := req.ParseForm()
@ -888,7 +891,7 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
}
}
remoteAddr := getClientString(p.realClientIPParser, req, true)
remoteAddr := ip.GetClientString(p.realClientIPParser, req, true)
if session == nil {
session, err = p.LoadCookiedSession(req)
if err != nil {

View File

@ -18,9 +18,11 @@ import (
"github.com/coreos/go-oidc"
"github.com/mbland/hmacauth"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
"github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
"github.com/oauth2-proxy/oauth2-proxy/providers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -71,7 +73,7 @@ func TestWebSocketProxy(t *testing.T) {
backendURL, _ := url.Parse(backend.URL)
options := NewOptions()
options := options.NewOptions()
var auth hmacauth.HmacAuth
options.PassHostHeader = true
proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, options, auth)
@ -121,7 +123,7 @@ func TestNewReverseProxy(t *testing.T) {
backendHost := net.JoinHostPort(backendHostname, backendPort)
proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
proxyHandler := NewReverseProxy(proxyURL, &Options{FlushInterval: time.Second})
proxyHandler := NewReverseProxy(proxyURL, &options.Options{FlushInterval: time.Second})
setProxyUpstreamHostHeader(proxyHandler, proxyURL)
frontend := httptest.NewServer(proxyHandler)
defer frontend.Close()
@ -143,7 +145,7 @@ func TestEncodedSlashes(t *testing.T) {
defer backend.Close()
b, _ := url.Parse(backend.URL)
proxyHandler := NewReverseProxy(b, &Options{FlushInterval: time.Second})
proxyHandler := NewReverseProxy(b, &options.Options{FlushInterval: time.Second})
setProxyDirector(proxyHandler)
frontend := httptest.NewServer(proxyHandler)
defer frontend.Close()
@ -161,11 +163,11 @@ func TestEncodedSlashes(t *testing.T) {
}
func TestRobotsTxt(t *testing.T) {
opts := NewOptions()
opts := options.NewOptions()
opts.ClientID = "asdlkjx"
opts.ClientSecret = "alkgks"
opts.Cookie.Secret = "asdkugkj"
opts.Validate()
validation.Validate(opts)
proxy := NewOAuthProxy(opts, func(string) bool { return true })
rw := httptest.NewRecorder()
@ -176,7 +178,7 @@ func TestRobotsTxt(t *testing.T) {
}
func TestIsValidRedirect(t *testing.T) {
opts := NewOptions()
opts := options.NewOptions()
opts.ClientID = "skdlfj"
opts.ClientSecret = "fgkdsgj"
opts.Cookie.Secret = "ljgiogbj"
@ -189,7 +191,7 @@ func TestIsValidRedirect(t *testing.T) {
"anyport.bar:*",
".sub.anyport.bar:*",
}
opts.Validate()
validation.Validate(opts)
proxy := NewOAuthProxy(opts, func(string) bool { return true })
@ -451,7 +453,7 @@ func TestBasicAuthPassword(t *testing.T) {
w.WriteHeader(200)
w.Write([]byte(payload))
}))
opts := NewOptions()
opts := options.NewOptions()
opts.Upstreams = append(opts.Upstreams, providerServer.URL)
// The CookieSecret must be 32 bytes in order to create the AES
// cipher.
@ -464,12 +466,12 @@ func TestBasicAuthPassword(t *testing.T) {
opts.PassUserHeaders = true
opts.PreferEmailToUser = true
opts.BasicAuthPassword = "This is a secure password"
opts.Validate()
validation.Validate(opts)
providerURL, _ := url.Parse(providerServer.URL)
const emailAddress = "john.doe@example.com"
opts.provider = NewTestProvider(providerURL, emailAddress)
opts.SetProvider(NewTestProvider(providerURL, emailAddress))
proxy := NewOAuthProxy(opts, func(email string) bool {
return email == emailAddress
})
@ -518,12 +520,12 @@ func TestBasicAuthPassword(t *testing.T) {
}
func TestBasicAuthWithEmail(t *testing.T) {
opts := NewOptions()
opts := options.NewOptions()
opts.PassBasicAuth = true
opts.PassUserHeaders = false
opts.PreferEmailToUser = false
opts.BasicAuthPassword = "This is a secure password"
opts.Validate()
validation.Validate(opts)
const emailAddress = "john.doe@example.com"
const userName = "9fcab5c9b889a557"
@ -564,11 +566,11 @@ func TestBasicAuthWithEmail(t *testing.T) {
}
func TestPassUserHeadersWithEmail(t *testing.T) {
opts := NewOptions()
opts := options.NewOptions()
opts.PassBasicAuth = false
opts.PassUserHeaders = true
opts.PreferEmailToUser = false
opts.Validate()
validation.Validate(opts)
const emailAddress = "john.doe@example.com"
const userName = "9fcab5c9b889a557"
@ -605,7 +607,7 @@ func TestPassUserHeadersWithEmail(t *testing.T) {
type PassAccessTokenTest struct {
providerServer *httptest.Server
proxy *OAuthProxy
opts *Options
opts *options.Options
}
type PassAccessTokenTestOptions struct {
@ -632,7 +634,7 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
w.Write([]byte(payload))
}))
t.opts = NewOptions()
t.opts = options.NewOptions()
t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
if opts.ProxyUpstream != "" {
t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream)
@ -644,12 +646,12 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
t.opts.ClientSecret = "gfjgojl"
t.opts.Cookie.Secure = false
t.opts.PassAccessToken = opts.PassAccessToken
t.opts.Validate()
validation.Validate(t.opts)
providerURL, _ := url.Parse(t.providerServer.URL)
const emailAddress = "michael.bland@gsa.gov"
t.opts.provider = NewTestProvider(providerURL, emailAddress)
t.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
return email == emailAddress
})
@ -779,7 +781,7 @@ func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
}
type SignInPageTest struct {
opts *Options
opts *options.Options
proxy *OAuthProxy
signInRegexp *regexp.Regexp
signInProviderRegexp *regexp.Regexp
@ -791,12 +793,12 @@ const signInSkipProvider = `>Found<`
func NewSignInPageTest(skipProvider bool) *SignInPageTest {
var sipTest SignInPageTest
sipTest.opts = NewOptions()
sipTest.opts = options.NewOptions()
sipTest.opts.Cookie.Secret = "adklsj2"
sipTest.opts.ClientID = "lkdgj"
sipTest.opts.ClientSecret = "sgiufgoi"
sipTest.opts.SkipProviderButton = skipProvider
sipTest.opts.Validate()
validation.Validate(sipTest.opts)
sipTest.proxy = NewOAuthProxy(sipTest.opts, func(email string) bool {
return true
@ -876,7 +878,7 @@ func TestSignInPageSkipProviderDirect(t *testing.T) {
}
type ProcessCookieTest struct {
opts *Options
opts *options.Options
proxy *OAuthProxy
rw *httptest.ResponseRecorder
req *http.Request
@ -887,12 +889,12 @@ type ProcessCookieTestOpts struct {
providerValidateCookieResponse bool
}
type OptionsModifier func(*Options)
type OptionsModifier func(*options.Options)
func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest {
var pcTest ProcessCookieTest
pcTest.opts = NewOptions()
pcTest.opts = options.NewOptions()
for _, modifier := range modifiers {
modifier(pcTest.opts)
}
@ -902,7 +904,7 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi
// First, set the CookieRefresh option so proxy.AesCipher is created,
// needed to encrypt the access_token.
pcTest.opts.Cookie.Refresh = time.Hour
pcTest.opts.Validate()
validation.Validate(pcTest.opts)
pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
return pcTest.validateUser
@ -971,7 +973,7 @@ func TestProcessCookieNoCookieError(t *testing.T) {
}
func TestProcessCookieRefreshNotSet(t *testing.T) {
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
opts.Cookie.Expire = time.Duration(23) * time.Hour
})
reference := time.Now().Add(time.Duration(-2) * time.Hour)
@ -988,7 +990,7 @@ func TestProcessCookieRefreshNotSet(t *testing.T) {
}
func TestProcessCookieFailIfCookieExpired(t *testing.T) {
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
opts.Cookie.Expire = time.Duration(24) * time.Hour
})
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
@ -1003,7 +1005,7 @@ func TestProcessCookieFailIfCookieExpired(t *testing.T) {
}
func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
opts.Cookie.Expire = time.Duration(24) * time.Hour
})
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
@ -1073,7 +1075,7 @@ func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
}
func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
test := NewAuthOnlyEndpointTest(func(opts *Options) {
test := NewAuthOnlyEndpointTest(func(opts *options.Options) {
opts.Cookie.Expire = time.Duration(24) * time.Hour
})
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
@ -1103,9 +1105,9 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
var pcTest ProcessCookieTest
pcTest.opts = NewOptions()
pcTest.opts = options.NewOptions()
pcTest.opts.SetXAuthRequest = true
pcTest.opts.Validate()
validation.Validate(pcTest.opts)
pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
return pcTest.validateUser
@ -1133,10 +1135,10 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
var pcTest ProcessCookieTest
pcTest.opts = NewOptions()
pcTest.opts = options.NewOptions()
pcTest.opts.SetXAuthRequest = true
pcTest.opts.SetBasicAuth = true
pcTest.opts.Validate()
validation.Validate(pcTest.opts)
pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
return pcTest.validateUser
@ -1166,10 +1168,10 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
var pcTest ProcessCookieTest
pcTest.opts = NewOptions()
pcTest.opts = options.NewOptions()
pcTest.opts.SetXAuthRequest = true
pcTest.opts.SetBasicAuth = false
pcTest.opts.Validate()
validation.Validate(pcTest.opts)
pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
return pcTest.validateUser
@ -1202,16 +1204,16 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
}))
defer upstream.Close()
opts := NewOptions()
opts := options.NewOptions()
opts.Upstreams = append(opts.Upstreams, upstream.URL)
opts.ClientID = "aljsal"
opts.ClientSecret = "jglkfsdgj"
opts.Cookie.Secret = "dkfjgdls"
opts.SkipAuthPreflight = true
opts.Validate()
validation.Validate(opts)
upstreamURL, _ := url.Parse(upstream.URL)
opts.provider = NewTestProvider(upstreamURL, "")
opts.SetProvider(NewTestProvider(upstreamURL, ""))
proxy := NewOAuthProxy(opts, func(string) bool { return false })
rw := httptest.NewRecorder()
@ -1242,7 +1244,7 @@ func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Req
}
type SignatureTest struct {
opts *Options
opts *options.Options
upstream *httptest.Server
upstreamHost string
provider *httptest.Server
@ -1252,7 +1254,7 @@ type SignatureTest struct {
}
func NewSignatureTest() *SignatureTest {
opts := NewOptions()
opts := options.NewOptions()
opts.Cookie.Secret = "cookie secret"
opts.ClientID = "client ID"
opts.ClientSecret = "client secret"
@ -1269,7 +1271,7 @@ func NewSignatureTest() *SignatureTest {
}
provider := httptest.NewServer(http.HandlerFunc(providerHandler))
providerURL, _ := url.Parse(provider.URL)
opts.provider = NewTestProvider(providerURL, "mbland@acm.org")
opts.SetProvider(NewTestProvider(providerURL, "mbland@acm.org"))
return &SignatureTest{
opts,
@ -1304,7 +1306,7 @@ func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
}
func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
err := st.opts.Validate()
err := validation.Validate(st.opts)
if err != nil {
panic(err)
}
@ -1360,8 +1362,8 @@ func TestRequestSignaturePostRequest(t *testing.T) {
}
func TestGetRedirect(t *testing.T) {
options := NewOptions()
_ = options.Validate()
options := options.NewOptions()
_ = validation.Validate(options)
require.NotEmpty(t, options.ProxyPrefix)
proxy := NewOAuthProxy(options, func(s string) bool { return false })
@ -1393,17 +1395,17 @@ func TestGetRedirect(t *testing.T) {
}
type ajaxRequestTest struct {
opts *Options
opts *options.Options
proxy *OAuthProxy
}
func newAjaxRequestTest() *ajaxRequestTest {
test := &ajaxRequestTest{}
test.opts = NewOptions()
test.opts = options.NewOptions()
test.opts.Cookie.Secret = "sdflsw"
test.opts.ClientID = "gkljfdl"
test.opts.ClientSecret = "sdflkjs"
test.opts.Validate()
validation.Validate(test.opts)
test.proxy = NewOAuthProxy(test.opts, func(email string) bool {
return true
})
@ -1457,7 +1459,7 @@ func TestAjaxForbiddendRequest(t *testing.T) {
}
func TestClearSplitCookie(t *testing.T) {
opts := NewOptions()
opts := options.NewOptions()
opts.Cookie.Name = "oauth2"
opts.Cookie.Domains = []string{"abc"}
store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
@ -1486,7 +1488,7 @@ func TestClearSplitCookie(t *testing.T) {
}
func TestClearSingleCookie(t *testing.T) {
opts := NewOptions()
opts := options.NewOptions()
opts.Cookie.Name = "oauth2"
opts.Cookie.Domains = []string{"abc"}
store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
@ -1542,12 +1544,12 @@ func TestGetJwtSession(t *testing.T) {
verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
test := NewAuthOnlyEndpointTest(func(opts *Options) {
test := NewAuthOnlyEndpointTest(func(opts *options.Options) {
opts.PassAuthorization = true
opts.SetAuthorization = true
opts.SetXAuthRequest = true
opts.SkipJwtBearerTokens = true
opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier)
opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
})
tp, _ := test.proxy.provider.(*TestProvider)
tp.GroupValidator = func(s string) bool {
@ -1666,10 +1668,10 @@ func Test_noCacheHeadersDoesNotExistsInResponseHeadersFromUpstream(t *testing.T)
}))
t.Cleanup(upstream.Close)
opts := NewOptions()
opts := options.NewOptions()
opts.Upstreams = []string{upstream.URL}
opts.SkipAuthRegex = []string{".*"}
_ = opts.Validate()
_ = validation.Validate(opts)
proxy := NewOAuthProxy(opts, func(email string) bool {
return true
})

11
pkg/apis/ip/interfaces.go Normal file
View File

@ -0,0 +1,11 @@
package ip
import (
"net"
"net/http"
)
// RealClientIPParser is an interface for a getting the client's real IP to be used for logging.
type RealClientIPParser interface {
GetRealClientIP(http.Header) (net.IP, error)
}

View File

@ -4,13 +4,13 @@ import "time"
// CookieOptions contains configuration options relating to Cookie configuration
type CookieOptions struct {
Name string `flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME"`
Secret string `flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET"`
Domains []string `flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN"`
Path string `flag:"cookie-path" cfg:"cookie_path" env:"OAUTH2_PROXY_COOKIE_PATH"`
Expire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE"`
Refresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"`
Secure bool `flag:"cookie-secure" cfg:"cookie_secure" env:"OAUTH2_PROXY_COOKIE_SECURE"`
HTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly" env:"OAUTH2_PROXY_COOKIE_HTTPONLY"`
SameSite string `flag:"cookie-samesite" cfg:"cookie_samesite" env:"OAUTH2_PROXY_COOKIE_SAMESITE"`
Name string `flag:"cookie-name" cfg:"cookie_name"`
Secret string `flag:"cookie-secret" cfg:"cookie_secret"`
Domains []string `flag:"cookie-domain" cfg:"cookie_domain"`
Path string `flag:"cookie-path" cfg:"cookie_path"`
Expire time.Duration `flag:"cookie-expire" cfg:"cookie_expire"`
Refresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh"`
Secure bool `flag:"cookie-secure" cfg:"cookie_secure"`
HTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"`
SameSite string `flag:"cookie-samesite" cfg:"cookie_samesite"`
}

View File

@ -295,6 +295,11 @@ var _ = Describe("Load", func() {
},
},
}),
Entry("with an empty Options struct, should return default values", &testOptionsTableInput{
flagSet: NewFlagSet,
input: &Options{},
expectedOutput: NewOptions(),
}),
)
})
})

340
pkg/apis/options/options.go Normal file
View File

@ -0,0 +1,340 @@
package options
import (
"crypto"
"net/url"
"regexp"
"time"
oidc "github.com/coreos/go-oidc"
ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/providers"
"github.com/spf13/pflag"
)
// SignatureData holds hmacauth signature hash and key
type SignatureData struct {
Hash crypto.Hash
Key string
}
// Options holds Configuration Options that can be set by Command Line Flag,
// or Config File
type Options struct {
ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix"`
PingPath string `flag:"ping-path" cfg:"ping_path"`
ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets"`
HTTPAddress string `flag:"http-address" cfg:"http_address"`
HTTPSAddress string `flag:"https-address" cfg:"https_address"`
ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy"`
RealClientIPHeader string `flag:"real-client-ip-header" cfg:"real_client_ip_header"`
ForceHTTPS bool `flag:"force-https" cfg:"force_https"`
RawRedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
ClientID string `flag:"client-id" cfg:"client_id"`
ClientSecret string `flag:"client-secret" cfg:"client_secret"`
ClientSecretFile string `flag:"client-secret-file" cfg:"client_secret_file"`
TLSCertFile string `flag:"tls-cert-file" cfg:"tls_cert_file"`
TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file"`
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group"`
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant"`
BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team"`
BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository"`
EmailDomains []string `flag:"email-domain" cfg:"email_domains"`
WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains"`
GitHubOrg string `flag:"github-org" cfg:"github_org"`
GitHubTeam string `flag:"github-team" cfg:"github_team"`
GitHubRepo string `flag:"github-repo" cfg:"github_repo"`
GitHubToken string `flag:"github-token" cfg:"github_token"`
GitLabGroup string `flag:"gitlab-group" cfg:"gitlab_group"`
GoogleGroups []string `flag:"google-group" cfg:"google_group"`
GoogleAdminEmail string `flag:"google-admin-email" cfg:"google_admin_email"`
GoogleServiceAccountJSON string `flag:"google-service-account-json" cfg:"google_service_account_json"`
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"`
DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"`
CustomTemplatesDir string `flag:"custom-templates-dir" cfg:"custom_templates_dir"`
Banner string `flag:"banner" cfg:"banner"`
Footer string `flag:"footer" cfg:"footer"`
Cookie CookieOptions `cfg:",squash"`
Session SessionOptions `cfg:",squash"`
Upstreams []string `flag:"upstream" cfg:"upstreams"`
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"`
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
SetBasicAuth bool `flag:"set-basic-auth" cfg:"set_basic_auth"`
PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user"`
BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`
SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button"`
PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers"`
SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
SSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify"`
SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest"`
SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header"`
PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header"`
SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
FlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval"`
// These options allow for other providers besides Google, with
// potential overrides.
ProviderType string `flag:"provider" cfg:"provider"`
ProviderName string `flag:"provider-display-name" cfg:"provider_display_name"`
OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url"`
InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email"`
InsecureOIDCSkipIssuerVerification bool `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification"`
SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery"`
OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url"`
LoginURL string `flag:"login-url" cfg:"login_url"`
RedeemURL string `flag:"redeem-url" cfg:"redeem_url"`
ProfileURL string `flag:"profile-url" cfg:"profile_url"`
ProtectedResource string `flag:"resource" cfg:"resource"`
ValidateURL string `flag:"validate-url" cfg:"validate_url"`
Scope string `flag:"scope" cfg:"scope"`
Prompt string `flag:"prompt" cfg:"prompt"`
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"`
// Configuration values for logging
LoggingFilename string `flag:"logging-filename" cfg:"logging_filename"`
LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size"`
LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age"`
LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups"`
LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time"`
LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress"`
StandardLogging bool `flag:"standard-logging" cfg:"standard_logging"`
StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format"`
RequestLogging bool `flag:"request-logging" cfg:"request_logging"`
RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format"`
ExcludeLoggingPaths string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths"`
SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging"`
AuthLogging bool `flag:"auth-logging" cfg:"auth_logging"`
AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format"`
SignatureKey string `flag:"signature-key" cfg:"signature_key"`
AcrValues string `flag:"acr-values" cfg:"acr_values"`
JWTKey string `flag:"jwt-key" cfg:"jwt_key"`
JWTKeyFile string `flag:"jwt-key-file" cfg:"jwt_key_file"`
PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url"`
GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks"`
// internal values that are set after config validation
redirectURL *url.URL
proxyURLs []*url.URL
compiledRegex []*regexp.Regexp
provider providers.Provider
sessionStore sessionsapi.SessionStore
signatureData *SignatureData
oidcVerifier *oidc.IDTokenVerifier
jwtBearerVerifiers []*oidc.IDTokenVerifier
realClientIPParser ipapi.RealClientIPParser
}
// Options for Getting internal values
func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
func (o *Options) GetProxyURLs() []*url.URL { return o.proxyURLs }
func (o *Options) GetCompiledRegex() []*regexp.Regexp { return o.compiledRegex }
func (o *Options) GetProvider() providers.Provider { return o.provider }
func (o *Options) GetSessionStore() sessionsapi.SessionStore { return o.sessionStore }
func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
func (o *Options) GetOIDCVerifier() *oidc.IDTokenVerifier { return o.oidcVerifier }
func (o *Options) GetJWTBearerVerifiers() []*oidc.IDTokenVerifier { return o.jwtBearerVerifiers }
func (o *Options) GetRealClientIPParser() ipapi.RealClientIPParser { return o.realClientIPParser }
// Options for Setting internal values
func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
func (o *Options) SetProxyURLs(s []*url.URL) { o.proxyURLs = s }
func (o *Options) SetCompiledRegex(s []*regexp.Regexp) { o.compiledRegex = s }
func (o *Options) SetProvider(s providers.Provider) { o.provider = s }
func (o *Options) SetSessionStore(s sessionsapi.SessionStore) { o.sessionStore = s }
func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
func (o *Options) SetOIDCVerifier(s *oidc.IDTokenVerifier) { o.oidcVerifier = s }
func (o *Options) SetJWTBearerVerifiers(s []*oidc.IDTokenVerifier) { o.jwtBearerVerifiers = s }
func (o *Options) SetRealClientIPParser(s ipapi.RealClientIPParser) { o.realClientIPParser = s }
// NewOptions constructs a new Options with defaulted values
func NewOptions() *Options {
return &Options{
ProxyPrefix: "/oauth2",
ProviderType: "google",
PingPath: "/ping",
ProxyWebSockets: true,
HTTPAddress: "127.0.0.1:4180",
HTTPSAddress: ":443",
RealClientIPHeader: "X-Real-IP",
ForceHTTPS: false,
DisplayHtpasswdForm: true,
Cookie: CookieOptions{
Name: "_oauth2_proxy",
Secure: true,
HTTPOnly: true,
Expire: time.Duration(168) * time.Hour,
Refresh: time.Duration(0),
Path: "/",
},
Session: SessionOptions{
Type: "cookie",
},
AzureTenant: "common",
SetXAuthRequest: false,
SkipAuthPreflight: false,
FlushInterval: time.Duration(1) * time.Second,
PassBasicAuth: true,
SetBasicAuth: false,
PassUserHeaders: true,
PassAccessToken: false,
PassHostHeader: true,
SetAuthorization: false,
PassAuthorization: false,
PreferEmailToUser: false,
Prompt: "", // Change to "login" when ApprovalPrompt officially deprecated
ApprovalPrompt: "force",
UserIDClaim: "email",
InsecureOIDCAllowUnverifiedEmail: false,
SkipOIDCDiscovery: false,
LoggingFilename: "",
LoggingMaxSize: 100,
LoggingMaxAge: 7,
LoggingMaxBackups: 0,
LoggingLocalTime: true,
LoggingCompress: false,
ExcludeLoggingPaths: "",
SilencePingLogging: false,
StandardLogging: true,
StandardLoggingFormat: logger.DefaultStandardLoggingFormat,
RequestLogging: true,
RequestLoggingFormat: logger.DefaultRequestLoggingFormat,
AuthLogging: true,
AuthLoggingFormat: logger.DefaultAuthLoggingFormat,
}
}
// NewFlagSet creates a new FlagSet with all of the flags required by Options
func NewFlagSet() *pflag.FlagSet {
flagSet := pflag.NewFlagSet("oauth2-proxy", pflag.ExitOnError)
flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients")
flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients")
flagSet.Bool("reverse-proxy", false, "are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted")
flagSet.String("real-client-ip-header", "X-Real-IP", "Header used to determine the real IP of the client (one of: X-Forwarded-For, X-Real-IP, or X-ProxyUser-IP)")
flagSet.Bool("force-https", false, "force HTTPS redirect for HTTP requests")
flagSet.String("tls-cert-file", "", "path to certificate file")
flagSet.String("tls-key-file", "", "path to private key file")
flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)")
flagSet.StringSlice("upstream", []string{}, "the http url(s) of the upstream endpoint, file:// paths for static files or static://<status_code> for static response. Routing is based on the path")
flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
flagSet.Bool("set-basic-auth", false, "set HTTP Basic Auth information in response (useful in Nginx auth_request mode)")
flagSet.Bool("prefer-email-to-user", false, "Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. Used in conjunction with -pass-basic-auth and -pass-user-headers")
flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")
flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream")
flagSet.Bool("set-authorization-header", false, "set Authorization response headers (useful in Nginx auth_request mode)")
flagSet.StringSlice("skip-auth-regex", []string{}, "bypass authentication for requests path's that match (may be given multiple times)")
flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start")
flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests")
flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers")
flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams")
flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses")
flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)")
flagSet.StringSlice("extra-jwt-issuers", []string{}, "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)")
flagSet.StringSlice("email-domain", []string{}, "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email")
flagSet.StringSlice("whitelist-domain", []string{}, "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)")
flagSet.String("keycloak-group", "", "restrict login to members of this group.")
flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
flagSet.String("bitbucket-team", "", "restrict logins to members of this team")
flagSet.String("bitbucket-repository", "", "restrict logins to user with access to this repository")
flagSet.String("github-org", "", "restrict logins to members of this organisation")
flagSet.String("github-team", "", "restrict logins to members of this team")
flagSet.String("github-repo", "", "restrict logins to collaborators of this repository")
flagSet.String("github-token", "", "the token to use when verifying repository collaborators (must have push access to the repository)")
flagSet.String("gitlab-group", "", "restrict logins to members of this group")
flagSet.StringSlice("google-group", []string{}, "restrict logins to members of this google group (may be given multiple times).")
flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls")
flagSet.String("google-service-account-json", "", "the path to the service account json credentials")
flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
flagSet.String("client-secret", "", "the OAuth Client Secret")
flagSet.String("client-secret-file", "", "the file with OAuth Client Secret")
flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption or \"htpasswd -B\" for bcrypt encryption")
flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
flagSet.String("custom-templates-dir", "", "path to custom html templates")
flagSet.String("banner", "", "custom banner string. Use \"-\" to disable default banner.")
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
flagSet.String("ping-path", "/ping", "the ping endpoint that can be used for basic health checks")
flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)")
flagSet.StringSlice("cookie-domain", []string{}, "Optional cookie domains to force cookies to (ie: `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match).")
flagSet.String("cookie-path", "/", "an optional cookie path to force cookies to (ie: /poc/)*")
flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag")
flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
flagSet.String("cookie-samesite", "", "set SameSite cookie attribute (ie: \"lax\", \"strict\", \"none\", or \"\"). ")
flagSet.String("session-store-type", "cookie", "the session storage provider to use")
flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])")
flagSet.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature")
flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel")
flagSet.String("redis-ca-path", "", "Redis custom CA path")
flagSet.Bool("redis-insecure-skip-tls-verify", false, "Use insecure TLS connection to redis")
flagSet.StringSlice("redis-sentinel-connection-urls", []string{}, "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel")
flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature")
flagSet.StringSlice("redis-cluster-connection-urls", []string{}, "List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-cluster")
flagSet.String("logging-filename", "", "File to log requests to, empty for stdout")
flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation")
flagSet.Int("logging-max-age", 7, "Maximum number of days to retain old log files")
flagSet.Int("logging-max-backups", 0, "Maximum number of old log files to retain; 0 to disable")
flagSet.Bool("logging-local-time", true, "If the time in log files and backup filenames are local or UTC time")
flagSet.Bool("logging-compress", false, "Should rotated log files be compressed using gzip")
flagSet.Bool("standard-logging", true, "Log standard runtime information")
flagSet.String("standard-logging-format", logger.DefaultStandardLoggingFormat, "Template for standard log lines")
flagSet.Bool("request-logging", true, "Log HTTP requests")
flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines")
flagSet.String("exclude-logging-paths", "", "Exclude logging requests to paths (eg: '/path1,/path2,/path3')")
flagSet.Bool("silence-ping-logging", false, "Disable logging of requests to ping endpoint")
flagSet.Bool("auth-logging", true, "Log authentication attempts")
flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines")
flagSet.String("provider", "google", "OAuth provider")
flagSet.String("provider-display-name", "", "Provider display name")
flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified")
flagSet.Bool("insecure-oidc-skip-issuer-verification", false, "Do not verify if issuer matches OIDC discovery URL")
flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints")
flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)")
flagSet.String("login-url", "", "Authentication endpoint")
flagSet.String("redeem-url", "", "Token redemption endpoint")
flagSet.String("profile-url", "", "Profile access endpoint")
flagSet.String("resource", "", "The resource that is protected (Azure AD only)")
flagSet.String("validate-url", "", "Access token validation endpoint")
flagSet.String("scope", "", "OAuth scope specification")
flagSet.String("prompt", "", "OIDC prompt")
flagSet.String("approval-prompt", "force", "OAuth approval_prompt")
flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)")
flagSet.String("acr-values", "", "acr values string: optional")
flagSet.String("jwt-key", "", "private key in PEM format used to sign JWT, so that you can say something like -jwt-key=\"${OAUTH2_PROXY_JWT_KEY}\": required by login.gov")
flagSet.String("jwt-key-file", "", "path to the private key file in PEM format used to sign the JWT so that you can say something like -jwt-key-file=/etc/ssl/private/jwt_signing_key.pem: required by login.gov")
flagSet.String("pubjwk-url", "", "JWK pubkey access endpoint: required by login.gov")
flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints")
flagSet.String("user-id-claim", "email", "which claim contains the user ID")
return flagSet
}

View File

@ -4,7 +4,7 @@ import "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
// SessionOptions contains configuration options for the SessionStore providers.
type SessionOptions struct {
Type string `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"`
Type string `flag:"session-store-type" cfg:"session_store_type"`
Cipher *encryption.Cipher `cfg:",internal"`
Redis RedisStoreOptions `cfg:",squash"`
}
@ -19,12 +19,12 @@ var RedisSessionStoreType = "redis"
// RedisStoreOptions contains configuration options for the RedisSessionStore.
type RedisStoreOptions struct {
ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"`
UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel" env:"OAUTH2_PROXY_REDIS_USE_SENTINEL"`
SentinelMasterName string `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name" env:"OAUTH2_PROXY_REDIS_SENTINEL_MASTER_NAME"`
SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls" env:"OAUTH2_PROXY_REDIS_SENTINEL_CONNECTION_URLS"`
UseCluster bool `flag:"redis-use-cluster" cfg:"redis_use_cluster" env:"OAUTH2_PROXY_REDIS_USE_CLUSTER"`
ClusterConnectionURLs []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls" env:"OAUTH2_PROXY_REDIS_CLUSTER_CONNECTION_URLS"`
CAPath string `flag:"redis-ca-path" cfg:"redis_ca_path" env:"OAUTH2_PROXY_REDIS_CA_PATH"`
InsecureSkipTLSVerify bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify" env:"OAUTH2_PROXY_REDIS_INSECURE_SKIP_TLS_VERIFY"`
ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url"`
UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel"`
SentinelMasterName string `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name"`
SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls"`
UseCluster bool `flag:"redis-use-cluster" cfg:"redis_use_cluster"`
ClusterConnectionURLs []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls"`
CAPath string `flag:"redis-ca-path" cfg:"redis_ca_path"`
InsecureSkipTLSVerify bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify"`
}

View File

@ -1,4 +1,4 @@
package main
package ip
import (
"fmt"
@ -6,14 +6,10 @@ import (
"net/http"
"strings"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip"
)
type realClientIPParser interface {
GetRealClientIP(http.Header) (net.IP, error)
}
func getRealClientIPParser(headerKey string) (realClientIPParser, error) {
func GetRealClientIPParser(headerKey string) (ipapi.RealClientIPParser, error) {
headerKey = http.CanonicalHeaderKey(headerKey)
switch headerKey {
@ -73,13 +69,11 @@ func getRemoteIP(req *http.Request) (net.IP, error) {
}
}
// getClientString obtains the human readable string of the remote IP and optionally the real client IP if available
func getClientString(p realClientIPParser, req *http.Request, full bool) (s string) {
// GetClientString obtains the human readable string of the remote IP and optionally the real client IP if available
func GetClientString(p ipapi.RealClientIPParser, req *http.Request, full bool) (s string) {
var realClientIPStr string
if p != nil {
if realClientIP, err := p.GetRealClientIP(req.Header); err != nil {
logger.Printf("Unable to get real client IP: %v", err)
} else if realClientIP != nil {
if realClientIP, err := p.GetRealClientIP(req.Header); err == nil && realClientIP != nil {
realClientIPStr = realClientIP.String()
}
}
@ -87,9 +81,6 @@ func getClientString(p realClientIPParser, req *http.Request, full bool) (s stri
var remoteIPStr string
if remoteIP, err := getRemoteIP(req); err == nil {
remoteIPStr = remoteIP.String()
} else {
// Should not happen, if it does, likely a bug.
logger.Printf("Unable to get remote IP(?!?!): %v", err)
}
if !full && realClientIPStr != "" {

View File

@ -1,4 +1,4 @@
package main
package ip
import (
"net"
@ -6,6 +6,7 @@ import (
"reflect"
"testing"
ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip"
"github.com/stretchr/testify/assert"
)
@ -26,7 +27,7 @@ func TestGetRealClientIPParser(t *testing.T) {
}
for _, test := range tests {
p, err := getRealClientIPParser(test.header)
p, err := GetRealClientIPParser(test.header)
if test.errString == "" {
assert.Nil(t, err)
@ -144,7 +145,7 @@ func TestGetClientString(t *testing.T) {
p := &xForwardedForClientIPParser{header: http.CanonicalHeaderKey("X-Forwarded-For")}
tests := []struct {
parser realClientIPParser
parser ipapi.RealClientIPParser
remoteAddr string
headerValue string
expectedClient string
@ -167,10 +168,10 @@ func TestGetClientString(t *testing.T) {
RemoteAddr: test.remoteAddr,
}
client := getClientString(test.parser, req, false)
client := GetClientString(test.parser, req, false)
assert.Equal(t, test.expectedClient, client)
clientFull := getClientString(test.parser, req, true)
clientFull := GetClientString(test.parser, req, true)
assert.Equal(t, test.expectedClientFull, clientFull)
}
}

View File

@ -1,4 +1,4 @@
package main
package validation
import (
"context"
@ -14,12 +14,12 @@ import (
"strings"
"time"
oidc "github.com/coreos/go-oidc"
"github.com/coreos/go-oidc"
"github.com/dgrijalva/jwt-go"
"github.com/mbland/hmacauth"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
"github.com/oauth2-proxy/oauth2-proxy/pkg/ip"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions"
@ -27,197 +27,9 @@ import (
"gopkg.in/natefinch/lumberjack.v2"
)
// Options holds Configuration Options that can be set by Command Line Flag,
// or Config File
type Options struct {
ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix" env:"OAUTH2_PROXY_PROXY_PREFIX"`
PingPath string `flag:"ping-path" cfg:"ping_path" env:"OAUTH2_PROXY_PING_PATH"`
ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets" env:"OAUTH2_PROXY_PROXY_WEBSOCKETS"`
HTTPAddress string `flag:"http-address" cfg:"http_address" env:"OAUTH2_PROXY_HTTP_ADDRESS"`
HTTPSAddress string `flag:"https-address" cfg:"https_address" env:"OAUTH2_PROXY_HTTPS_ADDRESS"`
ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy" env:"OAUTH2_PROXY_REVERSE_PROXY"`
RealClientIPHeader string `flag:"real-client-ip-header" cfg:"real_client_ip_header" env:"OAUTH2_PROXY_REAL_CLIENT_IP_HEADER"`
ForceHTTPS bool `flag:"force-https" cfg:"force_https" env:"OAUTH2_PROXY_FORCE_HTTPS"`
RedirectURL string `flag:"redirect-url" cfg:"redirect_url" env:"OAUTH2_PROXY_REDIRECT_URL"`
ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
ClientSecretFile string `flag:"client-secret-file" cfg:"client_secret_file" env:"OAUTH2_PROXY_CLIENT_SECRET_FILE"`
TLSCertFile string `flag:"tls-cert-file" cfg:"tls_cert_file" env:"OAUTH2_PROXY_TLS_CERT_FILE"`
TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"`
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"`
KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group" env:"OAUTH2_PROXY_KEYCLOAK_GROUP"`
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT"`
BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team" env:"OAUTH2_PROXY_BITBUCKET_TEAM"`
BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository" env:"OAUTH2_PROXY_BITBUCKET_REPOSITORY"`
EmailDomains []string `flag:"email-domain" cfg:"email_domains" env:"OAUTH2_PROXY_EMAIL_DOMAINS"`
WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS"`
GitHubOrg string `flag:"github-org" cfg:"github_org" env:"OAUTH2_PROXY_GITHUB_ORG"`
GitHubTeam string `flag:"github-team" cfg:"github_team" env:"OAUTH2_PROXY_GITHUB_TEAM"`
GitHubRepo string `flag:"github-repo" cfg:"github_repo" env:"OAUTH2_PROXY_GITHUB_REPO"`
GitHubToken string `flag:"github-token" cfg:"github_token" env:"OAUTH2_PROXY_GITHUB_TOKEN"`
GitLabGroup string `flag:"gitlab-group" cfg:"gitlab_group" env:"OAUTH2_PROXY_GITLAB_GROUP"`
GoogleGroups []string `flag:"google-group" cfg:"google_group" env:"OAUTH2_PROXY_GOOGLE_GROUPS"`
GoogleAdminEmail string `flag:"google-admin-email" cfg:"google_admin_email" env:"OAUTH2_PROXY_GOOGLE_ADMIN_EMAIL"`
GoogleServiceAccountJSON string `flag:"google-service-account-json" cfg:"google_service_account_json" env:"OAUTH2_PROXY_GOOGLE_SERVICE_ACCOUNT_JSON"`
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file" env:"OAUTH2_PROXY_HTPASSWD_FILE"`
DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form" env:"OAUTH2_PROXY_DISPLAY_HTPASSWD_FORM"`
CustomTemplatesDir string `flag:"custom-templates-dir" cfg:"custom_templates_dir" env:"OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR"`
Banner string `flag:"banner" cfg:"banner" env:"OAUTH2_PROXY_BANNER"`
Footer string `flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER"`
Cookie options.CookieOptions `cfg:",squash"`
Session options.SessionOptions `cfg:",squash"`
Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens" env:"OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS"`
ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers" env:"OAUTH2_PROXY_EXTRA_JWT_ISSUERS"`
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"`
SetBasicAuth bool `flag:"set-basic-auth" cfg:"set_basic_auth" env:"OAUTH2_PROXY_SET_BASIC_AUTH"`
PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user" env:"OAUTH2_PROXY_PREFER_EMAIL_TO_USER"`
BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"`
PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"`
PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header" env:"OAUTH2_PROXY_PASS_HOST_HEADER"`
SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button" env:"OAUTH2_PROXY_SKIP_PROVIDER_BUTTON"`
PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers" env:"OAUTH2_PROXY_PASS_USER_HEADERS"`
SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY"`
SSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_UPSTREAM_INSECURE_SKIP_VERIFY"`
SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest" env:"OAUTH2_PROXY_SET_XAUTHREQUEST"`
SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header" env:"OAUTH2_PROXY_SET_AUTHORIZATION_HEADER"`
PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header" env:"OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER"`
SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight" env:"OAUTH2_PROXY_SKIP_AUTH_PREFLIGHT"`
FlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval" env:"OAUTH2_PROXY_FLUSH_INTERVAL"`
// These options allow for other providers besides Google, with
// potential overrides.
Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"`
ProviderName string `flag:"provider-display-name" cfg:"provider_display_name" env:"OAUTH2_PROXY_PROVIDER_DISPLAY_NAME"`
OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"`
InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL"`
InsecureOIDCSkipIssuerVerification bool `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification" env:"OAUTH2_PROXY_INSECURE_OIDC_SKIP_ISSUER_VERIFICATION"`
SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_PROXY_SKIP_OIDC_DISCOVERY"`
OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_PROXY_OIDC_JWKS_URL"`
LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"`
RedeemURL string `flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL"`
ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"`
ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"`
ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"`
Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"`
Prompt string `flag:"prompt" cfg:"prompt" env:"OAUTH2_PROXY_PROMPT"`
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` // Deprecated by OIDC 1.0
UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim" env:"OAUTH2_PROXY_USER_ID_CLAIM"`
// Configuration values for logging
LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_PROXY_LOGGING_FILENAME"`
LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_PROXY_LOGGING_MAX_SIZE"`
LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_PROXY_LOGGING_MAX_AGE"`
LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_PROXY_LOGGING_MAX_BACKUPS"`
LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_PROXY_LOGGING_LOCAL_TIME"`
LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_PROXY_LOGGING_COMPRESS"`
StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_PROXY_STANDARD_LOGGING"`
StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"`
RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"`
RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"`
ExcludeLoggingPaths string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATHS"`
SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"`
AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_PROXY_LOGGING_AUTH_LOGGING"`
AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_PROXY_AUTH_LOGGING_FORMAT"`
SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
AcrValues string `flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES"`
JWTKey string `flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY"`
JWTKeyFile string `flag:"jwt-key-file" cfg:"jwt_key_file" env:"OAUTH2_PROXY_JWT_KEY_FILE"`
PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url" env:"OAUTH2_PROXY_PUBJWK_URL"`
GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS"`
// internal values that are set after config validation
redirectURL *url.URL
proxyURLs []*url.URL
compiledRegex []*regexp.Regexp
provider providers.Provider
sessionStore sessionsapi.SessionStore
signatureData *SignatureData
oidcVerifier *oidc.IDTokenVerifier
jwtBearerVerifiers []*oidc.IDTokenVerifier
realClientIPParser realClientIPParser
}
// SignatureData holds hmacauth signature hash and key
type SignatureData struct {
hash crypto.Hash
key string
}
// NewOptions constructs a new Options with defaulted values
func NewOptions() *Options {
return &Options{
ProxyPrefix: "/oauth2",
PingPath: "/ping",
ProxyWebSockets: true,
HTTPAddress: "127.0.0.1:4180",
HTTPSAddress: ":443",
ForceHTTPS: false,
DisplayHtpasswdForm: true,
Cookie: options.CookieOptions{
Name: "_oauth2_proxy",
Secure: true,
HTTPOnly: true,
Expire: time.Duration(168) * time.Hour,
Refresh: time.Duration(0),
},
Session: options.SessionOptions{
Type: "cookie",
},
SetXAuthRequest: false,
SkipAuthPreflight: false,
PassBasicAuth: true,
SetBasicAuth: false,
PassUserHeaders: true,
PassAccessToken: false,
PassHostHeader: true,
SetAuthorization: false,
PassAuthorization: false,
PreferEmailToUser: false,
Prompt: "", // Change to "login" when ApprovalPrompt officially deprecated
ApprovalPrompt: "force",
UserIDClaim: "email",
InsecureOIDCAllowUnverifiedEmail: false,
SkipOIDCDiscovery: false,
LoggingFilename: "",
LoggingMaxSize: 100,
LoggingMaxAge: 7,
LoggingMaxBackups: 0,
LoggingLocalTime: true,
LoggingCompress: false,
ExcludeLoggingPaths: "",
SilencePingLogging: false,
StandardLogging: true,
StandardLoggingFormat: logger.DefaultStandardLoggingFormat,
RequestLogging: true,
RequestLoggingFormat: logger.DefaultRequestLoggingFormat,
AuthLogging: true,
AuthLoggingFormat: logger.DefaultAuthLoggingFormat,
}
}
// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
type jwtIssuer struct {
issuerURI string
audience string
}
func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) {
parsed, err := url.Parse(toParse)
if err != nil {
return nil, append(msgs, fmt.Sprintf(
"error parsing %s-url=%q %s", urltype, toParse, err))
}
return parsed, msgs
}
// Validate checks that required options are set and validates those that they
// are of the correct format
func (o *Options) Validate() error {
func Validate(o *options.Options) error {
if o.SSLInsecureSkipVerify {
// TODO: Accept a certificate bundle.
insecureTransport := &http.Transport{
@ -234,7 +46,7 @@ func (o *Options) Validate() error {
msgs = append(msgs, "missing setting: client-id")
}
// login.gov uses a signed JWT to authenticate, not a client-secret
if o.Provider != "login.gov" {
if o.ProviderType != "login.gov" {
if o.ClientSecret == "" && o.ClientSecretFile == "" {
msgs = append(msgs, "missing setting: client-secret or client-secret-file")
}
@ -311,20 +123,20 @@ func (o *Options) Validate() error {
msgs = append(msgs, "missing setting: oidc-jwks-url")
}
keySet := oidc.NewRemoteKeySet(ctx, o.OIDCJwksURL)
o.oidcVerifier = oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{
o.SetOIDCVerifier(oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{
ClientID: o.ClientID,
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
})
}))
} else {
// Configure discoverable provider data.
provider, err := oidc.NewProvider(ctx, o.OIDCIssuerURL)
if err != nil {
return err
}
o.oidcVerifier = provider.Verifier(&oidc.Config{
o.SetOIDCVerifier(provider.Verifier(&oidc.Config{
ClientID: o.ClientID,
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
})
}))
o.LoginURL = provider.Endpoint().AuthURL
o.RedeemURL = provider.Endpoint().TokenURL
@ -340,8 +152,8 @@ func (o *Options) Validate() error {
if o.SkipJwtBearerTokens {
// If we are using an oidc provider, go ahead and add that provider to the list
if o.oidcVerifier != nil {
o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, o.oidcVerifier)
if o.GetOIDCVerifier() != nil {
o.SetJWTBearerVerifiers(append(o.GetJWTBearerVerifiers(), o.GetOIDCVerifier()))
}
// Configure extra issuers
if len(o.ExtraJwtIssuers) > 0 {
@ -352,12 +164,14 @@ func (o *Options) Validate() error {
if err != nil {
msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
}
o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, verifier)
o.SetJWTBearerVerifiers(append(o.GetJWTBearerVerifiers(), verifier))
}
}
}
o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs)
var redirectURL *url.URL
redirectURL, msgs = parseURL(o.RawRedirectURL, "redirect", msgs)
o.SetRedirectURL(redirectURL)
for _, u := range o.Upstreams {
upstreamURL, err := url.Parse(u)
@ -367,7 +181,7 @@ func (o *Options) Validate() error {
if upstreamURL.Path == "" {
upstreamURL.Path = "/"
}
o.proxyURLs = append(o.proxyURLs, upstreamURL)
o.SetProxyURLs(append(o.GetProxyURLs(), upstreamURL))
}
}
@ -377,7 +191,7 @@ func (o *Options) Validate() error {
msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err))
continue
}
o.compiledRegex = append(o.compiledRegex, compiledRegex)
o.SetCompiledRegex(append(o.GetCompiledRegex(), compiledRegex))
}
msgs = parseProviderInfo(o, msgs)
@ -418,7 +232,7 @@ func (o *Options) Validate() error {
if err != nil {
msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err))
} else {
o.sessionStore = sessionStore
o.SetSessionStore(sessionStore)
}
if o.Cookie.Refresh >= o.Cookie.Expire {
@ -458,10 +272,11 @@ func (o *Options) Validate() error {
msgs = setupLogger(o, msgs)
if o.ReverseProxy {
o.realClientIPParser, err = getRealClientIPParser(o.RealClientIPHeader)
parser, err := ip.GetRealClientIPParser(o.RealClientIPHeader)
if err != nil {
msgs = append(msgs, fmt.Sprintf("real_client_ip_header (%s) not accepted parameter value: %v", o.RealClientIPHeader, err))
}
o.SetRealClientIPParser(parser)
}
if len(msgs) != 0 {
@ -471,7 +286,7 @@ func (o *Options) Validate() error {
return nil
}
func parseProviderInfo(o *Options, msgs []string) []string {
func parseProviderInfo(o *options.Options, msgs []string) []string {
p := &providers.ProviderData{
Scope: o.Scope,
ClientID: o.ClientID,
@ -487,8 +302,8 @@ func parseProviderInfo(o *Options, msgs []string) []string {
p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs)
p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
o.provider = providers.New(o.Provider, p)
switch p := o.provider.(type) {
o.SetProvider(providers.New(o.ProviderType, p))
switch p := o.GetProvider().(type) {
case *providers.AzureProvider:
p.Configure(o.AzureTenant)
case *providers.GitHubProvider:
@ -511,18 +326,18 @@ func parseProviderInfo(o *Options, msgs []string) []string {
case *providers.OIDCProvider:
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
p.UserIDClaim = o.UserIDClaim
if o.oidcVerifier == nil {
if o.GetOIDCVerifier() == nil {
msgs = append(msgs, "oidc provider requires an oidc issuer URL")
} else {
p.Verifier = o.oidcVerifier
p.Verifier = o.GetOIDCVerifier()
}
case *providers.GitLabProvider:
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
p.Group = o.GitLabGroup
p.EmailDomains = o.EmailDomains
if o.oidcVerifier != nil {
p.Verifier = o.oidcVerifier
if o.GetOIDCVerifier() != nil {
p.Verifier = o.GetOIDCVerifier()
} else {
// Initialize with default verifier for gitlab.com
ctx := context.Background()
@ -573,7 +388,7 @@ func parseProviderInfo(o *Options, msgs []string) []string {
return msgs
}
func parseSignatureKey(o *Options, msgs []string) []string {
func parseSignatureKey(o *options.Options, msgs []string) []string {
if o.SignatureKey == "" {
return msgs
}
@ -591,7 +406,7 @@ func parseSignatureKey(o *Options, msgs []string) []string {
return append(msgs, "unsupported signature hash algorithm: "+
o.SignatureKey)
}
o.signatureData = &SignatureData{hash: hash, key: secretKey}
o.SetSignatureData(&options.SignatureData{Hash: hash, Key: secretKey})
return msgs
}
@ -634,7 +449,7 @@ func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error
return verifier, nil
}
func validateCookieName(o *Options, msgs []string) []string {
func validateCookieName(o *options.Options, msgs []string) []string {
cookie := &http.Cookie{Name: o.Cookie.Name}
if cookie.String() == "" {
return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.Cookie.Name))
@ -642,7 +457,7 @@ func validateCookieName(o *Options, msgs []string) []string {
return msgs
}
func setupLogger(o *Options, msgs []string) []string {
func setupLogger(o *options.Options, msgs []string) []string {
// Setup the log file
if len(o.LoggingFilename) > 0 {
// Validate that the file/dir can be written
@ -681,7 +496,7 @@ func setupLogger(o *Options, msgs []string) []string {
logger.SetAuthTemplate(o.AuthLoggingFormat)
logger.SetReqTemplate(o.RequestLoggingFormat)
logger.SetGetClientFunc(func(r *http.Request) string {
return getClientString(o.realClientIPParser, r, false)
return ip.GetClientString(o.GetRealClientIPParser(), r, false)
})
excludePaths := make([]string, 0)
@ -698,3 +513,18 @@ func setupLogger(o *Options, msgs []string) []string {
return msgs
}
// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
type jwtIssuer struct {
issuerURI string
audience string
}
func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) {
parsed, err := url.Parse(toParse)
if err != nil {
return nil, append(msgs, fmt.Sprintf(
"error parsing %s-url=%q %s", urltype, toParse, err))
}
return parsed, msgs
}

View File

@ -1,4 +1,4 @@
package main
package validation
import (
"crypto"
@ -10,6 +10,7 @@ import (
"testing"
"time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
"github.com/stretchr/testify/assert"
)
@ -19,8 +20,8 @@ const (
clientSecret = "xyzzyplugh"
)
func testOptions() *Options {
o := NewOptions()
func testOptions() *options.Options {
o := options.NewOptions()
o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
o.Cookie.Secret = cookieSecret
o.ClientID = clientID
@ -37,9 +38,9 @@ func errorMsg(msgs []string) string {
}
func TestNewOptions(t *testing.T) {
o := NewOptions()
o := options.NewOptions()
o.EmailDomains = []string{"*"}
err := o.Validate()
err := Validate(o)
assert.NotEqual(t, nil, err)
expected := errorMsg([]string{
@ -50,15 +51,15 @@ func TestNewOptions(t *testing.T) {
}
func TestClientSecretFileOptionFails(t *testing.T) {
o := NewOptions()
o := options.NewOptions()
o.Cookie.Secret = cookieSecret
o.ClientID = clientID
o.ClientSecretFile = clientSecret
o.EmailDomains = []string{"*"}
err := o.Validate()
err := Validate(o)
assert.NotEqual(t, nil, err)
p := o.provider.Data()
p := o.GetProvider().Data()
assert.Equal(t, clientSecret, p.ClientSecretFile)
assert.Equal(t, "", p.ClientSecret)
@ -80,15 +81,15 @@ func TestClientSecretFileOption(t *testing.T) {
clientSecretFileName := f.Name()
defer os.Remove(clientSecretFileName)
o := NewOptions()
o := options.NewOptions()
o.Cookie.Secret = cookieSecret
o.ClientID = clientID
o.ClientSecretFile = clientSecretFileName
o.EmailDomains = []string{"*"}
err = o.Validate()
err = Validate(o)
assert.Equal(t, nil, err)
p := o.provider.Data()
p := o.GetProvider().Data()
assert.Equal(t, clientSecretFileName, p.ClientSecretFile)
assert.Equal(t, "", p.ClientSecret)
@ -100,7 +101,7 @@ func TestClientSecretFileOption(t *testing.T) {
func TestGoogleGroupOptions(t *testing.T) {
o := testOptions()
o.GoogleGroups = []string{"googlegroup"}
err := o.Validate()
err := Validate(o)
assert.NotEqual(t, nil, err)
expected := errorMsg([]string{
@ -114,7 +115,7 @@ func TestGoogleGroupInvalidFile(t *testing.T) {
o.GoogleGroups = []string{"test_group"}
o.GoogleAdminEmail = "admin@example.com"
o.GoogleServiceAccountJSON = "file_doesnt_exist.json"
err := o.Validate()
err := Validate(o)
assert.NotEqual(t, nil, err)
expected := errorMsg([]string{
@ -125,36 +126,36 @@ func TestGoogleGroupInvalidFile(t *testing.T) {
func TestInitializedOptions(t *testing.T) {
o := testOptions()
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
}
// Note that it's not worth testing nonparseable URLs, since url.Parse()
// seems to parse damn near anything.
func TestRedirectURL(t *testing.T) {
o := testOptions()
o.RedirectURL = "https://myhost.com/oauth2/callback"
assert.Equal(t, nil, o.Validate())
o.RawRedirectURL = "https://myhost.com/oauth2/callback"
assert.Equal(t, nil, Validate(o))
expected := &url.URL{
Scheme: "https", Host: "myhost.com", Path: "/oauth2/callback"}
assert.Equal(t, expected, o.redirectURL)
assert.Equal(t, expected, o.GetRedirectURL())
}
func TestProxyURLs(t *testing.T) {
o := testOptions()
o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081")
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
expected := []*url.URL{
{Scheme: "http", Host: "127.0.0.1:8080", Path: "/"},
// note the '/' was added
{Scheme: "http", Host: "127.0.0.1:8081", Path: "/"},
}
assert.Equal(t, expected, o.proxyURLs)
assert.Equal(t, expected, o.GetProxyURLs())
}
func TestProxyURLsError(t *testing.T) {
o := testOptions()
o.Upstreams = append(o.Upstreams, "127.0.0.1:8081")
err := o.Validate()
err := Validate(o)
assert.NotEqual(t, nil, err)
assert.Contains(t, err.Error(), "error parsing upstream")
}
@ -163,9 +164,9 @@ func TestCompiledRegex(t *testing.T) {
o := testOptions()
regexps := []string{"/foo/.*", "/ba[rz]/quux"}
o.SkipAuthRegex = regexps
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
actual := make([]string, 0)
for _, regex := range o.compiledRegex {
for _, regex := range o.GetCompiledRegex() {
actual = append(actual, regex.String())
}
assert.Equal(t, regexps, actual)
@ -174,7 +175,7 @@ func TestCompiledRegex(t *testing.T) {
func TestCompiledRegexError(t *testing.T) {
o := testOptions()
o.SkipAuthRegex = []string{"(foobaz", "barquux)"}
err := o.Validate()
err := Validate(o)
assert.NotEqual(t, nil, err)
expected := errorMsg([]string{
@ -185,7 +186,7 @@ func TestCompiledRegexError(t *testing.T) {
assert.Equal(t, expected, err.Error())
o.SkipAuthRegex = []string{"foobaz", "barquux)"}
err = o.Validate()
err = Validate(o)
assert.NotEqual(t, nil, err)
expected = errorMsg([]string{
@ -196,8 +197,8 @@ func TestCompiledRegexError(t *testing.T) {
func TestDefaultProviderApiSettings(t *testing.T) {
o := testOptions()
assert.Equal(t, nil, o.Validate())
p := o.provider.Data()
assert.Equal(t, nil, Validate(o))
p := o.GetProvider().Data()
assert.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline",
p.LoginURL.String())
assert.Equal(t, "https://www.googleapis.com/oauth2/v3/token",
@ -208,76 +209,76 @@ func TestDefaultProviderApiSettings(t *testing.T) {
func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) {
o := testOptions()
assert.Equal(t, nil, o.Validate())
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, o.Validate())
assert.NotEqual(t, nil, Validate(o))
o.PassAccessToken = false
o.Cookie.Refresh = time.Duration(24) * time.Hour
assert.NotEqual(t, nil, o.Validate())
assert.NotEqual(t, nil, Validate(o))
o.Cookie.Secret = "16 bytes AES-128"
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
o.Cookie.Secret = "24 byte secret AES-192--"
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
o.Cookie.Secret = "32 byte secret for AES-256------"
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
}
func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
o := testOptions()
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
o.Cookie.Secret = "0123456789abcdef"
o.Cookie.Refresh = o.Cookie.Expire
assert.NotEqual(t, nil, o.Validate())
assert.NotEqual(t, nil, Validate(o))
o.Cookie.Refresh -= time.Duration(1)
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
}
func TestBase64CookieSecret(t *testing.T) {
o := testOptions()
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
// 32 byte, base64 (urlsafe) encoded key
o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ="
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
// 32 byte, base64 (urlsafe) encoded key, w/o padding
o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ"
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
// 24 byte, base64 (urlsafe) encoded key
o.Cookie.Secret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3"
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
// 16 byte, base64 (urlsafe) encoded key
o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA=="
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
// 16 byte, base64 (urlsafe) encoded key, w/o padding
o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA"
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
}
func TestValidateSignatureKey(t *testing.T) {
o := testOptions()
o.SignatureKey = "sha1:secret"
assert.Equal(t, nil, o.Validate())
assert.Equal(t, o.signatureData.hash, crypto.SHA1)
assert.Equal(t, o.signatureData.key, "secret")
assert.Equal(t, nil, Validate(o))
assert.Equal(t, o.GetSignatureData().Hash, crypto.SHA1)
assert.Equal(t, o.GetSignatureData().Key, "secret")
}
func TestValidateSignatureKeyInvalidSpec(t *testing.T) {
o := testOptions()
o.SignatureKey = "invalid spec"
err := o.Validate()
err := Validate(o)
assert.Equal(t, err.Error(), "invalid configuration:\n"+
" invalid signature hash:key spec: "+o.SignatureKey)
}
@ -285,7 +286,7 @@ func TestValidateSignatureKeyInvalidSpec(t *testing.T) {
func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) {
o := testOptions()
o.SignatureKey = "unsupported:default secret"
err := o.Validate()
err := Validate(o)
assert.Equal(t, err.Error(), "invalid configuration:\n"+
" unsupported signature hash algorithm: "+o.SignatureKey)
}
@ -293,24 +294,24 @@ func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) {
func TestValidateCookie(t *testing.T) {
o := testOptions()
o.Cookie.Name = "_valid_cookie_name"
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
}
func TestValidateCookieBadName(t *testing.T) {
o := testOptions()
o.Cookie.Name = "_bad_cookie_name{}"
err := o.Validate()
err := Validate(o)
assert.Equal(t, err.Error(), "invalid configuration:\n"+
fmt.Sprintf(" invalid cookie name: %q", o.Cookie.Name))
}
func TestSkipOIDCDiscovery(t *testing.T) {
o := testOptions()
o.Provider = "oidc"
o.ProviderType = "oidc"
o.OIDCIssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/"
o.SkipOIDCDiscovery = true
err := o.Validate()
err := Validate(o)
assert.Equal(t, "invalid configuration:\n"+
" missing setting: login-url\n missing setting: redeem-url\n missing setting: oidc-jwks-url", err.Error())
@ -318,54 +319,50 @@ func TestSkipOIDCDiscovery(t *testing.T) {
o.RedeemURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_sign_in"
o.OIDCJwksURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys"
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
}
func TestGCPHealthcheck(t *testing.T) {
o := testOptions()
o.GCPHealthChecks = true
assert.Equal(t, nil, o.Validate())
assert.Equal(t, nil, Validate(o))
}
func TestRealClientIPHeader(t *testing.T) {
var o *Options
var err error
var expected string
// Ensure nil if ReverseProxy not set.
o = testOptions()
o := testOptions()
o.RealClientIPHeader = "X-Real-IP"
assert.Equal(t, nil, o.Validate())
assert.Nil(t, o.realClientIPParser)
assert.Equal(t, nil, Validate(o))
assert.Nil(t, o.GetRealClientIPParser())
// Ensure simple use case works.
o = testOptions()
o.ReverseProxy = true
o.RealClientIPHeader = "X-Forwarded-For"
assert.Equal(t, nil, o.Validate())
assert.NotNil(t, o.realClientIPParser)
assert.Equal(t, nil, Validate(o))
assert.NotNil(t, o.GetRealClientIPParser())
// Ensure unknown header format process an error.
o = testOptions()
o.ReverseProxy = true
o.RealClientIPHeader = "Forwarded"
err = o.Validate()
err := Validate(o)
assert.NotEqual(t, nil, err)
expected = errorMsg([]string{
expected := errorMsg([]string{
"real_client_ip_header (Forwarded) not accepted parameter value: the http header key (Forwarded) is either invalid or unsupported",
})
assert.Equal(t, expected, err.Error())
assert.Nil(t, o.realClientIPParser)
assert.Nil(t, o.GetRealClientIPParser())
// Ensure invalid header format produces an error.
o = testOptions()
o.ReverseProxy = true
o.RealClientIPHeader = "!934invalidheader-23:"
err = o.Validate()
err = Validate(o)
assert.NotEqual(t, nil, err)
expected = errorMsg([]string{
"real_client_ip_header (!934invalidheader-23:) not accepted parameter value: the http header key (!934invalidheader-23:) is either invalid or unsupported",
})
assert.Equal(t, expected, err.Error())
assert.Nil(t, o.realClientIPParser)
assert.Nil(t, o.GetRealClientIPParser())
}