diff --git a/docs/toml.md b/docs/toml.md index f1941143c..ee0367254 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -844,6 +844,7 @@ Labels can be used on containers to override default behaviour: - `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend. - `traefik.frontend.priority=10`: override default frontend priority - `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. +- `traefik.frontend.auth.basic=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0`: Sets a Basic Auth for that frontend with the users test:test and test2:test2 - `traefik.docker.network`: Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with docker inspect ) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. If several ports need to be exposed from a container, the services labels can be used @@ -852,11 +853,11 @@ If several ports need to be exposed from a container, the services labels can be - `traefik..weight=10`: assign this service weight. Overrides `traefik.weight`. - `traefik..frontend.backend=fooBackend`: assign this service frontend to `foobackend`. Default is to assign to the service backend. - `traefik..frontend.entryPoints=http`: assign this service entrypoints. Overrides `traefik.frontend.entrypoints`. +- `traefik..frontend.auth.basic=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0` Sets a Basic Auth for that frontend with the users test:test and test2:test2. - `traefik..frontend.passHostHeader=true`: Forward client `Host` header to the backend. Overrides `traefik.frontend.passHostHeader`. - `traefik..frontend.priority=10`: assign the service frontend priority. Overrides `traefik.frontend.priority`. - `traefik..frontend.rule=Path:/foo`: assign the service frontend rule. Overrides `traefik.frontend.rule`. - NB: when running inside a container, Træfɪk will need network access through `docker network connect ` ## Marathon backend @@ -1601,6 +1602,7 @@ Labels can be used on task containers to override default behaviour: - `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend. - `traefik.frontend.priority=10`: override default frontend priority - `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. +- `traefik.frontend.auth.basic=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0`: Sets a Basic Auth for that frontend with the users test:test and test2:test2 ## DynamoDB backend diff --git a/provider/docker/docker.go b/provider/docker/docker.go index 62189c11f..eb84e6b0d 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -250,6 +250,7 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con "getPassHostHeader": p.getPassHostHeader, "getPriority": p.getPriority, "getEntryPoints": p.getEntryPoints, + "getBasicAuth": p.getBasicAuth, "getFrontendRule": p.getFrontendRule, "hasCircuitBreakerLabel": p.hasCircuitBreakerLabel, "getCircuitBreakerExpression": p.getCircuitBreakerExpression, @@ -266,6 +267,7 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con "getServiceWeight": p.getServiceWeight, "getServiceProtocol": p.getServiceProtocol, "getServiceEntryPoints": p.getServiceEntryPoints, + "getServiceBasicAuth": p.getServiceBasicAuth, "getServiceFrontendRule": p.getServiceFrontendRule, "getServicePassHostHeader": p.getServicePassHostHeader, "getServicePriority": p.getServicePriority, @@ -377,6 +379,15 @@ func (p *Provider) getServiceEntryPoints(container dockerData, serviceName strin } +// Extract basic auth from labels for a given service and a given docker container +func (p *Provider) getServiceBasicAuth(container dockerData, serviceName string) []string { + if basicAuth, ok := getContainerServiceLabel(container, serviceName, "frontend.auth.basic"); ok { + return strings.Split(basicAuth, ",") + } + return p.getBasicAuth(container) + +} + // Extract passHostHeader from labels for a given service and a given docker container func (p *Provider) getServicePassHostHeader(container dockerData, serviceName string) string { if servicePassHostHeader, ok := getContainerServiceLabel(container, serviceName, "frontend.passHostHeader"); ok { @@ -645,6 +656,14 @@ func (p *Provider) getEntryPoints(container dockerData) []string { return []string{} } +func (p *Provider) getBasicAuth(container dockerData) []string { + if basicAuth, err := getLabel(container, "traefik.frontend.auth.basic"); err == nil { + return strings.Split(basicAuth, ",") + } + + return []string{} +} + func isContainerEnabled(container dockerData, exposedByDefault bool) bool { return exposedByDefault && container.Labels["traefik.enable"] != "false" || container.Labels["traefik.enable"] == "true" } diff --git a/provider/docker/docker_test.go b/provider/docker/docker_test.go index 5c5960379..a9e4f94c4 100644 --- a/provider/docker/docker_test.go +++ b/provider/docker/docker_test.go @@ -662,6 +662,7 @@ func TestDockerLoadDockerConfig(t *testing.T) { Backend: "backend-test", PassHostHeader: true, EntryPoints: []string{}, + BasicAuth: []string{}, Routes: map[string]types.Route{ "route-frontend-Host-test-docker-localhost": { Rule: "Host:test.docker.localhost", @@ -688,6 +689,7 @@ func TestDockerLoadDockerConfig(t *testing.T) { labels(map[string]string{ "traefik.backend": "foobar", "traefik.frontend.entryPoints": "http,https", + "traefik.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", }), ports(nat.PortMap{ "80/tcp": {}, @@ -710,6 +712,7 @@ func TestDockerLoadDockerConfig(t *testing.T) { Backend: "backend-foobar", PassHostHeader: true, EntryPoints: []string{"http", "https"}, + BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, Routes: map[string]types.Route{ "route-frontend-Host-test1-docker-localhost": { Rule: "Host:test1.docker.localhost", @@ -720,6 +723,7 @@ func TestDockerLoadDockerConfig(t *testing.T) { Backend: "backend-foobar", PassHostHeader: true, EntryPoints: []string{}, + BasicAuth: []string{}, Routes: map[string]types.Route{ "route-frontend-Host-test2-docker-localhost": { Rule: "Host:test2.docker.localhost", @@ -766,6 +770,7 @@ func TestDockerLoadDockerConfig(t *testing.T) { Backend: "backend-foobar", PassHostHeader: true, EntryPoints: []string{"http", "https"}, + BasicAuth: []string{}, Routes: map[string]types.Route{ "route-frontend-Host-test1-docker-localhost": { Rule: "Host:test1.docker.localhost", diff --git a/provider/docker/service_test.go b/provider/docker/service_test.go index 2bbefb2d3..46749c553 100644 --- a/provider/docker/service_test.go +++ b/provider/docker/service_test.go @@ -332,6 +332,7 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { labels(map[string]string{ "traefik.service.port": "2503", "traefik.service.frontend.entryPoints": "http,https", + "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", }), ports(nat.PortMap{ "80/tcp": {}, @@ -344,6 +345,7 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { Backend: "backend-foo-service", PassHostHeader: true, EntryPoints: []string{"http", "https"}, + BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, Routes: map[string]types.Route{ "service-service": { Rule: "Host:foo.docker.localhost", @@ -376,6 +378,7 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { "traefik.service.frontend.rule": "Path:/mypath", "traefik.service.frontend.priority": "5000", "traefik.service.frontend.entryPoints": "http,https,ws", + "traefik.service.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", }), ports(nat.PortMap{ "80/tcp": {}, @@ -401,6 +404,7 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { PassHostHeader: false, Priority: 5000, EntryPoints: []string{"http", "https", "ws"}, + BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, Routes: map[string]types.Route{ "service-service": { Rule: "Path:/mypath", @@ -411,6 +415,7 @@ func TestDockerLoadDockerServiceConfig(t *testing.T) { Backend: "backend-test2-anotherservice", PassHostHeader: true, EntryPoints: []string{}, + BasicAuth: []string{}, Routes: map[string]types.Route{ "service-anotherservice": { Rule: "Path:/anotherpath", diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go index c5763f45f..feffcf002 100644 --- a/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -641,6 +641,7 @@ func TestSwarmLoadDockerConfig(t *testing.T) { Backend: "backend-test", PassHostHeader: true, EntryPoints: []string{}, + BasicAuth: []string{}, Routes: map[string]types.Route{ "route-frontend-Host-test-docker-localhost": { Rule: "Host:test.docker.localhost", @@ -674,6 +675,7 @@ func TestSwarmLoadDockerConfig(t *testing.T) { "traefik.port": "80", "traefik.backend": "foobar", "traefik.frontend.entryPoints": "http,https", + "traefik.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", }), withEndpointSpec(modeVIP), withEndpoint(virtualIP("1", "127.0.0.1/24")), @@ -693,6 +695,7 @@ func TestSwarmLoadDockerConfig(t *testing.T) { Backend: "backend-foobar", PassHostHeader: true, EntryPoints: []string{"http", "https"}, + BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, Routes: map[string]types.Route{ "route-frontend-Host-test1-docker-localhost": { Rule: "Host:test1.docker.localhost", @@ -703,6 +706,7 @@ func TestSwarmLoadDockerConfig(t *testing.T) { Backend: "backend-foobar", PassHostHeader: true, EntryPoints: []string{}, + BasicAuth: []string{}, Routes: map[string]types.Route{ "route-frontend-Host-test2-docker-localhost": { Rule: "Host:test2.docker.localhost", diff --git a/provider/rancher/rancher.go b/provider/rancher/rancher.go index 58be5f9b9..356a5b66a 100644 --- a/provider/rancher/rancher.go +++ b/provider/rancher/rancher.go @@ -88,6 +88,13 @@ func (p *Provider) getFrontendRule(service rancherData) string { return "Host:" + strings.ToLower(strings.Replace(service.Name, "/", ".", -1)) + "." + p.Domain } +func (p *Provider) getBasicAuth(service rancherData) []string { + if basicAuth, err := getServiceLabel(service, "traefik.frontend.auth.basic"); err == nil { + return strings.Split(basicAuth, ",") + } + return []string{} +} + func (p *Provider) getFrontendName(service rancherData) string { // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 return provider.Normalize(p.getFrontendRule(service)) @@ -411,6 +418,7 @@ func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuratio "getPassHostHeader": p.getPassHostHeader, "getPriority": p.getPriority, "getEntryPoints": p.getEntryPoints, + "getBasicAuth": p.getBasicAuth, "getFrontendRule": p.getFrontendRule, "hasCircuitBreakerLabel": p.hasCircuitBreakerLabel, "getCircuitBreakerExpression": p.getCircuitBreakerExpression, diff --git a/provider/rancher/rancher_test.go b/provider/rancher/rancher_test.go index df8511053..93bc3409d 100644 --- a/provider/rancher/rancher_test.go +++ b/provider/rancher/rancher_test.go @@ -396,7 +396,8 @@ func TestRancherLoadRancherConfig(t *testing.T) { { Name: "test/service", Labels: map[string]string{ - "traefik.port": "80", + "traefik.port": "80", + "traefik.frontend.auth.basic": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", }, Health: "healthy", Containers: []string{"127.0.0.1"}, @@ -407,6 +408,7 @@ func TestRancherLoadRancherConfig(t *testing.T) { Backend: "backend-test-service", PassHostHeader: true, EntryPoints: []string{}, + BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, Priority: 0, Routes: map[string]types.Route{ diff --git a/server.go b/server.go index 058066d01..3b855a9cc 100644 --- a/server.go +++ b/server.go @@ -580,6 +580,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo log.Errorf("Skipping frontend %s...", frontendName) continue frontend } + for _, entryPointName := range frontend.EntryPoints { log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName) if _, ok := serverEntryPoints[entryPointName]; !ok { @@ -587,6 +588,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo log.Errorf("Skipping frontend %s...", frontendName) continue frontend } + newServerRoute := &serverRoute{route: serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)} for routeName, route := range frontend.Routes { err := getRoute(newServerRoute, &route) @@ -597,6 +599,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo } log.Debugf("Creating route %s %s", routeName, route.Rule) } + entryPoint := globalConfiguration.EntryPoints[entryPointName] if entryPoint.Redirect != nil { if redirectHandlers[entryPointName] != nil { @@ -737,6 +740,24 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo negroni.Use(metricsMiddlewareBackend) } } + + if len(frontend.BasicAuth) > 0 { + users := types.Users{} + for _, user := range frontend.BasicAuth { + users = append(users, user) + } + + auth := &types.Auth{} + auth.Basic = &types.Basic{ + Users: users, + } + authMiddleware, err := middlewares.NewAuthenticator(auth) + if err != nil { + log.Fatal("Error creating Auth: ", err) + } + negroni.Use(authMiddleware) + } + if configuration.Backends[frontend.Backend].CircuitBreaker != nil { log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression) cbreaker, err := middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger)) diff --git a/templates/docker.tmpl b/templates/docker.tmpl index f118b26db..ec782df8e 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -46,6 +46,9 @@ priority = {{getServicePriority $container $serviceName}} entryPoints = [{{range getServiceEntryPoints $container $serviceName}} "{{.}}", + {{end}}] + basicAuth = [{{range getServiceBasicAuth $container $serviceName}} + "{{.}}", {{end}}] [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"] rule = "{{getServiceFrontendRule $container $serviceName}}" @@ -57,6 +60,9 @@ priority = {{getPriority $container}} entryPoints = [{{range getEntryPoints $container}} "{{.}}", + {{end}}] + basicAuth = [{{range getBasicAuth $container}} + "{{.}}", {{end}}] [frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"] rule = "{{getFrontendRule $container}}" diff --git a/templates/rancher.tmpl b/templates/rancher.tmpl index 15fb4571f..f98bc3289 100644 --- a/templates/rancher.tmpl +++ b/templates/rancher.tmpl @@ -33,6 +33,9 @@ entryPoints = [{{range getEntryPoints $service}} "{{.}}", {{end}}] + basicAuth = [{{range getBasicAuth $service}} + "{{.}}", + {{end}}] [frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"] rule = "{{getFrontendRule $service}}" {{end}} diff --git a/types/types.go b/types/types.go index d62d77d92..05e704adc 100644 --- a/types/types.go +++ b/types/types.go @@ -61,6 +61,7 @@ type Frontend struct { Routes map[string]Route `json:"routes,omitempty"` PassHostHeader bool `json:"passHostHeader,omitempty"` Priority int `json:"priority"` + BasicAuth []string `json:"basicAuth"` } // LoadBalancerMethod holds the method of load balancing to use.