diff --git a/CHANGELOG.md b/CHANGELOG.md index 99675350f..0427aa6bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ FIXES: BREAKING CHANGES: * The login handler by default has been switched off, you must enable for --enable-login-handler + * Changed the CORS format in the configuration file + * Changed the command line options scope -> scopes + * Changed the command line options log-json-format -> json-format #### **1.2.8** diff --git a/cli.go b/cli.go index fc37123fe..fbcbd8f1a 100644 --- a/cli.go +++ b/cli.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "os/signal" + "reflect" "syscall" "github.com/urfave/cli" @@ -92,7 +93,6 @@ func newOauthProxyApp() *cli.App { // getCLIOptions returns the command line options func getCLIOptions() []cli.Flag { defaults := newDefaultConfig() - return []cli.Flag{ cli.StringFlag{ Name: "config", @@ -105,6 +105,16 @@ func getCLIOptions() []cli.Flag { Value: defaults.Listen, EnvVar: "PROXY_LISTEN", }, + cli.StringFlag{ + Name: "listen-http", + Usage: "the interface you want the http-only service to use on", + EnvVar: "PROXY_HTTP_LISTEN", + }, + cli.StringFlag{ + Name: "discovery-url", + Usage: "the discovery url to retrieve the openid configuration", + EnvVar: "PROXY_DISCOVERY_URL", + }, cli.StringFlag{ Name: "client-secret", Usage: "the client secret used to authenticate to the oauth server (access_type: confidential)", @@ -115,13 +125,8 @@ func getCLIOptions() []cli.Flag { Usage: "the client id used to authenticate to the oauth service", EnvVar: "PROXY_CLIENT_ID", }, - cli.StringFlag{ - Name: "discovery-url", - Usage: "the discovery url to retrieve the openid configuration", - EnvVar: "PROXY_DISCOVERY_URL", - }, cli.StringSliceFlag{ - Name: "scope", + Name: "scopes", Usage: "a variable list of scopes requested when authenticating the user", }, cli.BoolFlag{ @@ -149,10 +154,6 @@ func getCLIOptions() []cli.Flag { Value: defaults.Upstream, EnvVar: "PROXY_UPSTREAM_URL", }, - cli.BoolFlag{ - Name: "enable-login-handler", - Usage: "this enables the login hanlder /oauth/login, by default this is disabled", - }, cli.BoolTFlag{ Name: "upstream-keepalives", Usage: "enables or disables the keepalive connections for upstream endpoint", @@ -167,14 +168,6 @@ func getCLIOptions() []cli.Flag { Usage: "specifies the keep-alive period for an active network connection", Value: defaults.UpstreamKeepaliveTimeout, }, - cli.BoolTFlag{ - Name: "enable-authorization-header", - Usage: "adds the authorization header to the proxy request", - }, - cli.BoolFlag{ - Name: "enable-refresh-tokens", - Usage: "enables the handling of the refresh tokens", - }, cli.BoolTFlag{ Name: "secure-cookie", Usage: "enforces the cookie to be secure, default to true", @@ -210,6 +203,18 @@ func getCLIOptions() []cli.Flag { Usage: "a list of hostnames the service will respond to, defaults to all", }, cli.BoolFlag{ + Name: "enable-login-handler", + Usage: "this enables the login hanlder /oauth/login, by default this is disabled", + }, + cli.BoolTFlag{ + Name: "enable-authorization-header", + Usage: "adds the authorization header to the proxy request", + }, + cli.BoolTFlag{ + Name: "enable-refresh-tokens", + Usage: "enables the handling of the refresh tokens", + }, + cli.BoolTFlag{ Name: "enable-metrics", Usage: "enable the prometheus metrics collector on /oauth/metrics", }, @@ -225,6 +230,14 @@ func getCLIOptions() []cli.Flag { Name: "enable-forwarding", Usage: "enables the forwarding proxy mode, signing outbound request", }, + cli.BoolTFlag{ + Name: "enable-profiling", + Usage: "switching on the golang profiling via pprof on /debug/pprof, /debug/pprof/heap etc", + }, + cli.BoolTFlag{ + Name: "enable-security-filter", + Usage: "enables the security filter handler", + }, cli.StringFlag{ Name: "forwarding-username", Usage: "the username to use when logging into the openid provider", @@ -282,7 +295,7 @@ func getCLIOptions() []cli.Flag { Usage: "Add custom headers to the upstream request, key=value", }, cli.StringFlag{ - Name: "signin-page", + Name: "sign-in-page", Usage: "a custom template displayed for signin", }, cli.StringFlag{ @@ -318,12 +331,12 @@ func getCLIOptions() []cli.Flag { Usage: "the credentials access control header (Access-Control-Allow-Credentials)", }, cli.BoolTFlag{ - Name: "enable-profiling", - Usage: "switching on the golang profiling via pprof on /debug/pprof, /debug/pprof/heap etc", + Name: "filter-browser-xss", + Usage: "enable the adds the X-XSS-Protection header with mode=block", }, cli.BoolTFlag{ - Name: "enable-security-filter", - Usage: "enables the security filter handler", + Name: "filter-content-nosniff", + Usage: "adds the X-Content-Type-Options header with the value nosniff", }, cli.BoolFlag{ Name: "skip-token-verification", @@ -349,161 +362,29 @@ func getCLIOptions() []cli.Flag { // @TODO look for a shorter way of doing this, we're maintaining the same options in multiple places, it's tedious! // func parseCLIOptions(cx *cli.Context, config *Config) (err error) { - if cx.String("listen") != "" { - config.Listen = cx.String("listen") - } - if cx.String("client-secret") != "" { - config.ClientSecret = cx.String("client-secret") - } - if cx.String("client-id") != "" { - config.ClientID = cx.String("client-id") - } - if cx.String("discovery-url") != "" { - config.DiscoveryURL = cx.String("discovery-url") - } - if cx.String("upstream-url") != "" { - config.Upstream = cx.String("upstream-url") - } - if cx.String("revocation-url") != "" { - config.RevocationEndpoint = cx.String("revocation-url") - } - if cx.IsSet("upstream-keepalives") { - config.UpstreamKeepalives = cx.Bool("upstream-keepalives") - } - if cx.IsSet("upstream-timeout") { - config.UpstreamTimeout = cx.Duration("upstream-timeout") - } - if cx.IsSet("upstream-keepalive-timeout") { - config.UpstreamKeepaliveTimeout = cx.Duration("upstream-keepalive-timeout") - } - if cx.IsSet("skip-token-verification") { - config.SkipTokenVerification = cx.Bool("skip-token-verification") - } - if cx.IsSet("skip-upstream-tls-verify") { - config.SkipUpstreamTLSVerify = cx.Bool("skip-upstream-tls-verify") - } - if cx.IsSet("skip-openid-provider-tls-verify") { - config.SkipOpenIDProviderTLSVerify = cx.Bool("skip-openid-provider-tls-verify") - } - if cx.IsSet("encryption-key") { - config.EncryptionKey = cx.String("encryption-key") - } - if cx.IsSet("secure-cookie") { - config.SecureCookie = cx.Bool("secure-cookie") - } - if cx.IsSet("http-only-cookie") { - config.HTTPOnlyCookie = cx.Bool("http-only-cookie") - } - if cx.IsSet("cookie-access-name") { - config.CookieAccessName = cx.String("cookie-access-name") - } - if cx.IsSet("cookie-refresh-name") { - config.CookieRefreshName = cx.String("cookie-refresh-name") - } - if cx.IsSet("cookie-domain") { - config.CookieDomain = cx.String("cookie-domain") - } - if cx.IsSet("add-claims") { - config.AddClaims = append(config.AddClaims, cx.StringSlice("add-claims")...) - } - if cx.String("store-url") != "" { - config.StoreURL = cx.String("store-url") - } - if cx.IsSet("no-redirects") { - config.NoRedirects = cx.Bool("no-redirects") - } - if cx.String("redirection-url") != "" { - config.RedirectionURL = cx.String("redirection-url") - } - if cx.IsSet("tls-cert") { - config.TLSCertificate = cx.String("tls-cert") - } - if cx.IsSet("tls-private-key") { - config.TLSPrivateKey = cx.String("tls-private-key") - } - if cx.IsSet("tls-ca-certificate") { - config.TLSCaCertificate = cx.String("tls-ca-certificate") - } - if cx.IsSet("tls-ca-key") { - config.TLSCaPrivateKey = cx.String("tls-ca-key") - } - if cx.IsSet("tls-client-certificate") { - config.TLSClientCertificate = cx.String("tls-client-certificate") - } - if cx.IsSet("enable-login-handler") { - config.EnableLoginHandler = cx.Bool("enable-login-handler") - } - if cx.IsSet("enable-profiling") { - config.EnableProfiling = cx.Bool("enable-profiling") - } - if cx.IsSet("enable-metrics") { - config.EnableMetrics = cx.Bool("enable-metrics") - } - if cx.IsSet("localhost-only-metrics") { - config.LocalhostMetrics = cx.Bool("localhost-only-metrics") - } - if cx.IsSet("enable-proxy-protocol") { - config.EnableProxyProtocol = cx.Bool("enable-proxy-protocol") - } - if cx.IsSet("enable-forwarding") { - config.EnableForwarding = cx.Bool("enable-forwarding") - } - if cx.IsSet("enable-authorization-header") { - config.EnableAuthorizationHeader = cx.Bool("enable-authorization-header") - } - if cx.IsSet("enable-refresh-tokens") { - config.EnableRefreshTokens = cx.Bool("enable-refresh-tokens") - } - if cx.IsSet("forwarding-username") { - config.ForwardingUsername = cx.String("forwarding-username") - } - if cx.IsSet("forwarding-password") { - config.ForwardingPassword = cx.String("forwarding-password") - } - if cx.IsSet("forwarding-domains") { - config.ForwardingDomains = append(config.ForwardingDomains, cx.StringSlice("forwarding-domains")...) - } - if cx.IsSet("signin-page") { - config.SignInPage = cx.String("signin-page") - } - if cx.IsSet("forbidden-page") { - config.ForbiddenPage = cx.String("forbidden-page") - } - if cx.IsSet("enable-security-filter") { - config.EnableSecurityFilter = true - } - if cx.IsSet("json-logging") { - config.LogJSONFormat = cx.Bool("json-logging") - } - if cx.IsSet("log-requests") { - config.LogRequests = cx.Bool("log-requests") - } - if cx.IsSet("verbose") { - config.Verbose = cx.Bool("verbose") - } - if cx.IsSet("scope") { - config.Scopes = cx.StringSlice("scope") - } - if cx.IsSet("hostname") { - config.Hostnames = append(config.Hostnames, cx.StringSlice("hostname")...) - } - if cx.IsSet("cors-origins") { - config.CrossOrigin.Origins = append(config.CrossOrigin.Origins, cx.StringSlice("cors-origins")...) - } - if cx.IsSet("cors-methods") { - config.CrossOrigin.Methods = append(config.CrossOrigin.Methods, cx.StringSlice("cors-methods")...) - } - if cx.IsSet("cors-headers") { - config.CrossOrigin.Headers = append(config.CrossOrigin.Headers, cx.StringSlice("cors-headers")...) - } - if cx.IsSet("cors-exposed-headers") { - config.CrossOrigin.ExposedHeaders = append(config.CrossOrigin.ExposedHeaders, cx.StringSlice("cors-exposed-headers")...) - } - if cx.IsSet("cors-max-age") { - config.CrossOrigin.MaxAge = cx.Duration("cors-max-age") - } - if cx.IsSet("cors-credentials") { - config.CrossOrigin.Credentials = cx.BoolT("cors-credentials") + // step: we can ignore these options in the Config struct + ignoredOptions := []string{"tag-data", "match-claims", "resources", "headers"} + // step: iterate the Config and grab command line options via reflection + count := reflect.TypeOf(config).Elem().NumField() + for i := 0; i < count; i++ { + field := reflect.TypeOf(config).Elem().Field(i) + name := field.Tag.Get("yaml") + if containedIn(name, ignoredOptions) { + continue + } + + if cx.IsSet(name) { + switch field.Type.Kind() { + case reflect.Bool: + reflect.ValueOf(config).Elem().FieldByName(field.Name).SetBool(cx.Bool(name)) + case reflect.String: + reflect.ValueOf(config).Elem().FieldByName(field.Name).SetString(cx.String(name)) + case reflect.Slice: + for _, x := range cx.StringSlice(name) { + reflect.Append(reflect.ValueOf(config).Elem().FieldByName(field.Name), reflect.ValueOf(x)) + } + } + } } if cx.IsSet("tag") { tags, err := decodeKeyPairs(cx.StringSlice("tag")) diff --git a/config.go b/config.go index 8f1d360db..5a62cb1da 100644 --- a/config.go +++ b/config.go @@ -38,7 +38,6 @@ func newDefaultConfig() *Config { SecureCookie: true, SkipUpstreamTLSVerify: true, SkipOpenIDProviderTLSVerify: false, - CrossOrigin: CORS{}, } } diff --git a/doc.go b/doc.go index ed1b904ff..53b997c16 100644 --- a/doc.go +++ b/doc.go @@ -85,8 +85,8 @@ type Resource struct { Roles []string `json:"roles" yaml:"roles"` } -// CORS access controls -type CORS struct { +// Cors access controls +type Cors struct { // Origins is a list of origins permitted Origins []string `json:"origins" yaml:"origins"` // Methods is a set of access control methods @@ -105,6 +105,8 @@ type CORS struct { type Config struct { // Listen is the binding interface Listen string `json:"listen" yaml:"listen"` + // ListenHTTP is the interface to bind the http only service on + ListenHTTP string `json:"listen-http" yaml:"listen-http" usage:""` // DiscoveryURL is the url for the keycloak server DiscoveryURL string `json:"discovery-url" yaml:"discovery-url"` // ClientID is the client id @@ -126,19 +128,34 @@ type Config struct { // Headers permits adding customs headers across the board Headers map[string]string `json:"headers" yaml:"headers"` + // EnableForwarding enables the forwarding proxy + EnableForwarding bool `json:"enable-forwarding" yaml:"enable-forwarding"` + // EnableSecurityFilter enabled the security handler + EnableSecurityFilter bool `json:"enable-security-filter" yaml:"enable-security-filter"` + // EnableRefreshTokens indicate's you wish to ignore using refresh tokens and re-auth on expiration of access token + EnableRefreshTokens bool `json:"enable-refresh-tokens" yaml:"enable-refresh-tokens"` + // EnableLoginHandler indicates we want the login handler enabled + EnableLoginHandler bool `json:"enable-login-handler" yaml:"enable-login-handler"` + // EnableAuthorizationHeader indicates we should pass the authorization header + EnableAuthorizationHeader bool `json:"enable-authorization-header" yaml:"enable-authorization-header"` + // EnableHTTPSRedirect indicate we should redirection http -> https + EnableHTTPSRedirect bool `json:"enable-https-redirection" yaml:"enable-https-redirection"` // EnableProfiling indicates if profiles is switched on EnableProfiling bool `json:"enable-profiling" yaml:"enable-profiling"` // EnableMetrics indicates if the metrics is enabled EnableMetrics bool `json:"enable-metrics" yaml:"enable-metrics"` // EnableURIMetrics indicates we want to keep metrics on uri request times EnableURIMetrics bool `json:"enable-uri-metrics" yaml:"enable-uri-metrics"` + // EnableBrowserXSSFilter indicates you want the filter on + EnableBrowserXSSFilter bool `json:"filter-browser-xss" yaml:"filter-browser-xss"` + // EnableContentNoSniff indicates you want the filter on + EnableContentNoSniff bool `json:"filter-content-nosniff" yaml:"filter-content-nosniff"` + // EnableFrameDeny indicates the filter is on + EnableFrameDeny bool `json:"filter-frame-deny" yaml:"filter-frame-deny"` + // ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value + ContentSecurityPolicy string `json:"content-security-policy" yaml:"content-security-policy"` // LocalhostMetrics indicated the metrics can only be consume via localhost - LocalhostMetrics bool `json:"localhost-only-metrics" yaml:"localhost-only-metrics"` - - // EnableLoginHandler indicates we want the login handler enabled - EnableLoginHandler bool `json:"enable-login-handler" yaml:"enable-login-handler"` - // EnableAuthorizationHeader indicates we should pass the authorization header - EnableAuthorizationHeader bool `json:"enable-authorization-header" yaml:"enable-authorization-header"` + LocalhostMetrics bool `json:"localhost-metrics" yaml:"localhost-metrics"` // CookieDomain is a list of domains the cookie is available to CookieDomain string `json:"cookie-domain" yaml:"cookie-domain"` @@ -169,8 +186,18 @@ type Config struct { // SkipUpstreamTLSVerify skips the verification of any upstream tls SkipUpstreamTLSVerify bool `json:"skip-upstream-tls-verify" yaml:"skip-upstream-tls-verify"` - // CrossOrigin permits adding headers to the /oauth handlers - CrossOrigin CORS `json:"cors" yaml:"cors"` + // CorsOrigins is a list of origins permitted + CorsOrigins []string `json:"cors-origins" yaml:"cors-origins"` + // CorsMethods is a set of access control methods + CorsMethods []string `json:"cors-methods" yaml:"cors-methods"` + // CorsHeaders is a set of cors headers + CorsHeaders []string `json:"cors-headers" yaml:"cors-headers"` + // CorsExposedHeaders are the exposed header fields + CorsExposedHeaders []string `json:"cors-exposed-headers" yaml:"cors-exposed-headers"` + // CorsCredentials set the creds flag + CorsCredentials bool `json:"cors-credentials" yaml:"cors-credentials"` + // CorsMaxAge is the age for CORS + CorsMaxAge time.Duration `json:"cors-max-age" yaml:"cors-max-age"` // Hostname is a list of hostname's the service should response to Hostnames []string `json:"hostnames" yaml:"hostnames"` @@ -180,14 +207,10 @@ type Config struct { // EncryptionKey is the encryption key used to encrypt the refresh token EncryptionKey string `json:"encryption-key" yaml:"encryption-key"` - // EnableSecurityFilter enabled the security handler - EnableSecurityFilter bool `json:"enable-security-filter" yaml:"enable-security-filter"` - // EnableRefreshTokens indicate's you wish to ignore using refresh tokens and re-auth on expiration of access token - EnableRefreshTokens bool `json:"enable-refresh-tokens" yaml:"enable-refresh-tokens"` // LogRequests indicates if we should log all the requests LogRequests bool `json:"log-requests" yaml:"log-requests"` // LogFormat is the logging format - LogJSONFormat bool `json:"log-json-format" yaml:"log-json-format"` + LogJSONFormat bool `json:"json-format" yaml:"json-format"` // NoRedirects informs we should hand back a 401 not a redirect NoRedirects bool `json:"no-redirects" yaml:"no-redirects"` // SkipTokenVerification tells the service to skipp verifying the access token - for testing purposes @@ -210,8 +233,6 @@ type Config struct { // TagData is passed to the templates TagData map[string]string `json:"tag-data" yaml:"tag-data"` - // EnableForwarding enables the forwarding proxy - EnableForwarding bool `json:"enable-forwarding" yaml:"enable-forwarding"` // ForwardingUsername is the username to login to the oauth service ForwardingUsername string `json:"forwarding-username" yaml:"forwarding-username"` // ForwardingPassword is the password to use for the above @@ -233,16 +254,12 @@ type storage interface { Close() error } -// // reverseProxy is a wrapper -// type reverseProxy interface { ServeHTTP(rw http.ResponseWriter, req *http.Request) } -// // userContext represents a user -// type userContext struct { // the id of the user id string diff --git a/middleware.go b/middleware.go index 010f0e5f0..8a91d5f65 100644 --- a/middleware.go +++ b/middleware.go @@ -369,7 +369,7 @@ func (r *oauthProxy) admissionMiddleware() gin.HandlerFunc { // // corsMiddleware injects the CORS headers, if set, for request made to /oauth // -func (r *oauthProxy) corsMiddleware(c CORS) gin.HandlerFunc { +func (r *oauthProxy) corsMiddleware(c Cors) gin.HandlerFunc { return func(cx *gin.Context) { if len(c.Origins) > 0 { cx.Writer.Header().Set("Access-Control-Allow-Origin", strings.Join(c.Origins, ",")) diff --git a/middleware_test.go b/middleware_test.go index a1feb9960..4caef58de 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -216,11 +216,11 @@ func TestCrossSiteHandler(t *testing.T) { p, _, _ := newTestProxyService(nil) cases := []struct { - Cors CORS + Cors Cors Headers map[string]string }{ { - Cors: CORS{ + Cors: Cors{ Origins: []string{"*"}, }, Headers: map[string]string{ @@ -228,7 +228,7 @@ func TestCrossSiteHandler(t *testing.T) { }, }, { - Cors: CORS{ + Cors: Cors{ Origins: []string{"*", "https://examples.com"}, Methods: []string{"GET"}, }, diff --git a/server.go b/server.go index eb5d26bf1..a181a709d 100644 --- a/server.go +++ b/server.go @@ -153,13 +153,11 @@ func (r *oauthProxy) createReverseProxy() error { // step: create the gin router engine := gin.New() engine.Use(gin.Recovery()) - // step: is profiling enabled? if r.config.EnableProfiling { log.Warn("Enabling the debug profiling on /debug/pprof") engine.Any("/debug/pprof/:name", r.debugHandler) } - // step: are we logging the traffic? if r.config.LogRequests { engine.Use(r.loggingMiddleware()) @@ -175,8 +173,15 @@ func (r *oauthProxy) createReverseProxy() error { engine.Use(r.securityMiddleware()) } - // step: add the routing - oauth := engine.Group(oauthURL).Use(r.corsMiddleware(r.config.CrossOrigin)) + // step: add the routing and cors middleware + oauth := engine.Group(oauthURL).Use(r.corsMiddleware(Cors{ + Origins: r.config.CorsOrigins, + Methods: r.config.CorsMethods, + Headers: r.config.CorsHeaders, + ExposedHeaders: r.config.CorsExposedHeaders, + Credentials: r.config.CorsCredentials, + MaxAge: r.config.CorsMaxAge, + })) oauth.GET(authorizationURL, r.oauthAuthorizationHandler) oauth.GET(callbackURL, r.oauthCallbackHandler) oauth.GET(healthURL, r.healthHandler) diff --git a/server_test.go b/server_test.go index be3835a6d..c4d41cbac 100644 --- a/server_test.go +++ b/server_test.go @@ -176,7 +176,6 @@ func newFakeKeycloakConfig() *Config { Roles: []string{}, }, }, - CrossOrigin: CORS{}, } }