138 lines
4.6 KiB
Go
138 lines
4.6 KiB
Go
// Copyright 2020 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"
|
|
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.
|
|
aggregatedAPIServerCA, err := certauthority.New(pkix.Name{CommonName: c.generatedCACommonName}, c.certDuration)
|
|
if err != nil {
|
|
return fmt.Errorf("could not initialize CA: %w", err)
|
|
}
|
|
|
|
// Using the CA from above, create a TLS server cert for the aggregated API server to use.
|
|
serviceEndpoint := c.serviceNameForGeneratedCertCommonName + "." + c.namespace + ".svc"
|
|
aggregatedAPIServerTLSCert, err := aggregatedAPIServerCA.Issue(
|
|
pkix.Name{CommonName: serviceEndpoint},
|
|
[]string{serviceEndpoint},
|
|
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(aggregatedAPIServerTLSCert)
|
|
if err != nil {
|
|
return fmt.Errorf("could not PEM encode serving certificate: %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(aggregatedAPIServerCA.Bundle()),
|
|
tlsPrivateKeySecretKey: string(tlsPrivateKeyPEM),
|
|
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
|
|
}
|