From 759a19bc4f4c9e8de08d2d41c11f273301743d61 Mon Sep 17 00:00:00 2001 From: Christophe Robin Date: Sat, 8 Jul 2017 19:21:14 +0900 Subject: [PATCH] Add whitelist configuration option for entrypoints * Add whitelist configuration option for entrypoints * Add whitelist support to --entrypoint flag --- docs/toml.md | 6 ++++ server/configuration.go | 29 ++++++++++------- server/server.go | 69 ++++++++++++++++++++++++----------------- server/server_test.go | 60 +++++++++++++++++++++++++++++++++++ traefik.sample.toml | 6 ++++ 5 files changed, 131 insertions(+), 39 deletions(-) diff --git a/docs/toml.md b/docs/toml.md index bda0cf8b6..5f5eb928b 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -279,6 +279,12 @@ To write JSON format logs, specify `json` as the format: # address = ":80" # compress = true +# To enable IP whitelisting at the entrypoint level: +# [entryPoints] +# [entryPoints.http] +# address = ":80" +# whiteListSourceRange = ["127.0.0.1/32"] + [entryPoints] [entryPoints.http] address = ":80" diff --git a/server/configuration.go b/server/configuration.go index 168bb0dfc..7b78493b3 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -189,7 +189,7 @@ func (ep *EntryPoints) String() string { // Set's argument is a string to be parsed to set the flag. // It's a comma-separated list, so we split it. func (ep *EntryPoints) Set(value string) error { - regex := regexp.MustCompile("(?:Name:(?P\\S*))\\s*(?:Address:(?P
\\S*))?\\s*(?:TLS:(?P\\S*))?\\s*((?PTLS))?\\s*(?:CA:(?P\\S*))?\\s*(?:Redirect.EntryPoint:(?P\\S*))?\\s*(?:Redirect.Regex:(?P\\S*))?\\s*(?:Redirect.Replacement:(?P\\S*))?\\s*(?:Compress:(?P\\S*))?") + regex := regexp.MustCompile("(?:Name:(?P\\S*))\\s*(?:Address:(?P
\\S*))?\\s*(?:TLS:(?P\\S*))?\\s*((?PTLS))?\\s*(?:CA:(?P\\S*))?\\s*(?:Redirect.EntryPoint:(?P\\S*))?\\s*(?:Redirect.Regex:(?P\\S*))?\\s*(?:Redirect.Replacement:(?P\\S*))?\\s*(?:Compress:(?P\\S*))?\\s*(?:WhiteListSourceRange:(?P\\S*))?") match := regex.FindAllStringSubmatch(value, -1) if match == nil { 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") } + whiteListSourceRange := []string{} + if len(result["WhiteListSourceRange"]) > 0 { + whiteListSourceRange = strings.Split(result["WhiteListSourceRange"], ",") + } + (*ep)[result["Name"]] = &EntryPoint{ - Address: result["Address"], - TLS: tls, - Redirect: redirect, - Compress: compress, + Address: result["Address"], + TLS: tls, + Redirect: redirect, + Compress: compress, + WhitelistSourceRange: whiteListSourceRange, } return nil @@ -260,12 +266,13 @@ func (ep *EntryPoints) Type() string { // EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...) type EntryPoint struct { - Network string - Address string - TLS *TLS - Redirect *Redirect - Auth *types.Auth - Compress bool + Network string + Address string + TLS *TLS + Redirect *Redirect + Auth *types.Auth + WhitelistSourceRange []string + Compress bool } // Redirect configures a redirection of an entry point to another, or to an URL diff --git a/server/server.go b/server/server.go index 955b3b28a..31cbdb5b7 100644 --- a/server/server.go +++ b/server/server.go @@ -188,38 +188,51 @@ func (server *Server) startHTTPServers() { server.serverEntryPoints = server.buildEntryPoints(server.globalConfiguration) for newServerEntryPointName, newServerEntryPoint := range server.serverEntryPoints { - 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{}) - } - 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 + serverEntryPoint := server.setupServerEntryPoint(newServerEntryPointName, newServerEntryPoint) 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) { lastReceivedConfiguration := safe.New(time.Unix(0, 0)) lastConfigs := cmap.New() diff --git a/server/server_test.go b/server/server_test.go index 93c37d01b..d0b15cb58 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/codegangsta/negroni" "github.com/containous/flaeg" "github.com/containous/mux" "github.com/containous/traefik/healthcheck" @@ -516,3 +517,62 @@ type okHTTPHandler struct{} func (okHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 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") + } + }) + } +} diff --git a/traefik.sample.toml b/traefik.sample.toml index ffc69b16f..5eb10f5d7 100644 --- a/traefik.sample.toml +++ b/traefik.sample.toml @@ -329,6 +329,12 @@ # [entryPoints.http] # 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 # # Optional