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:
commit
236c7fa60e
5
http.go
5
http.go
@ -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")) {
|
||||
|
@ -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
126
main.go
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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
11
pkg/apis/ip/interfaces.go
Normal 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)
|
||||
}
|
@ -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"`
|
||||
}
|
||||
|
@ -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
340
pkg/apis/options/options.go
Normal 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
|
||||
}
|
@ -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"`
|
||||
}
|
||||
|
@ -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 != "" {
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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())
|
||||
}
|
Loading…
Reference in New Issue
Block a user