Add Basic auth for consul catalog

This commit is contained in:
Michael 2017-09-07 15:28:02 +02:00 committed by Traefiker
parent 8c5514612f
commit b705e64a8a
5 changed files with 90 additions and 0 deletions

View file

@ -114,3 +114,4 @@ Additional settings can be defined using Consul Catalog tags:
| `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=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |

View file

@ -202,3 +202,42 @@ func (s *ConsulCatalogSuite) TestExposedByDefaultTrueSimpleServiceMultipleNode(c
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)
}
func (s *ConsulCatalogSuite) TestBasicAuthSimpleService(c *check.C) {
cmd, output := s.cmdTraefik(
withConfigFile("fixtures/consul_catalog/simple.toml"),
"--consulCatalog",
"--consulCatalog.exposedByDefault=true",
"--consulCatalog.endpoint="+s.consulIP+":8500",
"--consulCatalog.domain=consul.localhost")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
defer func() {
s.displayTraefikLog(c, output)
}()
nginx := s.composeProject.Container(c, "nginx")
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{
"traefik.frontend.auth.basic=test:$2a$06$O5NksJPAcgrC9MuANkSoE.Xe9DSg7KcLLFYNr1Lj6hPcMmvgwxhme,test2:$2y$10$xP1SZ70QbZ4K2bTGKJOhpujkpcLxQcB3kEPF6XAV19IdcqsZTyDEe",
})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.consul.localhost"
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusUnauthorized), try.HasBody())
c.Assert(err, checker.IsNil)
req.SetBasicAuth("test", "test")
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)
req.SetBasicAuth("test2", "test2")
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)
}

View file

@ -330,6 +330,14 @@ func (p *CatalogProvider) getAttribute(name string, tags []string, defaultValue
return p.getTag(p.getPrefixedName(name), tags, defaultValue)
}
func (p *CatalogProvider) getBasicAuth(tags []string) []string {
list := p.getAttribute("frontend.auth.basic", tags, "")
if list != "" {
return strings.Split(list, ",")
}
return []string{}
}
func (p *CatalogProvider) hasTag(name string, tags []string) bool {
// Very-very unlikely that a Consul tag would ever start with '=!='
tag := p.getTag(name, tags, "=!=")
@ -377,6 +385,7 @@ func (p *CatalogProvider) buildConfig(catalog []catalogUpdate) *types.Configurat
"getBackendName": p.getBackendName,
"getBackendAddress": p.getBackendAddress,
"getAttribute": p.getAttribute,
"getBasicAuth": p.getBasicAuth,
"getTag": p.getTag,
"hasTag": p.hasTag,
"getEntryPoints": p.getEntryPoints,

View file

@ -348,6 +348,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
"random.foo=bar",
"traefik.backend.maxconn.amount=1000",
"traefik.backend.maxconn.extractorfunc=client.ip",
"traefik.frontend.auth.basic=test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
},
},
Nodes: []*api.ServiceEntry{
@ -380,6 +381,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
Rule: "Host:test.localhost",
},
},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
},
},
expectedBackends: map[string]*types.Backend{
@ -411,6 +413,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
}
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
t.Fatalf("expected %#v, got %#v", c.expectedFrontends["frontend-test"].BasicAuth, actualConfig.Frontends["frontend-test"].BasicAuth)
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
}
}
@ -853,3 +856,38 @@ func TestConsulCatalogFilterEnabled(t *testing.T) {
})
}
}
func TestConsulCatalogGetBasicAuth(t *testing.T) {
cases := []struct {
desc string
tags []string
expected []string
}{
{
desc: "label missing",
tags: []string{},
expected: []string{},
},
{
desc: "label existing",
tags: []string{
"traefik.frontend.auth.basic=user:password",
},
expected: []string{"user:password"},
},
}
for _, c := range cases {
c := c
t.Run(c.desc, func(t *testing.T) {
t.Parallel()
provider := &CatalogProvider{
Prefix: "traefik",
}
actual := provider.getBasicAuth(c.tags)
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("actual %q, expected %q", actual, c.expected)
}
})
}
}

View file

@ -40,6 +40,9 @@
"{{.}}",
{{end}}]
{{end}}
basicAuth = [{{range getBasicAuth .Attributes}}
"{{.}}",
{{end}}]
[frontends."frontend-{{.ServiceName}}".routes."route-host-{{.ServiceName}}"]
rule = "{{getFrontendRule .}}"
{{end}}