0b300cbe42
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>
151 lines
4.9 KiB
Go
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
|
|
}
|