Skip to content

Commit

Permalink
Issue #108: Mark ApiGateway certificates as CA certs
Browse files Browse the repository at this point in the history
AWS API Gateway certficates are self-signed but don't have
the IsCA flag set to true and also don't mark the certificate
as to be used for certificate signing. This prevents Go to
accept the certificate for client-cert authentication. Since
it isn't possible to use a custom certificate for client authentication
in the AWS API Gateway we need to patch the certificate on the fly.
  • Loading branch information
magiconair committed Jun 20, 2016
1 parent b37e027 commit 015607d
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 11 deletions.
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Listen struct {
TLS bool
ReadTimeout time.Duration
WriteTimeout time.Duration
AWSApiGWCertCN string
}

type UI struct {
Expand Down
13 changes: 9 additions & 4 deletions config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -118,6 +124,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
}

Expand Down Expand Up @@ -202,7 +209,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)
Expand All @@ -224,8 +231,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
Expand Down
10 changes: 6 additions & 4 deletions config/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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{
Expand Down Expand Up @@ -87,9 +88,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{
Expand Down Expand Up @@ -233,7 +235,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)
}
Expand Down
15 changes: 15 additions & 0 deletions fabio.properties
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,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 =
21 changes: 18 additions & 3 deletions listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"crypto/tls"
"crypto/x509"
"errors"
"encoding/pem"
"io/ioutil"
"log"
"net"
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 015607d

Please sign in to comment.