From 1431ac57513b5de24fcd25fe1c910e2da5980145 Mon Sep 17 00:00:00 2001 From: Thibault Coupin Date: Thu, 4 Oct 2018 16:46:03 +0200 Subject: [PATCH] Basic Auth custom realm --- autogen/gentemplates/gen.go | 1 + configuration/entrypoints.go | 1 + configuration/entrypoints_test.go | 6 +++ docs/configuration/backends/docker.md | 1 + docs/configuration/entrypoints.md | 14 ++++++ middlewares/auth/authenticator.go | 7 ++- middlewares/auth/authenticator_test.go | 43 +++++++++++++++++++ .../docker/config_container_docker_test.go | 4 ++ .../docker/config_container_swarm_test.go | 4 ++ provider/docker/config_segment_test.go | 4 ++ provider/label/names.go | 2 + provider/label/partial.go | 1 + provider/label/partial_test.go | 3 +- templates/docker.tmpl | 1 + types/types.go | 1 + 15 files changed, 90 insertions(+), 3 deletions(-) diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index fd86522b8..168c7c653 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -444,6 +444,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} {{if $auth.Basic }} [frontends."frontend-{{ $frontendName }}".auth.basic] + realm = "{{ $auth.Basic.Realm }}" removeHeader = {{ $auth.Basic.RemoveHeader }} {{if $auth.Basic.Users }} users = [{{range $auth.Basic.Users }} diff --git a/configuration/entrypoints.go b/configuration/entrypoints.go index 3062ad237..2acb1ae93 100644 --- a/configuration/entrypoints.go +++ b/configuration/entrypoints.go @@ -121,6 +121,7 @@ func makeEntryPointAuth(result map[string]string) *types.Auth { var basic *types.Basic if v, ok := result["auth_basic_users"]; ok { basic = &types.Basic{ + Realm: result["auth_basic_realm"], Users: strings.Split(v, ","), RemoveHeader: toBool(result, "auth_basic_removeheader"), } diff --git a/configuration/entrypoints_test.go b/configuration/entrypoints_test.go index d3b5a4c84..a49072600 100644 --- a/configuration/entrypoints_test.go +++ b/configuration/entrypoints_test.go @@ -32,6 +32,7 @@ func Test_parseEntryPointsConfiguration(t *testing.T) { "Compress:true " + "ProxyProtocol.TrustedIPs:192.168.0.1 " + "ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " + + "Auth.Basic.Realm:myRealm " + "Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " + "Auth.Basic.RemoveHeader:true " + "Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " + @@ -52,6 +53,7 @@ func Test_parseEntryPointsConfiguration(t *testing.T) { "ClientIPStrategy.ExcludedIPs:10.0.0.3/24,20.0.0.3/24 ", expectedResult: map[string]string{ "address": ":8000", + "auth_basic_realm": "myRealm", "auth_basic_users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", "auth_basic_removeheader": "true", "auth_digest_users": "test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e", @@ -197,6 +199,7 @@ func TestEntryPoints_Set(t *testing.T) { "Compress:true " + "ProxyProtocol.TrustedIPs:192.168.0.1 " + "ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " + + "Auth.Basic.Realm:myRealm " + "Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " + "Auth.Basic.RemoveHeader:true " + "Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " + @@ -244,6 +247,7 @@ func TestEntryPoints_Set(t *testing.T) { }, Auth: &types.Auth{ Basic: &types.Basic{ + Realm: "myRealm", RemoveHeader: true, Users: types.Users{ "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", @@ -324,6 +328,7 @@ func TestEntryPoints_Set(t *testing.T) { "whiteList.sourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " + "proxyProtocol.TrustedIPs:192.168.0.1 " + "forwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " + + "auth.basic.realm:myRealm " + "auth.basic.users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " + "auth.digest.users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " + "auth.headerField:X-WebAuth-User " + @@ -364,6 +369,7 @@ func TestEntryPoints_Set(t *testing.T) { }, Auth: &types.Auth{ Basic: &types.Basic{ + Realm: "myRealm", Users: types.Users{ "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index d8c687a19..5fbb161d5 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -236,6 +236,7 @@ Labels can be used on containers to override default behavior. | `traefik.backend.maxconn.amount=10` | Sets a maximum number of connections to the backend.
Must be used in conjunction with the below label to take effect. | | `traefik.backend.maxconn.extractorfunc=client.ip` | Sets the function to be used against the request to determine what to limit maximum connections to the backend by.
Must be used in conjunction with the above label to take effect. | | `traefik.frontend.auth.basic=EXPR` | Sets the basic authentication to this frontend in CSV format: `User:Hash,User:Hash` [2] (DEPRECATED). | +| `traefik.frontend.auth.basic.realm=REALM` | Sets the realm of basic authentication to this frontend. | | `traefik.frontend.auth.basic.removeHeader=true` | If set to `true`, removes the `Authorization` header. | | `traefik.frontend.auth.basic.users=EXPR` | Sets the basic authentication to this frontend in CSV format: `User:Hash,User:Hash` [2]. | | `traefik.frontend.auth.basic.usersFile=/path/.htpasswd` | Sets the basic authentication with an external file; if users and usersFile are provided, both are merged, with external file contents having precedence. | diff --git a/docs/configuration/entrypoints.md b/docs/configuration/entrypoints.md index d07149209..8ca9ac64c 100644 --- a/docs/configuration/entrypoints.md +++ b/docs/configuration/entrypoints.md @@ -52,6 +52,7 @@ defaultEntryPoints = ["http", "https"] headerField = "X-WebAuth-User" [entryPoints.http.auth.basic] removeHeader = true + realm = "Your realm" users = [ "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", @@ -143,6 +144,7 @@ ProxyProtocol.Insecure:true ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 Auth.Basic.Removeheader:true +Auth.Basic.Realm:traefik Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e Auth.Digest.Removeheader:true Auth.HeaderField:X-WebAuth-User @@ -290,6 +292,18 @@ Users can be specified directly in the TOML file, or indirectly by referencing a Optionally, you can: +- customize the realm + +```toml +[entryPoints] + [entryPoints.http] + address = ":80" + [entryPoints.http.auth] + [entryPoints.http.auth.basic] + realm = "Your realm" + users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] +``` + - pass authenticated user to application via headers ```toml diff --git a/middlewares/auth/authenticator.go b/middlewares/auth/authenticator.go index ea223049e..97217adb5 100644 --- a/middlewares/auth/authenticator.go +++ b/middlewares/auth/authenticator.go @@ -45,8 +45,11 @@ func NewAuthenticator(authConfig *types.Auth, tracingMiddleware *tracing.Tracing if err != nil { return nil, err } - - basicAuth := goauth.NewBasicAuthenticator("traefik", authenticator.secretBasic) + realm := "traefik" + if authConfig.Basic.Realm != "" { + realm = authConfig.Basic.Realm + } + basicAuth := goauth.NewBasicAuthenticator(realm, authenticator.secretBasic) tracingAuth.handler = createAuthBasicHandler(basicAuth, authConfig) tracingAuth.name = "Auth Basic" tracingAuth.clientSpanKind = false diff --git a/middlewares/auth/authenticator_test.go b/middlewares/auth/authenticator_test.go index 360148600..2f2cdb0f8 100644 --- a/middlewares/auth/authenticator_test.go +++ b/middlewares/auth/authenticator_test.go @@ -129,6 +129,49 @@ func TestBasicAuthSuccess(t *testing.T) { assert.Equal(t, "traefik\n", string(body), "they should be equal") } +func TestBasicRealm(t *testing.T) { + authMiddlewareDefaultRealm, errdefault := NewAuthenticator(&types.Auth{ + Basic: &types.Basic{ + Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, + }, + }, &tracing.Tracing{}) + require.NoError(t, errdefault) + + authMiddlewareCustomRealm, errcustom := NewAuthenticator(&types.Auth{ + Basic: &types.Basic{ + Realm: "foobar", + Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, + }, + }, &tracing.Tracing{}) + require.NoError(t, errcustom) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "traefik") + }) + + n := negroni.New(authMiddlewareDefaultRealm) + n.UseHandler(handler) + ts := httptest.NewServer(n) + defer ts.Close() + + client := &http.Client{} + req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil) + res, err := client.Do(req) + require.NoError(t, err) + assert.Equal(t, "Basic realm=\"traefik\"", res.Header.Get("Www-Authenticate"), "they should be equal") + + n = negroni.New(authMiddlewareCustomRealm) + n.UseHandler(handler) + ts = httptest.NewServer(n) + defer ts.Close() + + client = &http.Client{} + req = testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil) + res, err = client.Do(req) + require.NoError(t, err) + assert.Equal(t, "Basic realm=\"foobar\"", res.Header.Get("Www-Authenticate"), "they should be equal") +} + func TestDigestAuthFail(t *testing.T) { _, err := NewAuthenticator(&types.Auth{ Digest: &types.Digest{ diff --git a/provider/docker/config_container_docker_test.go b/provider/docker/config_container_docker_test.go index ef755f1a3..8f3731a3c 100644 --- a/provider/docker/config_container_docker_test.go +++ b/provider/docker/config_container_docker_test.go @@ -72,6 +72,7 @@ func TestDockerBuildConfiguration(t *testing.T) { label.TraefikFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", label.TraefikFrontendAuthBasicUsersFile: ".htpasswd", label.TraefikFrontendAuthBasicRemoveHeader: "true", + label.TraefikFrontendAuthBasicRealm: "myRealm", }), ports(nat.PortMap{ "80/tcp": {}, @@ -87,6 +88,7 @@ func TestDockerBuildConfiguration(t *testing.T) { Auth: &types.Auth{ Basic: &types.Basic{ RemoveHeader: true, + Realm: "myRealm", Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, UsersFile: ".htpasswd", @@ -463,6 +465,7 @@ func TestDockerBuildConfiguration(t *testing.T) { label.TraefikFrontendPassTLSClientCertInfosSubjectSerialNumber: "true", label.TraefikFrontendAuthBasic: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + label.TraefikFrontendAuthBasicRealm: "myRealm", label.TraefikFrontendAuthBasicRemoveHeader: "true", label.TraefikFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", label.TraefikFrontendAuthBasicUsersFile: ".htpasswd", @@ -569,6 +572,7 @@ func TestDockerBuildConfiguration(t *testing.T) { Auth: &types.Auth{ HeaderField: "X-WebAuth-User", Basic: &types.Basic{ + Realm: "myRealm", RemoveHeader: true, Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, diff --git a/provider/docker/config_container_swarm_test.go b/provider/docker/config_container_swarm_test.go index 5e47ef7c8..830a354af 100644 --- a/provider/docker/config_container_swarm_test.go +++ b/provider/docker/config_container_swarm_test.go @@ -167,6 +167,7 @@ func TestSwarmBuildConfiguration(t *testing.T) { serviceLabels(map[string]string{ label.TraefikPort: "80", label.TraefikFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + label.TraefikFrontendAuthBasicRealm: "myRealm", label.TraefikFrontendAuthBasicUsersFile: ".htpasswd", label.TraefikFrontendAuthBasicRemoveHeader: "true", }), @@ -181,6 +182,7 @@ func TestSwarmBuildConfiguration(t *testing.T) { EntryPoints: []string{}, Auth: &types.Auth{ Basic: &types.Basic{ + Realm: "myRealm", RemoveHeader: true, Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, @@ -400,6 +402,7 @@ func TestSwarmBuildConfiguration(t *testing.T) { label.TraefikBackendBufferingRetryExpression: "IsNetworkError() && Attempts() <= 2", label.TraefikFrontendAuthBasicRemoveHeader: "true", + label.TraefikFrontendAuthBasicRealm: "myRealm", label.TraefikFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", label.TraefikFrontendAuthBasicUsersFile: ".htpasswd", label.TraefikFrontendAuthDigestRemoveHeader: "true", @@ -487,6 +490,7 @@ func TestSwarmBuildConfiguration(t *testing.T) { Auth: &types.Auth{ HeaderField: "X-WebAuth-User", Basic: &types.Basic{ + Realm: "myRealm", RemoveHeader: true, Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, diff --git a/provider/docker/config_segment_test.go b/provider/docker/config_segment_test.go index 81011b086..009e753c5 100644 --- a/provider/docker/config_segment_test.go +++ b/provider/docker/config_segment_test.go @@ -139,6 +139,7 @@ func TestSegmentBuildConfiguration(t *testing.T) { "traefik.sauternes.port": "2503", "traefik.sauternes.frontend.entryPoints": "http,https", label.Prefix + "sauternes." + label.SuffixFrontendAuthHeaderField: "X-WebAuth-User", + label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicRealm: "myRealm", label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsersFile: ".htpasswd", label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicRemoveHeader: "true", @@ -163,6 +164,7 @@ func TestSegmentBuildConfiguration(t *testing.T) { HeaderField: "X-WebAuth-User", Basic: &types.Basic{ RemoveHeader: true, + Realm: "myRealm", Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, UsersFile: ".htpasswd", @@ -363,6 +365,7 @@ func TestSegmentBuildConfiguration(t *testing.T) { label.Prefix + "sauternes." + label.SuffixFrontendPassTLSClientCertInfosSubjectSerialNumber: "true", label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicRemoveHeader: "true", + label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicRealm: "myRealm", label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsers: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", label.Prefix + "sauternes." + label.SuffixFrontendAuthBasicUsersFile: ".htpasswd", label.Prefix + "sauternes." + label.SuffixFrontendAuthDigestRemoveHeader: "true", @@ -464,6 +467,7 @@ func TestSegmentBuildConfiguration(t *testing.T) { HeaderField: "X-WebAuth-User", Basic: &types.Basic{ RemoveHeader: true, + Realm: "myRealm", Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, UsersFile: ".htpasswd", diff --git a/provider/label/names.go b/provider/label/names.go index bc659ddda..516720ea3 100644 --- a/provider/label/names.go +++ b/provider/label/names.go @@ -37,6 +37,7 @@ const ( SuffixFrontend = "frontend" SuffixFrontendAuth = SuffixFrontend + ".auth" SuffixFrontendAuthBasic = SuffixFrontendAuth + ".basic" + SuffixFrontendAuthBasicRealm = SuffixFrontendAuthBasic + ".realm" SuffixFrontendAuthBasicRemoveHeader = SuffixFrontendAuthBasic + ".removeHeader" SuffixFrontendAuthBasicUsers = SuffixFrontendAuthBasic + ".users" SuffixFrontendAuthBasicUsersFile = SuffixFrontendAuthBasic + ".usersFile" @@ -139,6 +140,7 @@ const ( TraefikFrontend = Prefix + SuffixFrontend TraefikFrontendAuth = Prefix + SuffixFrontendAuth TraefikFrontendAuthBasic = Prefix + SuffixFrontendAuthBasic + TraefikFrontendAuthBasicRealm = Prefix + SuffixFrontendAuthBasicRealm TraefikFrontendAuthBasicRemoveHeader = Prefix + SuffixFrontendAuthBasicRemoveHeader TraefikFrontendAuthBasicUsers = Prefix + SuffixFrontendAuthBasicUsers TraefikFrontendAuthBasicUsersFile = Prefix + SuffixFrontendAuthBasicUsersFile diff --git a/provider/label/partial.go b/provider/label/partial.go index c02c49146..472e4cda1 100644 --- a/provider/label/partial.go +++ b/provider/label/partial.go @@ -119,6 +119,7 @@ func GetAuth(labels map[string]string) *types.Auth { // getAuthBasic Create Basic Auth from labels func getAuthBasic(labels map[string]string) *types.Basic { basicAuth := &types.Basic{ + Realm: GetStringValue(labels, TraefikFrontendAuthBasicRealm, ""), UsersFile: GetStringValue(labels, TraefikFrontendAuthBasicUsersFile, ""), RemoveHeader: GetBoolValue(labels, TraefikFrontendAuthBasicRemoveHeader, false), } diff --git a/provider/label/partial_test.go b/provider/label/partial_test.go index abfd81715..abaf24d69 100644 --- a/provider/label/partial_test.go +++ b/provider/label/partial_test.go @@ -756,13 +756,14 @@ func TestGetAuth(t *testing.T) { desc: "should return a basic auth", labels: map[string]string{ TraefikFrontendAuthHeaderField: "myHeaderField", + TraefikFrontendAuthBasicRealm: "myRealm", TraefikFrontendAuthBasicUsers: "user:pwd,user2:pwd2", TraefikFrontendAuthBasicUsersFile: "myUsersFile", TraefikFrontendAuthBasicRemoveHeader: "true", }, expected: &types.Auth{ HeaderField: "myHeaderField", - Basic: &types.Basic{UsersFile: "myUsersFile", Users: []string{"user:pwd", "user2:pwd2"}, RemoveHeader: true}, + Basic: &types.Basic{UsersFile: "myUsersFile", Users: []string{"user:pwd", "user2:pwd2"}, RemoveHeader: true, Realm: "myRealm"}, }, }, { diff --git a/templates/docker.tmpl b/templates/docker.tmpl index 43890012e..e41a4c57d 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -120,6 +120,7 @@ {{if $auth.Basic }} [frontends."frontend-{{ $frontendName }}".auth.basic] + realm = "{{ $auth.Basic.Realm }}" removeHeader = {{ $auth.Basic.RemoveHeader }} {{if $auth.Basic.Users }} users = [{{range $auth.Basic.Users }} diff --git a/types/types.go b/types/types.go index 77cf04413..1819a28a4 100644 --- a/types/types.go +++ b/types/types.go @@ -401,6 +401,7 @@ type Users []string // Basic HTTP basic authentication type Basic struct { + Realm string `json:"realm,omitempty"` Users `json:"users,omitempty" mapstructure:","` UsersFile string `json:"usersFile,omitempty"` RemoveHeader bool `json:"removeHeader,omitempty"`