Add whitelist configuration option for entrypoints

* Add whitelist configuration option for entrypoints
* Add whitelist support to --entrypoint flag
This commit is contained in:
Christophe Robin 2017-07-08 19:21:14 +09:00 committed by Ludovic Fernandez
parent a7ec785994
commit 759a19bc4f
5 changed files with 131 additions and 39 deletions

View file

@ -279,6 +279,12 @@ To write JSON format logs, specify `json` as the format:
# address = ":80" # address = ":80"
# compress = true # compress = true
# To enable IP whitelisting at the entrypoint level:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# whiteListSourceRange = ["127.0.0.1/32"]
[entryPoints] [entryPoints]
[entryPoints.http] [entryPoints.http]
address = ":80" address = ":80"

View file

@ -189,7 +189,7 @@ func (ep *EntryPoints) String() string {
// Set's argument is a string to be parsed to set the flag. // Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it. // It's a comma-separated list, so we split it.
func (ep *EntryPoints) Set(value string) error { func (ep *EntryPoints) Set(value string) error {
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*((?P<TLSACME>TLS))?\\s*(?:CA:(?P<CA>\\S*))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?\\s*(?:Compress:(?P<Compress>\\S*))?") regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*((?P<TLSACME>TLS))?\\s*(?:CA:(?P<CA>\\S*))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?\\s*(?:Compress:(?P<Compress>\\S*))?\\s*(?:WhiteListSourceRange:(?P<WhiteListSourceRange>\\S*))?")
match := regex.FindAllStringSubmatch(value, -1) match := regex.FindAllStringSubmatch(value, -1)
if match == nil { if match == nil {
return fmt.Errorf("bad EntryPoints format: %s", value) return fmt.Errorf("bad EntryPoints format: %s", value)
@ -233,11 +233,17 @@ func (ep *EntryPoints) Set(value string) error {
compress = strings.EqualFold(result["Compress"], "enable") || strings.EqualFold(result["Compress"], "on") compress = strings.EqualFold(result["Compress"], "enable") || strings.EqualFold(result["Compress"], "on")
} }
whiteListSourceRange := []string{}
if len(result["WhiteListSourceRange"]) > 0 {
whiteListSourceRange = strings.Split(result["WhiteListSourceRange"], ",")
}
(*ep)[result["Name"]] = &EntryPoint{ (*ep)[result["Name"]] = &EntryPoint{
Address: result["Address"], Address: result["Address"],
TLS: tls, TLS: tls,
Redirect: redirect, Redirect: redirect,
Compress: compress, Compress: compress,
WhitelistSourceRange: whiteListSourceRange,
} }
return nil return nil
@ -260,12 +266,13 @@ func (ep *EntryPoints) Type() string {
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...) // EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
type EntryPoint struct { type EntryPoint struct {
Network string Network string
Address string Address string
TLS *TLS TLS *TLS
Redirect *Redirect Redirect *Redirect
Auth *types.Auth Auth *types.Auth
Compress bool WhitelistSourceRange []string
Compress bool
} }
// Redirect configures a redirection of an entry point to another, or to an URL // Redirect configures a redirection of an entry point to another, or to an URL

View file

@ -188,38 +188,51 @@ func (server *Server) startHTTPServers() {
server.serverEntryPoints = server.buildEntryPoints(server.globalConfiguration) server.serverEntryPoints = server.buildEntryPoints(server.globalConfiguration)
for newServerEntryPointName, newServerEntryPoint := range server.serverEntryPoints { for newServerEntryPointName, newServerEntryPoint := range server.serverEntryPoints {
serverMiddlewares := []negroni.Handler{middlewares.NegroniRecoverHandler(), metrics} serverEntryPoint := server.setupServerEntryPoint(newServerEntryPointName, newServerEntryPoint)
if server.accessLoggerMiddleware != nil {
serverMiddlewares = append(serverMiddlewares, server.accessLoggerMiddleware)
}
metrics := newMetrics(server.globalConfiguration, newServerEntryPointName)
if metrics != nil {
serverMiddlewares = append(serverMiddlewares, middlewares.NewMetricsWrapper(metrics))
}
if server.globalConfiguration.Web != nil && server.globalConfiguration.Web.Statistics != nil {
statsRecorder = middlewares.NewStatsRecorder(server.globalConfiguration.Web.Statistics.RecentErrors)
serverMiddlewares = append(serverMiddlewares, statsRecorder)
}
if server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth != nil {
authMiddleware, err := middlewares.NewAuthenticator(server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth)
if err != nil {
log.Fatal("Error starting server: ", err)
}
serverMiddlewares = append(serverMiddlewares, authMiddleware)
}
if server.globalConfiguration.EntryPoints[newServerEntryPointName].Compress {
serverMiddlewares = append(serverMiddlewares, &middlewares.Compress{})
}
newsrv, err := server.prepareServer(newServerEntryPointName, newServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], serverMiddlewares...)
if err != nil {
log.Fatal("Error preparing server: ", err)
}
serverEntryPoint := server.serverEntryPoints[newServerEntryPointName]
serverEntryPoint.httpServer = newsrv
go server.startServer(serverEntryPoint.httpServer, server.globalConfiguration) go server.startServer(serverEntryPoint.httpServer, server.globalConfiguration)
} }
} }
func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newServerEntryPoint *serverEntryPoint) *serverEntryPoint {
serverMiddlewares := []negroni.Handler{middlewares.NegroniRecoverHandler(), metrics}
if server.accessLoggerMiddleware != nil {
serverMiddlewares = append(serverMiddlewares, server.accessLoggerMiddleware)
}
metrics := newMetrics(server.globalConfiguration, newServerEntryPointName)
if metrics != nil {
serverMiddlewares = append(serverMiddlewares, middlewares.NewMetricsWrapper(metrics))
}
if server.globalConfiguration.Web != nil && server.globalConfiguration.Web.Statistics != nil {
statsRecorder = middlewares.NewStatsRecorder(server.globalConfiguration.Web.Statistics.RecentErrors)
serverMiddlewares = append(serverMiddlewares, statsRecorder)
}
if server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth != nil {
authMiddleware, err := middlewares.NewAuthenticator(server.globalConfiguration.EntryPoints[newServerEntryPointName].Auth)
if err != nil {
log.Fatal("Error starting server: ", err)
}
serverMiddlewares = append(serverMiddlewares, authMiddleware)
}
if server.globalConfiguration.EntryPoints[newServerEntryPointName].Compress {
serverMiddlewares = append(serverMiddlewares, &middlewares.Compress{})
}
if len(server.globalConfiguration.EntryPoints[newServerEntryPointName].WhitelistSourceRange) > 0 {
ipWhitelistMiddleware, err := middlewares.NewIPWhitelister(server.globalConfiguration.EntryPoints[newServerEntryPointName].WhitelistSourceRange)
if err != nil {
log.Fatal("Error starting server: ", err)
}
serverMiddlewares = append(serverMiddlewares, ipWhitelistMiddleware)
}
newsrv, err := server.prepareServer(newServerEntryPointName, newServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], serverMiddlewares...)
if err != nil {
log.Fatal("Error preparing server: ", err)
}
serverEntryPoint := server.serverEntryPoints[newServerEntryPointName]
serverEntryPoint.httpServer = newsrv
return serverEntryPoint
}
func (server *Server) listenProviders(stop chan bool) { func (server *Server) listenProviders(stop chan bool) {
lastReceivedConfiguration := safe.New(time.Unix(0, 0)) lastReceivedConfiguration := safe.New(time.Unix(0, 0))
lastConfigs := cmap.New() lastConfigs := cmap.New()

View file

@ -8,6 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/codegangsta/negroni"
"github.com/containous/flaeg" "github.com/containous/flaeg"
"github.com/containous/mux" "github.com/containous/mux"
"github.com/containous/traefik/healthcheck" "github.com/containous/traefik/healthcheck"
@ -516,3 +517,62 @@ type okHTTPHandler struct{}
func (okHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (okHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func TestServerEntrypointWhitelistConfig(t *testing.T) {
tests := []struct {
desc string
entrypoint *EntryPoint
wantMiddleware bool
}{
{
desc: "no whitelist middleware if no config on entrypoint",
entrypoint: &EntryPoint{
Address: ":8080",
},
wantMiddleware: false,
},
{
desc: "whitelist middleware should be added if configured on entrypoint",
entrypoint: &EntryPoint{
Address: ":8080",
WhitelistSourceRange: []string{
"127.0.0.1/32",
},
},
wantMiddleware: true,
},
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
srv := Server{
globalConfiguration: GlobalConfiguration{
EntryPoints: map[string]*EntryPoint{
"test": test.entrypoint,
},
},
}
srv.serverEntryPoints = srv.buildEntryPoints(srv.globalConfiguration)
srvEntryPoint := srv.setupServerEntryPoint("test", srv.serverEntryPoints["test"])
handler := srvEntryPoint.httpServer.Handler.(*negroni.Negroni)
found := false
for _, handler := range handler.Handlers() {
if reflect.TypeOf(handler) == reflect.TypeOf((*middlewares.IPWhitelister)(nil)) {
found = true
}
}
if found && !test.wantMiddleware {
t.Errorf("ip whitelist middleware was installed even though it should not")
}
if !found && test.wantMiddleware {
t.Errorf("ip whitelist middleware was not installed even though it should have")
}
})
}
}

View file

@ -329,6 +329,12 @@
# [entryPoints.http] # [entryPoints.http]
# address = "10.42.13.37:80" # address = "10.42.13.37:80"
# To enable IP whitelisting at the entrypoint level:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# whiteListSourceRange = ["127.0.0.1/32"]
# Enable retry sending request if network error # Enable retry sending request if network error
# #
# Optional # Optional