From b560b45fcb0d18bdf3e837dae0f5dabcf109f114 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 29 Nov 2020 21:01:29 -0300 Subject: [PATCH] change oauth2 to path scope Configures OAuth2 from backend scope to path. This will allow to only configure OAuth2 in some paths of the same backend. As of any backend -> path scope change, this should lead to backward compatibility change if the configuration has a previous conflict in this configuration. --- docs/content/en/docs/configuration/keys.md | 16 +- pkg/converters/ingress/annotations/backend.go | 83 +++--- .../ingress/annotations/backend_test.go | 238 ++++++++++++------ pkg/haproxy/instance_test.go | 21 +- pkg/haproxy/types/types.go | 2 +- rootfs/etc/templates/haproxy/haproxy.tmpl | 10 +- 6 files changed, 233 insertions(+), 137 deletions(-) diff --git a/docs/content/en/docs/configuration/keys.md b/docs/content/en/docs/configuration/keys.md index 7d677cbe2..2ec918f7f 100644 --- a/docs/content/en/docs/configuration/keys.md +++ b/docs/content/en/docs/configuration/keys.md @@ -371,9 +371,9 @@ The table below describes all supported configuration keys. | [`nbproc-ssl`](#nbproc) | number of process | Global | `0` | | [`nbthread`](#nbthread) | number of threads | Global | `2` | | [`no-tls-redirect-locations`](#ssl-redirect) | comma-separated list of URIs | Global | `/.well-known/acme-challenge` | -| [`oauth`](#oauth) | "oauth2_proxy" | Backend | | -| [`oauth-headers`](#oauth) | `
:,...` | Backend | | -| [`oauth-uri-prefix`](#oauth) | URI prefix | Backend | | +| [`oauth`](#oauth) | "oauth2_proxy" | Path | | +| [`oauth-headers`](#oauth) | `
:,...` | Path | | +| [`oauth-uri-prefix`](#oauth) | URI prefix | Path | | | [`path-type`](#path-type) | path matching type | Host | `begin` | | [`path-type-order`](#path-type) | comma-separated path type list | Global | `exact,prefix,begin,regex` | | [`prometheus-port`](#bind-port) | port number | Global | | @@ -1563,11 +1563,11 @@ See also: ## OAuth -| Configuration key | Scope | Default | Since | -|-------------------|-----------|---------|-------| -| `oauth` | `Backend` | | | -| `oauth-headers` | `Backend` | | | -| `oauth-uri-prefix`| `Backend` | | | +| Configuration key | Scope | Default | Since | +|-------------------|--------|---------|-------| +| `oauth` | `Path` | | | +| `oauth-headers` | `Path` | | | +| `oauth-uri-prefix`| `Path` | | | Configure OAuth2 via Bitly's `oauth2_proxy`. diff --git a/pkg/converters/ingress/annotations/backend.go b/pkg/converters/ingress/annotations/backend.go index 329875bee..0a27c868e 100644 --- a/pkg/converters/ingress/annotations/backend.go +++ b/pkg/converters/ingress/annotations/backend.go @@ -525,51 +525,54 @@ var ( ) func (c *updater) buildBackendOAuth(d *backData) { - oauth := d.mapper.Get(ingtypes.BackOAuth) - if oauth.Source == nil { - return - } - if oauth.Value != "oauth2_proxy" { - c.logger.Warn("ignoring invalid oauth implementation '%s' on %v", oauth, oauth.Source) - return - } - external := c.haproxy.Global().External - if external.IsExternal() && !external.HasLua { - c.logger.Warn("oauth2_proxy on %v needs Lua socket, install Lua libraries and enable 'external-has-lua' global config", oauth.Source) - return - } - uriPrefix := "/oauth2" - headers := []string{"X-Auth-Request-Email:auth_response_email"} - if prefix := d.mapper.Get(ingtypes.BackOAuthURIPrefix); prefix.Source != nil { - uriPrefix = prefix.Value - } - h := d.mapper.Get(ingtypes.BackOAuthHeaders) - if h.Source != nil { - headers = strings.Split(h.Value, ",") - } - uriPrefix = strings.TrimRight(uriPrefix, "/") - namespace := oauth.Source.Namespace - backend := c.findBackend(namespace, uriPrefix) - if backend == nil { - c.logger.Error("path '%s' was not found on namespace '%s'", uriPrefix, namespace) - return - } - headersMap := make(map[string]string, len(headers)) - for _, header := range headers { - if len(header) == 0 { + for _, path := range d.backend.Paths { + config := d.mapper.GetConfig(path.Link) + oauth := config.Get(ingtypes.BackOAuth) + if oauth.Source == nil { continue } - if !oauthHeaderRegex.MatchString(header) { - c.logger.Warn("invalid header format '%s' on %v", header, h.Source) + if oauth.Value != "oauth2_proxy" { + c.logger.Warn("ignoring invalid oauth implementation '%s' on %v", oauth, oauth.Source) continue } - h := strings.Split(header, ":") - headersMap[h[0]] = h[1] + external := c.haproxy.Global().External + if external.IsExternal() && !external.HasLua { + c.logger.Warn("oauth2_proxy on %v needs Lua socket, install Lua libraries and enable 'external-has-lua' global config", oauth.Source) + return + } + uriPrefix := "/oauth2" + headers := []string{"X-Auth-Request-Email:auth_response_email"} + if prefix := config.Get(ingtypes.BackOAuthURIPrefix); prefix.Source != nil { + uriPrefix = prefix.Value + } + h := config.Get(ingtypes.BackOAuthHeaders) + if h.Source != nil { + headers = strings.Split(h.Value, ",") + } + uriPrefix = strings.TrimRight(uriPrefix, "/") + namespace := oauth.Source.Namespace + backend := c.findBackend(namespace, uriPrefix) + if backend == nil { + c.logger.Error("path '%s' was not found on namespace '%s'", uriPrefix, namespace) + continue + } + headersMap := make(map[string]string, len(headers)) + for _, header := range headers { + if len(header) == 0 { + continue + } + if !oauthHeaderRegex.MatchString(header) { + c.logger.Warn("invalid header format '%s' on %v", header, h.Source) + continue + } + h := strings.Split(header, ":") + headersMap[h[0]] = h[1] + } + path.OAuth.Impl = oauth.Value + path.OAuth.BackendName = backend.ID + path.OAuth.URIPrefix = uriPrefix + path.OAuth.Headers = headersMap } - d.backend.OAuth.Impl = oauth.Value - d.backend.OAuth.BackendName = backend.ID - d.backend.OAuth.URIPrefix = uriPrefix - d.backend.OAuth.Headers = headersMap } func (c *updater) findBackend(namespace, uriPrefix string) *hatypes.HostBackend { diff --git a/pkg/converters/ingress/annotations/backend_test.go b/pkg/converters/ingress/annotations/backend_test.go index b8158ef71..2fd48f249 100644 --- a/pkg/converters/ingress/annotations/backend_test.go +++ b/pkg/converters/ingress/annotations/backend_test.go @@ -1098,158 +1098,236 @@ func TestHSTS(t *testing.T) { func TestOAuth(t *testing.T) { testCases := []struct { annDefault map[string]string - ann map[string]string + ann map[string]map[string]string external bool haslua bool backend string - oauthExp hatypes.OAuthConfig + oauthExp map[string]hatypes.OAuthConfig logging string }{ // 0 { - ann: map[string]string{}, - oauthExp: hatypes.OAuthConfig{}, + ann: map[string]map[string]string{}, + oauthExp: map[string]hatypes.OAuthConfig{}, }, // 1 { - ann: map[string]string{ - ingtypes.BackOAuth: "none", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "none", + }, + }, + oauthExp: map[string]hatypes.OAuthConfig{ + "/": {}, }, logging: "WARN ignoring invalid oauth implementation 'none' on ingress 'default/ing1'", }, // 2 { - ann: map[string]string{ - ingtypes.BackOAuth: "oauth2_proxy", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "oauth2_proxy", + }, + }, + oauthExp: map[string]hatypes.OAuthConfig{ + "/": {}, }, logging: "ERROR path '/oauth2' was not found on namespace 'default'", }, // 3 { - ann: map[string]string{ - ingtypes.BackOAuth: "oauth2_proxy", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "oauth2_proxy", + }, }, backend: "default:back:/oauth2", - oauthExp: hatypes.OAuthConfig{ - Impl: "oauth2_proxy", - BackendName: "default_back_8080", - URIPrefix: "/oauth2", - Headers: map[string]string{"X-Auth-Request-Email": "auth_response_email"}, + oauthExp: map[string]hatypes.OAuthConfig{ + "/": { + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{"X-Auth-Request-Email": "auth_response_email"}, + }, }, }, // 4 { - ann: map[string]string{ - ingtypes.BackOAuth: "oauth2_proxy", - ingtypes.BackOAuthURIPrefix: "/auth", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "oauth2_proxy", + ingtypes.BackOAuthURIPrefix: "/auth", + }, }, backend: "default:back:/auth", - oauthExp: hatypes.OAuthConfig{ - Impl: "oauth2_proxy", - BackendName: "default_back_8080", - URIPrefix: "/auth", - Headers: map[string]string{"X-Auth-Request-Email": "auth_response_email"}, + oauthExp: map[string]hatypes.OAuthConfig{ + "/": { + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/auth", + Headers: map[string]string{"X-Auth-Request-Email": "auth_response_email"}, + }, }, }, // 5 { - ann: map[string]string{ - ingtypes.BackOAuth: "oauth2_proxy", - ingtypes.BackOAuthHeaders: "X-Auth-New:attr_from_lua", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "oauth2_proxy", + ingtypes.BackOAuthHeaders: "X-Auth-New:attr_from_lua", + }, }, backend: "default:back:/oauth2", - oauthExp: hatypes.OAuthConfig{ - Impl: "oauth2_proxy", - BackendName: "default_back_8080", - URIPrefix: "/oauth2", - Headers: map[string]string{"X-Auth-New": "attr_from_lua"}, + oauthExp: map[string]hatypes.OAuthConfig{ + "/": { + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{"X-Auth-New": "attr_from_lua"}, + }, }, }, // 6 { - ann: map[string]string{ - ingtypes.BackOAuth: "oauth2_proxy", - ingtypes.BackOAuthHeaders: "space before:attr", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "oauth2_proxy", + ingtypes.BackOAuthHeaders: "space before:attr", + }, }, backend: "default:back:/oauth2", - oauthExp: hatypes.OAuthConfig{ - Impl: "oauth2_proxy", - BackendName: "default_back_8080", - URIPrefix: "/oauth2", - Headers: map[string]string{}, + oauthExp: map[string]hatypes.OAuthConfig{ + "/": { + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{}, + }, }, logging: "WARN invalid header format 'space before:attr' on ingress 'default/ing1'", }, // 7 { - ann: map[string]string{ - ingtypes.BackOAuth: "oauth2_proxy", - ingtypes.BackOAuthHeaders: "no-colon", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "oauth2_proxy", + ingtypes.BackOAuthHeaders: "no-colon", + }, }, backend: "default:back:/oauth2", - oauthExp: hatypes.OAuthConfig{ - Impl: "oauth2_proxy", - BackendName: "default_back_8080", - URIPrefix: "/oauth2", - Headers: map[string]string{}, + oauthExp: map[string]hatypes.OAuthConfig{ + "/": { + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{}, + }, }, logging: "WARN invalid header format 'no-colon' on ingress 'default/ing1'", }, // 8 { - ann: map[string]string{ - ingtypes.BackOAuth: "oauth2_proxy", - ingtypes.BackOAuthHeaders: "more:colons:unsupported", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "oauth2_proxy", + ingtypes.BackOAuthHeaders: "more:colons:unsupported", + }, }, backend: "default:back:/oauth2", - oauthExp: hatypes.OAuthConfig{ - Impl: "oauth2_proxy", - BackendName: "default_back_8080", - URIPrefix: "/oauth2", - Headers: map[string]string{}, + oauthExp: map[string]hatypes.OAuthConfig{ + "/": { + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{}, + }, }, logging: "WARN invalid header format 'more:colons:unsupported' on ingress 'default/ing1'", }, // 9 { - ann: map[string]string{ - ingtypes.BackOAuth: "oauth2_proxy", - ingtypes.BackOAuthHeaders: ",,X-Auth-Request-Email:auth_response_email,,X-Auth-New:attr_from_lua,", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "oauth2_proxy", + ingtypes.BackOAuthHeaders: ",,X-Auth-Request-Email:auth_response_email,,X-Auth-New:attr_from_lua,", + }, }, backend: "default:back:/oauth2", - oauthExp: hatypes.OAuthConfig{ - Impl: "oauth2_proxy", - BackendName: "default_back_8080", - URIPrefix: "/oauth2", - Headers: map[string]string{ - "X-Auth-Request-Email": "auth_response_email", - "X-Auth-New": "attr_from_lua", + oauthExp: map[string]hatypes.OAuthConfig{ + "/": { + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{ + "X-Auth-Request-Email": "auth_response_email", + "X-Auth-New": "attr_from_lua", + }, }, }, }, // 10 { - ann: map[string]string{ - ingtypes.BackOAuth: "oauth2_proxy", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "oauth2_proxy", + }, + "/app": { + ingtypes.BackOAuth: "oauth2_proxy", + }, }, external: true, backend: "default:back:/oauth2", - oauthExp: hatypes.OAuthConfig{}, - logging: "WARN oauth2_proxy on ingress 'default/ing1' needs Lua socket, install Lua libraries and enable 'external-has-lua' global config", + oauthExp: map[string]hatypes.OAuthConfig{ + "/": {}, + "/app": {}, + }, + logging: "WARN oauth2_proxy on ingress 'default/ing1' needs Lua socket, install Lua libraries and enable 'external-has-lua' global config", }, // 11 { - ann: map[string]string{ - ingtypes.BackOAuth: "oauth2_proxy", + ann: map[string]map[string]string{ + "/": { + ingtypes.BackOAuth: "oauth2_proxy", + }, + "/app": { + ingtypes.BackOAuth: "oauth2_proxy", + }, }, external: true, haslua: true, backend: "default:back:/oauth2", - oauthExp: hatypes.OAuthConfig{ - Impl: "oauth2_proxy", - BackendName: "default_back_8080", - URIPrefix: "/oauth2", - Headers: map[string]string{"X-Auth-Request-Email": "auth_response_email"}, + oauthExp: map[string]hatypes.OAuthConfig{ + "/": { + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{"X-Auth-Request-Email": "auth_response_email"}, + }, + "/app": { + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{"X-Auth-Request-Email": "auth_response_email"}, + }, + }, + }, + // 12 + { + ann: map[string]map[string]string{ + "/": {}, + "/app": { + ingtypes.BackOAuth: "oauth2_proxy", + }, + }, + backend: "default:back:/oauth2", + oauthExp: map[string]hatypes.OAuthConfig{ + "/": {}, + "/app": { + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{"X-Auth-Request-Email": "auth_response_email"}, + }, }, }, } @@ -1261,7 +1339,7 @@ func TestOAuth(t *testing.T) { } for i, test := range testCases { c := setup(t) - d := c.createBackendData("default/app", source, test.ann, test.annDefault) + d := c.createBackendMappingData("default/app", source, test.annDefault, test.ann, []string{}) if test.external { c.haproxy.Global().External.MasterSocket = "/tmp/master.sock" } @@ -1272,7 +1350,11 @@ func TestOAuth(t *testing.T) { c.haproxy.Hosts().AcquireHost("app.local").AddPath(backend, b[2], hatypes.MatchBegin) } c.createUpdater().buildBackendOAuth(d) - c.compareObjects("oauth", i, d.backend.OAuth, test.oauthExp) + actual := map[string]hatypes.OAuthConfig{} + for _, path := range d.backend.Paths { + actual[path.Path()] = path.OAuth + } + c.compareObjects("oauth", i, actual, test.oauthExp) c.logger.CompareLogging(test.logging) c.teardown() } diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 2bd74e232..c44fbbf9f 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -405,16 +405,21 @@ d1.local/ path01`, }, { doconfig: func(g *hatypes.Global, h *hatypes.Host, b *hatypes.Backend) { - b.OAuth.Impl = "oauth2_proxy" - b.OAuth.BackendName = "system_oauth_4180" - b.OAuth.URIPrefix = "/oauth2" - b.OAuth.Headers = map[string]string{"X-Auth-Request-Email": "auth_response_email"} + oauth := &b.FindBackendPath(h.FindPath("/app2").Link).OAuth + oauth.Impl = "oauth2_proxy" + oauth.BackendName = "system_oauth_4180" + oauth.URIPrefix = "/oauth2" + oauth.Headers = map[string]string{"X-Auth-Request-Email": "auth_response_email"} }, + path: []string{"/app1", "/app2"}, expected: ` - http-request set-header X-Real-IP %[src] - http-request lua.auth-request system_oauth_4180 /oauth2/auth - http-request redirect location /oauth2/start?rd=%[path] if !{ path_beg /oauth2/ } !{ var(txn.auth_response_successful) -m bool } - http-request set-header X-Auth-Request-Email %[var(txn.auth_response_email)] if { var(txn.auth_response_email) -m found }`, + # path01 = d1.local/app1 + # path02 = d1.local/app2 + http-request set-var(txn.pathID) var(req.base),lower,map_beg(/etc/haproxy/maps/_back_d1_app_8080_idpath__begin.map) + http-request set-header X-Real-IP %[src] if { var(txn.pathID) path02 } + http-request lua.auth-request system_oauth_4180 /oauth2/auth if { var(txn.pathID) path02 } + http-request redirect location /oauth2/start?rd=%[path] if !{ path_beg /oauth2/ } !{ var(txn.auth_response_successful) -m bool } { var(txn.pathID) path02 } + http-request set-header X-Auth-Request-Email %[var(txn.auth_response_email)] if { var(txn.auth_response_email) -m found } { var(txn.pathID) path02 }`, }, { doconfig: func(g *hatypes.Global, h *hatypes.Host, b *hatypes.Backend) { diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 354af69c6..0e334fe22 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -489,7 +489,6 @@ type Backend struct { HealthCheck HealthCheck Limit BackendLimit ModeTCP bool - OAuth OAuthConfig Resolver string Server ServerConfig Timeout BackendTimeoutConfig @@ -541,6 +540,7 @@ type BackendPath struct { Cors Cors HSTS HSTS MaxBodySize int64 + OAuth OAuthConfig RewriteURL string SSLRedirect bool WAF WAF diff --git a/rootfs/etc/templates/haproxy/haproxy.tmpl b/rootfs/etc/templates/haproxy/haproxy.tmpl index 94333dac8..af3388508 100644 --- a/rootfs/etc/templates/haproxy/haproxy.tmpl +++ b/rootfs/etc/templates/haproxy/haproxy.tmpl @@ -548,14 +548,20 @@ backend {{ $backend.ID }} {{- end }} {{- /*------------------------------------*/}} -{{- if $backend.OAuth.Impl }} -{{- $oauth := $backend.OAuth }} +{{- $oauthCfg := $backend.PathConfig "OAuth" }} +{{- range $i, $oauth := $oauthCfg.Items }} +{{- range $pathIDs := $oauthCfg.PathIDs $i }} {{- if eq $oauth.Impl "oauth2_proxy" }} http-request set-header X-Real-IP %[src] + {{- if $pathIDs }} if { var(txn.pathID) {{ $pathIDs }} }{{ end }} http-request lua.auth-request {{ $oauth.BackendName }} {{ $oauth.URIPrefix }}/auth + {{- if $pathIDs }} if { var(txn.pathID) {{ $pathIDs }} }{{ end }} http-request redirect location {{ $oauth.URIPrefix }}/start?rd=%[path] if !{ path_beg {{ $oauth.URIPrefix }}/ } !{ var(txn.auth_response_successful) -m bool } + {{- if $pathIDs }} { var(txn.pathID) {{ $pathIDs }} }{{ end }} {{- range $header, $attr := $oauth.Headers }} http-request set-header {{ $header }} %[var(txn.{{ $attr }})] if { var(txn.{{ $attr }}) -m found } + {{- if $pathIDs }} { var(txn.pathID) {{ $pathIDs }} }{{ end }} +{{- end }} {{- end }} {{- end }} {{- end }}