Skip to content

Commit

Permalink
PR #258: Allow UI/API to be served over HTTPS
Browse files Browse the repository at this point in the history
This patch provides optional HTTPS support for the UI and API endpoint.
Certificates are provided through a certificate store.
  • Loading branch information
tmessi authored and magiconair committed Apr 10, 2017
1 parent 785b9f5 commit 05f366b
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 31 deletions.
6 changes: 4 additions & 2 deletions admin/server.go
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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{})
Expand All @@ -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) {
Expand Down
7 changes: 4 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -118,4 +118,5 @@ type Consul struct {
ServiceStatus []string
CheckInterval time.Duration
CheckTimeout time.Duration
CheckScheme string
}
8 changes: 7 additions & 1 deletion config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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,
Expand All @@ -60,7 +63,10 @@ var defaultConfig = &Config{
GOMAXPROCS: runtime.NumCPU(),
},
UI: UI{
Addr: ":9998",
Listen: Listen{
Addr: ":9998",
Proto: "http",
},
Color: "light-green",
},
}
15 changes: 14 additions & 1 deletion config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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 {
Expand Down
15 changes: 14 additions & 1 deletion config/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
},
Expand Down
33 changes: 20 additions & 13 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,35 +161,42 @@ 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,
Version: version,
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)
}
}()
}

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 {
Expand Down
2 changes: 1 addition & 1 deletion registry/consul/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
18 changes: 9 additions & 9 deletions registry/consul/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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(),
},
}

Expand Down

0 comments on commit 05f366b

Please sign in to comment.