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 # To enable basic auth on an entrypoint
# with 2 user/pass: test:test and test2:test2 # 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 # 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]
# [entryPoints.http] # [entryPoints.http]
# address = ":80" # address = ":80"
# [entryPoints.http.auth.basic] # [entryPoints.http.auth.basic]
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] # users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
# usersFile = "/path/to/.htpasswd"
# #
# To enable digest auth on an entrypoint # To enable digest auth on an entrypoint
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2 # with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
# You can use htdigest to generate those ones # 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]
# [entryPoints.http] # [entryPoints.http]
# address = ":80" # address = ":80"
# [entryPoints.http.auth.basic] # [entryPoints.http.auth.basic]
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"] # 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): # To specify an https entrypoint with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls):
# [entryPoints] # [entryPoints]
@ -551,14 +555,17 @@ address = ":8080"
# To enable basic auth on the webui # To enable basic auth on the webui
# with 2 user/pass: test:test and test2:test2 # 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 # 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] # [web.auth.basic]
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] # users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
# usersFile = "/path/to/.htpasswd"
# To enable digest auth on the webui # To enable digest auth on the webui
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2 # with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
# You can use htdigest to generate those ones # 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] # [web.auth.digest]
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"] # users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
# usersFile = "/path/to/.htdigest"
``` ```
- `/`: provides a simple HTML frontend of Træfik - `/`: provides a simple HTML frontend of Træfik

View file

@ -2,6 +2,7 @@ package middlewares
import ( import (
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
@ -25,7 +26,7 @@ func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) {
var err error var err error
authenticator := Authenticator{} authenticator := Authenticator{}
if authConfig.Basic != nil { if authConfig.Basic != nil {
authenticator.users, err = parserBasicUsers(authConfig.Basic.Users) authenticator.users, err = parserBasicUsers(authConfig.Basic)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -43,7 +44,7 @@ func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) {
} }
}) })
} else if authConfig.Digest != nil { } else if authConfig.Digest != nil {
authenticator.users, err = parserDigestUsers(authConfig.Digest.Users) authenticator.users, err = parserDigestUsers(authConfig.Digest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -64,9 +65,17 @@ func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) {
return &authenticator, nil 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) userMap := make(map[string]string)
for _, user := range users { for _, user := range userStrs {
split := strings.Split(user, ":") split := strings.Split(user, ":")
if len(split) != 2 { if len(split) != 2 {
return nil, fmt.Errorf("Error parsing Authenticator user: %v", user) 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 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) userMap := make(map[string]string)
for _, user := range users { for _, user := range userStrs {
split := strings.Split(user, ":") split := strings.Split(user, ":")
if len(split) != 3 { if len(split) != 3 {
return nil, fmt.Errorf("Error parsing Authenticator user: %v", user) 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 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 { func (a *Authenticator) secretBasic(user, realm string) string {
if secret, ok := a.users[user]; ok { if secret, ok := a.users[user]; ok {
return secret return secret

View file

@ -5,6 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"testing" "testing"
"github.com/codegangsta/negroni" "github.com/codegangsta/negroni"
@ -12,6 +13,57 @@ import (
"github.com/stretchr/testify/assert" "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) { func TestBasicAuthFail(t *testing.T) {
authMiddleware, err := NewAuthenticator(&types.Auth{ authMiddleware, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{ Basic: &types.Basic{

View file

@ -247,20 +247,24 @@
# To enable basic auth on an entrypoint # To enable basic auth on an entrypoint
# with 2 user/pass: test:test and test2:test2 # 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 # 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]
# [entryPoints.http] # [entryPoints.http]
# address = ":80" # address = ":80"
# [entryPoints.http.auth.basic] # [entryPoints.http.auth.basic]
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] # users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
# usersFile = "/path/to/.htpasswd"
# #
# To enable digest auth on an entrypoint # To enable digest auth on an entrypoint
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2 # with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
# You can use htdigest to generate those ones # 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]
# [entryPoints.http] # [entryPoints.http]
# address = ":80" # address = ":80"
# [entryPoints.http.auth.basic] # [entryPoints.http.auth.basic]
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"] # 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): # To specify an https entrypoint with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls):
# [entryPoints] # [entryPoints]
@ -340,13 +344,17 @@
# To enable basic auth on the webui # To enable basic auth on the webui
# with 2 user/pass: test:test and test2:test2 # 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 # 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] # [web.auth.basic]
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"] # users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
# usersFile = "/path/to/.htpasswd"
# To enable digest auth on the webui # To enable digest auth on the webui
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2 # with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
# You can use htdigest to generate those ones # 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] # [web.auth.digest]
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"] # users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
# usersFile = "/path/to/.htdigest"
################################################################ ################################################################

View file

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