From 62fb8f6cce6e51e39dff9227da097b3814f5ccd6 Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Thu, 25 Sep 2025 15:16:54 +0300 Subject: [PATCH] Reuse a secret then it's there and ignore missing PCRs This allows the operator to re-use an existing passphrase but let the sealed volume be re-created automatically (so decryption can still happen, we don't loose the original passphrase). Also allows the operator to skip a PCR (e.g. 11) if they want to by simply removing it after the initial enrollement or by manuall creating the initial sealed volume but only with the PCRs they are interested in by setting those to empty strings. This is useful if a PCR is expected to change often, e.g. PCR 11 because of kernel upgrades. Signed-off-by: Dimitris Karakasilis --- pkg/challenger/challenger.go | 48 +++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/pkg/challenger/challenger.go b/pkg/challenger/challenger.go index f20c53b..7839d93 100644 --- a/pkg/challenger/challenger.go +++ b/pkg/challenger/challenger.go @@ -21,6 +21,7 @@ import ( "github.com/kairos-io/kairos-challenger/controllers" tpm "github.com/kairos-io/tpm-helpers" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" @@ -121,8 +122,10 @@ func generateTOFUPassphrase() (string, error) { return passphrase, nil } -// createTOFUSecret creates a Kubernetes secret containing the generated passphrase -func createTOFUSecret(kclient *kubernetes.Clientset, namespace, secretName, secretPath, passphrase string) error { +// createOrReuseTOFUSecret creates a Kubernetes secret containing the generated passphrase +// If a secret with the same name already exists, it returns the existing passphrase +// Returns the passphrase that should be used (either new or existing) +func createOrReuseTOFUSecret(kclient *kubernetes.Clientset, namespace, secretName, secretPath, passphrase string, logger logr.Logger) (string, error) { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: secretName, @@ -136,10 +139,29 @@ func createTOFUSecret(kclient *kubernetes.Clientset, namespace, secretName, secr _, err := kclient.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) if err != nil { - return fmt.Errorf("creating TOFU secret: %w", err) + if errors.IsAlreadyExists(err) { + // Secret exists - this can happen when a SealedVolume was deleted but secret remained + // Retrieve and return the existing passphrase + logger.Info("Secret already exists, reusing existing secret", "secretName", secretName, "reason", "previous SealedVolume may have been deleted") + + existingSecret, getErr := kclient.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + if getErr != nil { + return "", fmt.Errorf("retrieving existing secret: %w", getErr) + } + + existingPassphrase, exists := existingSecret.Data[secretPath] + if !exists || len(existingPassphrase) == 0 { + return "", fmt.Errorf("existing secret does not contain expected passphrase data at path %s", secretPath) + } + + logger.Info("Successfully retrieved passphrase from existing secret", "secretName", secretName) + return string(existingPassphrase), nil + } + return "", fmt.Errorf("creating TOFU secret: %w", err) } - return nil + logger.Info("Successfully created new TOFU secret", "secretName", secretName) + return passphrase, nil } // createTOFUSealedVolumeWithPCRs creates a SealedVolume resource for automatic TOFU enrollment with PCR values @@ -639,8 +661,10 @@ func performInitialEnrollment(ctx *EnrollmentContext, attestation *ClientAttesta return fmt.Errorf("generating TOFU passphrase: %w", err) } - // Create Kubernetes secret - if err := createTOFUSecret(kclient, namespace, secretName, secretPath, passphrase); err != nil { + // Create Kubernetes secret (or reuse if it already exists from a previous enrollment) + logger.Info("Creating TOFU secret", "secretName", secretName, "secretPath", secretPath) + actualPassphrase, err := createOrReuseTOFUSecret(kclient, namespace, secretName, secretPath, passphrase, logger) + if err != nil { return fmt.Errorf("creating TOFU secret: %w", err) } @@ -668,7 +692,12 @@ func performInitialEnrollment(ctx *EnrollmentContext, attestation *ClientAttesta PartitionLabel: volumeData.PartitionLabel, } - logger.Info("TOFU enrollment completed", "secretName", secretName, "secretPath", secretPath) + logger.Info("TOFU enrollment completed", "secretName", secretName, "secretPath", secretPath, "passphraseSource", func() string { + if actualPassphrase == passphrase { + return "newly_generated" + } + return "reused_existing" + }()) return nil } @@ -1011,8 +1040,9 @@ func updateAttestationDataSelective(attestation *keyserverv1alpha1.AttestationSp } for pcrIndex, currentValue := range currentPCRs.PCRs { - // Only update if stored value is empty (re-enrollment mode) - if storedValue, exists := attestation.PCRValues.PCRs[pcrIndex]; !exists || storedValue == "" { + // Only update if stored value exists AND is empty (re-enrollment mode) + // Omitted PCRs (not in the map) should be skipped entirely per spec + if storedValue, exists := attestation.PCRValues.PCRs[pcrIndex]; exists && storedValue == "" { attestation.PCRValues.PCRs[pcrIndex] = currentValue logger.Info("Updated PCR value during selective enrollment", "pcr", pcrIndex) updated = true