20b21e8639
- The certs manager controller, along with its sibling certs expirer and certs observer controllers, are generally useful for any process that wants to create its own CA and TLS certs, but only if the updating of the APIService is not included in those controllers - So that functionality for updating APIServices is moved to a new controller which watches the same Secret which is used by those other controllers - Also parameterize `NewCertsManagerController` with the service name and the CA common name to make the controller more reusable
134 lines
4.3 KiB
Go
134 lines
4.3 KiB
Go
/*
|
|
Copyright 2020 VMware, Inc.
|
|
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"
|
|
|
|
"github.com/suzerain-io/pinniped/internal/certauthority"
|
|
pinnipedcontroller "github.com/suzerain-io/pinniped/internal/controller"
|
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
|
)
|
|
|
|
const (
|
|
//nolint: gosec
|
|
certsSecretName = "api-serving-cert"
|
|
caCertificateSecretKey = "caCertificate"
|
|
tlsPrivateKeySecretKey = "tlsPrivateKey"
|
|
tlsCertificateChainSecretKey = "tlsCertificateChain"
|
|
)
|
|
|
|
type certsManagerController struct {
|
|
namespace 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,
|
|
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,
|
|
k8sClient: k8sClient,
|
|
secretInformer: secretInformer,
|
|
certDuration: certDuration,
|
|
generatedCACommonName: generatedCACommonName,
|
|
serviceNameForGeneratedCertCommonName: serviceNameForGeneratedCertCommonName,
|
|
},
|
|
},
|
|
withInformer(
|
|
secretInformer,
|
|
pinnipedcontroller.NameAndNamespaceExactMatchFilterFactory(certsSecretName, 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: certsSecretName,
|
|
}),
|
|
)
|
|
}
|
|
|
|
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(certsSecretName)
|
|
notFound := k8serrors.IsNotFound(err)
|
|
if err != nil && !notFound {
|
|
return fmt.Errorf("failed to get %s/%s secret: %w", c.namespace, certsSecretName, 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: certsSecretName,
|
|
Namespace: c.namespace,
|
|
},
|
|
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
|
|
}
|