From 0eb5a3c0cc47ddcab957981a6f535f5f22175586 Mon Sep 17 00:00:00 2001 From: Timothy Messier Date: Thu, 6 Apr 2017 07:03:48 -0400 Subject: [PATCH] Allow UI to be served over HTTPS This allows for using the cert store to provide a cert for the ui and to server it over https. For example: ./fabio \ -proxy.cs 'cs=ui;type=file;cert=./cert.pem;key=./key.pem' \ -ui.addr ':9998;cs=ui' --- admin/server.go | 6 ++++-- config/config.go | 7 ++++--- config/default.go | 8 +++++++- config/load.go | 15 ++++++++++++++- config/load_test.go | 15 ++++++++++++++- main.go | 33 ++++++++++++++++++++------------- registry/consul/backend.go | 2 +- registry/consul/register.go | 18 +++++++++--------- 8 files changed, 73 insertions(+), 31 deletions(-) diff --git a/admin/server.go b/admin/server.go index da9b9295b..3543377bc 100644 --- a/admin/server.go +++ b/admin/server.go @@ -1,12 +1,14 @@ package admin import ( + "crypto/tls" "fmt" "net/http" "github.com/eBay/fabio/admin/api" "github.com/eBay/fabio/admin/ui" "github.com/eBay/fabio/config" + "github.com/eBay/fabio/proxy" ) // Server provides the HTTP server for the admin UI and API. @@ -19,7 +21,7 @@ type Server struct { } // ListenAndServe starts the admin server. -func (s *Server) ListenAndServe(addr string) error { +func (s *Server) ListenAndServe(l config.Listen, tlscfg *tls.Config) error { http.Handle("/api/config", &api.ConfigHandler{s.Cfg}) http.Handle("/api/manual", &api.ManualHandler{}) http.Handle("/api/routes", &api.RoutesHandler{}) @@ -28,7 +30,7 @@ func (s *Server) ListenAndServe(addr string) error { http.Handle("/routes", &ui.RoutesHandler{Color: s.Color, Title: s.Title, Version: s.Version}) http.HandleFunc("/health", handleHealth) http.Handle("/", http.RedirectHandler("/routes", http.StatusSeeOther)) - return http.ListenAndServe(addr, nil) + return proxy.ListenAndServeHTTP(l, nil, tlscfg) } func handleHealth(w http.ResponseWriter, r *http.Request) { diff --git a/config/config.go b/config/config.go index 262c8214f..addd2c489 100644 --- a/config/config.go +++ b/config/config.go @@ -37,9 +37,9 @@ type Listen struct { } type UI struct { - Addr string - Color string - Title string + Listen Listen + Color string + Title string } type Proxy struct { @@ -118,4 +118,5 @@ type Consul struct { ServiceStatus []string CheckInterval time.Duration CheckTimeout time.Duration + CheckScheme string } diff --git a/config/default.go b/config/default.go index 88134c506..3e187d53a 100644 --- a/config/default.go +++ b/config/default.go @@ -10,10 +10,12 @@ var defaultValues = struct { CertSourcesValue []map[string]string ReadTimeout time.Duration WriteTimeout time.Duration + UIListenerValue string GZIPContentTypesValue string }{ ListenerValue: []string{":9999"}, CertSourcesValue: []map[string]string{}, + UIListenerValue: ":9998", } var defaultConfig = &Config{ @@ -51,6 +53,7 @@ var defaultConfig = &Config{ ServiceStatus: []string{"passing"}, CheckInterval: time.Second, CheckTimeout: 3 * time.Second, + CheckScheme: "http", }, Timeout: 10 * time.Second, Retry: 500 * time.Millisecond, @@ -60,7 +63,10 @@ var defaultConfig = &Config{ GOMAXPROCS: runtime.NumCPU(), }, UI: UI{ - Addr: ":9998", + Listen: Listen{ + Addr: ":9998", + Proto: "http", + }, Color: "light-green", }, } diff --git a/config/load.go b/config/load.go index 79b795c95..d91b8fdb9 100644 --- a/config/load.go +++ b/config/load.go @@ -109,6 +109,7 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c // config values var listenerValue []string + var uiListenerValue string var certSourcesValue []map[string]string var readTimeout, writeTimeout time.Duration var gzipContentTypesValue string @@ -163,7 +164,7 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c f.DurationVar(&cfg.Registry.Consul.CheckTimeout, "registry.consul.register.checkTimeout", defaultConfig.Registry.Consul.CheckTimeout, "service check timeout") f.IntVar(&cfg.Runtime.GOGC, "runtime.gogc", defaultConfig.Runtime.GOGC, "sets runtime.GOGC") f.IntVar(&cfg.Runtime.GOMAXPROCS, "runtime.gomaxprocs", defaultConfig.Runtime.GOMAXPROCS, "sets runtime.GOMAXPROCS") - f.StringVar(&cfg.UI.Addr, "ui.addr", defaultConfig.UI.Addr, "address the UI/API is listening on") + f.StringVar(&uiListenerValue, "ui.addr", defaultValues.UIListenerValue, "Address the UI/API is listening on") f.StringVar(&cfg.UI.Color, "ui.color", defaultConfig.UI.Color, "background color of the UI") f.StringVar(&cfg.UI.Title, "ui.title", defaultConfig.UI.Title, "optional title for the UI") @@ -191,11 +192,23 @@ func load(cmdline, environ, envprefix []string, props *properties.Properties) (c return nil, err } + if uiListenerValue != "" { + cfg.UI.Listen, err = parseListen(uiListenerValue, certSources, 0, 0) + if err != nil { + return nil, err + } + } + cfg.Listen, err = parseListeners(listenerValue, certSources, readTimeout, writeTimeout) if err != nil { return nil, err } + cfg.Registry.Consul.CheckScheme = defaultConfig.Registry.Consul.CheckScheme + if cfg.UI.Listen.CertSource.Name != "" { + cfg.Registry.Consul.CheckScheme = "https" + } + if gzipContentTypesValue != "" { cfg.Proxy.GZIPContentTypes, err = regexp.Compile(gzipContentTypesValue) if err != nil { diff --git a/config/load_test.go b/config/load_test.go index ae5b720dc..ed39b5b1d 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -568,7 +568,20 @@ func TestLoad(t *testing.T) { { args: []string{"-ui.addr", "1.2.3.4:5555"}, cfg: func(cfg *Config) *Config { - cfg.UI.Addr = "1.2.3.4:5555" + cfg.UI.Listen.Addr = "1.2.3.4:5555" + cfg.UI.Listen.Proto = "http" + return cfg + }, + }, + { + args: []string{"-ui.addr", ":9998;cs=ui", "-proxy.cs", "cs=ui;type=file;cert=value"}, + cfg: func(cfg *Config) *Config { + cfg.UI.Listen.Addr = ":9998" + cfg.UI.Listen.Proto = "https" + cfg.UI.Listen.CertSource.Name = "ui" + cfg.UI.Listen.CertSource.Type = "file" + cfg.UI.Listen.CertSource.CertPath = "value" + cfg.Registry.Consul.CheckScheme = "https" return cfg }, }, diff --git a/main.go b/main.go index 47f352417..472fe7e9d 100644 --- a/main.go +++ b/main.go @@ -161,9 +161,26 @@ func lookupHostFn(cfg *config.Config) func(string) string { } } +func makeTLSConfig(l config.Listen) *tls.Config { + if l.CertSource.Name == "" { + return nil + } + src, err := cert.NewSource(l.CertSource) + if err != nil { + exit.Fatalf("[FATAL] Failed to create cert source %s. %s", l.CertSource.Name, err) + } + tlscfg, err := cert.TLSConfig(src, l.StrictMatch) + if err != nil { + exit.Fatalf("[FATAL] Failed to create TLS config for cert source %s. %s", l.CertSource.Name, err) + } + return tlscfg +} + func startAdmin(cfg *config.Config) { - log.Printf("[INFO] Admin server listening on %q", cfg.UI.Addr) + log.Printf("[INFO] Admin server listening on %q", cfg.UI.Listen.Addr) go func() { + l := cfg.UI.Listen + tlscfg := makeTLSConfig(l) srv := &admin.Server{ Color: cfg.UI.Color, Title: cfg.UI.Title, @@ -171,7 +188,7 @@ func startAdmin(cfg *config.Config) { Commands: route.Commands, Cfg: cfg, } - if err := srv.ListenAndServe(cfg.UI.Addr); err != nil { + if err := srv.ListenAndServe(l, tlscfg); err != nil { exit.Fatal("[FATAL] ui: ", err) } }() @@ -179,17 +196,7 @@ func startAdmin(cfg *config.Config) { func startServers(cfg *config.Config) { for _, l := range cfg.Listen { - var tlscfg *tls.Config - if l.CertSource.Name != "" { - src, err := cert.NewSource(l.CertSource) - if err != nil { - exit.Fatal("[FATAL] Failed to create cert source %s. %s", l.CertSource.Name, err) - } - tlscfg, err = cert.TLSConfig(src, l.StrictMatch) - if err != nil { - exit.Fatal("[FATAL] Failed to create TLS config for cert source %s. %s", l.CertSource.Name, err) - } - } + tlscfg := makeTLSConfig(l) log.Printf("[INFO] %s proxy listening on %s", strings.ToUpper(l.Proto), l.Addr) if tlscfg != nil && tlscfg.ClientAuth == tls.RequireAndVerifyClientCert { diff --git a/registry/consul/backend.go b/registry/consul/backend.go index 73b274fab..3ad0ece1d 100644 --- a/registry/consul/backend.go +++ b/registry/consul/backend.go @@ -42,7 +42,7 @@ func (b *be) Register() error { return nil } - service, err := serviceRegistration(b.cfg.ServiceAddr, b.cfg.ServiceName, b.cfg.ServiceTags, b.cfg.CheckInterval, b.cfg.CheckTimeout) + service, err := serviceRegistration(b.cfg) if err != nil { return err } diff --git a/registry/consul/register.go b/registry/consul/register.go index b993edae0..021dbee9d 100644 --- a/registry/consul/register.go +++ b/registry/consul/register.go @@ -76,12 +76,12 @@ func register(c *api.Client, service *api.AgentServiceRegistration) (dereg chan return dereg } -func serviceRegistration(addr, name string, tags []string, interval, timeout time.Duration) (*api.AgentServiceRegistration, error) { +func serviceRegistration(cfg *config.Consul) (*api.AgentServiceRegistration, error) { hostname, err := os.Hostname() if err != nil { return nil, err } - ipstr, portstr, err := net.SplitHostPort(addr) + ipstr, portstr, err := net.SplitHostPort(cfg.ServiceAddr) if err != nil { return nil, err } @@ -101,23 +101,23 @@ func serviceRegistration(addr, name string, tags []string, interval, timeout tim } } - serviceID := fmt.Sprintf("%s-%s-%d", name, hostname, port) + serviceID := fmt.Sprintf("%s-%s-%d", cfg.ServiceName, hostname, port) - checkURL := fmt.Sprintf("http://%s:%d/health", ip, port) + checkURL := fmt.Sprintf("%s://%s:%d/health", cfg.CheckScheme, ip, port) if ip.To16() != nil { - checkURL = fmt.Sprintf("http://[%s]:%d/health", ip, port) + checkURL = fmt.Sprintf("%s://[%s]:%d/health", cfg.CheckScheme, ip, port) } service := &api.AgentServiceRegistration{ ID: serviceID, - Name: name, + Name: cfg.ServiceName, Address: ip.String(), Port: port, - Tags: tags, + Tags: cfg.ServiceTags, Check: &api.AgentServiceCheck{ HTTP: checkURL, - Interval: interval.String(), - Timeout: timeout.String(), + Interval: cfg.CheckInterval.String(), + Timeout: cfg.CheckTimeout.String(), }, }