From 1959e1fd4412176c02bc3a6d7a6e318b5cd14684 Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Thu, 5 Sep 2019 13:42:04 +0200 Subject: [PATCH] Auth middlewares in kubernetes CRD uses secrets --- docs/content/middlewares/basicauth.md | 222 +++++++- docs/content/middlewares/digestauth.md | 237 ++++++++- docs/content/middlewares/forwardauth.md | 491 ++++++++++++++++-- docs/content/observability/access-logs.md | 6 +- pkg/middlewares/buffering/buffering.go | 2 +- .../kubernetes/crd/fixtures/with_auth.yml | 65 +++ pkg/provider/kubernetes/crd/kubernetes.go | 196 ++++++- .../kubernetes/crd/kubernetes_test.go | 37 ++ .../crd/traefik/v1alpha1/middleware.go | 81 ++- .../traefik/v1alpha1/zz_generated.deepcopy.go | 84 ++- pkg/server/router/tcp/router.go | 9 +- 11 files changed, 1306 insertions(+), 124 deletions(-) create mode 100644 pkg/provider/kubernetes/crd/fixtures/with_auth.yml diff --git a/docs/content/middlewares/basicauth.md b/docs/content/middlewares/basicauth.md index cd032f03f..2e44eb076 100644 --- a/docs/content/middlewares/basicauth.md +++ b/docs/content/middlewares/basicauth.md @@ -16,7 +16,7 @@ The BasicAuth middleware is a quick way to restrict access to your services to k # To create user:password pair, it's possible to use this command: # echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g labels: - - "traefik.http.middlewares.test-auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" +- "traefik.http.middlewares.test-auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" ``` ```yaml tab="Kubernetes" @@ -27,9 +27,7 @@ metadata: name: test-auth spec: basicAuth: - users: - - test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/ - - test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 + secret: secretName ``` ```json tab="Marathon" @@ -41,7 +39,7 @@ spec: ```yaml tab="Rancher" # Declaring the user list labels: - - "traefik.http.middlewares.test-auth.basicauth.users=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" +- "traefik.http.middlewares.test-auth.basicauth.users=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" ``` ```toml tab="File (TOML)" @@ -79,12 +77,140 @@ Passwords must be encoded using MD5, SHA1, or BCrypt. The `users` option is an array of authorized users. Each user will be declared using the `name:encoded-password` format. +!!! Note + + - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. + - For security reasons, the field `users` doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +```yaml tab="Docker" +# Declaring the user list +# +# Note: all dollar signs in the hash need to be doubled for escaping. +# To create user:password pair, it's possible to use this command: +# echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g +labels: +- "traefik.http.middlewares.test-auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" +``` + +```yaml tab="Kubernetes" +# Declaring the user list +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + basicAuth: + secret: authsecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: authsecret + namespace: default + +data: + users: |2 + dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5 + aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.basicauth.users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" +} +``` + +```yaml tab="Rancher" +# Declaring the user list +labels: +- "traefik.http.middlewares.test-auth.basicauth.users=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" +``` + +```toml tab="File (TOML)" +# Declaring the user list +[http.middlewares] + [http.middlewares.test-auth.basicAuth] + users = [ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + ] +``` + +```yaml tab="File (YAML)" +# Declaring the user list +http: + middlewares: + test-auth: + basicAuth: + users: + - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" + - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" +``` + ### `usersFile` The `usersFile` option is the path to an external file that contains the authorized users for the middleware. The file content is a list of `name:encoded-password`. +!!! Note + + - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. + - Because it does not make much sense to refer to a file path on Kubernetes, the `usersFile` field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.basicauth.usersfile=/path/to/my/usersfile" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + basicAuth: + secret: authsecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: authsecret + namespace: default + +data: + users: |2 + dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5 + aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.basicauth.usersfile": "/path/to/my/usersfile" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.basicauth.usersfile=/path/to/my/usersfile" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.basicAuth] + usersFile = "/path/to/my/usersfile" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + basicAuth: + usersFile: "/path/to/my/usersfile" +``` + ??? example "A file containing test/test and test2/test2" ```txt @@ -92,21 +218,57 @@ The file content is a list of `name:encoded-password`. test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 ``` -!!! Note - - If both `users` and `usersFile` are provided, the two are merged. The content of `usersFile` has precedence over `users`. - ### `realm` You can customize the realm for the authentication with the `realm` option. The default value is `traefik`. +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.basicauth.realm=MyRealm" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + basicAuth: + realm: MyRealm +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.basicauth.realm": "MyRealm" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.basicauth.realm=MyRealm" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.basicAuth] + realm = "MyRealm" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + basicAuth: + realm: "MyRealm" +``` + ### `headerField` You can define a header field to store the authenticated user using the `headerField`option. ```yaml tab="Docker" labels: - - "traefik.http.middlewares.my-auth.basicauth.headerField=X-WebAuth-User" +- "traefik.http.middlewares.my-auth.basicauth.headerField=X-WebAuth-User" ``` ```yaml tab="Kubernetes" @@ -144,3 +306,43 @@ http: ### `removeHeader` Set the `removeHeader` option to `true` to remove the authorization header before forwarding the request to your service. (Default value is `false`.) + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.basicauth.removeheader=true" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + basicAuth: + removeHeader: true +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.basicauth.removeheader": "true" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.basicauth.removeheader=true" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.basicAuth] + removeHeader = true +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + basicAuth: + removeHeader: true +``` diff --git a/docs/content/middlewares/digestauth.md b/docs/content/middlewares/digestauth.md index 7af3a5390..0462599bd 100644 --- a/docs/content/middlewares/digestauth.md +++ b/docs/content/middlewares/digestauth.md @@ -10,6 +10,7 @@ The DigestAuth middleware is a quick way to restrict access to your services to ## Configuration Examples ```yaml tab="Docker" +# Declaring the user list labels: - "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e" ``` @@ -22,9 +23,82 @@ metadata: name: test-auth spec: digestAuth: - users: - - test:traefik:a2688e031edb4be6a3797f3882655c05 - - test2:traefik:518845800f9e2bfb1f1f740ec24f074e + secret: userssecret +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.digestauth.users": "test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e" +} +``` + +```yaml tab="Rancher" +# Declaring the user list +labels: +- "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e" +``` + +```toml tab="File (TOML)" +# Declaring the user list +[http.middlewares] + [http.middlewares.test-auth.digestAuth] + users = [ + "test:traefik:a2688e031edb4be6a3797f3882655c05", + "test2:traefik:518845800f9e2bfb1f1f740ec24f074e", + ] +``` + +```yaml tab="File (YAML)" +# Declaring the user list +http: + middlewares: + test-auth: + digestAuth: + users: + - "test:traefik:a2688e031edb4be6a3797f3882655c05" + - "test2:traefik:518845800f9e2bfb1f1f740ec24f074e" +``` + +## Configuration Options + +!!! tip + + Use `htdigest` to generate passwords. + +### `users` + +The `users` option is an array of authorized users. Each user will be declared using the `name:realm:encoded-password` format. + +!!! Note + + - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. + - For security reasons, the field `users` doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.digestauth.users=test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + digestAuth: + secret: authsecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: authsecret + namespace: default + +data: + users: |2 + dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5 + aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK ``` ```json tab="Marathon" @@ -57,26 +131,69 @@ http: - "test2:traefik:518845800f9e2bfb1f1f740ec24f074e" ``` -!!! tip - - Use `htdigest` to generate passwords. - -## Configuration Options - -### `users` - -The `users` option is an array of authorized users. Each user will be declared using the `name:realm:encoded-password` format. - -!!! Note - - If both `users` and `usersFile` are provided, the two are merged. The content of `usersFile` has precedence over `users`. - ### `usersFile` The `usersFile` option is the path to an external file that contains the authorized users for the middleware. The file content is a list of `name:realm:encoded-password`. +!!! Note + + - If both `users` and `usersFile` are provided, the two are merged. The contents of `usersFile` have precedence over the values in `users`. + - Because it does not make much sense to refer to a file path on Kubernetes, the `usersFile` field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.digestauth.usersfile=/path/to/my/usersfile" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + digestAuth: + secret: authsecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: authsecret + namespace: default + +data: + users: |2 + dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5 + aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.digestauth.usersfile": "/path/to/my/usersfile" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.digestauth.usersfile=/path/to/my/usersfile" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.digestAuth] + usersFile = "/path/to/my/usersfile" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + digestAuth: + usersFile: "/path/to/my/usersfile" +``` + ??? example "A file containing test/test and test2/test2" ```txt @@ -84,20 +201,54 @@ The file content is a list of `name:realm:encoded-password`. test2:traefik:518845800f9e2bfb1f1f740ec24f074e ``` -!!! Note - - If both `users` and `usersFile` are provided, the two are merged. The content of `usersFile` has precedence over `users`. - ### `realm` You can customize the realm for the authentication with the `realm` option. The default value is `traefik`. +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.digestauth.realm=MyRealm" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + digestAuth: + realm: MyRealm +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.digestauth.realm": "MyRealm" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.digestauth.realm=MyRealm" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.digestAuth] + realm = "MyRealm" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + digestAuth: + realm: "MyRealm" +``` + ### `headerField` You can customize the header field for the authenticated user using the `headerField`option. -Example "File -- Passing Authenticated User to Services Via Headers" - ```yaml tab="Docker" labels: - "traefik.http.middlewares.my-auth.digestauth.headerField=X-WebAuth-User" @@ -143,3 +294,43 @@ http: ### `removeHeader` Set the `removeHeader` option to `true` to remove the authorization header before forwarding the request to your service. (Default value is `false`.) + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.digestauth.removeheader=true" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + digestAuth: + removeHeader: true +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.digestauth.removeheader": "true" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.digestauth.removeheader=true" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.digestAuth] + removeHeader = true +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + digestAuth: + removeHeader: true +``` diff --git a/docs/content/middlewares/forwardauth.md b/docs/content/middlewares/forwardauth.md index 29c02b7a8..3bd35b84f 100644 --- a/docs/content/middlewares/forwardauth.md +++ b/docs/content/middlewares/forwardauth.md @@ -15,12 +15,99 @@ Otherwise, the response from the authentication server is returned. # Forward authentication to authserver.com labels: - "traefik.http.middlewares.test-auth.forwardauth.address=https://authserver.com/auth" -- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret" -- "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt" -- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true" -- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" -- "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify=true" -- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" +``` + +```yaml tab="Kubernetes" +# Forward authentication to authserver.com +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.address": "https://authserver.com/auth" +} +``` + +```yaml tab="Rancher" +# Forward authentication to authserver.com +labels: +- "traefik.http.middlewares.test-auth.forwardauth.address=https://authserver.com/auth" +``` + +```toml tab="File (TOML)" +# Forward authentication to authserver.com +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" +``` + +```yaml tab="File (YAML)" +# Forward authentication to authserver.com +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" +``` + +## Configuration Options + +### `address` + +The `address` option defines the authentication server address. + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.address=https://authserver.com/auth" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.address": "https://authserver.com/auth" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.address=https://authserver.com/auth" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" +``` + +### `trustForwardHeader` + +Set the `trustForwardHeader` option to `true` to trust all the existing `X-Forwarded-*` headers. + +```yaml tab="Docker" +labels: - "traefik.http.middlewares.test-auth.forwardauth.trustForwardHeader=true" ``` @@ -33,89 +120,381 @@ spec: forwardAuth: address: https://authserver.com/auth trustForwardHeader: true - authResponseHeaders: - - X-Auth-User - - X-Secret - tls: - ca: path/to/local.crt - caOptional: true - cert: path/to/foo.cert - key: path/to/foo.key ``` ```json tab="Marathon" "labels": { - "traefik.http.middlewares.test-auth.forwardauth.address": "https://authserver.com/auth", - "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders": "X-Auth-User,X-Secret", - "traefik.http.middlewares.test-auth.forwardauth.tls.ca": "path/to/local.crt", - "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional": "true", - "traefik.http.middlewares.test-auth.forwardauth.tls.cert": "path/to/foo.cert", - "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify": "true", - "traefik.http.middlewares.test-auth.forwardauth.tls.key": "path/to/foo.key", "traefik.http.middlewares.test-auth.forwardauth.trustForwardHeader": "true" } ``` ```yaml tab="Rancher" -# Forward authentication to authserver.com labels: -- "traefik.http.middlewares.test-auth.forwardauth.address=https://authserver.com/auth" -- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret" -- "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt" -- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true" -- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" -- "traefik.http.middlewares.test-auth.forwardauth.tls.InisecureSkipVerify=true" -- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" - "traefik.http.middlewares.test-auth.forwardauth.trustForwardHeader=true" ``` ```toml tab="File (TOML)" -# Forward authentication to authserver.com [http.middlewares] [http.middlewares.test-auth.forwardAuth] address = "https://authserver.com/auth" trustForwardHeader = true - authResponseHeaders = ["X-Auth-User", "X-Secret"] - - [http.middlewares.test-auth.forwardAuth.tls] - ca = "path/to/local.crt" - caOptional = true - cert = "path/to/foo.cert" - key = "path/to/foo.key" ``` ```yaml tab="File (YAML)" -# Forward authentication to authserver.com http: middlewares: test-auth: forwardAuth: address: "https://authserver.com/auth" trustForwardHeader: true - authResponseHeaders: - - "X-Auth-User" - - "X-Secret" - tls: - ca: "path/to/local.crt" - caOptional: true - cert: "path/to/foo.cert" - key: "path/to/foo.key" ``` -## Configuration Options - -### `address` - -The `address` option defines the authentication server address. - -### `trustForwardHeader` - -Set the `trustForwardHeader` option to `true` to trust all the existing `X-Forwarded-*` headers. - ### `authResponseHeaders` The `authResponseHeaders` option is the list of the headers to copy from the authentication server to the request. +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + authResponseHeaders: + - X-Auth-User + - X-Secret +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders": "X-Auth-User,X-Secret" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeaders=X-Auth-User, X-Secret" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + authResponseHeaders = ["X-Auth-User", "X-Secret"] +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + authResponseHeaders: + - "X-Auth-User" + - "X-Secret" +``` + ### `tls` The `tls` option is the TLS configuration from Traefik to the authentication server. + +#### `tls.ca` + +TODO + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + tls: + caSecret: mycasercret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: mycasercret + namespace: default + +data: + ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.tls.ca": "path/to/local.crt" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.ca=path/to/local.crt" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + [http.middlewares.test-auth.forwardAuth.tls] + ca = "path/to/local.crt" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + tls: + ca: "path/to/local.crt" +``` + +#### `tls.caOptional` + +TODO + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + tls: + caOptional: true +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional": "true" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + [http.middlewares.test-auth.forwardAuth.tls] + caOptional = true +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + tls: + caOptional: true +``` + +#### `tls.cert` + +TODO + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" +- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + tls: + certSecret: mytlscert + +--- +apiVersion: v1 +kind: Secret +metadata: + name: mytlscert + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.tls.cert": "path/to/foo.cert", + "traefik.http.middlewares.test-auth.forwardauth.tls.key": "path/to/foo.key" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" +- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + [http.middlewares.test-auth.forwardAuth.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + tls: + cert: "path/to/foo.cert" + key: "path/to/foo.key" +``` + +!!! Note + For security reasons, the field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +#### `tls.key` + +TODO + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" +- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + tls: + certSecret: mytlscert + +--- +apiVersion: v1 +kind: Secret +metadata: + name: mytlscert + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.tls.cert": "path/to/foo.cert", + "traefik.http.middlewares.test-auth.forwardauth.tls.key": "path/to/foo.key" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.cert=path/to/foo.cert" +- "traefik.http.middlewares.test-auth.forwardauth.tls.key=path/to/foo.key" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + [http.middlewares.test-auth.forwardAuth.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + tls: + cert: "path/to/foo.cert" + key: "path/to/foo.key" +``` + +!!! Note + For security reasons, the field doesn't exist for Kubernetes IngressRoute, and one should use the `secret` field instead. + +#### `tls.insecureSkipVerify` + +TODO + +```yaml tab="Docker" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify=true" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://authserver.com/auth + insecureSkipVerify: true +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.tls.insecureSkipVerify": "true" +} +``` + +```yaml tab="Rancher" +labels: +- "traefik.http.middlewares.test-auth.forwardauth.tls.InsecureSkipVerify=true" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://authserver.com/auth" + insecureSkipVerify: true +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://authserver.com/auth" + insecureSkipVerify: true +``` diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 0effd5d1b..2c00e9dd2 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -157,9 +157,9 @@ accessLog: headers: defaultMode: keep names: - - User-Agent: redact - - Authorization: drop - - Content-Type: keep + User-Agent: redact + Authorization: drop + Content-Type: keep ``` ```bash tab="CLI" diff --git a/pkg/middlewares/buffering/buffering.go b/pkg/middlewares/buffering/buffering.go index 97fdd1314..e6183a723 100644 --- a/pkg/middlewares/buffering/buffering.go +++ b/pkg/middlewares/buffering/buffering.go @@ -24,7 +24,7 @@ type buffer struct { func New(ctx context.Context, next http.Handler, config dynamic.Buffering, name string) (http.Handler, error) { logger := middlewares.GetLogger(ctx, name, typeName) logger.Debug("Creating middleware") - logger.Debug("Setting up buffering: request limits: %d (mem), %d (max), response limits: %d (mem), %d (max) with retry: '%s'", + logger.Debugf("Setting up buffering: request limits: %d (mem), %d (max), response limits: %d (mem), %d (max) with retry: '%s'", config.MemRequestBodyBytes, config.MaxRequestBodyBytes, config.MemResponseBodyBytes, config.MaxResponseBodyBytes, config.RetryExpression) oxyBuffer, err := oxybuffer.New( diff --git a/pkg/provider/kubernetes/crd/fixtures/with_auth.yml b/pkg/provider/kubernetes/crd/fixtures/with_auth.yml new file mode 100644 index 000000000..b03b82b64 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_auth.yml @@ -0,0 +1,65 @@ +apiVersion: v1 +kind: Secret +metadata: + name: authsecret + namespace: default + +data: + users: |2 + dGVzdDokYXByMSRINnVza2trVyRJZ1hMUDZld1RyU3VCa1RycUU4d2ovCnRlc3QyOiRhcHIxJGQ5 + aHI5SEJCJDRIeHdnVWlyM0hQNEVzZ2dQL1FObzAK +--- +apiVersion: v1 +kind: Secret +metadata: + name: casecret + namespace: default + +data: + ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: tlssecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: basicauth + namespace: default + +spec: + basicAuth: + secret: authsecret + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: digestauth + namespace: default + +spec: + digestAuth: + secret: authsecret +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: forwardauth + namespace: default + +spec: + forwardAuth: + address: test.com + tls: + certSecret: tlssecret + caSecret: casecret diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 6296463a6..c8fbd61f2 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -1,6 +1,8 @@ package crd import ( + "bufio" + "bytes" "context" "crypto/sha256" "fmt" @@ -149,6 +151,25 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) for _, middleware := range client.GetMiddlewares() { id := makeID(middleware.Namespace, middleware.Name) ctxMid := log.With(ctx, log.Str(log.MiddlewareName, id)) + + basicAuth, err := createBasicAuthMiddleware(client, middleware.Namespace, middleware.Spec.BasicAuth) + if err != nil { + log.FromContext(ctxMid).Errorf("Error while reading basic auth middleware: %v", err) + continue + } + + digestAuth, err := createDigestAuthMiddleware(client, middleware.Namespace, middleware.Spec.DigestAuth) + if err != nil { + log.FromContext(ctxMid).Errorf("Error while reading digest auth middleware: %v", err) + continue + } + + forwardAuth, err := createForwardAuthMiddleware(client, middleware.Namespace, middleware.Spec.ForwardAuth) + if err != nil { + log.FromContext(ctxMid).Errorf("Error while reading forward auth middleware: %v", err) + continue + } + conf.HTTP.Middlewares[id] = &dynamic.Middleware{ AddPrefix: middleware.Spec.AddPrefix, StripPrefix: middleware.Spec.StripPrefix, @@ -162,9 +183,9 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) RateLimit: middleware.Spec.RateLimit, RedirectRegex: middleware.Spec.RedirectRegex, RedirectScheme: middleware.Spec.RedirectScheme, - BasicAuth: middleware.Spec.BasicAuth, - DigestAuth: middleware.Spec.DigestAuth, - ForwardAuth: middleware.Spec.ForwardAuth, + BasicAuth: basicAuth, + DigestAuth: digestAuth, + ForwardAuth: forwardAuth, InFlightReq: middleware.Spec.InFlightReq, Buffering: middleware.Spec.Buffering, CircuitBreaker: middleware.Spec.CircuitBreaker, @@ -178,6 +199,175 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) return conf } +func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alpha1.ForwardAuth) (*dynamic.ForwardAuth, error) { + if auth == nil { + return nil, nil + } + if len(auth.Address) == 0 { + return nil, fmt.Errorf("forward authentication requires an address") + } + + forwardAuth := &dynamic.ForwardAuth{ + Address: auth.Address, + TrustForwardHeader: auth.TrustForwardHeader, + AuthResponseHeaders: auth.AuthResponseHeaders, + } + + if auth.TLS == nil { + return forwardAuth, nil + } + + forwardAuth.TLS = &dynamic.ClientTLS{ + CAOptional: auth.TLS.CAOptional, + InsecureSkipVerify: auth.TLS.InsecureSkipVerify, + } + + if len(auth.TLS.CASecret) > 0 { + caSecret, err := loadCASecret(namespace, auth.TLS.CASecret, k8sClient) + if err != nil { + return nil, fmt.Errorf("failed to load auth ca secret: %v", err) + } + forwardAuth.TLS.CA = caSecret + } + + if len(auth.TLS.CertSecret) > 0 { + authSecretCert, authSecretKey, err := loadAuthTLSSecret(namespace, auth.TLS.CertSecret, k8sClient) + if err != nil { + return nil, fmt.Errorf("failed to load auth secret: %s", err) + } + forwardAuth.TLS.Cert = authSecretCert + forwardAuth.TLS.Key = authSecretKey + } + + return forwardAuth, nil +} + +func loadCASecret(namespace, secretName string, k8sClient Client) (string, error) { + secret, ok, err := k8sClient.GetSecret(namespace, secretName) + if err != nil { + return "", fmt.Errorf("failed to fetch secret '%s/%s': %s", namespace, secretName, err) + } + if !ok { + return "", fmt.Errorf("secret '%s/%s' not found", namespace, secretName) + } + if secret == nil { + return "", fmt.Errorf("data for secret '%s/%s' must not be nil", namespace, secretName) + } + if len(secret.Data) != 1 { + return "", fmt.Errorf("found %d elements for secret '%s/%s', must be single element exactly", len(secret.Data), namespace, secretName) + } + + for _, v := range secret.Data { + return string(v), nil + } + return "", 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 '%s/%s': %s", namespace, secretName, err) + } + if !exists { + return "", "", fmt.Errorf("secret '%s/%s' does not exist", namespace, secretName) + } + if secret == nil { + return "", "", fmt.Errorf("data for secret '%s/%s' must not be nil", namespace, secretName) + } + if len(secret.Data) != 2 { + return "", "", fmt.Errorf("found %d elements for secret '%s/%s', must be two elements exactly", len(secret.Data), namespace, secretName) + } + + return getCertificateBlocks(secret, namespace, secretName) +} + +func createBasicAuthMiddleware(client Client, namespace string, basicAuth *v1alpha1.BasicAuth) (*dynamic.BasicAuth, error) { + if basicAuth == nil { + return nil, nil + } + + credentials, err := getAuthCredentials(client, basicAuth.Secret, namespace) + if err != nil { + return nil, err + } + + return &dynamic.BasicAuth{ + Users: credentials, + Realm: basicAuth.Realm, + RemoveHeader: basicAuth.RemoveHeader, + HeaderField: basicAuth.HeaderField, + }, nil +} + +func createDigestAuthMiddleware(client Client, namespace string, digestAuth *v1alpha1.DigestAuth) (*dynamic.DigestAuth, error) { + if digestAuth == nil { + return nil, nil + } + + credentials, err := getAuthCredentials(client, digestAuth.Secret, namespace) + if err != nil { + return nil, err + } + + return &dynamic.DigestAuth{ + Users: credentials, + Realm: digestAuth.Realm, + RemoveHeader: digestAuth.RemoveHeader, + HeaderField: digestAuth.HeaderField, + }, nil +} + +func getAuthCredentials(k8sClient Client, authSecret, namespace string) ([]string, error) { + if authSecret == "" { + return nil, fmt.Errorf("auth secret must be set") + } + + auth, err := loadAuthCredentials(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 '%s/%s': %s", namespace, secretName, err) + } + if !ok { + return nil, fmt.Errorf("secret '%s/%s' not found", namespace, secretName) + } + if secret == nil { + return nil, fmt.Errorf("data for secret '%s/%s' must not be nil", namespace, secretName) + } + if len(secret.Data) != 1 { + return nil, fmt.Errorf("found %d elements for secret '%s/%s', 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 err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading secret for %v/%v: %v", namespace, secretName, err) + } + if len(credentials) == 0 { + return nil, fmt.Errorf("secret '%s/%s' does not contain any credentials", namespace, secretName) + } + + return credentials, nil +} + func createChainMiddleware(ctx context.Context, namespace string, chain *v1alpha1.Chain) *dynamic.Chain { if chain == nil { return nil diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index cb9d87408..be7a3adef 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -1461,6 +1461,43 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, + { + desc: "Simple Ingress Route, with basic auth middleware", + paths: []string{"services.yml", "with_auth.yml"}, + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{}, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{ + "default/basicauth": { + BasicAuth: &dynamic.BasicAuth{ + Users: dynamic.Users{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, + }, + }, + "default/digestauth": { + DigestAuth: &dynamic.DigestAuth{ + Users: dynamic.Users{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, + }, + }, + "default/forwardauth": { + ForwardAuth: &dynamic.ForwardAuth{ + Address: "test.com", + TLS: &dynamic.ClientTLS{ + CA: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----", + Cert: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----", + Key: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----", + }, + }, + }, + }, + Services: map[string]*dynamic.Service{}, + }, + }, + }, { desc: "port selected by name (TODO)", }, diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go index c5c2c4154..486f8987f 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go @@ -18,29 +18,29 @@ type Middleware struct { // +k8s:deepcopy-gen=true -// Middleware holds the Middleware configuration. +// MiddlewareSpec holds the Middleware configuration. type MiddlewareSpec struct { - AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty"` - StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty" toml:"stripPrefix,omitempty" yaml:"stripPrefix,omitempty"` - StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty" toml:"stripPrefixRegex,omitempty" yaml:"stripPrefixRegex,omitempty"` - ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty" toml:"replacePath,omitempty" yaml:"replacePath,omitempty"` - ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty" toml:"replacePathRegex,omitempty" yaml:"replacePathRegex,omitempty"` - Chain *Chain `json:"chain,omitempty" toml:"chain,omitempty" yaml:"chain,omitempty"` - IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty" toml:"ipWhiteList,omitempty" yaml:"ipWhiteList,omitempty"` - Headers *dynamic.Headers `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` - Errors *dynamic.ErrorPage `json:"errors,omitempty" toml:"errors,omitempty" yaml:"errors,omitempty"` - RateLimit *dynamic.RateLimit `json:"rateLimit,omitempty" toml:"rateLimit,omitempty" yaml:"rateLimit,omitempty"` - RedirectRegex *dynamic.RedirectRegex `json:"redirectRegex,omitempty" toml:"redirectRegex,omitempty" yaml:"redirectRegex,omitempty"` - RedirectScheme *dynamic.RedirectScheme `json:"redirectScheme,omitempty" toml:"redirectScheme,omitempty" yaml:"redirectScheme,omitempty"` - BasicAuth *dynamic.BasicAuth `json:"basicAuth,omitempty" toml:"basicAuth,omitempty" yaml:"basicAuth,omitempty"` - DigestAuth *dynamic.DigestAuth `json:"digestAuth,omitempty" toml:"digestAuth,omitempty" yaml:"digestAuth,omitempty"` - ForwardAuth *dynamic.ForwardAuth `json:"forwardAuth,omitempty" toml:"forwardAuth,omitempty" yaml:"forwardAuth,omitempty"` - InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty" toml:"inFlightReq,omitempty" yaml:"inFlightReq,omitempty"` - Buffering *dynamic.Buffering `json:"buffering,omitempty" toml:"buffering,omitempty" yaml:"buffering,omitempty"` - CircuitBreaker *dynamic.CircuitBreaker `json:"circuitBreaker,omitempty" toml:"circuitBreaker,omitempty" yaml:"circuitBreaker,omitempty"` - Compress *dynamic.Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty"` - PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty" toml:"passTLSClientCert,omitempty" yaml:"passTLSClientCert,omitempty"` - Retry *dynamic.Retry `json:"retry,omitempty" toml:"retry,omitempty" yaml:"retry,omitempty"` + AddPrefix *dynamic.AddPrefix `json:"addPrefix,omitempty"` + StripPrefix *dynamic.StripPrefix `json:"stripPrefix,omitempty"` + StripPrefixRegex *dynamic.StripPrefixRegex `json:"stripPrefixRegex,omitempty"` + ReplacePath *dynamic.ReplacePath `json:"replacePath,omitempty"` + ReplacePathRegex *dynamic.ReplacePathRegex `json:"replacePathRegex,omitempty"` + Chain *Chain `json:"chain,omitempty"` + IPWhiteList *dynamic.IPWhiteList `json:"ipWhiteList,omitempty"` + Headers *dynamic.Headers `json:"headers,omitempty"` + Errors *dynamic.ErrorPage `json:"errors,omitempty"` + RateLimit *dynamic.RateLimit `json:"rateLimit,omitempty"` + RedirectRegex *dynamic.RedirectRegex `json:"redirectRegex,omitempty"` + RedirectScheme *dynamic.RedirectScheme `json:"redirectScheme,omitempty"` + BasicAuth *BasicAuth `json:"basicAuth,omitempty"` + DigestAuth *DigestAuth `json:"digestAuth,omitempty"` + ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"` + InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"` + Buffering *dynamic.Buffering `json:"buffering,omitempty"` + CircuitBreaker *dynamic.CircuitBreaker `json:"circuitBreaker,omitempty"` + Compress *dynamic.Compress `json:"compress,omitempty"` + PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"` + Retry *dynamic.Retry `json:"retry,omitempty"` } // +k8s:deepcopy-gen=true @@ -50,6 +50,43 @@ type Chain struct { Middlewares []MiddlewareRef `json:"middlewares,omitempty"` } +// +k8s:deepcopy-gen=true + +// BasicAuth holds the HTTP basic authentication configuration. +type BasicAuth struct { + Secret string `json:"secret,omitempty"` + Realm string `json:"realm,omitempty"` + RemoveHeader bool `json:"removeHeader,omitempty"` + HeaderField string `json:"headerField,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// DigestAuth holds the Digest HTTP authentication configuration. +type DigestAuth struct { + Secret string `json:"secret,omitempty"` + RemoveHeader bool `json:"removeHeader,omitempty"` + Realm string `json:"realm,omitempty"` + HeaderField string `json:"headerField,omitempty"` +} + +// +k8s:deepcopy-gen=true + +// ForwardAuth holds the http forward authentication configuration. +type ForwardAuth struct { + Address string `json:"address,omitempty"` + TrustForwardHeader bool `json:"trustForwardHeader,omitempty"` + AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"` + TLS *ClientTLS `json:"tls,omitempty"` +} + +type ClientTLS struct { + CASecret string `json:"caSecret,omitempty"` + CAOptional bool `json:"caOptional,omitempty"` + CertSecret string `json:"certSecret,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` +} + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // MiddlewareList is a list of Middleware resources. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go index 67aa55d0c..65ea369c8 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go @@ -33,6 +33,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BasicAuth) DeepCopyInto(out *BasicAuth) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BasicAuth. +func (in *BasicAuth) DeepCopy() *BasicAuth { + if in == nil { + return nil + } + out := new(BasicAuth) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Chain) DeepCopyInto(out *Chain) { *out = *in @@ -75,6 +91,64 @@ func (in *ClientAuth) DeepCopy() *ClientAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClientTLS) DeepCopyInto(out *ClientTLS) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTLS. +func (in *ClientTLS) DeepCopy() *ClientTLS { + if in == nil { + return nil + } + out := new(ClientTLS) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DigestAuth) DeepCopyInto(out *DigestAuth) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DigestAuth. +func (in *DigestAuth) DeepCopy() *DigestAuth { + if in == nil { + return nil + } + out := new(DigestAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) { + *out = *in + if in.AuthResponseHeaders != nil { + in, out := &in.AuthResponseHeaders, &out.AuthResponseHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(ClientTLS) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForwardAuth. +func (in *ForwardAuth) DeepCopy() *ForwardAuth { + if in == nil { + return nil + } + out := new(ForwardAuth) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HealthCheck) DeepCopyInto(out *HealthCheck) { *out = *in @@ -425,17 +499,17 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) { } if in.BasicAuth != nil { in, out := &in.BasicAuth, &out.BasicAuth - *out = new(dynamic.BasicAuth) - (*in).DeepCopyInto(*out) + *out = new(BasicAuth) + **out = **in } if in.DigestAuth != nil { in, out := &in.DigestAuth, &out.DigestAuth - *out = new(dynamic.DigestAuth) - (*in).DeepCopyInto(*out) + *out = new(DigestAuth) + **out = **in } if in.ForwardAuth != nil { in, out := &in.ForwardAuth, &out.ForwardAuth - *out = new(dynamic.ForwardAuth) + *out = new(ForwardAuth) (*in).DeepCopyInto(*out) } if in.InFlightReq != nil { diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index bf4931695..55b42ba9d 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -177,6 +177,13 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string continue } + if routerConfig.Rule == "" { + err := errors.New("router has no rule") + routerConfig.AddError(err, true) + logger.Error(err) + continue + } + handler, err := m.serviceManager.BuildTCP(ctxRouter, routerConfig.Service) if err != nil { routerConfig.AddError(err, true) @@ -188,7 +195,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string if err != nil { routerErr := fmt.Errorf("unknown rule %s", routerConfig.Rule) routerConfig.AddError(routerErr, true) - logger.Debug(routerErr) + logger.Error(routerErr) continue }