mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
03702476fa
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
268 lines
8.3 KiB
Go
268 lines
8.3 KiB
Go
package tls
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/kyverno/kyverno/api/kyverno"
|
|
"github.com/kyverno/kyverno/pkg/config"
|
|
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
|
|
corev1 "k8s.io/api/core/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
const (
|
|
// CertRenewalInterval is the renewal interval for rootCA
|
|
CertRenewalInterval = 12 * time.Hour
|
|
// CAValidityDuration is the valid duration for CA certificates
|
|
CAValidityDuration = 365 * 24 * time.Hour
|
|
// TLSValidityDuration is the valid duration for TLS certificates
|
|
TLSValidityDuration = 150 * 24 * time.Hour
|
|
// managedByLabel is added to Kyverno managed secrets
|
|
managedByLabel = "cert.kyverno.io/managed-by"
|
|
rootCAKey = "rootCA.crt"
|
|
)
|
|
|
|
type CertValidator interface {
|
|
// ValidateCert checks the certificates validity
|
|
ValidateCert(context.Context) (bool, error)
|
|
}
|
|
|
|
type CertRenewer interface {
|
|
// RenewCA renews the CA certificate if needed
|
|
RenewCA(context.Context) error
|
|
// RenewTLS renews the TLS certificate if needed
|
|
RenewTLS(context.Context) error
|
|
}
|
|
|
|
// certRenewer creates rootCA and pem pair to register
|
|
// webhook configurations and webhook server
|
|
// renews RootCA at the given interval
|
|
type certRenewer struct {
|
|
client controllerutils.ObjectClient[*corev1.Secret]
|
|
certRenewalInterval time.Duration
|
|
caValidityDuration time.Duration
|
|
tlsValidityDuration time.Duration
|
|
|
|
// server is an IP address or domain name where Kyverno controller runs. Only required if out-of-cluster.
|
|
server string
|
|
}
|
|
|
|
// NewCertRenewer returns an instance of CertRenewer
|
|
func NewCertRenewer(
|
|
client controllerutils.ObjectClient[*corev1.Secret],
|
|
certRenewalInterval,
|
|
caValidityDuration,
|
|
tlsValidityDuration time.Duration,
|
|
server string,
|
|
) *certRenewer {
|
|
return &certRenewer{
|
|
client: client,
|
|
certRenewalInterval: certRenewalInterval,
|
|
caValidityDuration: caValidityDuration,
|
|
tlsValidityDuration: tlsValidityDuration,
|
|
server: server,
|
|
}
|
|
}
|
|
|
|
// RenewCA renews the CA certificate if needed
|
|
func (c *certRenewer) RenewCA(ctx context.Context) error {
|
|
secret, key, certs, err := c.decodeCASecret(ctx)
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
logger.Error(err, "failed to read CA")
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
certs = removeExpiredCertificates(now, certs...)
|
|
if !allCertificatesExpired(now.Add(5*c.certRenewalInterval), certs...) {
|
|
logger.V(4).Info("CA certificate does not need to be renewed")
|
|
return nil
|
|
}
|
|
if !isSecretManagedByKyverno(secret) {
|
|
err := fmt.Errorf("tls is not valid but certificates are not managed by kyverno, we can't renew them")
|
|
logger.Error(err, "tls is not valid but certificates are not managed by kyverno, we can't renew them")
|
|
return err
|
|
}
|
|
if secret != nil && secret.Type != corev1.SecretTypeTLS {
|
|
logger.Info("CA secret type is not TLS, we're going to delete it and regenrate one")
|
|
err := c.client.Delete(ctx, secret.Name, metav1.DeleteOptions{})
|
|
if err != nil {
|
|
logger.Error(err, "failed to delete CA secret")
|
|
}
|
|
return err
|
|
}
|
|
caKey, caCert, err := generateCA(key, c.caValidityDuration)
|
|
if err != nil {
|
|
logger.Error(err, "failed to generate CA")
|
|
return err
|
|
}
|
|
certs = append(certs, caCert)
|
|
if err := c.writeCASecret(ctx, caKey, certs...); err != nil {
|
|
logger.Error(err, "failed to write CA")
|
|
return err
|
|
}
|
|
logger.Info("CA was renewed")
|
|
return nil
|
|
}
|
|
|
|
// RenewTLS renews the TLS certificate if needed
|
|
func (c *certRenewer) RenewTLS(ctx context.Context) error {
|
|
_, caKey, caCerts, err := c.decodeCASecret(ctx)
|
|
if err != nil {
|
|
logger.Error(err, "failed to read CA")
|
|
return err
|
|
}
|
|
secret, _, cert, err := c.decodeTLSSecret(ctx)
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
logger.Error(err, "failed to read TLS")
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
if cert != nil && !allCertificatesExpired(now.Add(5*c.certRenewalInterval), cert) {
|
|
logger.V(4).Info("TLS certificate does not need to be renewed")
|
|
return nil
|
|
}
|
|
if !isSecretManagedByKyverno(secret) {
|
|
err := fmt.Errorf("tls is not valid but certificates are not managed by kyverno, we can't renew them")
|
|
logger.Error(err, "tls is not valid but certificates are not managed by kyverno, we can't renew them")
|
|
return err
|
|
}
|
|
if secret != nil && secret.Type != corev1.SecretTypeTLS {
|
|
logger.Info("TLS secret type is not TLS, we're going to delete it and regenrate one")
|
|
err := c.client.Delete(ctx, secret.Name, metav1.DeleteOptions{})
|
|
if err != nil {
|
|
logger.Error(err, "failed to delete TLS secret")
|
|
}
|
|
return err
|
|
}
|
|
tlsKey, tlsCert, err := generateTLS(c.server, caCerts[len(caCerts)-1], caKey, c.tlsValidityDuration)
|
|
if err != nil {
|
|
logger.Error(err, "failed to generate TLS")
|
|
return err
|
|
}
|
|
if err := c.writeTLSSecret(ctx, tlsKey, tlsCert); err != nil {
|
|
logger.Error(err, "failed to write TLS")
|
|
return err
|
|
}
|
|
logger.Info("TLS was renewed")
|
|
return nil
|
|
}
|
|
|
|
// ValidateCert validates the CA Cert
|
|
func (c *certRenewer) ValidateCert(ctx context.Context) (bool, error) {
|
|
_, _, caCerts, err := c.decodeCASecret(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
_, _, cert, err := c.decodeTLSSecret(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return validateCert(time.Now(), cert, caCerts...), nil
|
|
}
|
|
|
|
func (c *certRenewer) getSecret(ctx context.Context, name string) (*corev1.Secret, error) {
|
|
if s, err := c.client.Get(ctx, name, metav1.GetOptions{}); err != nil {
|
|
return nil, err
|
|
} else {
|
|
return s, nil
|
|
}
|
|
}
|
|
|
|
func (c *certRenewer) decodeSecret(ctx context.Context, name string) (*corev1.Secret, *rsa.PrivateKey, []*x509.Certificate, error) {
|
|
secret, err := c.getSecret(ctx, name)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
var certBytes, keyBytes []byte
|
|
if secret != nil {
|
|
keyBytes = secret.Data[corev1.TLSPrivateKeyKey]
|
|
certBytes = secret.Data[corev1.TLSCertKey]
|
|
if len(certBytes) == 0 {
|
|
certBytes = secret.Data[rootCAKey]
|
|
}
|
|
}
|
|
var key *rsa.PrivateKey
|
|
if keyBytes != nil {
|
|
usedkey, err := pemToPrivateKey(keyBytes)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
key = usedkey
|
|
}
|
|
return secret, key, pemToCertificates(certBytes), nil
|
|
}
|
|
|
|
func (c *certRenewer) decodeCASecret(ctx context.Context) (*corev1.Secret, *rsa.PrivateKey, []*x509.Certificate, error) {
|
|
return c.decodeSecret(ctx, GenerateRootCASecretName())
|
|
}
|
|
|
|
func (c *certRenewer) decodeTLSSecret(ctx context.Context) (*corev1.Secret, *rsa.PrivateKey, *x509.Certificate, error) {
|
|
secret, key, certs, err := c.decodeSecret(ctx, GenerateTLSPairSecretName())
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if len(certs) == 0 {
|
|
return secret, key, nil, nil
|
|
} else if len(certs) == 1 {
|
|
return secret, key, certs[0], nil
|
|
} else {
|
|
return nil, nil, nil, err
|
|
}
|
|
}
|
|
|
|
func (c *certRenewer) writeSecret(ctx context.Context, name string, key *rsa.PrivateKey, certs ...*x509.Certificate) error {
|
|
logger := logger.WithValues("name", name, "namespace", config.KyvernoNamespace())
|
|
secret, err := c.getSecret(ctx, name)
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
logger.Error(err, "failed to get CA secret")
|
|
return err
|
|
}
|
|
if secret == nil {
|
|
secret = &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: config.KyvernoNamespace(),
|
|
Labels: map[string]string{
|
|
managedByLabel: kyverno.ValueKyvernoApp,
|
|
},
|
|
},
|
|
Type: corev1.SecretTypeTLS,
|
|
}
|
|
}
|
|
secret.Type = corev1.SecretTypeTLS
|
|
secret.Data = map[string][]byte{
|
|
corev1.TLSCertKey: certificateToPem(certs...),
|
|
corev1.TLSPrivateKeyKey: privateKeyToPem(key),
|
|
}
|
|
if secret.ResourceVersion == "" {
|
|
if _, err := c.client.Create(ctx, secret, metav1.CreateOptions{}); err != nil {
|
|
logger.Error(err, "failed to update secret")
|
|
return err
|
|
} else {
|
|
logger.Info("secret created")
|
|
}
|
|
} else {
|
|
if _, err := c.client.Update(ctx, secret, metav1.UpdateOptions{}); err != nil {
|
|
logger.Error(err, "failed to update secret")
|
|
return err
|
|
} else {
|
|
logger.Info("secret updated")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// writeCASecret stores the CA cert in secret
|
|
func (c *certRenewer) writeCASecret(ctx context.Context, key *rsa.PrivateKey, certs ...*x509.Certificate) error {
|
|
return c.writeSecret(ctx, GenerateRootCASecretName(), key, certs...)
|
|
}
|
|
|
|
// writeTLSSecret Writes the pair of TLS certificate and key to the specified secret.
|
|
func (c *certRenewer) writeTLSSecret(ctx context.Context, key *rsa.PrivateKey, cert *x509.Certificate) error {
|
|
return c.writeSecret(ctx, GenerateTLSPairSecretName(), key, cert)
|
|
}
|