ContainerImage.Pinniped/internal/controller/apicerts/certs_manager.go
Ryan Richard 0b300cbe42 Use TokenCredentialRequest instead of base64 token with impersonator
To make an impersonation request, first make a TokenCredentialRequest
to get a certificate. That cert will either be issued by the Kube
API server's CA or by a new CA specific to the impersonator. Either
way, you can then make a request to the impersonator and present
that client cert for auth and the impersonator will accept it and
make the impesonation call on your behalf.

The impersonator http handler now borrows some Kube library code
to handle request processing. This will allow us to more closely
mimic the behavior of a real API server, e.g. the client cert
auth will work exactly like the real API server.

Signed-off-by: Monis Khan <mok@vmware.com>
2021-03-10 10:30:06 -08:00

151 lines
4.9 KiB
Go

// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package apicerts
import (
"crypto/x509/pkix"
"fmt"
"time"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
"go.pinniped.dev/internal/certauthority"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controllerlib"
)
const (
CACertificateSecretKey = "caCertificate"
CACertificatePrivateKeySecretKey = "caCertificatePrivateKey"
tlsPrivateKeySecretKey = "tlsPrivateKey"
TLSCertificateChainSecretKey = "tlsCertificateChain"
)
type certsManagerController struct {
namespace string
certsSecretResourceName string
certsSecretLabels map[string]string
k8sClient kubernetes.Interface
secretInformer corev1informers.SecretInformer
// certDuration is the lifetime of both the serving certificate and its CA
// certificate that this controller will use when issuing the certificates.
certDuration time.Duration
generatedCACommonName string
serviceNameForGeneratedCertCommonName string
}
func NewCertsManagerController(
namespace string,
certsSecretResourceName string,
certsSecretLabels map[string]string,
k8sClient kubernetes.Interface,
secretInformer corev1informers.SecretInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc,
withInitialEvent pinnipedcontroller.WithInitialEventOptionFunc,
certDuration time.Duration,
generatedCACommonName string,
serviceNameForGeneratedCertCommonName string,
) controllerlib.Controller {
return controllerlib.New(
controllerlib.Config{
Name: "certs-manager-controller",
Syncer: &certsManagerController{
namespace: namespace,
certsSecretResourceName: certsSecretResourceName,
certsSecretLabels: certsSecretLabels,
k8sClient: k8sClient,
secretInformer: secretInformer,
certDuration: certDuration,
generatedCACommonName: generatedCACommonName,
serviceNameForGeneratedCertCommonName: serviceNameForGeneratedCertCommonName,
},
},
withInformer(
secretInformer,
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretResourceName, namespace),
controllerlib.InformerOption{},
),
// Be sure to run once even if the Secret that the informer is watching doesn't exist.
withInitialEvent(controllerlib.Key{
Namespace: namespace,
Name: certsSecretResourceName,
}),
)
}
func (c *certsManagerController) Sync(ctx controllerlib.Context) error {
// Try to get the secret from the informer cache.
_, err := c.secretInformer.Lister().Secrets(c.namespace).Get(c.certsSecretResourceName)
notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound {
return fmt.Errorf("failed to get %s/%s secret: %w", c.namespace, c.certsSecretResourceName, err)
}
if !notFound {
// The secret already exists, so nothing to do.
return nil
}
// Create a CA.
ca, err := certauthority.New(pkix.Name{CommonName: c.generatedCACommonName}, c.certDuration)
if err != nil {
return fmt.Errorf("could not initialize CA: %w", err)
}
caPrivateKeyPEM, err := ca.PrivateKeyToPEM()
if err != nil {
return fmt.Errorf("could not get CA private key: %w", err)
}
secret := corev1.Secret{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: c.certsSecretResourceName,
Namespace: c.namespace,
Labels: c.certsSecretLabels,
},
StringData: map[string]string{
CACertificateSecretKey: string(ca.Bundle()),
CACertificatePrivateKeySecretKey: string(caPrivateKeyPEM),
},
}
// Using the CA from above, create a TLS server cert if we have service name.
if len(c.serviceNameForGeneratedCertCommonName) != 0 {
serviceEndpoint := c.serviceNameForGeneratedCertCommonName + "." + c.namespace + ".svc"
tlsCert, err := ca.Issue(
pkix.Name{CommonName: serviceEndpoint},
[]string{serviceEndpoint},
nil,
c.certDuration,
)
if err != nil {
return fmt.Errorf("could not issue serving certificate: %w", err)
}
// Write the CA's public key bundle and the serving certs to a secret.
tlsCertChainPEM, tlsPrivateKeyPEM, err := certauthority.ToPEM(tlsCert)
if err != nil {
return fmt.Errorf("could not PEM encode serving certificate: %w", err)
}
secret.StringData[tlsPrivateKeySecretKey] = string(tlsPrivateKeyPEM)
secret.StringData[TLSCertificateChainSecretKey] = string(tlsCertChainPEM)
}
_, err = c.k8sClient.CoreV1().Secrets(c.namespace).Create(ctx.Context, &secret, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("could not create secret: %w", err)
}
klog.Info("certsManagerController Sync successfully created secret")
return nil
}