diff --git a/config/config.go b/config/config.go index 22bb38e01..a269a57a9 100644 --- a/config/config.go +++ b/config/config.go @@ -19,6 +19,7 @@ type Listen struct { TLS bool ReadTimeout time.Duration WriteTimeout time.Duration + AWSApiGWCertCN string } type UI struct { diff --git a/config/load.go b/config/load.go index 25815da4c..2c5f0ef57 100644 --- a/config/load.go +++ b/config/load.go @@ -51,11 +51,17 @@ func fromProperties(p *properties.Properties) (cfg *Config, err error) { readTimeout := durationVal(p, time.Duration(0), "proxy.readtimeout") writeTimeout := durationVal(p, time.Duration(0), "proxy.writetimeout") + awsApiGwCertCN := stringVal(p, "", "aws.apigw.cert.cn") - cfg.Listen, err = parseListen(stringVal(p, Default.Listen[0].Addr, "proxy.addr"), readTimeout, writeTimeout) + cfg.Listen, err = parseListen(stringVal(p, Default.Listen[0].Addr, "proxy.addr")) if err != nil { return nil, err } + for i := range cfg.Listen { + cfg.Listen[i].ReadTimeout = readTimeout + cfg.Listen[i].WriteTimeout = writeTimeout + cfg.Listen[i].AWSApiGWCertCN = awsApiGwCertCN + } cfg.Metrics = parseMetrics( stringVal(p, Default.Metrics[0].Target, "metrics.target"), @@ -119,6 +125,7 @@ func fromProperties(p *properties.Properties) (cfg *Config, err error) { Color: stringVal(p, Default.UI.Color, "ui.color"), Title: stringVal(p, Default.UI.Title, "ui.title"), } + return cfg, nil } @@ -203,7 +210,7 @@ func parseMetrics(target, prefix, graphiteAddr string, interval time.Duration) [ return []Metrics{m} } -func parseListen(addrs string, readTimeout, writeTimeout time.Duration) ([]Listen, error) { +func parseListen(addrs string) ([]Listen, error) { listen := []Listen{} for _, addr := range strings.Split(addrs, ",") { addr = strings.TrimSpace(addr) @@ -225,8 +232,6 @@ func parseListen(addrs string, readTimeout, writeTimeout time.Duration) ([]Liste default: return nil, fmt.Errorf("invalid address %s", addr) } - l.ReadTimeout = readTimeout - l.WriteTimeout = writeTimeout listen = append(listen, l) } return listen, nil diff --git a/config/load_test.go b/config/load_test.go index 70090acc7..e07c9cad6 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -49,6 +49,7 @@ runtime.gomaxprocs = 12 ui.addr = 7.8.9.0:1234 ui.color = fonzy ui.title = fabfab +aws.apigw.cert.cn = furb ` out := &Config{ Proxy: Proxy{ @@ -89,9 +90,10 @@ ui.title = fabfab }, Listen: []Listen{ { - Addr: ":1234", - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, + Addr: ":1234", + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + AWSApiGWCertCN: "furb", }, }, Metrics: []Metrics{ @@ -235,7 +237,7 @@ func TestParseAddr(t *testing.T) { } for i, tt := range tests { - l, err := parseListen(tt.in, time.Duration(0), time.Duration(0)) + l, err := parseListen(tt.in) if got, want := err, tt.err; (got != nil || want != "") && got.Error() != want { t.Errorf("%d: got %v want %v", i, got, want) } diff --git a/fabio.properties b/fabio.properties index 5c9f47e2c..b74b9c08b 100644 --- a/fabio.properties +++ b/fabio.properties @@ -369,3 +369,18 @@ # The default is # # ui.title = + + +# aws.apigw.cert.cn configures a workaround for using AWS Api Gateway +# client certificates for authentication. +# +# AWS Api Gateway certificates for client authentication are self-signed +# certificates generated by the Amazon AWS dashboard. They do not have the CA +# flag set which makes them unsuitable for client certificate authentication +# in Go. By setting this value to the common name of the certificate which is +# usually 'ApiGateway' a workaround is enabled which allows the use of these +# certificates to be used for client certificate authentication. +# +# The default is +# +# aws.apigw.cert.cn = diff --git a/listen.go b/listen.go index 527e70f6b..6a77377f6 100644 --- a/listen.go +++ b/listen.go @@ -3,7 +3,7 @@ package main import ( "crypto/tls" "crypto/x509" - "errors" + "encoding/pem" "io/ioutil" "log" "net" @@ -89,10 +89,25 @@ func newServer(l config.Listen, h http.Handler) (*http.Server, error) { if err != nil { return nil, err } + pool := x509.NewCertPool() - if !pool.AppendCertsFromPEM(pemBlock) { - return nil, errors.New("failed to add client auth certs") + for p, rest := pem.Decode(pemBlock); p != nil; p, rest = pem.Decode(rest) { + cert, err := x509.ParseCertificate(p.Bytes) + if err != nil { + return nil, err + } + + // Issue #108: Allow generated AWS API Gateway certs to be used for client cert authentication + if l.AWSApiGWCertCN != "" && l.AWSApiGWCertCN == cert.Issuer.CommonName { + cert.BasicConstraintsValid = true + cert.IsCA = true + cert.KeyUsage = x509.KeyUsageCertSign + log.Print("[INFO] Enabling AWS Api Gateway workaround for certificate %s", cert.Issuer.CommonName) + } + + pool.AddCert(cert) } + srv.TLSConfig.ClientCAs = pool srv.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert }