1
0
Fork 0

Dynamic Configuration Refactoring

This commit is contained in:
Ludovic Fernandez 2018-11-14 10:18:03 +01:00 committed by Traefiker Bot
parent d3ae88f108
commit a09dfa3ce1
452 changed files with 21023 additions and 9419 deletions

View file

@ -0,0 +1,167 @@
package auth
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
goauth "github.com/abbot/go-http-auth"
"github.com/containous/traefik/old/log"
"github.com/containous/traefik/old/middlewares/accesslog"
"github.com/containous/traefik/old/middlewares/tracing"
"github.com/containous/traefik/old/types"
"github.com/urfave/negroni"
)
// Authenticator is a middleware that provides HTTP basic and digest authentication
type Authenticator struct {
handler negroni.Handler
users map[string]string
}
type tracingAuthenticator struct {
name string
handler negroni.Handler
clientSpanKind bool
}
const (
authorizationHeader = "Authorization"
)
// NewAuthenticator builds a new Authenticator given a config
func NewAuthenticator(authConfig *types.Auth, tracingMiddleware *tracing.Tracing) (*Authenticator, error) {
if authConfig == nil {
return nil, fmt.Errorf("error creating Authenticator: auth is nil")
}
var err error
authenticator := &Authenticator{}
tracingAuth := tracingAuthenticator{}
if authConfig.Basic != nil {
authenticator.users, err = parserBasicUsers(authConfig.Basic)
if err != nil {
return nil, err
}
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
} else if authConfig.Digest != nil {
authenticator.users, err = parserDigestUsers(authConfig.Digest)
if err != nil {
return nil, err
}
digestAuth := goauth.NewDigestAuthenticator("traefik", authenticator.secretDigest)
tracingAuth.handler = createAuthDigestHandler(digestAuth, authConfig)
tracingAuth.name = "Auth Digest"
tracingAuth.clientSpanKind = false
} else if authConfig.Forward != nil {
tracingAuth.handler = createAuthForwardHandler(authConfig)
tracingAuth.name = "Auth Forward"
tracingAuth.clientSpanKind = true
}
if tracingMiddleware != nil {
authenticator.handler = tracingMiddleware.NewNegroniHandlerWrapper(tracingAuth.name, tracingAuth.handler, tracingAuth.clientSpanKind)
} else {
authenticator.handler = tracingAuth.handler
}
return authenticator, nil
}
func createAuthForwardHandler(authConfig *types.Auth) negroni.HandlerFunc {
return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
Forward(authConfig.Forward, w, r, next)
})
}
func createAuthDigestHandler(digestAuth *goauth.DigestAuth, authConfig *types.Auth) negroni.HandlerFunc {
return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if username, _ := digestAuth.CheckAuth(r); username == "" {
log.Debugf("Digest auth failed")
digestAuth.RequireAuth(w, r)
} else {
log.Debugf("Digest auth succeeded")
// set username in request context
r = accesslog.WithUserName(r, username)
if authConfig.HeaderField != "" {
r.Header[authConfig.HeaderField] = []string{username}
}
if authConfig.Digest.RemoveHeader {
log.Debugf("Remove the Authorization header from the Digest auth")
r.Header.Del(authorizationHeader)
}
next.ServeHTTP(w, r)
}
})
}
func createAuthBasicHandler(basicAuth *goauth.BasicAuth, authConfig *types.Auth) negroni.HandlerFunc {
return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if username := basicAuth.CheckAuth(r); username == "" {
log.Debugf("Basic auth failed")
basicAuth.RequireAuth(w, r)
} else {
log.Debugf("Basic auth succeeded")
// set username in request context
r = accesslog.WithUserName(r, username)
if authConfig.HeaderField != "" {
r.Header[authConfig.HeaderField] = []string{username}
}
if authConfig.Basic.RemoveHeader {
log.Debugf("Remove the Authorization header from the Basic auth")
r.Header.Del(authorizationHeader)
}
next.ServeHTTP(w, r)
}
})
}
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
}
log.Debugf("User not found: %s", user)
return ""
}
func (a *Authenticator) secretDigest(user, realm string) string {
if secret, ok := a.users[user+":"+realm]; ok {
return secret
}
log.Debugf("User not found: %s:%s", user, realm)
return ""
}
func (a *Authenticator) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
a.handler.ServeHTTP(rw, r, next)
}

View file

@ -0,0 +1,297 @@
package auth
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/containous/traefik/old/middlewares/tracing"
"github.com/containous/traefik/old/types"
"github.com/containous/traefik/testhelpers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/negroni"
)
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")
require.NoError(t, err)
defer os.Remove(usersFile.Name())
_, err = usersFile.Write([]byte(test.usersStr))
require.NoError(t, err)
users, err := test.parserFunc(usersFile.Name())
require.NoError(t, err)
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) {
_, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{
Users: []string{"test"},
},
}, &tracing.Tracing{})
assert.Contains(t, err.Error(), "error parsing Authenticator user", "should contains")
authMiddleware, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{
Users: []string{"test:test"},
},
}, &tracing.Tracing{})
require.NoError(t, err)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(authMiddleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusUnauthorized, res.StatusCode, "they should be equal")
}
func TestBasicAuthSuccess(t *testing.T) {
authMiddleware, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"},
},
}, &tracing.Tracing{})
require.NoError(t, err)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(authMiddleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal")
body, err := ioutil.ReadAll(res.Body)
require.NoError(t, err)
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{
Users: []string{"test"},
},
}, &tracing.Tracing{})
assert.Contains(t, err.Error(), "error parsing Authenticator user", "should contains")
authMiddleware, err := NewAuthenticator(&types.Auth{
Digest: &types.Digest{
Users: []string{"test:traefik:test"},
},
}, &tracing.Tracing{})
require.NoError(t, err)
assert.NotNil(t, authMiddleware, "this should not be nil")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(authMiddleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusUnauthorized, res.StatusCode, "they should be equal")
}
func TestBasicAuthUserHeader(t *testing.T) {
middleware, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"},
},
HeaderField: "X-Webauth-User",
}, &tracing.Tracing{})
require.NoError(t, err)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "test", r.Header["X-Webauth-User"][0], "auth user should be set")
fmt.Fprintln(w, "traefik")
})
n := negroni.New(middleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal")
body, err := ioutil.ReadAll(res.Body)
require.NoError(t, err)
assert.Equal(t, "traefik\n", string(body), "they should be equal")
}
func TestBasicAuthHeaderRemoved(t *testing.T) {
middleware, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{
RemoveHeader: true,
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"},
},
}, &tracing.Tracing{})
require.NoError(t, err)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Empty(t, r.Header.Get(authorizationHeader))
fmt.Fprintln(w, "traefik")
})
n := negroni.New(middleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal")
body, err := ioutil.ReadAll(res.Body)
require.NoError(t, err)
assert.Equal(t, "traefik\n", string(body), "they should be equal")
}
func TestBasicAuthHeaderPresent(t *testing.T) {
middleware, err := NewAuthenticator(&types.Auth{
Basic: &types.Basic{
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"},
},
}, &tracing.Tracing{})
require.NoError(t, err)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.NotEmpty(t, r.Header.Get(authorizationHeader))
fmt.Fprintln(w, "traefik")
})
n := negroni.New(middleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{}
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
req.SetBasicAuth("test", "test")
res, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal")
body, err := ioutil.ReadAll(res.Body)
require.NoError(t, err)
assert.Equal(t, "traefik\n", string(body), "they should be equal")
}

View file

@ -0,0 +1,157 @@
package auth
import (
"io/ioutil"
"net"
"net/http"
"strings"
"github.com/containous/traefik/old/log"
"github.com/containous/traefik/old/middlewares/tracing"
"github.com/containous/traefik/old/types"
"github.com/vulcand/oxy/forward"
"github.com/vulcand/oxy/utils"
)
const (
xForwardedURI = "X-Forwarded-Uri"
xForwardedMethod = "X-Forwarded-Method"
)
// Forward the authentication to a external server
func Forward(config *types.Forward, w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
// Ensure our request client does not follow redirects
httpClient := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
if config.TLS != nil {
tlsConfig, err := config.TLS.CreateTLSConfig()
if err != nil {
tracing.SetErrorAndDebugLog(r, "Unable to configure TLS to call %s. Cause %s", config.Address, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
httpClient.Transport = &http.Transport{
TLSClientConfig: tlsConfig,
}
}
forwardReq, err := http.NewRequest(http.MethodGet, config.Address, http.NoBody)
tracing.LogRequest(tracing.GetSpan(r), forwardReq)
if err != nil {
tracing.SetErrorAndDebugLog(r, "Error calling %s. Cause %s", config.Address, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
writeHeader(r, forwardReq, config.TrustForwardHeader)
tracing.InjectRequestHeaders(forwardReq)
forwardResponse, forwardErr := httpClient.Do(forwardReq)
if forwardErr != nil {
tracing.SetErrorAndDebugLog(r, "Error calling %s. Cause: %s", config.Address, forwardErr)
w.WriteHeader(http.StatusInternalServerError)
return
}
body, readError := ioutil.ReadAll(forwardResponse.Body)
if readError != nil {
tracing.SetErrorAndDebugLog(r, "Error reading body %s. Cause: %s", config.Address, readError)
w.WriteHeader(http.StatusInternalServerError)
return
}
defer forwardResponse.Body.Close()
// Pass the forward response's body and selected headers if it
// didn't return a response within the range of [200, 300).
if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices {
log.Debugf("Remote error %s. StatusCode: %d", config.Address, forwardResponse.StatusCode)
utils.CopyHeaders(w.Header(), forwardResponse.Header)
utils.RemoveHeaders(w.Header(), forward.HopHeaders...)
// Grab the location header, if any.
redirectURL, err := forwardResponse.Location()
if err != nil {
if err != http.ErrNoLocation {
tracing.SetErrorAndDebugLog(r, "Error reading response location header %s. Cause: %s", config.Address, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
} else if redirectURL.String() != "" {
// Set the location in our response if one was sent back.
w.Header().Set("Location", redirectURL.String())
}
tracing.LogResponseCode(tracing.GetSpan(r), forwardResponse.StatusCode)
w.WriteHeader(forwardResponse.StatusCode)
if _, err = w.Write(body); err != nil {
log.Error(err)
}
return
}
for _, headerName := range config.AuthResponseHeaders {
r.Header.Set(headerName, forwardResponse.Header.Get(headerName))
}
r.RequestURI = r.URL.RequestURI()
next(w, r)
}
func writeHeader(req *http.Request, forwardReq *http.Request, trustForwardHeader bool) {
utils.CopyHeaders(forwardReq.Header, req.Header)
utils.RemoveHeaders(forwardReq.Header, forward.HopHeaders...)
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
if trustForwardHeader {
if prior, ok := req.Header[forward.XForwardedFor]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
}
forwardReq.Header.Set(forward.XForwardedFor, clientIP)
}
if xMethod := req.Header.Get(xForwardedMethod); xMethod != "" && trustForwardHeader {
forwardReq.Header.Set(xForwardedMethod, xMethod)
} else if req.Method != "" {
forwardReq.Header.Set(xForwardedMethod, req.Method)
} else {
forwardReq.Header.Del(xForwardedMethod)
}
if xfp := req.Header.Get(forward.XForwardedProto); xfp != "" && trustForwardHeader {
forwardReq.Header.Set(forward.XForwardedProto, xfp)
} else if req.TLS != nil {
forwardReq.Header.Set(forward.XForwardedProto, "https")
} else {
forwardReq.Header.Set(forward.XForwardedProto, "http")
}
if xfp := req.Header.Get(forward.XForwardedPort); xfp != "" && trustForwardHeader {
forwardReq.Header.Set(forward.XForwardedPort, xfp)
}
if xfh := req.Header.Get(forward.XForwardedHost); xfh != "" && trustForwardHeader {
forwardReq.Header.Set(forward.XForwardedHost, xfh)
} else if req.Host != "" {
forwardReq.Header.Set(forward.XForwardedHost, req.Host)
} else {
forwardReq.Header.Del(forward.XForwardedHost)
}
if xfURI := req.Header.Get(xForwardedURI); xfURI != "" && trustForwardHeader {
forwardReq.Header.Set(xForwardedURI, xfURI)
} else if req.URL.RequestURI() != "" {
forwardReq.Header.Set(xForwardedURI, req.URL.RequestURI())
} else {
forwardReq.Header.Del(xForwardedURI)
}
}

View file

@ -0,0 +1,392 @@
package auth
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/containous/traefik/old/middlewares/tracing"
"github.com/containous/traefik/old/types"
"github.com/containous/traefik/testhelpers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/negroni"
"github.com/vulcand/oxy/forward"
)
func TestForwardAuthFail(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Forbidden", http.StatusForbidden)
}))
defer server.Close()
middleware, err := NewAuthenticator(&types.Auth{
Forward: &types.Forward{
Address: server.URL,
},
}, &tracing.Tracing{})
assert.NoError(t, err, "there should be no error")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(middleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
res, err := http.DefaultClient.Do(req)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusForbidden, res.StatusCode, "they should be equal")
body, err := ioutil.ReadAll(res.Body)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, "Forbidden\n", string(body), "they should be equal")
}
func TestForwardAuthSuccess(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Auth-User", "user@example.com")
w.Header().Set("X-Auth-Secret", "secret")
fmt.Fprintln(w, "Success")
}))
defer server.Close()
middleware, err := NewAuthenticator(&types.Auth{
Forward: &types.Forward{
Address: server.URL,
AuthResponseHeaders: []string{"X-Auth-User"},
},
}, &tracing.Tracing{})
assert.NoError(t, err, "there should be no error")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "user@example.com", r.Header.Get("X-Auth-User"))
assert.Empty(t, r.Header.Get("X-Auth-Secret"))
fmt.Fprintln(w, "traefik")
})
n := negroni.New(middleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
res, err := http.DefaultClient.Do(req)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal")
body, err := ioutil.ReadAll(res.Body)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, "traefik\n", string(body), "they should be equal")
}
func TestForwardAuthRedirect(t *testing.T) {
authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "http://example.com/redirect-test", http.StatusFound)
}))
defer authTs.Close()
authMiddleware, err := NewAuthenticator(&types.Auth{
Forward: &types.Forward{
Address: authTs.URL,
},
}, &tracing.Tracing{})
assert.NoError(t, err, "there should be no error")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(authMiddleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
res, err := client.Do(req)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusFound, res.StatusCode, "they should be equal")
location, err := res.Location()
assert.NoError(t, err, "there should be no error")
assert.Equal(t, "http://example.com/redirect-test", location.String(), "they should be equal")
body, err := ioutil.ReadAll(res.Body)
assert.NoError(t, err, "there should be no error")
assert.NotEmpty(t, string(body), "there should be something in the body")
}
func TestForwardAuthRemoveHopByHopHeaders(t *testing.T) {
authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
headers := w.Header()
for _, header := range forward.HopHeaders {
if header == forward.TransferEncoding {
headers.Add(header, "identity")
} else {
headers.Add(header, "test")
}
}
http.Redirect(w, r, "http://example.com/redirect-test", http.StatusFound)
}))
defer authTs.Close()
authMiddleware, err := NewAuthenticator(&types.Auth{
Forward: &types.Forward{
Address: authTs.URL,
},
}, &tracing.Tracing{})
assert.NoError(t, err, "there should be no error")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(authMiddleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
client := &http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
res, err := client.Do(req)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusFound, res.StatusCode, "they should be equal")
for _, header := range forward.HopHeaders {
assert.Equal(t, "", res.Header.Get(header), "hop-by-hop header '%s' mustn't be set", header)
}
location, err := res.Location()
assert.NoError(t, err, "there should be no error")
assert.Equal(t, "http://example.com/redirect-test", location.String(), "they should be equal")
body, err := ioutil.ReadAll(res.Body)
assert.NoError(t, err, "there should be no error")
assert.NotEmpty(t, string(body), "there should be something in the body")
}
func TestForwardAuthFailResponseHeaders(t *testing.T) {
authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie := &http.Cookie{Name: "example", Value: "testing", Path: "/"}
http.SetCookie(w, cookie)
w.Header().Add("X-Foo", "bar")
http.Error(w, "Forbidden", http.StatusForbidden)
}))
defer authTs.Close()
authMiddleware, err := NewAuthenticator(&types.Auth{
Forward: &types.Forward{
Address: authTs.URL,
},
}, &tracing.Tracing{})
assert.NoError(t, err, "there should be no error")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "traefik")
})
n := negroni.New(authMiddleware)
n.UseHandler(handler)
ts := httptest.NewServer(n)
defer ts.Close()
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
client := &http.Client{}
res, err := client.Do(req)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, http.StatusForbidden, res.StatusCode, "they should be equal")
require.Len(t, res.Cookies(), 1)
for _, cookie := range res.Cookies() {
assert.Equal(t, "testing", cookie.Value, "they should be equal")
}
expectedHeaders := http.Header{
"Content-Length": []string{"10"},
"Content-Type": []string{"text/plain; charset=utf-8"},
"X-Foo": []string{"bar"},
"Set-Cookie": []string{"example=testing; Path=/"},
"X-Content-Type-Options": []string{"nosniff"},
}
assert.Len(t, res.Header, 6)
for key, value := range expectedHeaders {
assert.Equal(t, value, res.Header[key])
}
body, err := ioutil.ReadAll(res.Body)
assert.NoError(t, err, "there should be no error")
assert.Equal(t, "Forbidden\n", string(body), "they should be equal")
}
func Test_writeHeader(t *testing.T) {
testCases := []struct {
name string
headers map[string]string
trustForwardHeader bool
emptyHost bool
expectedHeaders map[string]string
checkForUnexpectedHeaders bool
}{
{
name: "trust Forward Header",
headers: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "fii.bir",
},
trustForwardHeader: true,
expectedHeaders: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "fii.bir",
},
},
{
name: "not trust Forward Header",
headers: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "fii.bir",
},
trustForwardHeader: false,
expectedHeaders: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "foo.bar",
},
},
{
name: "trust Forward Header with empty Host",
headers: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "fii.bir",
},
trustForwardHeader: true,
emptyHost: true,
expectedHeaders: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "fii.bir",
},
},
{
name: "not trust Forward Header with empty Host",
headers: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "fii.bir",
},
trustForwardHeader: false,
emptyHost: true,
expectedHeaders: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "",
},
},
{
name: "trust Forward Header with forwarded URI",
headers: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "fii.bir",
"X-Forwarded-Uri": "/forward?q=1",
},
trustForwardHeader: true,
expectedHeaders: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "fii.bir",
"X-Forwarded-Uri": "/forward?q=1",
},
},
{
name: "not trust Forward Header with forward requested URI",
headers: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "fii.bir",
"X-Forwarded-Uri": "/forward?q=1",
},
trustForwardHeader: false,
expectedHeaders: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "foo.bar",
"X-Forwarded-Uri": "/path?q=1",
},
}, {
name: "trust Forward Header with forwarded request Method",
headers: map[string]string{
"X-Forwarded-Method": "OPTIONS",
},
trustForwardHeader: true,
expectedHeaders: map[string]string{
"X-Forwarded-Method": "OPTIONS",
},
},
{
name: "not trust Forward Header with forward request Method",
headers: map[string]string{
"X-Forwarded-Method": "OPTIONS",
},
trustForwardHeader: false,
expectedHeaders: map[string]string{
"X-Forwarded-Method": "GET",
},
},
{
name: "remove hop-by-hop headers",
headers: map[string]string{
forward.Connection: "Connection",
forward.KeepAlive: "KeepAlive",
forward.ProxyAuthenticate: "ProxyAuthenticate",
forward.ProxyAuthorization: "ProxyAuthorization",
forward.Te: "Te",
forward.Trailers: "Trailers",
forward.TransferEncoding: "TransferEncoding",
forward.Upgrade: "Upgrade",
"X-CustomHeader": "CustomHeader",
},
trustForwardHeader: false,
expectedHeaders: map[string]string{
"X-CustomHeader": "CustomHeader",
"X-Forwarded-Proto": "http",
"X-Forwarded-Host": "foo.bar",
"X-Forwarded-Uri": "/path?q=1",
"X-Forwarded-Method": "GET",
},
checkForUnexpectedHeaders: true,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/path?q=1", nil)
for key, value := range test.headers {
req.Header.Set(key, value)
}
if test.emptyHost {
req.Host = ""
}
forwardReq := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/path?q=1", nil)
writeHeader(req, forwardReq, test.trustForwardHeader)
actualHeaders := forwardReq.Header
expectedHeaders := test.expectedHeaders
for key, value := range expectedHeaders {
assert.Equal(t, value, actualHeaders.Get(key))
actualHeaders.Del(key)
}
if test.checkForUnexpectedHeaders {
for key := range actualHeaders {
assert.Fail(t, "Unexpected header found", key)
}
}
})
}
}

View file

@ -0,0 +1,48 @@
package auth
import (
"fmt"
"strings"
"github.com/containous/traefik/old/types"
)
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 userStrs {
split := strings.Split(user, ":")
if len(split) != 2 {
return nil, fmt.Errorf("error parsing Authenticator user: %v", user)
}
userMap[split[0]] = split[1]
}
return userMap, nil
}
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 userStrs {
split := strings.Split(user, ":")
if len(split) != 3 {
return nil, fmt.Errorf("error parsing Authenticator user: %v", user)
}
userMap[split[0]+":"+split[1]] = split[2]
}
return userMap, nil
}