fix(cert): checking api server certificate SAN entries (#641)

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
This commit is contained in:
Dario Tranchitella
2024-12-01 15:06:02 +01:00
committed by GitHub
parent 7904b4d04a
commit 11e1e6c25b
2 changed files with 49 additions and 8 deletions

View File

@@ -13,9 +13,11 @@ import (
"fmt"
"math/big"
mathrand "math/rand"
"net"
"time"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/sets"
)
// CheckPublicAndPrivateKeyValidity checks if the given bytes for the private and public keys are valid.
@@ -37,6 +39,39 @@ func CheckPublicAndPrivateKeyValidity(publicKey []byte, privateKey []byte) (bool
return checkPublicKeys(privKey.PublicKey, *pubKey), nil
}
// CheckCertificateSAN checks if the Kubernetes API Server certificate matches the SAN stored in the kubeadm:
// it must check both IPs and DNS names, and returns a false if the required entry isn't available.
// In case of removal of entries, this function returns true nevertheless to avoid reloading a Control Plane uselessly.
func CheckCertificateSAN(certificateBytes []byte, certSANs []string) (bool, error) {
crt, err := ParseCertificateBytes(certificateBytes)
if err != nil {
return false, err
}
ips := sets.New[string]()
for _, ip := range crt.IPAddresses {
ips.Insert(ip.String())
}
dns := sets.New[string](crt.DNSNames...)
for _, e := range certSANs {
if ip := net.ParseIP(e); ip != nil {
if !ips.Has(ip.String()) {
return false, nil
}
continue
}
if !dns.Has(e) {
return false, nil
}
}
return true, nil
}
// CheckCertificateAndPrivateKeyPairValidity checks if the certificate and private key pair are valid.
func CheckCertificateAndPrivateKeyPairValidity(certificate []byte, privateKey []byte) (bool, error) {
switch {

View File

@@ -84,6 +84,14 @@ func (r *APIServerCertificate) UpdateTenantControlPlaneStatus(_ context.Context,
func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
return func() error {
logger := log.FromContext(ctx, "resource", r.GetName())
// The Kubeadm configuration must be retrieved in advance:
// this is required to check also the certificate SAN
config, kadmErr := getStoredKubeadmConfiguration(ctx, r.Client, r.TmpDirectory, tenantControlPlane)
if kadmErr != nil {
logger.Error(kadmErr, "cannot retrieve stored kubeadm configuration", "err", kadmErr.Error())
return fmt.Errorf("failed to generate certificate and private key: %w", kadmErr)
}
// Retrieving the TenantControlPlane CA:
// this is required to trigger a new generation in case of Certificate Authority rotation.
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
@@ -121,18 +129,16 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
logger.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.APIServerCertAndKeyBaseName, err.Error()))
}
if isCAValid && isCertValid {
dnsNamesMatches, dnsErr := crypto.CheckCertificateSAN(r.resource.Data[kubeadmconstants.APIServerCertName], config.InitConfiguration.APIServer.CertSANs)
if dnsErr != nil {
logger.Info(fmt.Sprintf("%s SAN check returned an error: %s", kubeadmconstants.APIServerCertAndKeyBaseName, err.Error()))
}
if isCAValid && isCertValid && dnsNamesMatches {
return nil
}
}
config, err := getStoredKubeadmConfiguration(ctx, r.Client, r.TmpDirectory, tenantControlPlane)
if err != nil {
logger.Error(err, "cannot generate certificate and private key in api server certificate", "details", err.Error())
return fmt.Errorf("failed to generate certificate and private key: %w", err)
}
ca := kubeadm.CertificatePrivateKeyPair{
Name: kubeadmconstants.CACertAndKeyBaseName,
Certificate: secretCA.Data[kubeadmconstants.CACertName],