Skip to content

Commit

Permalink
Always use latest requestheader client CA from configMap during TLS h…
Browse files Browse the repository at this point in the history
…andshake

Signed-off-by: David Vossel <[email protected]>
  • Loading branch information
davidvossel committed May 1, 2019
1 parent 293df2c commit d860fde
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 202 deletions.
13 changes: 13 additions & 0 deletions pkg/controller/virtinformers.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
rbacv1 "k8s.io/api/rbac/v1"
extv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
extclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -79,6 +80,9 @@ type KubeInformerFactory interface {
// Watches VirtualMachineInstanceMigration objects
VirtualMachineInstanceMigration() cache.SharedIndexInformer

// Watches for k8s extensions api configmap
ApiAuthConfigMap() cache.SharedIndexInformer

// Watches for ConfigMap objects
ConfigMap() cache.SharedIndexInformer

Expand Down Expand Up @@ -282,6 +286,15 @@ func (f *kubeInformerFactory) DummyDataVolume() cache.SharedIndexInformer {
})
}

func (f *kubeInformerFactory) ApiAuthConfigMap() cache.SharedIndexInformer {
return f.getInformer("extensionsConfigMapInformer", func() cache.SharedIndexInformer {
restClient := f.clientSet.CoreV1().RESTClient()
fieldSelector := fields.OneTermEqualSelector("metadata.name", "extension-apiserver-authentication")
lw := cache.NewListWatchFromClient(restClient, "configmaps", metav1.NamespaceSystem, fieldSelector)
return cache.NewSharedIndexInformer(lw, &k8sv1.ConfigMap{}, f.defaultResync, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
})
}

func (f *kubeInformerFactory) ConfigMap() cache.SharedIndexInformer {
return f.getInformer("configMapInformer", func() cache.SharedIndexInformer {
restClient := f.clientSet.CoreV1().RESTClient()
Expand Down
2 changes: 2 additions & 0 deletions pkg/virt-api/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ go_library(
name = "go_default_library",
srcs = [
"api.go",
"ca-manager.go",
"filesystem.go",
"generated_mock_filesystem.go",
],
importpath = "kubevirt.io/kubevirt/pkg/virt-api",
visibility = ["//visibility:public"],
deps = [
"//pkg/api/v1:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/healthz:go_default_library",
"//pkg/kubecli:go_default_library",
"//pkg/log:go_default_library",
Expand Down
108 changes: 62 additions & 46 deletions pkg/virt-api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"

v1 "kubevirt.io/kubevirt/pkg/api/v1"
"kubevirt.io/kubevirt/pkg/controller"
"kubevirt.io/kubevirt/pkg/healthz"
"kubevirt.io/kubevirt/pkg/kubecli"
"kubevirt.io/kubevirt/pkg/log"
Expand Down Expand Up @@ -108,16 +109,15 @@ type virtAPIApp struct {
authorizor rest.VirtApiAuthorizor
certsDirectory string

signingCertBytes []byte
certBytes []byte
keyBytes []byte
requestHeaderClientCABytes []byte
certFile string
keyFile string
caFile string
signingCertFile string
namespace string
tlsConfig *tls.Config
signingCertBytes []byte
certBytes []byte
keyBytes []byte
certFile string
keyFile string
caFile string
signingCertFile string
namespace string
tlsConfig *tls.Config
}

var _ service.Service = &virtAPIApp{}
Expand Down Expand Up @@ -405,7 +405,7 @@ func deserializeStrings(in string) ([]string, error) {
return ret, nil
}

func (app *virtAPIApp) getClientCert() error {
func (app *virtAPIApp) readRequestHeader() error {
authConfigMap, err := app.virtCli.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get("extension-apiserver-authentication", metav1.GetOptions{})
if err != nil {
return err
Expand All @@ -414,11 +414,10 @@ func (app *virtAPIApp) getClientCert() error {
// The request-header CA is mandatory. It can be retrieved from the configmap as we do here, or it must be provided
// via flag on start of this apiserver. Since we don't do the latter, the former is mandatory for us
// see https://github.com/kubernetes-incubator/apiserver-builder-alpha/blob/master/docs/concepts/auth.md#requestheader-authentication
requestHeaderClientCA, ok := authConfigMap.Data["requestheader-client-ca-file"]
_, ok := authConfigMap.Data["requestheader-client-ca-file"]
if !ok {
return fmt.Errorf("requestheader-client-ca-file not found in extension-apiserver-authentication ConfigMap")
}
app.requestHeaderClientCABytes = []byte(requestHeaderClientCA)

// This config map also contains information about what
// headers our authorizor should inspect
Expand Down Expand Up @@ -936,19 +935,14 @@ func (app *virtAPIApp) createSubresourceApiservice() error {
return nil
}

func (app *virtAPIApp) setupTLS(fs Filesystem) error {
func (app *virtAPIApp) setupTLS(fs Filesystem, caManager ClientCAManager) error {

app.keyFile = filepath.Join(app.certsDirectory, "/key.pem")
app.certFile = filepath.Join(app.certsDirectory, "/cert.pem")
app.signingCertFile = filepath.Join(app.certsDirectory, "/signingCert.pem")
app.caFile = filepath.Join(app.certsDirectory, "/clientCA.crt")

// Write the certs to disk
err := fs.WriteFile(app.caFile, app.requestHeaderClientCABytes, 0600)
if err != nil {
return err
}
err = fs.WriteFile(app.keyFile, app.keyBytes, 0600)
err := fs.WriteFile(app.keyFile, app.keyBytes, 0600)
if err != nil {
return err
}
Expand All @@ -961,41 +955,63 @@ func (app *virtAPIApp) setupTLS(fs Filesystem) error {
return err
}

// create the client CA pool.
// This ensures we're talking to the k8s api server
pool, err := cert.NewPool(app.caFile)
certPair, err := tls.LoadX509KeyPair(app.certFile, app.keyFile)
if err != nil {
return err
return fmt.Errorf("some special error: %b", err)
}

app.tlsConfig = &tls.Config{
ClientCAs: pool,
// A VerifyClientCertIfGiven request means we're not guaranteed
// a client has been authenticated unless they provide a peer
// cert.
//
// Make sure to verify in subresource endpoint that peer cert
// was provided before processing request. If the peer cert is
// given on the connection, then we can be guaranteed that it
// was signed by the client CA in our pool.
//
// There is another ClientAuth type called 'RequireAndVerifyClientCert'
// We can't use this type here because during the aggregated api status
// check it attempts to hit '/' on our api endpoint to verify an http
// response is given. That status request won't send a peer cert regardless
// if the TLS handshake requests it. As a result, the TLS handshake fails
// and our aggregated endpoint never becomes available.
ClientAuth: tls.VerifyClientCertIfGiven,
Certificates: []tls.Certificate{certPair},
GetConfigForClient: func(hi *tls.ClientHelloInfo) (*tls.Config, error) {

pool, err := caManager.GetCurrent()
if err != nil {
log.Log.Reason(err).Error("Failed to get requestheader client CA")
return nil, err
}
config := &tls.Config{
Certificates: []tls.Certificate{certPair},
ClientCAs: pool,
// A VerifyClientCertIfGiven request means we're not guaranteed
// a client has been authenticated unless they provide a peer
// cert.
//
// Make sure to verify in subresource endpoint that peer cert
// was provided before processing request. If the peer cert is
// given on the connection, then we can be guaranteed that it
// was signed by the client CA in our pool.
//
// There is another ClientAuth type called 'RequireAndVerifyClientCert'
// We can't use this type here because during the aggregated api status
// check it attempts to hit '/' on our api endpoint to verify an http
// response is given. That status request won't send a peer cert regardless
// if the TLS handshake requests it. As a result, the TLS handshake fails
// and our aggregated endpoint never becomes available.
ClientAuth: tls.VerifyClientCertIfGiven,
}

config.BuildNameToCertificate()
return config, nil
},
}
app.tlsConfig.BuildNameToCertificate()
return nil
}

func (app *virtAPIApp) startTLS() error {
func (app *virtAPIApp) startTLS(stopCh <-chan struct{}) error {

errors := make(chan error)

err := app.setupTLS(IOUtil{})
informerFactory := controller.NewKubeInformerFactory(app.virtCli.RestClient(), app.virtCli, app.namespace)

authConfigMapInformer := informerFactory.ApiAuthConfigMap()
informerFactory.Start(stopCh)

cache.WaitForCacheSync(stopCh, authConfigMapInformer.HasSynced)

caManager := NewClientCAManager(authConfigMapInformer.GetStore())

err := app.setupTLS(IOUtil{}, caManager)
if err != nil {
return err
}
Expand All @@ -1009,7 +1025,7 @@ func (app *virtAPIApp) startTLS() error {
TLSConfig: app.tlsConfig,
}

errors <- server.ListenAndServeTLS(app.certFile, app.keyFile)
errors <- server.ListenAndServeTLS("", "")
}()

// wait for server to exit
Expand All @@ -1018,7 +1034,7 @@ func (app *virtAPIApp) startTLS() error {

func (app *virtAPIApp) Run() {
// get client Cert
err := app.getClientCert()
err := app.readRequestHeader()
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -1058,7 +1074,7 @@ func (app *virtAPIApp) Run() {
}

// start TLS server
err = app.startTLS()
err = app.startTLS(stopChan)
if err != nil {
panic(err)
}
Expand Down
Loading

0 comments on commit d860fde

Please sign in to comment.