Allow usersFile to be specified for basic or digest auth

This commit is contained in:
Kent Rancourt 2017-02-23 21:46:50 -05:00
parent 91bf627275
commit 7357417f48
5 changed files with 112 additions and 9 deletions

View file

@ -191,20 +191,24 @@ Supported filters:
# To enable basic auth on an entrypoint
# with 2 user/pass: test:test and test2:test2
# Passwords can be encoded in MD5, SHA1 and BCrypt: you can use htpasswd to generate those ones
# Users can be specified directly in the toml file, or indirectly by referencing an external file; if both are provided, the two are merged, with external file contents having precedence
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.auth.basic]
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
# usersFile = "/path/to/.htpasswd"
#
# To enable digest auth on an entrypoint
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
# You can use htdigest to generate those ones
# Users can be specified directly in the toml file, or indirectly by referencing an external file; if both are provided, the two are merged, with external file contents having precedence
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.auth.basic]
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
# usersFile = "/path/to/.htdigest"
#
# To specify an https entrypoint with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls):
# [entryPoints]
@ -551,14 +555,17 @@ address = ":8080"
# To enable basic auth on the webui
# with 2 user/pass: test:test and test2:test2
# Passwords can be encoded in MD5, SHA1 and BCrypt: you can use htpasswd to generate those ones
# Users can be specified directly in the toml file, or indirectly by referencing an external file; if both are provided, the two are merged, with external file contents having precedence
# [web.auth.basic]
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
# usersFile = "/path/to/.htpasswd"
# To enable digest auth on the webui
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
# You can use htdigest to generate those ones
# Users can be specified directly in the toml file, or indirectly by referencing an external file; if both are provided, the two are merged, with external file contents having precedence
# [web.auth.digest]
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
# usersFile = "/path/to/.htdigest"
```
- `/`: provides a simple HTML frontend of Træfik

View file

@ -2,6 +2,7 @@ package middlewares
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
@ -25,7 +26,7 @@ func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) {
var err error
authenticator := Authenticator{}
if authConfig.Basic != nil {
authenticator.users, err = parserBasicUsers(authConfig.Basic.Users)
authenticator.users, err = parserBasicUsers(authConfig.Basic)
if err != nil {
return nil, err
}
@ -43,7 +44,7 @@ func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) {
}
})
} else if authConfig.Digest != nil {
authenticator.users, err = parserDigestUsers(authConfig.Digest.Users)
authenticator.users, err = parserDigestUsers(authConfig.Digest)
if err != nil {
return nil, err
}
@ -64,9 +65,17 @@ func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) {
return &authenticator, nil
}
func parserBasicUsers(users types.Users) (map[string]string, error) {
func parserBasicUsers(basic *types.Basic) (map[string]string, error) {
var userStrs []string
if basic.UsersFile != "" {
var err error
if userStrs, err = getLinesFromFile(basic.UsersFile); err != nil {
return nil, err
}
}
userStrs = append(basic.Users, userStrs...)
userMap := make(map[string]string)
for _, user := range users {
for _, user := range userStrs {
split := strings.Split(user, ":")
if len(split) != 2 {
return nil, fmt.Errorf("Error parsing Authenticator user: %v", user)
@ -76,9 +85,17 @@ func parserBasicUsers(users types.Users) (map[string]string, error) {
return userMap, nil
}
func parserDigestUsers(users types.Users) (map[string]string, error) {
func parserDigestUsers(digest *types.Digest) (map[string]string, error) {
var userStrs []string
if digest.UsersFile != "" {
var err error
if userStrs, err = getLinesFromFile(digest.UsersFile); err != nil {
return nil, err
}
}
userStrs = append(digest.Users, userStrs...)
userMap := make(map[string]string)
for _, user := range users {
for _, user := range userStrs {
split := strings.Split(user, ":")
if len(split) != 3 {
return nil, fmt.Errorf("Error parsing Authenticator user: %v", user)
@ -88,6 +105,23 @@ func parserDigestUsers(users types.Users) (map[string]string, error) {
return userMap, nil
}
func getLinesFromFile(filename string) ([]string, error) {
dat, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
// Trim lines and filter out blanks
rawLines := strings.Split(string(dat), "\n")
var filteredLines []string
for _, rawLine := range rawLines {
line := strings.TrimSpace(rawLine)
if line != "" {
filteredLines = append(filteredLines, line)
}
}
return filteredLines, nil
}
func (a *Authenticator) secretBasic(user, realm string) string {
if secret, ok := a.users[user]; ok {
return secret

View file

@ -5,6 +5,7 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/codegangsta/negroni"
@ -12,6 +13,57 @@ import (
"github.com/stretchr/testify/assert"
)
func TestAuthUsersFromFile(t *testing.T) {
tests := []struct {
authType string
usersStr string
userKeys []string
parserFunc func(fileName string) (map[string]string, error)
}{
{
authType: "basic",
usersStr: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/\ntest2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0\n",
userKeys: []string{"test", "test2"},
parserFunc: func(fileName string) (map[string]string, error) {
basic := &types.Basic{
UsersFile: fileName,
}
return parserBasicUsers(basic)
},
},
{
authType: "digest",
usersStr: "test:traefik:a2688e031edb4be6a3797f3882655c05 \ntest2:traefik:518845800f9e2bfb1f1f740ec24f074e\n",
userKeys: []string{"test:traefik", "test2:traefik"},
parserFunc: func(fileName string) (map[string]string, error) {
digest := &types.Digest{
UsersFile: fileName,
}
return parserDigestUsers(digest)
},
},
}
for _, test := range tests {
test := test
t.Run(test.authType, func(t *testing.T) {
t.Parallel()
usersFile, err := ioutil.TempFile("", "auth-users")
assert.NoError(t, err, "there should be no error")
defer os.Remove(usersFile.Name())
_, err = usersFile.Write([]byte(test.usersStr))
assert.NoError(t, err, "there should be no error")
users, err := test.parserFunc(usersFile.Name())
assert.NoError(t, err, "there should be no error")
assert.Equal(t, 2, len(users), "they should be equal")
_, ok := users[test.userKeys[0]]
assert.True(t, ok, "user test should be found")
_, ok = users[test.userKeys[1]]
assert.True(t, ok, "user test2 should be found")
})
}
}
func TestBasicAuthFail(t *testing.T) {
authMiddleware, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{

View file

@ -247,20 +247,24 @@
# To enable basic auth on an entrypoint
# with 2 user/pass: test:test and test2:test2
# Passwords can be encoded in MD5, SHA1 and BCrypt: you can use htpasswd to generate those ones
# Users can be specified directly in the toml file, or indirectly by referencing an external file; if both are provided, the two are merged, with external file contents having precedence
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.auth.basic]
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
# usersFile = "/path/to/.htpasswd"
#
# To enable digest auth on an entrypoint
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
# You can use htdigest to generate those ones
# Users can be specified directly in the toml file, or indirectly by referencing an external file; if both are provided, the two are merged, with external file contents having precedence
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.auth.basic]
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
# usersFile = "/path/to/.htdigest"
#
# To specify an https entrypoint with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls):
# [entryPoints]
@ -340,13 +344,17 @@
# To enable basic auth on the webui
# with 2 user/pass: test:test and test2:test2
# Passwords can be encoded in MD5, SHA1 and BCrypt: you can use htpasswd to generate those ones
# Users can be specified directly in the toml file, or indirectly by referencing an external file; if both are provided, the two are merged, with external file contents having precedence
# [web.auth.basic]
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
# usersFile = "/path/to/.htpasswd"
# To enable digest auth on the webui
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
# You can use htdigest to generate those ones
# Users can be specified directly in the toml file, or indirectly by referencing an external file; if both are provided, the two are merged, with external file contents having precedence
# [web.auth.digest]
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
# usersFile = "/path/to/.htdigest"
################################################################

View file

@ -241,12 +241,14 @@ type Users []string
// Basic HTTP basic authentication
type Basic struct {
Users `mapstructure:","`
Users `mapstructure:","`
UsersFile string
}
// Digest HTTP authentication
type Digest struct {
Users `mapstructure:","`
Users `mapstructure:","`
UsersFile string
}
// CanonicalDomain returns a lower case domain with trim space