Skip to content

Commit

Permalink
Merge pull request planetlabs#39 from justinbarrick/tls
Browse files Browse the repository at this point in the history
Support TLS and mutual TLS to verify upstream.
  • Loading branch information
jacobstr authored Sep 9, 2019
2 parents fb01973 + 89001a6 commit 75b0795
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 5 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ Flags:
--kubecfg-template=KUBECFG-TEMPLATE
A kubecfg file containing clusters to populate
with a user and contexts.
--client-ca=CLIENT-CA If set, enables mutual TLS and specifies the path to CA file
to use when validating client connections.
--client-ca-subject=CLIENT-CA-SUBJECT
If set, requires that the client CA matches the provided
subject (requires --client-ca).
--tls-cert=TLS-CERT If set, enables TLS and specifies the path to TLS
certificate to use for HTTPS server (requires --tls-key).
--tls-key=TLS-KEY Path to TLS key to use for HTTPS server (requires
--tls-cert).
Args:
<secret> Secret for JWT HMAC signature and verification.
Expand Down
70 changes: 68 additions & 2 deletions cmd/kubehook/kubehook.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ package main

import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
Expand All @@ -33,7 +36,9 @@ import (
"github.com/planetlabs/kubehook/handlers/kubecfg"
_ "github.com/planetlabs/kubehook/statik"

"github.com/dyson/certman"
"github.com/julienschmidt/httprouter"
"github.com/pkg/errors"
"github.com/rakyll/statik/fs"
"go.uber.org/zap"
kingpin "gopkg.in/alecthomas/kingpin.v2"
Expand All @@ -60,6 +65,51 @@ func envVarName(app, arg string) string {
return strings.Replace(strings.ToUpper(fmt.Sprintf("%s_%s", app, arg)), "-", "_", -1)
}

func listenAndServe(s *http.Server, tlsCert, tlsKey string) error {
if tlsCert != "" && tlsKey != "" {
cm, err := certman.New(tlsCert, tlsKey)
if err != nil {
return err
}

if err := cm.Watch(); err != nil {
return err
}

s.TLSConfig.GetCertificate = cm.GetCertificate

return s.ListenAndServeTLS("", "")
}

return s.ListenAndServe()
}

func makeTLSConfig(clientCA []byte, clientCASubject string) *tls.Config {
tlsConfig := &tls.Config{}

if clientCASubject != "" {
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
return errors.New("client did not present any TLS certificates")
}

return verifiedChains[0][0].VerifyHostname(clientCASubject)
}
}

if len(clientCA) > 0 {
clientCertPool := x509.NewCertPool()
clientCertPool.AppendCertsFromPEM(clientCA)

tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs = clientCertPool

tlsConfig.BuildNameToCertificate()
}

return tlsConfig
}

func main() {
var (
app = kingpin.New(filepath.Base(os.Args[0]), "Authenticates Kubernetes users via JWT tokens.").DefaultEnvars()
Expand All @@ -72,6 +122,10 @@ func main() {
groupHeaderDelim = app.Flag("group-header-delimiter", "Delimiter separating group names in the group-header.").Default(handlers.DefaultGroupHeaderDelimiter).String()
maxlife = app.Flag("max-lifetime", "Maximum allowed JWT lifetime, in Go's time.ParseDuration format.").Default(jwt.DefaultMaxLifetime.String()).Duration()
template = app.Flag("kubecfg-template", "A kubecfg file containing clusters to populate with a user and contexts.").ExistingFile()
clientCA = app.Flag("client-ca", "If set, enables mutual TLS and specifies the path to CA file to use when validating client connections.").File()
clientCASubject = app.Flag("client-ca-subject", "If set, requires that the client CA matches the provided subject (requires --client-ca).").String()
tlsCert = app.Flag("tls-cert", "If set, enables TLS and specifies the path to TLS certificate to use for HTTPS server (requires --tls-key).").ExistingFile()
tlsKey = app.Flag("tls-key", "Path to TLS key to use for HTTPS server (requires --tls-cert).").ExistingFile()

secret = app.Arg("secret", "Secret for JWT HMAC signature and verification.").Required().Envar(envVarName(app.Name, "secret")).String()
)
Expand All @@ -89,7 +143,18 @@ func main() {
kingpin.FatalIfError(err, "cannot create JWT authenticator")

r := httprouter.New()
s := &http.Server{Addr: *listen, Handler: logRequests(r, log)}

var clientCACert []byte
if *clientCA != nil {
clientCACert, err = ioutil.ReadAll(*clientCA)
kingpin.FatalIfError(err, "cannot load client CA certificate file")
}

s := &http.Server{
Addr: *listen,
Handler: logRequests(r, log),
TLSConfig: makeTLSConfig(clientCACert, *clientCASubject),
}

ctx, cancel := context.WithTimeout(context.Background(), *grace)
done := make(chan struct{})
Expand Down Expand Up @@ -132,7 +197,8 @@ func main() {
r.HandlerFunc("GET", "/kubecfg", handlers.NotImplemented())
}

log.Info("shutdown", zap.Error(s.ListenAndServe()))
log.Info("shutdown", zap.Error(listenAndServe(s, *tlsCert, *tlsKey)))

<-done
cancel()
}
10 changes: 7 additions & 3 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import:
subpackages:
- tools/clientcmd
- tools/clientcmd/api
- package: github.com/dyson/certman
version: ~0.2.1
testImport:
- package: github.com/go-test/deep
version: v1.0.0

0 comments on commit 75b0795

Please sign in to comment.