Auth support in frontends for k8s and file
This commit is contained in:
parent
e8e36bd9d5
commit
bb14ec70bd
14 changed files with 867 additions and 181 deletions
|
@ -1103,6 +1103,41 @@ var _templatesKubernetesTmpl = []byte(`[backends]
|
|||
"{{.}}",
|
||||
{{end}}]
|
||||
|
||||
{{if $frontend.Auth }}
|
||||
[frontends."{{ $frontendName }}".auth]
|
||||
headerField = "X-WebAuth-User"
|
||||
|
||||
{{if $frontend.Auth.Basic }}
|
||||
[frontends."{{ $frontendName }}".auth.basic]
|
||||
users = [{{range $frontend.Auth.Basic.Users }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Auth.Digest }}
|
||||
[frontends."{{ $frontendName }}".auth.digest]
|
||||
users = [{{range $frontend.Auth.Digest.Users }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Auth.Forward }}
|
||||
[frontends."{{ $frontendName }}".auth.forward]
|
||||
address = "{{ $frontend.Auth.Forward.Address }}"
|
||||
authResponseHeaders = [{{range $frontend.Auth.Forward.AuthResponseHeaders }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
trustForwardHeader = {{ $frontend.Auth.Forward.TrustForwardHeader }}
|
||||
{{if $frontend.Auth.Forward.TLS }}
|
||||
[frontends."{{ $frontendName }}".auth.forward.tls]
|
||||
cert = "{{ $frontend.Auth.Forward.TLS.Cert }}"
|
||||
key = "{{ $frontend.Auth.Forward.TLS.Key }}"
|
||||
insecureSkipVerify = {{ $frontend.Auth.Forward.TLS.InsecureSkipVerify }}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.WhiteList }}
|
||||
[frontends."{{ $frontendName }}".whiteList]
|
||||
sourceRange = [{{range $frontend.WhiteList.SourceRange }}
|
||||
|
|
|
@ -55,11 +55,38 @@ Træfik can be configured with a file.
|
|||
passHostHeader = true
|
||||
passTLSCert = true
|
||||
priority = 42
|
||||
|
||||
# Use frontends.frontend1.auth.basic below instead
|
||||
basicAuth = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
|
||||
[frontends.frontend1.auth]
|
||||
headerField = "X-WebAuth-User"
|
||||
[frontends.frontend1.auth.basic]
|
||||
users = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
usersFile = "/path/to/.htpasswd"
|
||||
[frontends.frontend1.auth.digest]
|
||||
users = [
|
||||
"test:traefik:a2688e031edb4be6a3797f3882655c05",
|
||||
"test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
|
||||
]
|
||||
usersFile = "/path/to/.htdigest"
|
||||
[frontends.frontend1.auth.forward]
|
||||
address = "https://authserver.com/auth"
|
||||
trustForwardHeader = true
|
||||
authResponseHeaders = ["X-Auth-User"]
|
||||
[frontends.frontend1.auth.forward.tls]
|
||||
ca = [ "path/to/local.crt"]
|
||||
caOptional = true
|
||||
cert = "path/to/foo.cert"
|
||||
key = "path/to/foo.key"
|
||||
insecureSkipVerify = true
|
||||
|
||||
[frontends.frontend1.whiteList]
|
||||
sourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
|
||||
useXForwardedFor = true
|
||||
|
|
|
@ -140,25 +140,25 @@ If the service port defined in the ingress spec is 443, then the backend communi
|
|||
|
||||
The following general annotations are applicable on the Ingress object:
|
||||
|
||||
| Annotation | Description |
|
||||
|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.ingress.kubernetes.io/buffering: <YML>` | (3) See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.ingress.kubernetes.io/error-pages: <YML>` | (1) See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.ingress.kubernetes.io/frontend-entry-points: http,https` | Override the default frontend endpoints. |
|
||||
| `traefik.ingress.kubernetes.io/pass-tls-cert: "true"` | Override the default frontend PassTLSCert value. Default: `false`. |
|
||||
| `traefik.ingress.kubernetes.io/preserve-host: "true"` | Forward client `Host` header to the backend. |
|
||||
| `traefik.ingress.kubernetes.io/priority: "3"` | Override the default frontend rule priority. |
|
||||
| `traefik.ingress.kubernetes.io/rate-limit: <YML>` | (2) See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.ingress.kubernetes.io/redirect-entry-point: https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS). |
|
||||
| `traefik.ingress.kubernetes.io/redirect-permanent: "true"` | Return 301 instead of 302. |
|
||||
| `traefik.ingress.kubernetes.io/redirect-regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-replacement`. |
|
||||
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
|
||||
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
|
||||
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
|
||||
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access (6). |
|
||||
| `ingress.kubernetes.io/whitelist-x-forwarded-for: "true"` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for `/` to the defined path. (4) |
|
||||
| `traefik.ingress.kubernetes.io/service-weights: <YML>` | Set ingress backend weights specified as percentage or decimal numbers in YAML. (5) |
|
||||
| Annotation | Description |
|
||||
|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|
|
||||
| `traefik.ingress.kubernetes.io/buffering: <YML>` | (3) See [buffering](/configuration/commons/#buffering) section. |
|
||||
| `traefik.ingress.kubernetes.io/error-pages: <YML>` | (1) See [custom error pages](/configuration/commons/#custom-error-pages) section. |
|
||||
| `traefik.ingress.kubernetes.io/frontend-entry-points: http,https` | Override the default frontend endpoints. |
|
||||
| `traefik.ingress.kubernetes.io/pass-tls-cert: "true"` | Override the default frontend PassTLSCert value. Default: `false`. |
|
||||
| `traefik.ingress.kubernetes.io/preserve-host: "true"` | Forward client `Host` header to the backend. |
|
||||
| `traefik.ingress.kubernetes.io/priority: "3"` | Override the default frontend rule priority. |
|
||||
| `traefik.ingress.kubernetes.io/rate-limit: <YML>` | (2) See [rate limiting](/configuration/commons/#rate-limiting) section. |
|
||||
| `traefik.ingress.kubernetes.io/redirect-entry-point: https` | Enables Redirect to another entryPoint for that frontend (e.g. HTTPS). |
|
||||
| `traefik.ingress.kubernetes.io/redirect-permanent: "true"` | Return 301 instead of 302. |
|
||||
| `traefik.ingress.kubernetes.io/redirect-regex: ^http://localhost/(.*)` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-replacement`. |
|
||||
| `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. |
|
||||
| `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. |
|
||||
| `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. |
|
||||
| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access (6). |
|
||||
| `ingress.kubernetes.io/whitelist-x-forwarded-for: "true"` | Use `X-Forwarded-For` header as valid source of IP for the white list. |
|
||||
| `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for `/` to the defined path. (4) |
|
||||
| `traefik.ingress.kubernetes.io/service-weights: <YML>` | Set ingress backend weights specified as percentage or decimal numbers in YAML. (5) |
|
||||
|
||||
|
||||
<1> `traefik.ingress.kubernetes.io/error-pages` example:
|
||||
|
@ -297,14 +297,20 @@ The following security annotations are applicable on the Ingress object:
|
|||
Additional authentication annotations can be added to the Ingress object.
|
||||
The source of the authentication is a Secret object that contains the credentials.
|
||||
|
||||
| Annotation | Description |
|
||||
|-----------------------------------------------|-------------------------------------------------------------------------------------------------------------|
|
||||
| `ingress.kubernetes.io/auth-type: basic` | Contains the authentication type. The only permitted type is `basic`. |
|
||||
| `ingress.kubernetes.io/auth-secret: mysecret` | Name of Secret containing the username and password with access to the paths defined in the Ingress object. |
|
||||
| Annotation | basic | digest | forward | Description |
|
||||
|----------------------------------------------------------------------|-------|--------|---------|-------------------------------------------------------------------------------------------------------------|
|
||||
| `ingress.kubernetes.io/auth-type: basic` | x | x | x | Contains the authentication type: `basic`, `digest`, `forward`. |
|
||||
| `ingress.kubernetes.io/auth-secret: mysecret` | x | x | | Name of Secret containing the username and password with access to the paths defined in the Ingress object. |
|
||||
| `ingress.kubernetes.io/auth-header-field: X-WebAuth-User` | x | x | | Pass Authenticated user to application via headers. |
|
||||
| `ingress.kubernetes.io/auth-url: https://example.com` | | | x | [The URL of the authentication server](configuration/entrypoints/#forward-authentication). |
|
||||
| `ingress.kubernetes.io/auth-trust-headers: false` | | | x | Trust `X-Forwarded-*` headers. |
|
||||
| `ingress.kubernetes.io/auth-response-headers: X-Auth-User, X-Secret` | | | x | Copy headers from the authentication server to the request. |
|
||||
| `ingress.kubernetes.io/auth-tls-secret: secret` | | | x | Name of Secret containing the certificate and key for the forward auth. |
|
||||
| `ingress.kubernetes.io/auth-tls-insecure` | | | x | If set to `true` invalid SSL certificates are accepted. |
|
||||
|
||||
The secret must be created in the same namespace as the Ingress object.
|
||||
|
||||
The following limitations hold:
|
||||
The following limitations hold for basic/digest auth:
|
||||
|
||||
- The realm is not configurable; the only supported (and default) value is `traefik`.
|
||||
- The Secret must contain a single file only.
|
||||
|
|
|
@ -54,14 +54,13 @@
|
|||
[entryPoints.http.auth.forward]
|
||||
address = "https://authserver.com/auth"
|
||||
trustForwardHeader = true
|
||||
authResponseHeaders = ["X-Auth-User"]
|
||||
[entryPoints.http.auth.forward.tls]
|
||||
ca = [ "path/to/local.crt"]
|
||||
caOptional = true
|
||||
cert = "path/to/foo.cert"
|
||||
key = "path/to/foo.key"
|
||||
insecureSkipVerify = true
|
||||
[entryPoints.http.auth.forward]
|
||||
authResponseHeaders = ["X-Auth-User"]
|
||||
|
||||
[entryPoints.http.proxyProtocol]
|
||||
insecure = true
|
||||
|
@ -273,6 +272,18 @@ Users can be specified directly in the TOML file, or indirectly by referencing a
|
|||
usersFile = "/path/to/.htpasswd"
|
||||
```
|
||||
|
||||
Optionally, you can pass authenticated user to application via headers
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth]
|
||||
headerField = "X-WebAuth-User" # <--
|
||||
[entryPoints.http.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
```
|
||||
|
||||
### Digest Authentication
|
||||
|
||||
You can use `htdigest` to generate them.
|
||||
|
@ -290,6 +301,18 @@ Users can be specified directly in the TOML file, or indirectly by referencing a
|
|||
usersFile = "/path/to/.htdigest"
|
||||
```
|
||||
|
||||
Optionally, you can pass authenticated user to application via headers
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth]
|
||||
headerField = "X-WebAuth-User" # <--
|
||||
[entryPoints.http.auth.digest]
|
||||
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
```
|
||||
|
||||
### Forward Authentication
|
||||
|
||||
This configuration will first forward the request to `http://authserver.com/auth`.
|
||||
|
@ -313,17 +336,21 @@ Otherwise, the response from the authentication server is returned.
|
|||
#
|
||||
trustForwardHeader = true
|
||||
|
||||
# Copy headers from the authentication server to the request
|
||||
[entryPoints.http.auth.forward]
|
||||
authResponseHeaders = ["X-Auth-User", "X-Secret"]
|
||||
|
||||
# Enable forward auth TLS connection.
|
||||
# Copy headers from the authentication server to the request.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
[entryPoints.http.auth.forward.tls]
|
||||
cert = "authserver.crt"
|
||||
key = "authserver.key"
|
||||
authResponseHeaders = ["X-Auth-User", "X-Secret"]
|
||||
|
||||
# Enable forward auth TLS connection.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
[entryPoints.http.auth.forward.tls]
|
||||
ca = [ "path/to/local.crt"]
|
||||
caOptional = true
|
||||
cert = "path/to/foo.cert"
|
||||
key = "path/to/foo.key"
|
||||
```
|
||||
|
||||
## Specify Minimum TLS Version
|
||||
|
|
|
@ -322,42 +322,6 @@ The `consul` provider contains the configuration.
|
|||
rule = "Path:/test"
|
||||
```
|
||||
|
||||
## Enable Basic authentication in an entry point
|
||||
|
||||
With two user/pass:
|
||||
|
||||
- `test`:`test`
|
||||
- `test2`:`test2`
|
||||
|
||||
Passwords are encoded in MD5: you can use `htpasswd` to generate them.
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
```
|
||||
|
||||
## Pass Authenticated user to application via headers
|
||||
|
||||
Providing an authentication method as described above, it is possible to pass the user to the application
|
||||
via a configurable header value.
|
||||
|
||||
```toml
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth]
|
||||
headerField = "X-WebAuth-User"
|
||||
[entryPoints.http.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
```
|
||||
|
||||
## Override the Traefik HTTP server idleTimeout and/or throttle configurations from re-loading too quickly
|
||||
|
||||
```toml
|
||||
|
|
|
@ -5,33 +5,39 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
annotationKubernetesIngressClass = "kubernetes.io/ingress.class"
|
||||
annotationKubernetesAuthRealm = "ingress.kubernetes.io/auth-realm"
|
||||
annotationKubernetesAuthType = "ingress.kubernetes.io/auth-type"
|
||||
annotationKubernetesAuthSecret = "ingress.kubernetes.io/auth-secret"
|
||||
annotationKubernetesRewriteTarget = "ingress.kubernetes.io/rewrite-target"
|
||||
annotationKubernetesWhiteListSourceRange = "ingress.kubernetes.io/whitelist-source-range"
|
||||
annotationKubernetesWhiteListUseXForwardedFor = "ingress.kubernetes.io/whitelist-x-forwarded-for"
|
||||
annotationKubernetesPreserveHost = "ingress.kubernetes.io/preserve-host"
|
||||
annotationKubernetesPassTLSCert = "ingress.kubernetes.io/pass-tls-cert"
|
||||
annotationKubernetesFrontendEntryPoints = "ingress.kubernetes.io/frontend-entry-points"
|
||||
annotationKubernetesPriority = "ingress.kubernetes.io/priority"
|
||||
annotationKubernetesCircuitBreakerExpression = "ingress.kubernetes.io/circuit-breaker-expression"
|
||||
annotationKubernetesLoadBalancerMethod = "ingress.kubernetes.io/load-balancer-method"
|
||||
annotationKubernetesAffinity = "ingress.kubernetes.io/affinity"
|
||||
annotationKubernetesSessionCookieName = "ingress.kubernetes.io/session-cookie-name"
|
||||
annotationKubernetesRuleType = "ingress.kubernetes.io/rule-type"
|
||||
annotationKubernetesRedirectEntryPoint = "ingress.kubernetes.io/redirect-entry-point"
|
||||
annotationKubernetesRedirectPermanent = "ingress.kubernetes.io/redirect-permanent"
|
||||
annotationKubernetesRedirectRegex = "ingress.kubernetes.io/redirect-regex"
|
||||
annotationKubernetesRedirectReplacement = "ingress.kubernetes.io/redirect-replacement"
|
||||
annotationKubernetesMaxConnAmount = "ingress.kubernetes.io/max-conn-amount"
|
||||
annotationKubernetesMaxConnExtractorFunc = "ingress.kubernetes.io/max-conn-extractor-func"
|
||||
annotationKubernetesRateLimit = "ingress.kubernetes.io/rate-limit"
|
||||
annotationKubernetesErrorPages = "ingress.kubernetes.io/error-pages"
|
||||
annotationKubernetesBuffering = "ingress.kubernetes.io/buffering"
|
||||
annotationKubernetesAppRoot = "ingress.kubernetes.io/app-root"
|
||||
annotationKubernetesServiceWeights = "ingress.kubernetes.io/service-weights"
|
||||
annotationKubernetesIngressClass = "kubernetes.io/ingress.class"
|
||||
annotationKubernetesAuthRealm = "ingress.kubernetes.io/auth-realm"
|
||||
annotationKubernetesAuthType = "ingress.kubernetes.io/auth-type"
|
||||
annotationKubernetesAuthSecret = "ingress.kubernetes.io/auth-secret"
|
||||
annotationKubernetesAuthHeaderField = "ingress.kubernetes.io/auth-header-field"
|
||||
annotationKubernetesAuthForwardResponseHeaders = "ingress.kubernetes.io/auth-response-headers"
|
||||
annotationKubernetesAuthForwardURL = "ingress.kubernetes.io/auth-url"
|
||||
annotationKubernetesAuthForwardTrustHeaders = "ingress.kubernetes.io/auth-trust-headers"
|
||||
annotationKubernetesAuthForwardTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
||||
annotationKubernetesAuthForwardTLSInsecure = "ingress.kubernetes.io/auth-tls-insecure"
|
||||
annotationKubernetesRewriteTarget = "ingress.kubernetes.io/rewrite-target"
|
||||
annotationKubernetesWhiteListSourceRange = "ingress.kubernetes.io/whitelist-source-range"
|
||||
annotationKubernetesWhiteListUseXForwardedFor = "ingress.kubernetes.io/whitelist-x-forwarded-for"
|
||||
annotationKubernetesPreserveHost = "ingress.kubernetes.io/preserve-host"
|
||||
annotationKubernetesPassTLSCert = "ingress.kubernetes.io/pass-tls-cert"
|
||||
annotationKubernetesFrontendEntryPoints = "ingress.kubernetes.io/frontend-entry-points"
|
||||
annotationKubernetesPriority = "ingress.kubernetes.io/priority"
|
||||
annotationKubernetesCircuitBreakerExpression = "ingress.kubernetes.io/circuit-breaker-expression"
|
||||
annotationKubernetesLoadBalancerMethod = "ingress.kubernetes.io/load-balancer-method"
|
||||
annotationKubernetesAffinity = "ingress.kubernetes.io/affinity"
|
||||
annotationKubernetesSessionCookieName = "ingress.kubernetes.io/session-cookie-name"
|
||||
annotationKubernetesRuleType = "ingress.kubernetes.io/rule-type"
|
||||
annotationKubernetesRedirectEntryPoint = "ingress.kubernetes.io/redirect-entry-point"
|
||||
annotationKubernetesRedirectPermanent = "ingress.kubernetes.io/redirect-permanent"
|
||||
annotationKubernetesRedirectRegex = "ingress.kubernetes.io/redirect-regex"
|
||||
annotationKubernetesRedirectReplacement = "ingress.kubernetes.io/redirect-replacement"
|
||||
annotationKubernetesMaxConnAmount = "ingress.kubernetes.io/max-conn-amount"
|
||||
annotationKubernetesMaxConnExtractorFunc = "ingress.kubernetes.io/max-conn-extractor-func"
|
||||
annotationKubernetesRateLimit = "ingress.kubernetes.io/rate-limit"
|
||||
annotationKubernetesErrorPages = "ingress.kubernetes.io/error-pages"
|
||||
annotationKubernetesBuffering = "ingress.kubernetes.io/buffering"
|
||||
annotationKubernetesAppRoot = "ingress.kubernetes.io/app-root"
|
||||
annotationKubernetesServiceWeights = "ingress.kubernetes.io/service-weights"
|
||||
|
||||
annotationKubernetesSSLForceHost = "ingress.kubernetes.io/ssl-force-host"
|
||||
annotationKubernetesSSLRedirect = "ingress.kubernetes.io/ssl-redirect"
|
||||
|
|
|
@ -208,9 +208,52 @@ func entryPoints(eps ...string) func(*types.Frontend) {
|
|||
}
|
||||
}
|
||||
|
||||
func basicAuth(auth ...string) func(*types.Frontend) {
|
||||
// Deprecated
|
||||
func basicAuthDeprecated(auth ...string) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
f.BasicAuth = auth
|
||||
f.Auth = &types.Auth{Basic: &types.Basic{Users: auth}}
|
||||
}
|
||||
}
|
||||
|
||||
func auth(opt func(*types.Auth)) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
auth := &types.Auth{}
|
||||
opt(auth)
|
||||
f.Auth = auth
|
||||
}
|
||||
}
|
||||
|
||||
func basicAuth(users ...string) func(*types.Auth) {
|
||||
return func(a *types.Auth) {
|
||||
a.Basic = &types.Basic{Users: users}
|
||||
}
|
||||
}
|
||||
|
||||
func forwardAuth(forwardURL string, opts ...func(*types.Forward)) func(*types.Auth) {
|
||||
return func(a *types.Auth) {
|
||||
fwd := &types.Forward{Address: forwardURL}
|
||||
for _, opt := range opts {
|
||||
opt(fwd)
|
||||
}
|
||||
a.Forward = fwd
|
||||
}
|
||||
}
|
||||
|
||||
func fwdAuthResponseHeaders(headers ...string) func(*types.Forward) {
|
||||
return func(f *types.Forward) {
|
||||
f.AuthResponseHeaders = headers
|
||||
}
|
||||
}
|
||||
|
||||
func fwdTrustForwardHeader() func(*types.Forward) {
|
||||
return func(f *types.Forward) {
|
||||
f.TrustForwardHeader = true
|
||||
}
|
||||
}
|
||||
|
||||
func fwdAuthTLS(cert, key string, insecure bool) func(*types.Forward) {
|
||||
return func(f *types.Forward) {
|
||||
f.TLS = &types.ClientTLS{Cert: cert, Key: key, InsecureSkipVerify: insecure}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -221,9 +221,9 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
|||
}
|
||||
|
||||
if _, exists := templateObjects.Frontends[baseName]; !exists {
|
||||
basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
|
||||
auth, err := getAuthConfig(i, k8sClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to retrieve basic auth configuration for ingress %s/%s: %s", i.Namespace, i.Name, err)
|
||||
log.Errorf("Failed to retrieve auth configuration for ingress %s/%s: %s", i.Namespace, i.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -238,13 +238,13 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
|||
PassTLSCert: passTLSCert,
|
||||
Routes: make(map[string]types.Route),
|
||||
Priority: priority,
|
||||
BasicAuth: basicAuthCreds,
|
||||
WhiteList: getWhiteList(i),
|
||||
Redirect: getFrontendRedirect(i),
|
||||
EntryPoints: entryPoints,
|
||||
Headers: getHeader(i),
|
||||
Errors: getErrorPages(i),
|
||||
RateLimit: getRateLimit(i),
|
||||
Auth: auth,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -438,67 +438,11 @@ func getRuleForHost(host string) string {
|
|||
return "Host:" + host
|
||||
}
|
||||
|
||||
func handleBasicAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) ([]string, error) {
|
||||
annotationAuthType := getAnnotationName(i.Annotations, annotationKubernetesAuthType)
|
||||
authType, exists := i.Annotations[annotationAuthType]
|
||||
if !exists {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if strings.ToLower(authType) != "basic" {
|
||||
return nil, fmt.Errorf("unsupported auth-type on annotation ingress.kubernetes.io/auth-type: %q", authType)
|
||||
}
|
||||
|
||||
authSecret := getStringValue(i.Annotations, annotationKubernetesAuthSecret, "")
|
||||
if authSecret == "" {
|
||||
return nil, errors.New("auth-secret annotation ingress.kubernetes.io/auth-secret must be set")
|
||||
}
|
||||
|
||||
basicAuthCreds, err := loadAuthCredentials(i.Namespace, authSecret, k8sClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load auth credentials: %s", err)
|
||||
}
|
||||
|
||||
return basicAuthCreds, nil
|
||||
}
|
||||
|
||||
func loadAuthCredentials(namespace, secretName string, k8sClient Client) ([]string, error) {
|
||||
secret, ok, err := k8sClient.GetSecret(namespace, secretName)
|
||||
switch { // keep order of case conditions
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("failed to fetch secret %q/%q: %s", namespace, secretName, err)
|
||||
case !ok:
|
||||
return nil, fmt.Errorf("secret %q/%q not found", namespace, secretName)
|
||||
case secret == nil:
|
||||
return nil, fmt.Errorf("data for secret %q/%q must not be nil", namespace, secretName)
|
||||
case len(secret.Data) != 1:
|
||||
return nil, fmt.Errorf("found %d elements for secret %q/%q, must be single element exactly", len(secret.Data), namespace, secretName)
|
||||
default:
|
||||
}
|
||||
var firstSecret []byte
|
||||
for _, v := range secret.Data {
|
||||
firstSecret = v
|
||||
break
|
||||
}
|
||||
creds := make([]string, 0)
|
||||
scanner := bufio.NewScanner(bytes.NewReader(firstSecret))
|
||||
for scanner.Scan() {
|
||||
if cred := scanner.Text(); cred != "" {
|
||||
creds = append(creds, cred)
|
||||
}
|
||||
}
|
||||
if len(creds) == 0 {
|
||||
return nil, fmt.Errorf("secret %q/%q does not contain any credentials", namespace, secretName)
|
||||
}
|
||||
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func getTLS(ingress *extensionsv1beta1.Ingress, k8sClient Client) ([]*tls.Configuration, error) {
|
||||
var tlsConfigs []*tls.Configuration
|
||||
|
||||
for _, t := range ingress.Spec.TLS {
|
||||
tlsSecret, exists, err := k8sClient.GetSecret(ingress.Namespace, t.SecretName)
|
||||
secret, exists, err := k8sClient.GetSecret(ingress.Namespace, t.SecretName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch secret %s/%s: %v", ingress.Namespace, t.SecretName, err)
|
||||
}
|
||||
|
@ -506,19 +450,9 @@ func getTLS(ingress *extensionsv1beta1.Ingress, k8sClient Client) ([]*tls.Config
|
|||
return nil, fmt.Errorf("secret %s/%s does not exist", ingress.Namespace, t.SecretName)
|
||||
}
|
||||
|
||||
tlsCrtData, tlsCrtExists := tlsSecret.Data["tls.crt"]
|
||||
tlsKeyData, tlsKeyExists := tlsSecret.Data["tls.key"]
|
||||
|
||||
var missingEntries []string
|
||||
if !tlsCrtExists {
|
||||
missingEntries = append(missingEntries, "tls.crt")
|
||||
}
|
||||
if !tlsKeyExists {
|
||||
missingEntries = append(missingEntries, "tls.key")
|
||||
}
|
||||
if len(missingEntries) > 0 {
|
||||
return nil, fmt.Errorf("secret %s/%s is missing the following TLS data entries: %s",
|
||||
ingress.Namespace, t.SecretName, strings.Join(missingEntries, ", "))
|
||||
cert, key, err := getCertificateBlocks(secret, ingress.Namespace, t.SecretName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entryPoints := getSliceStringValue(ingress.Annotations, annotationKubernetesFrontendEntryPoints)
|
||||
|
@ -526,8 +460,8 @@ func getTLS(ingress *extensionsv1beta1.Ingress, k8sClient Client) ([]*tls.Config
|
|||
tlsConfig := &tls.Configuration{
|
||||
EntryPoints: entryPoints,
|
||||
Certificate: &tls.Certificate{
|
||||
CertFile: tls.FileOrContent(tlsCrtData),
|
||||
KeyFile: tls.FileOrContent(tlsKeyData),
|
||||
CertFile: tls.FileOrContent(cert),
|
||||
KeyFile: tls.FileOrContent(key),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -537,6 +471,42 @@ func getTLS(ingress *extensionsv1beta1.Ingress, k8sClient Client) ([]*tls.Config
|
|||
return tlsConfigs, nil
|
||||
}
|
||||
|
||||
func getCertificateBlocks(secret *corev1.Secret, namespace, secretName string) (string, string, error) {
|
||||
var missingEntries []string
|
||||
|
||||
tlsCrtData, tlsCrtExists := secret.Data["tls.crt"]
|
||||
if !tlsCrtExists {
|
||||
missingEntries = append(missingEntries, "tls.crt")
|
||||
}
|
||||
|
||||
tlsKeyData, tlsKeyExists := secret.Data["tls.key"]
|
||||
if !tlsKeyExists {
|
||||
missingEntries = append(missingEntries, "tls.key")
|
||||
}
|
||||
|
||||
if len(missingEntries) > 0 {
|
||||
return "", "", fmt.Errorf("secret %s/%s is missing the following TLS data entries: %s",
|
||||
namespace, secretName, strings.Join(missingEntries, ", "))
|
||||
}
|
||||
|
||||
cert := string(tlsCrtData[:])
|
||||
if cert == "" {
|
||||
missingEntries = append(missingEntries, "tls.crt")
|
||||
}
|
||||
|
||||
key := string(tlsKeyData[:])
|
||||
if key == "" {
|
||||
missingEntries = append(missingEntries, "tls.key")
|
||||
}
|
||||
|
||||
if len(missingEntries) > 0 {
|
||||
return "", "", fmt.Errorf("secret %s/%s contains the following empty TLS data entries: %s",
|
||||
namespace, secretName, strings.Join(missingEntries, ", "))
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
// endpointPortNumber returns the port to be used for this endpoint. It is zero
|
||||
// if the endpoint does not match the given service port.
|
||||
func endpointPortNumber(servicePort corev1.ServicePort, endpointPorts []corev1.EndpointPort) int32 {
|
||||
|
@ -573,6 +543,160 @@ func (p *Provider) shouldProcessIngress(annotationIngressClass string) bool {
|
|||
return annotationIngressClass == p.IngressClass
|
||||
}
|
||||
|
||||
func getAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*types.Auth, error) {
|
||||
authType := getStringValue(i.Annotations, annotationKubernetesAuthType, "")
|
||||
if len(authType) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
auth := &types.Auth{
|
||||
HeaderField: getStringValue(i.Annotations, annotationKubernetesAuthHeaderField, ""),
|
||||
}
|
||||
|
||||
switch strings.ToLower(authType) {
|
||||
case "basic":
|
||||
basic, err := getBasicAuthConfig(i, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth.Basic = basic
|
||||
case "digest":
|
||||
digest, err := getDigestAuthConfig(i, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth.Digest = digest
|
||||
case "forward":
|
||||
forward, err := getForwardAuthConfig(i, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth.Forward = forward
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported auth-type on annotation %s: %s", annotationKubernetesAuthType, authType)
|
||||
}
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func getBasicAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*types.Basic, error) {
|
||||
credentials, err := getAuthCredentials(i, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.Basic{Users: credentials}, nil
|
||||
}
|
||||
|
||||
func getDigestAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*types.Digest, error) {
|
||||
credentials, err := getAuthCredentials(i, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.Digest{Users: credentials}, nil
|
||||
}
|
||||
|
||||
func getAuthCredentials(i *extensionsv1beta1.Ingress, k8sClient Client) ([]string, error) {
|
||||
authSecret := getStringValue(i.Annotations, annotationKubernetesAuthSecret, "")
|
||||
if authSecret == "" {
|
||||
return nil, fmt.Errorf("auth-secret annotation %s must be set", annotationKubernetesAuthSecret)
|
||||
}
|
||||
|
||||
auth, err := loadAuthCredentials(i.Namespace, authSecret, k8sClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load auth credentials: %s", err)
|
||||
}
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func loadAuthCredentials(namespace, secretName string, k8sClient Client) ([]string, error) {
|
||||
secret, ok, err := k8sClient.GetSecret(namespace, secretName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch secret %q/%q: %s", namespace, secretName, err)
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret %q/%q not found", namespace, secretName)
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("data for secret %q/%q must not be nil", namespace, secretName)
|
||||
}
|
||||
if len(secret.Data) != 1 {
|
||||
return nil, fmt.Errorf("found %d elements for secret %q/%q, must be single element exactly", len(secret.Data), namespace, secretName)
|
||||
}
|
||||
|
||||
var firstSecret []byte
|
||||
for _, v := range secret.Data {
|
||||
firstSecret = v
|
||||
break
|
||||
}
|
||||
|
||||
var credentials []string
|
||||
scanner := bufio.NewScanner(bytes.NewReader(firstSecret))
|
||||
for scanner.Scan() {
|
||||
if cred := scanner.Text(); len(cred) > 0 {
|
||||
credentials = append(credentials, cred)
|
||||
}
|
||||
}
|
||||
|
||||
if len(credentials) == 0 {
|
||||
return nil, fmt.Errorf("secret %q/%q does not contain any credentials", namespace, secretName)
|
||||
}
|
||||
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
func getForwardAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*types.Forward, error) {
|
||||
authURL := getStringValue(i.Annotations, annotationKubernetesAuthForwardURL, "")
|
||||
if len(authURL) == 0 {
|
||||
return nil, fmt.Errorf("forward authentication requires a url")
|
||||
}
|
||||
|
||||
forwardAuth := &types.Forward{
|
||||
Address: authURL,
|
||||
TrustForwardHeader: getBoolValue(i.Annotations, annotationKubernetesAuthForwardTrustHeaders, false),
|
||||
AuthResponseHeaders: getSliceStringValue(i.Annotations, annotationKubernetesAuthForwardResponseHeaders),
|
||||
}
|
||||
|
||||
authSecretName := getStringValue(i.Annotations, annotationKubernetesAuthForwardTLSSecret, "")
|
||||
if len(authSecretName) > 0 {
|
||||
authSecretCert, authSecretKey, err := loadAuthTLSSecret(i.Namespace, authSecretName, k8sClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load auth secret: %s", err)
|
||||
}
|
||||
|
||||
forwardAuth.TLS = &types.ClientTLS{
|
||||
Cert: authSecretCert,
|
||||
Key: authSecretKey,
|
||||
InsecureSkipVerify: getBoolValue(i.Annotations, annotationKubernetesAuthForwardTLSInsecure, false),
|
||||
}
|
||||
}
|
||||
|
||||
return forwardAuth, nil
|
||||
}
|
||||
|
||||
func loadAuthTLSSecret(namespace, secretName string, k8sClient Client) (string, string, error) {
|
||||
secret, exists, err := k8sClient.GetSecret(namespace, secretName)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to fetch secret %q/%q: %s", namespace, secretName, err)
|
||||
}
|
||||
if !exists {
|
||||
return "", "", fmt.Errorf("secret %q/%q does not exist", namespace, secretName)
|
||||
}
|
||||
if secret == nil {
|
||||
return "", "", fmt.Errorf("data for secret %q/%q must not be nil", namespace, secretName)
|
||||
}
|
||||
if len(secret.Data) != 2 {
|
||||
return "", "", fmt.Errorf("found %d elements for secret %q/%q, must be two elements exactly", len(secret.Data), namespace, secretName)
|
||||
}
|
||||
|
||||
return getCertificateBlocks(secret, namespace, secretName)
|
||||
}
|
||||
|
||||
func getFrontendRedirect(i *extensionsv1beta1.Ingress) *types.Redirect {
|
||||
permanent := getBoolValue(i.Annotations, annotationKubernetesRedirectPermanent, false)
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -1032,7 +1031,7 @@ rateset:
|
|||
),
|
||||
frontend("basic/auth",
|
||||
passHostHeader(),
|
||||
basicAuth("myUser:myEncodedPW"),
|
||||
basicAuthDeprecated("myUser:myEncodedPW"),
|
||||
routes(
|
||||
route("/auth", "PathPrefix:/auth"),
|
||||
route("basic", "Host:basic")),
|
||||
|
@ -1680,7 +1679,7 @@ func TestMissingResources(t *testing.T) {
|
|||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestBasicAuthInTemplate(t *testing.T) {
|
||||
func TestLoadIngressesBasicAuth(t *testing.T) {
|
||||
ingresses := []*extensionsv1beta1.Ingress{
|
||||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
|
@ -1734,9 +1733,372 @@ func TestBasicAuthInTemplate(t *testing.T) {
|
|||
|
||||
actual = provider.loadConfig(*actual)
|
||||
require.NotNil(t, actual)
|
||||
got := actual.Frontends["basic/auth"].BasicAuth
|
||||
if !reflect.DeepEqual(got, []string{"myUser:myEncodedPW"}) {
|
||||
t.Fatalf("unexpected credentials: %+v", got)
|
||||
got := actual.Frontends["basic/auth"].Auth.Basic.Users
|
||||
assert.Equal(t, types.Users{"myUser:myEncodedPW"}, got)
|
||||
}
|
||||
|
||||
func TestLoadIngressesForwardAuth(t *testing.T) {
|
||||
ingresses := []*extensionsv1beta1.Ingress{
|
||||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesAuthType, "forward"),
|
||||
iAnnotation(annotationKubernetesAuthForwardURL, "https://auth.host"),
|
||||
iAnnotation(annotationKubernetesAuthForwardTrustHeaders, "true"),
|
||||
iAnnotation(annotationKubernetesAuthForwardResponseHeaders, "X-Auth,X-Test,X-Secret"),
|
||||
iRules(
|
||||
iRule(iHost("foo"),
|
||||
iPaths(
|
||||
onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
services := []*corev1.Service{
|
||||
buildService(
|
||||
sName("service1"),
|
||||
sNamespace("testing"),
|
||||
sUID("1"),
|
||||
sSpec(
|
||||
clusterIP("10.0.0.1"),
|
||||
sPorts(sPort(80, ""))),
|
||||
),
|
||||
}
|
||||
|
||||
endpoints := []*corev1.Endpoints{
|
||||
buildEndpoint(
|
||||
eNamespace("testing"),
|
||||
eName("service1"),
|
||||
eUID("1"),
|
||||
subset(
|
||||
eAddresses(eAddress("10.10.0.1")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
),
|
||||
}
|
||||
|
||||
watchChan := make(chan interface{})
|
||||
client := clientMock{
|
||||
ingresses: ingresses,
|
||||
services: services,
|
||||
endpoints: endpoints,
|
||||
watchChan: watchChan,
|
||||
}
|
||||
provider := Provider{}
|
||||
|
||||
actual, err := provider.loadIngresses(client)
|
||||
require.NoError(t, err, "error loading ingresses")
|
||||
|
||||
expected := buildConfiguration(
|
||||
backends(
|
||||
backend("foo/bar",
|
||||
lbMethod("wrr"),
|
||||
servers(
|
||||
server("http://10.10.0.1:8080", weight(1))),
|
||||
),
|
||||
),
|
||||
frontends(
|
||||
frontend("foo/bar",
|
||||
passHostHeader(),
|
||||
auth(forwardAuth("https://auth.host",
|
||||
fwdTrustForwardHeader(),
|
||||
fwdAuthResponseHeaders("X-Auth", "X-Test", "X-Secret"))),
|
||||
routes(
|
||||
route("/bar", "PathPrefix:/bar"),
|
||||
route("foo", "Host:foo")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestLoadIngressesForwardAuthMissingURL(t *testing.T) {
|
||||
ingresses := []*extensionsv1beta1.Ingress{
|
||||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesAuthType, "forward"),
|
||||
iRules(
|
||||
iRule(iHost("foo"),
|
||||
iPaths(
|
||||
onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
services := []*corev1.Service{
|
||||
buildService(
|
||||
sName("service1"),
|
||||
sNamespace("testing"),
|
||||
sUID("1"),
|
||||
sSpec(
|
||||
clusterIP("10.0.0.1"),
|
||||
sPorts(sPort(80, ""))),
|
||||
),
|
||||
}
|
||||
|
||||
endpoints := []*corev1.Endpoints{
|
||||
buildEndpoint(
|
||||
eNamespace("testing"),
|
||||
eName("service1"),
|
||||
eUID("1"),
|
||||
subset(
|
||||
eAddresses(eAddress("10.10.0.1")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
),
|
||||
}
|
||||
|
||||
watchChan := make(chan interface{})
|
||||
client := clientMock{
|
||||
ingresses: ingresses,
|
||||
services: services,
|
||||
endpoints: endpoints,
|
||||
watchChan: watchChan,
|
||||
}
|
||||
provider := Provider{}
|
||||
|
||||
actual, err := provider.loadIngresses(client)
|
||||
require.NoError(t, err, "error loading ingresses")
|
||||
|
||||
expected := buildConfiguration(
|
||||
backends(
|
||||
backend("foo/bar",
|
||||
lbMethod("wrr"),
|
||||
servers(),
|
||||
),
|
||||
),
|
||||
frontends(),
|
||||
)
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestLoadIngressesForwardAuthWithTLSSecret(t *testing.T) {
|
||||
ingresses := []*extensionsv1beta1.Ingress{
|
||||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesAuthType, "forward"),
|
||||
iAnnotation(annotationKubernetesAuthForwardURL, "https://auth.host"),
|
||||
iAnnotation(annotationKubernetesAuthForwardTLSSecret, "secret"),
|
||||
iAnnotation(annotationKubernetesAuthForwardTLSInsecure, "true"),
|
||||
iRules(
|
||||
iRule(iHost("foo"),
|
||||
iPaths(
|
||||
onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
secrets := []*corev1.Secret{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
UID: "1",
|
||||
Namespace: "testing",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"tls.crt": []byte("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"),
|
||||
"tls.key": []byte("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"),
|
||||
},
|
||||
}}
|
||||
|
||||
services := []*corev1.Service{
|
||||
buildService(
|
||||
sName("service1"),
|
||||
sNamespace("testing"),
|
||||
sUID("1"),
|
||||
sSpec(
|
||||
clusterIP("10.0.0.1"),
|
||||
sPorts(sPort(80, ""))),
|
||||
),
|
||||
}
|
||||
|
||||
endpoints := []*corev1.Endpoints{
|
||||
buildEndpoint(
|
||||
eNamespace("testing"),
|
||||
eName("service1"),
|
||||
eUID("1"),
|
||||
subset(
|
||||
eAddresses(eAddress("10.10.0.1")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
),
|
||||
}
|
||||
|
||||
watchChan := make(chan interface{})
|
||||
client := clientMock{
|
||||
ingresses: ingresses,
|
||||
services: services,
|
||||
endpoints: endpoints,
|
||||
secrets: secrets,
|
||||
watchChan: watchChan,
|
||||
}
|
||||
provider := Provider{}
|
||||
|
||||
actual, err := provider.loadIngresses(client)
|
||||
require.NoError(t, err, "error loading ingresses")
|
||||
|
||||
expected := buildConfiguration(
|
||||
backends(
|
||||
backend("foo/bar",
|
||||
lbMethod("wrr"),
|
||||
servers(
|
||||
server("http://10.10.0.1:8080", weight(1))),
|
||||
),
|
||||
),
|
||||
frontends(
|
||||
frontend("foo/bar",
|
||||
passHostHeader(),
|
||||
auth(
|
||||
forwardAuth("https://auth.host",
|
||||
fwdAuthTLS(
|
||||
"-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
|
||||
"-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----",
|
||||
true))),
|
||||
routes(
|
||||
route("/bar", "PathPrefix:/bar"),
|
||||
route("foo", "Host:foo")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestLoadIngressesForwardAuthWithTLSSecretFailures(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
secretName string
|
||||
certName string
|
||||
certData string
|
||||
keyName string
|
||||
keyData string
|
||||
}{
|
||||
{
|
||||
desc: "empty certificate and key",
|
||||
secretName: "secret",
|
||||
certName: "",
|
||||
certData: "",
|
||||
keyName: "",
|
||||
keyData: "",
|
||||
},
|
||||
{
|
||||
desc: "wrong secret name, empty certificate and key",
|
||||
secretName: "wrongSecret",
|
||||
certName: "",
|
||||
certData: "",
|
||||
keyName: "",
|
||||
keyData: "",
|
||||
},
|
||||
{
|
||||
desc: "empty certificate data",
|
||||
secretName: "secret",
|
||||
certName: "tls.crt",
|
||||
certData: "",
|
||||
keyName: "tls.key",
|
||||
keyData: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----",
|
||||
},
|
||||
{
|
||||
desc: "empty key data",
|
||||
secretName: "secret",
|
||||
certName: "tls.crt",
|
||||
certData: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
|
||||
keyName: "tls.key",
|
||||
keyData: "",
|
||||
},
|
||||
{
|
||||
desc: "wrong cert name",
|
||||
secretName: "secret",
|
||||
certName: "cert.crt",
|
||||
certData: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE----",
|
||||
keyName: "tls.key",
|
||||
keyData: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----",
|
||||
},
|
||||
{
|
||||
desc: "wrong key name",
|
||||
secretName: "secret",
|
||||
certName: "tls.crt",
|
||||
certData: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
|
||||
keyName: "cert.key",
|
||||
keyData: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----",
|
||||
},
|
||||
}
|
||||
|
||||
ingresses := []*extensionsv1beta1.Ingress{
|
||||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesAuthType, "forward"),
|
||||
iAnnotation(annotationKubernetesAuthForwardURL, "https://auth.host"),
|
||||
iAnnotation(annotationKubernetesAuthForwardTLSSecret, "secret"),
|
||||
iRules(
|
||||
iRule(iHost("foo"),
|
||||
iPaths(
|
||||
onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))))),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
services := []*corev1.Service{
|
||||
buildService(
|
||||
sName("service1"),
|
||||
sNamespace("testing"),
|
||||
sUID("1"),
|
||||
sSpec(
|
||||
clusterIP("10.0.0.1"),
|
||||
sPorts(sPort(80, ""))),
|
||||
),
|
||||
}
|
||||
|
||||
endpoints := []*corev1.Endpoints{
|
||||
buildEndpoint(
|
||||
eNamespace("testing"),
|
||||
eName("service1"),
|
||||
eUID("1"),
|
||||
subset(
|
||||
eAddresses(eAddress("10.10.0.1")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
),
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
secrets := []*corev1.Secret{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: test.secretName,
|
||||
UID: "1",
|
||||
Namespace: "testing",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
test.certName: []byte(test.certData),
|
||||
test.keyName: []byte(test.keyData),
|
||||
},
|
||||
}}
|
||||
|
||||
watchChan := make(chan interface{})
|
||||
client := clientMock{
|
||||
ingresses: ingresses,
|
||||
services: services,
|
||||
endpoints: endpoints,
|
||||
secrets: secrets,
|
||||
watchChan: watchChan,
|
||||
}
|
||||
provider := Provider{}
|
||||
|
||||
actual, err := provider.loadIngresses(client)
|
||||
require.NoError(t, err, "error loading ingresses")
|
||||
|
||||
expected := buildConfiguration(
|
||||
backends(
|
||||
backend("foo/bar",
|
||||
lbMethod("wrr"),
|
||||
servers(),
|
||||
),
|
||||
),
|
||||
frontends(),
|
||||
)
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -108,6 +108,17 @@ func (s *Server) buildMiddlewares(frontendName string, frontend *types.Frontend,
|
|||
middle = append(middle, handler)
|
||||
}
|
||||
|
||||
// Authentication
|
||||
if frontend.Auth != nil {
|
||||
authMiddleware, err := mauth.NewAuthenticator(frontend.Auth, s.tracingMiddleware)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
handler := s.wrapNegroniHandlerWithAccessLog(authMiddleware, fmt.Sprintf("Auth for %s", frontendName))
|
||||
middle = append(middle, handler)
|
||||
}
|
||||
|
||||
return middle, buildModifyResponse(secureMiddleware, headerMiddleware), postConfig, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -251,3 +251,40 @@ func TestBuildRedirectHandler(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerGenericFrontendAuthFail(t *testing.T) {
|
||||
globalConfig := configuration.GlobalConfiguration{
|
||||
EntryPoints: configuration.EntryPoints{
|
||||
"http": &configuration.EntryPoint{ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}},
|
||||
},
|
||||
}
|
||||
|
||||
dynamicConfigs := types.Configurations{
|
||||
"config": &types.Configuration{
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"frontend": {
|
||||
EntryPoints: []string{"http"},
|
||||
Backend: "backend",
|
||||
BasicAuth: []string{""},
|
||||
},
|
||||
},
|
||||
Backends: map[string]*types.Backend{
|
||||
"backend": {
|
||||
Servers: map[string]types.Server{
|
||||
"server": {
|
||||
URL: "http://localhost",
|
||||
},
|
||||
},
|
||||
LoadBalancer: &types.LoadBalancer{
|
||||
Method: "Wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
srv := NewServer(globalConfig, nil, nil)
|
||||
|
||||
_, err := srv.loadConfig(dynamicConfigs, globalConfig)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -56,6 +56,41 @@
|
|||
"{{.}}",
|
||||
{{end}}]
|
||||
|
||||
{{if $frontend.Auth }}
|
||||
[frontends."{{ $frontendName }}".auth]
|
||||
headerField = "X-WebAuth-User"
|
||||
|
||||
{{if $frontend.Auth.Basic }}
|
||||
[frontends."{{ $frontendName }}".auth.basic]
|
||||
users = [{{range $frontend.Auth.Basic.Users }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Auth.Digest }}
|
||||
[frontends."{{ $frontendName }}".auth.digest]
|
||||
users = [{{range $frontend.Auth.Digest.Users }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.Auth.Forward }}
|
||||
[frontends."{{ $frontendName }}".auth.forward]
|
||||
address = "{{ $frontend.Auth.Forward.Address }}"
|
||||
authResponseHeaders = [{{range $frontend.Auth.Forward.AuthResponseHeaders }}
|
||||
"{{.}}",
|
||||
{{end}}]
|
||||
trustForwardHeader = {{ $frontend.Auth.Forward.TrustForwardHeader }}
|
||||
{{if $frontend.Auth.Forward.TLS }}
|
||||
[frontends."{{ $frontendName }}".auth.forward.tls]
|
||||
cert = "{{ $frontend.Auth.Forward.TLS.Cert }}"
|
||||
key = "{{ $frontend.Auth.Forward.TLS.Key }}"
|
||||
insecureSkipVerify = {{ $frontend.Auth.Forward.TLS.InsecureSkipVerify }}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
|
||||
{{if $frontend.WhiteList }}
|
||||
[frontends."{{ $frontendName }}".whiteList]
|
||||
sourceRange = [{{range $frontend.WhiteList.SourceRange }}
|
||||
|
|
|
@ -138,12 +138,20 @@ func WithRoute(name string, rule string) func(*types.Route) string {
|
|||
}
|
||||
|
||||
// WithBasicAuth is a helper to create a configuration
|
||||
// Deprecated
|
||||
func WithBasicAuth(username string, password string) func(*types.Frontend) {
|
||||
return func(fe *types.Frontend) {
|
||||
fe.BasicAuth = []string{username + ":" + password}
|
||||
}
|
||||
}
|
||||
|
||||
// WithFrontEndAuth is a helper to create a configuration
|
||||
func WithFrontEndAuth(auth *types.Auth) func(*types.Frontend) {
|
||||
return func(fe *types.Frontend) {
|
||||
fe.Auth = auth
|
||||
}
|
||||
}
|
||||
|
||||
// WithLBSticky is a helper to create a configuration
|
||||
func WithLBSticky(cookieName string) func(*types.Backend) {
|
||||
return func(b *types.Backend) {
|
||||
|
|
|
@ -184,13 +184,14 @@ type Frontend struct {
|
|||
PassHostHeader bool `json:"passHostHeader,omitempty"`
|
||||
PassTLSCert bool `json:"passTLSCert,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
BasicAuth []string `json:"basicAuth"`
|
||||
BasicAuth []string `json:"basicAuth"` // Deprecated
|
||||
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"` // Deprecated
|
||||
WhiteList *WhiteList `json:"whiteList,omitempty"`
|
||||
Headers *Headers `json:"headers,omitempty"`
|
||||
Errors map[string]*ErrorPage `json:"errors,omitempty"`
|
||||
RateLimit *RateLimit `json:"ratelimit,omitempty"`
|
||||
Redirect *Redirect `json:"redirect,omitempty"`
|
||||
Auth *Auth `json:"auth,omitempty"`
|
||||
}
|
||||
|
||||
// Hash returns the hash value of a Frontend struct.
|
||||
|
|
Loading…
Reference in a new issue