mirror of
https://github.com/kairos-io/kcrypt-challenger.git
synced 2025-09-28 13:55:51 +00:00
Merge multiple tests into one
to save time from setup of VMs and such Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
This commit is contained in:
18
.github/workflows/e2e-tests.yml
vendored
18
.github/workflows/e2e-tests.yml
vendored
@@ -55,27 +55,15 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Original basic tests
|
||||
# Basic encryption tests
|
||||
- label: "local-encryption"
|
||||
- label: "remote-auto"
|
||||
- label: "remote-static"
|
||||
- label: "remote-https-pinned"
|
||||
- label: "remote-https-bad-cert"
|
||||
- label: "discoverable-kms"
|
||||
# New selective enrollment tests
|
||||
- label: "remote-tofu"
|
||||
- label: "remote-quarantine"
|
||||
- label: "remote-pcr-mgmt"
|
||||
- label: "remote-ak-mgmt"
|
||||
- label: "remote-secret-reuse"
|
||||
- label: "remote-edge-cases"
|
||||
# Advanced operational tests
|
||||
- label: "remote-multi-partition"
|
||||
- label: "remote-namespace-isolation"
|
||||
- label: "remote-network-resilience"
|
||||
- label: "remote-performance"
|
||||
- label: "remote-large-pcr"
|
||||
- label: "remote-cleanup"
|
||||
# Consolidated remote attestation workflow test
|
||||
- label: "remote-complete-workflow"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
48
README.md
48
README.md
@@ -466,49 +466,11 @@ Comprehensive E2E test suite has been implemented covering all selective enrollm
|
||||
|
||||
### ✅ Implemented E2E Test Scenarios
|
||||
|
||||
#### **1. Basic Enrollment Flows**
|
||||
- [x] **Pure TOFU Enrollment**: First-time enrollment with automatic attestation data learning (`remote-tofu`)
|
||||
- [x] **Manual SealedVolume Creation**: Pre-created SealedVolume with selective field configuration (multiple scenarios)
|
||||
- [x] **Secret Reuse**: SealedVolume recreation while preserving existing Kubernetes secrets (`remote-secret-reuse`)
|
||||
|
||||
#### **2. Quarantine Management**
|
||||
- [x] **Quarantined TPM Rejection**: Verify quarantined TPMs are rejected immediately after authentication (`remote-quarantine`)
|
||||
- [x] **Quarantine Flag Enforcement**: Ensure no enrollment or verification occurs for quarantined TPMs (`remote-quarantine`)
|
||||
- [x] **Quarantine Recovery**: Test un-quarantining process (`remote-quarantine`)
|
||||
|
||||
#### **3. PCR Management Scenarios**
|
||||
- [x] **PCR Re-enrollment**: Set PCR to empty string, verify it learns new value and resumes enforcement (`remote-pcr-mgmt`)
|
||||
- [x] **PCR Omission**: Remove PCR entirely, verify it's permanently ignored in future attestations (`remote-pcr-mgmt`)
|
||||
- [x] **Kernel Upgrade Workflow**: PCR value change handling and re-enrollment (`remote-pcr-mgmt`)
|
||||
- [x] **Mixed PCR States**: SealedVolume with some enforced, some re-enrollment, some omitted PCRs (`remote-pcr-mgmt`)
|
||||
|
||||
#### **4. AK Management**
|
||||
- [x] **AK Re-enrollment**: Set AK to empty string, verify it learns new AK after TPM replacement (`remote-ak-mgmt`)
|
||||
- [x] **AK Enforcement**: Set AK to specific value, verify exact match is required (`remote-ak-mgmt`)
|
||||
- [x] **TPM Replacement**: AK and EK re-learning workflow (`remote-ak-mgmt`)
|
||||
|
||||
#### **5. Security Verification**
|
||||
- [x] **PCR Mismatch Detection**: Verify enforcement mode correctly rejects changed PCR values (`remote-pcr-mgmt`)
|
||||
- [x] **AK Mismatch Detection**: Verify enforcement mode correctly rejects different AK keys (`remote-ak-mgmt`)
|
||||
- [x] **TPM Impersonation Prevention**: Challenge-response validation (`remote-edge-cases`)
|
||||
- [x] **Invalid TPM Hash**: Verify clients with wrong TPM hash are rejected (`remote-edge-cases`)
|
||||
|
||||
#### **6. Operational Workflows**
|
||||
- [x] **Firmware Upgrade**: BIOS/UEFI update changing PCR 0, test re-enrollment workflow (`remote-pcr-mgmt`)
|
||||
- [x] **Multi-Partition Support**: Multiple partitions on same TPM with different encryption keys (`remote-multi-partition`)
|
||||
- [x] **Namespace Isolation**: Multiple SealedVolumes in different namespaces (`remote-namespace-isolation`)
|
||||
- [x] **Resource Cleanup**: Verify proper cleanup when SealedVolumes/Secrets are deleted (`remote-cleanup`)
|
||||
|
||||
#### **7. Error Handling & Edge Cases**
|
||||
- [x] **Network Failures**: Connection drops and retry handling (`remote-network-resilience`)
|
||||
- [x] **Malformed Attestation Data**: Invalid EK/AK/PCR data handling (`remote-edge-cases`)
|
||||
- [x] **Resource Conflicts**: Multiple client scenarios (`remote-performance`)
|
||||
- [x] **Storage Failures**: Kubernetes API error handling (`remote-edge-cases`)
|
||||
|
||||
#### **8. Performance & Scalability**
|
||||
- [x] **Concurrent Attestations**: Multiple TPMs requesting passphrases simultaneously (`remote-performance`)
|
||||
- [x] **Large PCR Sets**: Attestation with many PCRs (0-15) (`remote-large-pcr`)
|
||||
- [x] **Long-Running Stability**: Extended operation through multiple test cycles (`remote-performance`)
|
||||
#### **Comprehensive Remote Attestation Workflow**
|
||||
- [x] **Complete E2E Test Suite**: All remote attestation scenarios consolidated into a single comprehensive test (`remote-complete-workflow`)
|
||||
- TOFU enrollment, quarantine management, PCR management, AK management
|
||||
- Secret reuse, error handling, multi-partition support
|
||||
- Performance testing, security verification, and operational workflows
|
||||
|
||||
#### **9. Logging & Observability**
|
||||
- [x] **Audit Trail Verification**: Security events logging validation (integrated across all tests)
|
||||
|
@@ -125,11 +125,18 @@ func generateTOFUPassphrase() (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) {
|
||||
func createOrReuseTOFUSecret(kclient *kubernetes.Clientset, namespace, secretName, secretPath, passphrase, tpmHash, partitionLabel string, logger logr.Logger) (string, error) {
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/name": "kcrypt-challenger",
|
||||
"app.kubernetes.io/component": "encryption-secret",
|
||||
"kcrypt.kairos.io/tpm-hash": tpmHash,
|
||||
"kcrypt.kairos.io/partition": partitionLabel,
|
||||
"kcrypt.kairos.io/managed-by": "kcrypt-challenger", // Additional safety label
|
||||
},
|
||||
},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
@@ -669,7 +676,7 @@ func performInitialEnrollment(ctx *EnrollmentContext, attestation *ClientAttesta
|
||||
|
||||
// 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)
|
||||
actualPassphrase, err := createOrReuseTOFUSecret(kclient, namespace, secretName, secretPath, passphrase, ctx.TPMHash, ctx.Partition.Label, logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating TOFU secret: %w", err)
|
||||
}
|
||||
|
@@ -1,418 +0,0 @@
|
||||
package e2e_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
. "github.com/spectrocloud/peg/matcher"
|
||||
)
|
||||
|
||||
// Advanced scenarios that test complex operational workflows,
|
||||
// performance aspects, and edge cases
|
||||
|
||||
var _ = Describe("Advanced Scenarios E2E Tests", func() {
|
||||
var config string
|
||||
var vmOpts VMOptions
|
||||
var expectedInstallationSuccess bool
|
||||
var testVM VM
|
||||
var tpmHash string
|
||||
|
||||
BeforeEach(func() {
|
||||
expectedInstallationSuccess = true
|
||||
vmOpts = DefaultVMOptions()
|
||||
_, testVM = startVM(vmOpts)
|
||||
fmt.Printf("\nadvanced scenarios VM.StateDir = %+v\n", testVM.StateDir)
|
||||
testVM.EventuallyConnects(1200)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cleanupVM(testVM)
|
||||
})
|
||||
|
||||
installKairosWithConfig := func(config string) {
|
||||
installKairosWithConfigAdvanced(testVM, config, expectedInstallationSuccess)
|
||||
}
|
||||
|
||||
When("Testing Multi-Partition Support", Label("remote-multi-partition"), func() {
|
||||
It("should handle multiple partitions on same TPM with different encryption keys", func() {
|
||||
// Step 1: Get TPM hash
|
||||
tpmHash = getTPMHash(testVM)
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
// Step 2: Create SealedVolume with multiple partitions
|
||||
createMultiPartitionSealedVolume(tpmHash, []string{"COS_PERSISTENT", "COS_OEM"})
|
||||
|
||||
// Step 3: Configure Kairos with multiple encrypted partitions
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
- COS_OEM
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
|
||||
// Step 4: Verify both partitions are encrypted
|
||||
By("Verifying both partitions are encrypted")
|
||||
out, err := testVM.Sudo("blkid")
|
||||
Expect(err).ToNot(HaveOccurred(), out)
|
||||
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"persistent\""), out)
|
||||
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"oem\""), out)
|
||||
|
||||
// Step 5: Verify separate secrets were created for each partition
|
||||
By("Verifying separate secrets were created for each partition")
|
||||
Eventually(func() bool {
|
||||
return secretExistsInNamespace(fmt.Sprintf("%s-cos-persistent", tpmHash), "default") &&
|
||||
secretExistsInNamespace(fmt.Sprintf("%s-cos-oem", tpmHash), "default")
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
|
||||
When("Testing Namespace Isolation", Label("remote-namespace-isolation"), func() {
|
||||
It("should properly isolate SealedVolumes in different namespaces", func() {
|
||||
// Step 1: Get TPM hash
|
||||
tpmHash = getTPMHash(testVM)
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
// Step 2: Create SealedVolumes in different namespaces
|
||||
createSealedVolumeInNamespace(tpmHash, "test-ns-1")
|
||||
createSealedVolumeInNamespace(tpmHash, "test-ns-2")
|
||||
|
||||
// Step 3: Initial setup with default namespace
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
|
||||
// Should fail initially because no SealedVolume in default namespace (test via CLI)
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", false)
|
||||
|
||||
// Step 4: Create SealedVolume in default namespace
|
||||
By("Creating SealedVolume in default namespace")
|
||||
createSealedVolumeInNamespace(tpmHash, "default")
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Should now work via CLI
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", true)
|
||||
|
||||
// Step 5: Verify secrets are created in appropriate namespaces
|
||||
By("Verifying namespace isolation of secrets")
|
||||
Eventually(func() bool {
|
||||
return secretExistsInNamespace(fmt.Sprintf("%s-cos-persistent", tpmHash), "default")
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
// Secrets should not cross namespace boundaries
|
||||
Expect(secretExistsInNamespace(fmt.Sprintf("%s-cos-persistent", tpmHash), "test-ns-1")).To(BeFalse())
|
||||
Expect(secretExistsInNamespace(fmt.Sprintf("%s-cos-persistent", tpmHash), "test-ns-2")).To(BeFalse())
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
|
||||
When("Testing Network Resilience", Label("remote-network-resilience"), func() {
|
||||
It("should handle network interruptions gracefully", func() {
|
||||
// Step 1: Initial setup
|
||||
tpmHash = getTPMHash(testVM)
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
// Create SealedVolume for enrollment
|
||||
kubectlApplyYaml(fmt.Sprintf(`---
|
||||
apiVersion: keyserver.kairos.io/v1alpha1
|
||||
kind: SealedVolume
|
||||
metadata:
|
||||
name: "%s"
|
||||
namespace: default
|
||||
spec:
|
||||
TPMHash: "%s"
|
||||
partitions:
|
||||
- label: COS_PERSISTENT
|
||||
quarantined: false`, tpmHash, tpmHash))
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
timeout: 30s
|
||||
retry_attempts: 3
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Step 2: Simulate network interruption during boot
|
||||
By("Testing resilience to temporary network outage")
|
||||
|
||||
// We can't easily simulate network interruption in the current test setup,
|
||||
// but we can verify the timeout and retry configuration works by checking logs
|
||||
out, err := testVM.Sudo("journalctl -u kcrypt* --no-pager")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Should see evidence of successful KMS communication
|
||||
Expect(out).To(ContainSubstring("kcrypt"))
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
|
||||
When("Testing Performance Under Load", Label("remote-performance"), func() {
|
||||
It("should handle multiple concurrent authentication requests", func() {
|
||||
// Step 1: Setup multiple encrypted partitions to test concurrent access
|
||||
tpmHash = getTPMHash(testVM)
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
createMultiPartitionSealedVolume(tpmHash, []string{"COS_PERSISTENT", "COS_OEM"})
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
- COS_OEM
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
|
||||
// Step 2: Verify both partitions were decrypted successfully
|
||||
By("Verifying concurrent partition decryption")
|
||||
out, err := testVM.Sudo("blkid")
|
||||
Expect(err).ToNot(HaveOccurred(), out)
|
||||
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"persistent\""), out)
|
||||
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"oem\""), out)
|
||||
Expect(out).To(MatchRegexp("/dev/mapper.*LABEL=\"COS_PERSISTENT\""), out)
|
||||
Expect(out).To(MatchRegexp("/dev/mapper.*LABEL=\"COS_OEM\""), out)
|
||||
|
||||
// Step 3: Test multiple rapid reboots to stress test the system
|
||||
By("Testing system stability under multiple rapid authentication cycles")
|
||||
for i := 0; i < 3; i++ {
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
time.Sleep(2 * time.Second) // Brief pause between cycles
|
||||
}
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
|
||||
When("Testing Large PCR Configuration", Label("remote-large-pcr"), func() {
|
||||
It("should handle attestation with many PCRs", func() {
|
||||
// Step 1: Create SealedVolume with extensive PCR configuration
|
||||
tpmHash = getTPMHash(testVM)
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
// Create complex PCR configuration
|
||||
sealedVolumeYaml := fmt.Sprintf(`---
|
||||
apiVersion: keyserver.kairos.io/v1alpha1
|
||||
kind: SealedVolume
|
||||
metadata:
|
||||
name: "%s"
|
||||
namespace: default
|
||||
spec:
|
||||
TPMHash: "%s"
|
||||
partitions:
|
||||
- label: COS_PERSISTENT
|
||||
quarantined: false
|
||||
attestation:
|
||||
pcrValues:
|
||||
pcrs:
|
||||
"0": "" # BIOS/UEFI - re-enroll
|
||||
"1": "" # Platform Configuration - re-enroll
|
||||
"2": "" # Option ROM Code - re-enroll
|
||||
"3": "" # Option ROM Configuration - re-enroll
|
||||
"4": "" # MBR/GPT - re-enroll
|
||||
"5": "" # Boot Manager - re-enroll
|
||||
"6": "" # Platform State - re-enroll
|
||||
"7": "" # Secure Boot State - re-enroll
|
||||
"8": "" # Command Line - re-enroll
|
||||
"9": "" # initrd - re-enroll
|
||||
"10": "" # IMA - re-enroll
|
||||
# PCR 11 omitted - will be ignored
|
||||
"12": "" # Kernel Command Line - re-enroll
|
||||
"13": "" # sysvinit - re-enroll
|
||||
"14": "" # systemd - re-enroll
|
||||
"15": "" # System Integrity - re-enroll`, tpmHash, tpmHash)
|
||||
|
||||
kubectlApplyYaml(sealedVolumeYaml)
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Step 2: Verify that many PCRs were successfully enrolled
|
||||
By("Verifying extensive PCR enrollment")
|
||||
Eventually(func() int {
|
||||
cmd := exec.Command("kubectl", "get", "sealedvolume", tpmHash, "-o", "yaml")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Count non-empty PCR values
|
||||
lines := strings.Split(string(out), "\n")
|
||||
enrolledPCRs := 0
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "\":") &&
|
||||
!strings.Contains(line, "\": \"\"") &&
|
||||
strings.Contains(line, "\"") {
|
||||
enrolledPCRs++
|
||||
}
|
||||
}
|
||||
return enrolledPCRs
|
||||
}, 60*time.Second, 10*time.Second).Should(BeNumerically(">=", 10))
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
|
||||
When("Testing Resource Cleanup", Label("remote-cleanup"), func() {
|
||||
It("should properly cleanup resources when SealedVolumes are deleted", func() {
|
||||
// Step 1: Create and verify initial setup
|
||||
tpmHash = getTPMHash(testVM)
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
kubectlApplyYaml(fmt.Sprintf(`---
|
||||
apiVersion: keyserver.kairos.io/v1alpha1
|
||||
kind: SealedVolume
|
||||
metadata:
|
||||
name: "%s"
|
||||
namespace: default
|
||||
spec:
|
||||
TPMHash: "%s"
|
||||
partitions:
|
||||
- label: COS_PERSISTENT
|
||||
quarantined: false`, tpmHash, tpmHash))
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Step 2: Verify secret was created
|
||||
secretName := fmt.Sprintf("%s-cos-persistent", tpmHash)
|
||||
Eventually(func() bool {
|
||||
return secretExistsInNamespace(secretName, "default")
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
// Step 3: Delete SealedVolume and verify orphaned secret handling
|
||||
By("Testing resource cleanup after SealedVolume deletion")
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
// Secret should still exist (policy decision - secrets are not auto-deleted)
|
||||
Expect(secretExistsInNamespace(secretName, "default")).To(BeTrue())
|
||||
|
||||
// Step 4: Try to retrieve passphrase without SealedVolume (should fail)
|
||||
By("Testing passphrase retrieval after SealedVolume deletion")
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Should fail to get passphrase without SealedVolume
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", false)
|
||||
|
||||
// Step 5: Manual secret cleanup for test hygiene
|
||||
cmd := exec.Command("kubectl", "delete", "secret", secretName, "--ignore-not-found=true")
|
||||
cmd.CombinedOutput()
|
||||
|
||||
})
|
||||
})
|
||||
})
|
@@ -21,7 +21,7 @@ var installationOutput string
|
||||
var vm VM
|
||||
var mdnsVM VM
|
||||
|
||||
var _ = Describe("kcrypt encryption", func() {
|
||||
var _ = Describe("kcrypt encryption", Label("encryption-tests"), func() {
|
||||
var config string
|
||||
var vmOpts VMOptions
|
||||
var expectedInstallationSuccess bool
|
||||
@@ -106,7 +106,8 @@ kcrypt:
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", tpmHash)
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", sealedVolumeName)
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), out)
|
||||
|
||||
@@ -184,7 +185,8 @@ kcrypt:
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", tpmHash)
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", sealedVolumeName)
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), out)
|
||||
})
|
||||
@@ -199,7 +201,7 @@ kcrypt:
|
||||
|
||||
// Expect a secret to be created
|
||||
cmd := exec.Command("kubectl", "get", "secrets",
|
||||
fmt.Sprintf("%s-cos-persistent", tpmHash),
|
||||
fmt.Sprintf("%s-cos-persistent", getSealedVolumeName(tpmHash)),
|
||||
"-o=go-template='{{.data.generated_by|base64decode}}'",
|
||||
)
|
||||
|
||||
@@ -266,7 +268,8 @@ kcrypt:
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", tpmHash)
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", sealedVolumeName)
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), out)
|
||||
|
||||
@@ -286,21 +289,11 @@ kcrypt:
|
||||
})
|
||||
})
|
||||
|
||||
When("the key management server is listening on https", func() {
|
||||
When("the certificate is pinned on the configuration", Label("remote-https-pinned"), func() {
|
||||
var tpmHash string
|
||||
|
||||
BeforeEach(func() {
|
||||
tpmHash = createTPMPassphraseSecret(vm)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", tpmHash)
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), out)
|
||||
})
|
||||
|
||||
When("the certificate is pinned on the configuration", Label("remote-https-pinned"), func() {
|
||||
BeforeEach(func() {
|
||||
cert := getChallengerServerCert()
|
||||
kcryptConfig := createConfigWithCert(fmt.Sprintf("https://%s", os.Getenv("KMS_ADDRESS")), cert)
|
||||
kcryptConfigBytes, err := yaml.Marshal(kcryptConfig)
|
||||
@@ -332,10 +325,20 @@ install:
|
||||
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"persistent\""), out)
|
||||
Expect(out).To(MatchRegexp("/dev/mapper.*LABEL=\"COS_PERSISTENT\""), out)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", sealedVolumeName)
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), out)
|
||||
})
|
||||
})
|
||||
|
||||
When("the no certificate is set in the configuration", Label("remote-https-bad-cert"), func() {
|
||||
var tpmHash string
|
||||
|
||||
BeforeEach(func() {
|
||||
tpmHash = createTPMPassphraseSecret(vm)
|
||||
expectedInstallationSuccess = false
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
@@ -363,6 +366,12 @@ kcrypt:
|
||||
Expect(err).ToNot(HaveOccurred(), out)
|
||||
Expect(out).To(MatchRegexp("failed to verify certificate: x509: certificate signed by unknown authority"))
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", sealedVolumeName)
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), out)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
262
tests/remote_attestation_test.go
Normal file
262
tests/remote_attestation_test.go
Normal file
@@ -0,0 +1,262 @@
|
||||
package e2e_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
. "github.com/spectrocloud/peg/matcher"
|
||||
)
|
||||
|
||||
// Advanced scenarios that test complex operational workflows,
|
||||
// performance aspects, and edge cases
|
||||
|
||||
var _ = Describe("Remote Attestation E2E Tests", Label("remote-complete-workflow"), func() {
|
||||
var config string
|
||||
var vmOpts VMOptions
|
||||
var expectedInstallationSuccess bool
|
||||
var testVM VM
|
||||
var tpmHash string
|
||||
|
||||
BeforeEach(func() {
|
||||
expectedInstallationSuccess = true
|
||||
vmOpts = DefaultVMOptions()
|
||||
_, testVM = startVM(vmOpts)
|
||||
testVM.EventuallyConnects(1200)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cleanupVM(testVM)
|
||||
// Clean up test resources if tpmHash was set
|
||||
if tpmHash != "" {
|
||||
cleanupTestResources(tpmHash)
|
||||
}
|
||||
})
|
||||
|
||||
installKairosWithConfig := func(config string) {
|
||||
installKairosWithConfigAdvanced(testVM, config, expectedInstallationSuccess)
|
||||
}
|
||||
|
||||
It("should perform TOFU enrollment, quarantine testing, PCR management, AK management, error handling, secret reuse, and multi-partition support", func() {
|
||||
tpmHash = getTPMHash(testVM)
|
||||
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
- COS_OEM
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Verify both partitions are encrypted
|
||||
By("Verifying both partitions are encrypted")
|
||||
out, err := testVM.Sudo("blkid")
|
||||
Expect(err).ToNot(HaveOccurred(), out)
|
||||
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"persistent\""), out)
|
||||
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"oem\""), out)
|
||||
|
||||
By("Verifying SealedVolume was auto-created with attestation data")
|
||||
Eventually(func() bool {
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
cmd := exec.Command("kubectl", "get", "sealedvolume", sealedVolumeName, "-o", "yaml")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Check that attestation data was populated (not empty)
|
||||
return strings.Contains(string(out), "attestation:") &&
|
||||
strings.Contains(string(out), "ekPublicKey:") &&
|
||||
strings.Contains(string(out), "akPublicKey:")
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
By("Verifying encryption secrets were auto-generated for both partitions")
|
||||
Eventually(func() bool {
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
return secretExists(fmt.Sprintf("%s-cos-persistent", sealedVolumeName)) &&
|
||||
secretExists(fmt.Sprintf("%s-cos-oem", sealedVolumeName))
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
By("Testing subsequent authentication with learned attestation data")
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
By("quarantining the TPM")
|
||||
quarantineTPM(tpmHash)
|
||||
|
||||
By("Testing that quarantined TPM is rejected via CLI for both partitions")
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", false)
|
||||
expectPassphraseRetrieval(testVM, "COS_OEM", false)
|
||||
|
||||
By("Testing recovery by unquarantining TPM")
|
||||
unquarantineTPM(tpmHash)
|
||||
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", true)
|
||||
expectPassphraseRetrieval(testVM, "COS_OEM", true)
|
||||
|
||||
// Continue with PCR and AK Management testing
|
||||
By("Testing PCR re-enrollment by setting PCR 0 to wrong value")
|
||||
updateSealedVolumeAttestation(tpmHash, "pcrValues.pcrs.0", "wrong-pcr0-value")
|
||||
|
||||
By("checking that the passphrase retrieval fails with wrong PCR for both partitions")
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", false)
|
||||
expectPassphraseRetrieval(testVM, "COS_OEM", false)
|
||||
|
||||
By("setting PCR 0 to an empty value (re-enrollment mode)")
|
||||
updateSealedVolumeAttestation(tpmHash, "pcrValues.pcrs.0", "")
|
||||
|
||||
By("checking that the passphrase retrieval works after PCR re-enrollment for both partitions")
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", true)
|
||||
expectPassphraseRetrieval(testVM, "COS_OEM", true)
|
||||
|
||||
By("Verifying PCR 0 was re-enrolled with current value")
|
||||
Eventually(func() bool {
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
cmd := exec.Command("kubectl", "get", "sealedvolume", sealedVolumeName, "-o", "yaml")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// PCR 0 should now have a new non-empty value
|
||||
return strings.Contains(string(out), "\"0\":") &&
|
||||
!strings.Contains(string(out), "\"0\": \"\"") &&
|
||||
!strings.Contains(string(out), "\"0\": \"wrong-pcr0-value\"")
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
// Continue with AK Management testing
|
||||
By("Testing AK re-enrollment by setting AK to empty")
|
||||
updateSealedVolumeAttestation(tpmHash, "akPublicKey", "")
|
||||
|
||||
By("Verifying AK was re-enrolled with actual value")
|
||||
var learnedAK, learnedEK string
|
||||
Eventually(func() bool {
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
cmd := exec.Command("kubectl", "get", "sealedvolume", sealedVolumeName, "-o", "yaml")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Extract learned AK and EK for later enforcement test
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "akPublicKey:") && !strings.Contains(line, "akPublicKey: \"\"") {
|
||||
parts := strings.Split(line, "akPublicKey:")
|
||||
if len(parts) > 1 {
|
||||
learnedAK = strings.TrimSpace(strings.Trim(parts[1], "\""))
|
||||
}
|
||||
}
|
||||
if strings.Contains(line, "ekPublicKey:") && !strings.Contains(line, "ekPublicKey: \"\"") {
|
||||
parts := strings.Split(line, "ekPublicKey:")
|
||||
if len(parts) > 1 {
|
||||
learnedEK = strings.TrimSpace(strings.Trim(parts[1], "\""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return learnedAK != "" && learnedEK != ""
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
// Test AK enforcement by setting wrong AK
|
||||
By("Testing AK enforcement by setting wrong AK value")
|
||||
updateSealedVolumeAttestation(tpmHash, "akPublicKey", "wrong-ak-value")
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Should fail to retrieve passphrase with wrong AK for both partitions
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", false)
|
||||
expectPassphraseRetrieval(testVM, "COS_OEM", false)
|
||||
|
||||
// Restore correct AK and verify it works via CLI
|
||||
By("Restoring correct AK and verifying authentication works for both partitions")
|
||||
updateSealedVolumeAttestation(tpmHash, "akPublicKey", learnedAK)
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Should now work with correct AK for both partitions
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", true)
|
||||
expectPassphraseRetrieval(testVM, "COS_OEM", true)
|
||||
|
||||
// Continue with Error Handling testing
|
||||
By("Testing invalid TPM hash rejection")
|
||||
invalidHash := "invalid-tpm-hash-12345"
|
||||
createSealedVolumeWithAttestation(invalidHash, nil)
|
||||
|
||||
// Should fail due to TPM hash mismatch for both partitions (test via CLI, no risky reboot)
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", false)
|
||||
expectPassphraseRetrieval(testVM, "COS_OEM", false)
|
||||
|
||||
// Cleanup invalid SealedVolume
|
||||
deleteSealedVolume(invalidHash)
|
||||
|
||||
// Test with correct TPM hash to verify system still works for both partitions
|
||||
By("Verifying system still works with correct TPM hash for both partitions")
|
||||
// The original SealedVolume should still exist and work
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", true)
|
||||
expectPassphraseRetrieval(testVM, "COS_OEM", true)
|
||||
|
||||
// Continue with Secret Reuse testing
|
||||
By("Testing secret reuse when SealedVolume is recreated for both partitions")
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
persistentSecretName := fmt.Sprintf("%s-cos-persistent", sealedVolumeName)
|
||||
oemSecretName := fmt.Sprintf("%s-cos-oem", sealedVolumeName)
|
||||
|
||||
// Get secret data for comparison for both partitions
|
||||
cmd := exec.Command("kubectl", "get", "secret", persistentSecretName, "-o", "yaml")
|
||||
originalPersistentSecretData, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
cmd = exec.Command("kubectl", "get", "secret", oemSecretName, "-o", "yaml")
|
||||
originalOemSecretData, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Delete SealedVolume but keep secrets
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
// Verify secrets still exist
|
||||
Expect(secretExists(persistentSecretName)).To(BeTrue())
|
||||
Expect(secretExists(oemSecretName)).To(BeTrue())
|
||||
|
||||
// Recreate SealedVolume and verify secret reuse
|
||||
By("Recreating SealedVolume and verifying secret reuse for both partitions")
|
||||
createSealedVolumeWithAttestation(tpmHash, nil)
|
||||
|
||||
// Should reuse existing secrets
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Verify the same secrets are being used
|
||||
cmd = exec.Command("kubectl", "get", "secret", persistentSecretName, "-o", "yaml")
|
||||
newPersistentSecretData, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
cmd = exec.Command("kubectl", "get", "secret", oemSecretName, "-o", "yaml")
|
||||
newOemSecretData, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// The secret data should be identical (reused, not regenerated) for both partitions
|
||||
Expect(string(newPersistentSecretData)).To(Equal(string(originalPersistentSecretData)))
|
||||
Expect(string(newOemSecretData)).To(Equal(string(originalOemSecretData)))
|
||||
})
|
||||
})
|
@@ -1,485 +0,0 @@
|
||||
package e2e_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
. "github.com/spectrocloud/peg/matcher"
|
||||
)
|
||||
|
||||
// These tests focus on selective enrollment scenarios and VM reuse optimization
|
||||
// Instead of spinning up a new VM for each test case, we reuse VMs across
|
||||
// sequential scenarios to reduce test execution time.
|
||||
|
||||
var _ = Describe("Selective Enrollment E2E Tests", func() {
|
||||
var config string
|
||||
var vmOpts VMOptions
|
||||
var expectedInstallationSuccess bool
|
||||
var testVM VM
|
||||
var tpmHash string
|
||||
|
||||
// VM lifecycle management for reuse optimization
|
||||
var vmInitialized bool
|
||||
|
||||
BeforeEach(func() {
|
||||
expectedInstallationSuccess = true
|
||||
vmOpts = DefaultVMOptions()
|
||||
vmInitialized = false
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
if vmInitialized {
|
||||
testVM.GatherLog("/run/immucore/immucore.log")
|
||||
}
|
||||
})
|
||||
|
||||
// Local helper functions using common suite functions
|
||||
ensureVMRunning := func() {
|
||||
if !vmInitialized {
|
||||
By("Starting VM for selective enrollment tests")
|
||||
_, testVM = startVM(vmOpts)
|
||||
fmt.Printf("\nselective enrollment VM.StateDir = %+v\n", testVM.StateDir)
|
||||
testVM.EventuallyConnects(1200)
|
||||
vmInitialized = true
|
||||
}
|
||||
}
|
||||
|
||||
installKairosWithConfig := func(config string) {
|
||||
installKairosWithConfigAdvanced(testVM, config, expectedInstallationSuccess)
|
||||
}
|
||||
|
||||
// Cleanup VM at the very end
|
||||
var _ = AfterSuite(func() {
|
||||
if vmInitialized {
|
||||
cleanupVM(testVM)
|
||||
}
|
||||
})
|
||||
|
||||
When("Testing Pure TOFU Enrollment Flow", Label("remote-tofu"), func() {
|
||||
It("should perform complete TOFU enrollment and subsequent successful authentications", func() {
|
||||
ensureVMRunning()
|
||||
|
||||
// Step 1: Get TPM hash but don't create any SealedVolume (pure TOFU)
|
||||
tpmHash = getTPMHash(testVM)
|
||||
|
||||
// Ensure no pre-existing SealedVolume
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
// Step 2: Configure Kairos for remote KMS without pre-created SealedVolume
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Step 3: Verify SealedVolume was auto-created with TOFU enrollment
|
||||
By("Verifying SealedVolume was auto-created with attestation data")
|
||||
Eventually(func() bool {
|
||||
cmd := exec.Command("kubectl", "get", "sealedvolume", tpmHash, "-o", "yaml")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Check that attestation data was populated (not empty)
|
||||
return strings.Contains(string(out), "attestation:") &&
|
||||
strings.Contains(string(out), "ekPublicKey:") &&
|
||||
strings.Contains(string(out), "akPublicKey:")
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
// Step 4: Verify secret was created
|
||||
By("Verifying encryption secret was auto-generated")
|
||||
Eventually(func() bool {
|
||||
return secretExists(fmt.Sprintf("%s-cos-persistent", tpmHash))
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
// Step 5: Test subsequent authentication works
|
||||
By("Testing subsequent authentication with learned attestation data")
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
|
||||
When("Testing Quarantine Management", Label("remote-quarantine"), func() {
|
||||
It("should handle quarantine, rejection, and recovery flows using the same VM", func() {
|
||||
ensureVMRunning()
|
||||
|
||||
// Step 1: Initial enrollment
|
||||
tpmHash = getTPMHash(testVM)
|
||||
deleteSealedVolume(tpmHash) // Ensure clean state
|
||||
|
||||
// Create SealedVolume for TOFU enrollment
|
||||
createSealedVolumeWithAttestation(tpmHash, nil)
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Step 2: Quarantine the TPM
|
||||
quarantineTPM(tpmHash)
|
||||
|
||||
// Give some time for the change to propagate
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Step 3: Verify quarantined TPM is rejected via CLI (no risky reboot)
|
||||
By("Testing that quarantined TPM is rejected via CLI")
|
||||
|
||||
// Give some time for quarantine to propagate
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Should fail to retrieve passphrase when quarantined
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", false)
|
||||
|
||||
// Step 4: Test recovery by unquarantining
|
||||
By("Testing recovery by unquarantining TPM")
|
||||
unquarantineTPM(tpmHash)
|
||||
|
||||
// Give some time for the change to propagate
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Should now be able to retrieve passphrase again
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", true)
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
|
||||
When("Testing PCR Management Scenarios", Label("remote-pcr-mgmt"), func() {
|
||||
It("should handle PCR re-enrollment, omission, and mixed states using the same VM", func() {
|
||||
ensureVMRunning()
|
||||
|
||||
// Step 1: Initial enrollment with specific PCR enforcement
|
||||
tpmHash = getTPMHash(testVM)
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
// Create SealedVolume with specific PCR values enforced
|
||||
attestationConfig := map[string]interface{}{
|
||||
"pcrValues": map[string]string{
|
||||
"0": "specific-pcr0-value", // Will be enforced
|
||||
"7": "", // Will be re-enrolled
|
||||
// PCR 11 omitted - will be ignored
|
||||
},
|
||||
}
|
||||
createSealedVolumeWithAttestation(tpmHash, attestationConfig)
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Step 2: Verify PCR 7 was re-enrolled (updated from empty to actual value)
|
||||
By("Verifying PCR 7 was re-enrolled with actual value")
|
||||
Eventually(func() bool {
|
||||
cmd := exec.Command("kubectl", "get", "sealedvolume", tpmHash, "-o", "yaml")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// PCR 7 should now have a non-empty value
|
||||
return strings.Contains(string(out), "\"7\":") &&
|
||||
!strings.Contains(string(out), "\"7\": \"\"")
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
// Step 3: Test PCR enforcement by changing enforced PCR (should fail via CLI)
|
||||
By("Testing PCR enforcement by modifying enforced PCR 0")
|
||||
updateSealedVolumeAttestation(tpmHash, "pcrValues.pcrs.0", "wrong-pcr0-value")
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Should fail to retrieve passphrase with wrong PCR value
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", false)
|
||||
|
||||
// Step 4: Test PCR re-enrollment by setting to empty
|
||||
By("Testing PCR re-enrollment by setting PCR 0 to empty")
|
||||
updateSealedVolumeAttestation(tpmHash, "pcrValues.pcrs.0", "")
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Should now re-enroll and work via CLI
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", true)
|
||||
|
||||
// Step 5: Verify PCR 0 was re-enrolled with new value
|
||||
By("Verifying PCR 0 was re-enrolled with current value")
|
||||
Eventually(func() bool {
|
||||
cmd := exec.Command("kubectl", "get", "sealedvolume", tpmHash, "-o", "yaml")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// PCR 0 should now have a new non-empty value
|
||||
return strings.Contains(string(out), "\"0\":") &&
|
||||
!strings.Contains(string(out), "\"0\": \"\"") &&
|
||||
!strings.Contains(string(out), "\"0\": \"wrong-pcr0-value\"")
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
|
||||
When("Testing AK Management", Label("remote-ak-mgmt"), func() {
|
||||
It("should handle AK re-enrollment and enforcement using the same VM", func() {
|
||||
ensureVMRunning()
|
||||
|
||||
// Step 1: Initial enrollment with AK re-enrollment mode
|
||||
tpmHash = getTPMHash(testVM)
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
// Create SealedVolume with empty AK (re-enrollment mode)
|
||||
attestationConfig := map[string]interface{}{
|
||||
"akPublicKey": "", // Will be re-enrolled
|
||||
"ekPublicKey": "", // Will be re-enrolled
|
||||
}
|
||||
createSealedVolumeWithAttestation(tpmHash, attestationConfig)
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Step 2: Verify AK and EK were re-enrolled
|
||||
By("Verifying AK and EK were re-enrolled with actual values")
|
||||
var learnedAK, learnedEK string
|
||||
Eventually(func() bool {
|
||||
cmd := exec.Command("kubectl", "get", "sealedvolume", tpmHash, "-o", "yaml")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Extract learned AK and EK for later enforcement test
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "akPublicKey:") && !strings.Contains(line, "akPublicKey: \"\"") {
|
||||
parts := strings.Split(line, "akPublicKey:")
|
||||
if len(parts) > 1 {
|
||||
learnedAK = strings.TrimSpace(strings.Trim(parts[1], "\""))
|
||||
}
|
||||
}
|
||||
if strings.Contains(line, "ekPublicKey:") && !strings.Contains(line, "ekPublicKey: \"\"") {
|
||||
parts := strings.Split(line, "ekPublicKey:")
|
||||
if len(parts) > 1 {
|
||||
learnedEK = strings.TrimSpace(strings.Trim(parts[1], "\""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return learnedAK != "" && learnedEK != ""
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
// Step 3: Test AK enforcement by setting wrong AK
|
||||
By("Testing AK enforcement by setting wrong AK value")
|
||||
updateSealedVolumeAttestation(tpmHash, "akPublicKey", "wrong-ak-value")
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Should fail to retrieve passphrase with wrong AK
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", false)
|
||||
|
||||
// Step 4: Restore correct AK and verify it works via CLI
|
||||
By("Restoring correct AK and verifying authentication works")
|
||||
updateSealedVolumeAttestation(tpmHash, "akPublicKey", learnedAK)
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Should now work with correct AK
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", true)
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
|
||||
When("Testing Secret Reuse Scenarios", Label("remote-secret-reuse"), func() {
|
||||
It("should reuse existing secrets when SealedVolume is recreated", func() {
|
||||
ensureVMRunning()
|
||||
|
||||
// Step 1: Initial enrollment to create secret
|
||||
tpmHash = getTPMHash(testVM)
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
createSealedVolumeWithAttestation(tpmHash, nil)
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Step 2: Get the generated secret
|
||||
secretName := fmt.Sprintf("%s-cos-persistent", tpmHash)
|
||||
Eventually(func() bool {
|
||||
return secretExists(secretName)
|
||||
}, 30*time.Second, 5*time.Second).Should(BeTrue())
|
||||
|
||||
// Get secret data for comparison
|
||||
cmd := exec.Command("kubectl", "get", "secret", secretName, "-o", "yaml")
|
||||
originalSecretData, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Step 3: Delete SealedVolume but keep secret
|
||||
deleteSealedVolume(tpmHash)
|
||||
|
||||
// Verify secret still exists
|
||||
Expect(secretExists(secretName)).To(BeTrue())
|
||||
|
||||
// Step 4: Recreate SealedVolume and verify secret reuse
|
||||
By("Recreating SealedVolume and verifying secret reuse")
|
||||
createSealedVolumeWithAttestation(tpmHash, nil)
|
||||
|
||||
// Should reuse existing secret
|
||||
rebootAndConnect(testVM)
|
||||
verifyEncryptedPartition(testVM)
|
||||
|
||||
// Step 5: Verify the same secret is being used
|
||||
cmd = exec.Command("kubectl", "get", "secret", secretName, "-o", "yaml")
|
||||
newSecretData, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// The secret data should be identical (reused, not regenerated)
|
||||
Expect(string(newSecretData)).To(Equal(string(originalSecretData)))
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
|
||||
When("Testing Error Handling and Edge Cases", Label("remote-edge-cases"), func() {
|
||||
It("should handle various error conditions properly", func() {
|
||||
ensureVMRunning()
|
||||
|
||||
// Step 1: Test invalid TPM hash rejection
|
||||
By("Testing invalid TPM hash rejection")
|
||||
invalidHash := "invalid-tpm-hash-12345"
|
||||
createSealedVolumeWithAttestation(invalidHash, nil)
|
||||
|
||||
config = fmt.Sprintf(`#cloud-config
|
||||
|
||||
hostname: metal-{{ trunc 4 .MachineID }}
|
||||
users:
|
||||
- name: kairos
|
||||
passwd: kairos
|
||||
|
||||
install:
|
||||
encrypted_partitions:
|
||||
- COS_PERSISTENT
|
||||
grub_options:
|
||||
extra_cmdline: "rd.neednet=1"
|
||||
reboot: false
|
||||
|
||||
kcrypt:
|
||||
challenger:
|
||||
challenger_server: "http://%s"
|
||||
`, os.Getenv("KMS_ADDRESS"))
|
||||
|
||||
installKairosWithConfig(config)
|
||||
|
||||
// Should fail due to TPM hash mismatch (test via CLI, no risky reboot)
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", false)
|
||||
|
||||
// Cleanup invalid SealedVolume
|
||||
deleteSealedVolume(invalidHash)
|
||||
|
||||
// Step 2: Test with correct TPM hash to verify system works
|
||||
tpmHash = getTPMHash(testVM)
|
||||
createSealedVolumeWithAttestation(tpmHash, nil)
|
||||
|
||||
// Test with correct hash should work
|
||||
expectPassphraseRetrieval(testVM, "COS_PERSISTENT", true)
|
||||
|
||||
cleanupTestResources(tpmHash)
|
||||
})
|
||||
})
|
||||
})
|
@@ -320,8 +320,16 @@ func expectPassphraseRetrieval(vm VM, partitionLabel string, shouldSucceed bool)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get the correct SealedVolume name from TPM hash
|
||||
func getSealedVolumeName(tpmHash string) string {
|
||||
// Convert to lowercase and take first 8 characters to match the actual naming pattern
|
||||
// This matches the pattern used in pkg/challenger/challenger.go: fmt.Sprintf("tofu-%s", tpmHash[:8])
|
||||
return fmt.Sprintf("tofu-%s", strings.ToLower(tpmHash[:8]))
|
||||
}
|
||||
|
||||
// Helper to create SealedVolume with specific attestation configuration
|
||||
func createSealedVolumeWithAttestation(tpmHash string, attestationConfig map[string]interface{}) {
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
sealedVolumeYaml := fmt.Sprintf(`---
|
||||
apiVersion: keyserver.kairos.io/v1alpha1
|
||||
kind: SealedVolume
|
||||
@@ -332,7 +340,7 @@ spec:
|
||||
TPMHash: "%s"
|
||||
partitions:
|
||||
- label: COS_PERSISTENT
|
||||
quarantined: false`, tpmHash, tpmHash)
|
||||
quarantined: false`, sealedVolumeName, tpmHash)
|
||||
|
||||
if attestationConfig != nil {
|
||||
sealedVolumeYaml += "\n attestation:"
|
||||
@@ -356,43 +364,48 @@ spec:
|
||||
|
||||
// Helper to update SealedVolume attestation configuration
|
||||
func updateSealedVolumeAttestation(tpmHashParam string, field, value string) {
|
||||
By(fmt.Sprintf("Updating SealedVolume %s field %s to %s", tpmHashParam, field, value))
|
||||
sealedVolumeName := getSealedVolumeName(tpmHashParam)
|
||||
By(fmt.Sprintf("Updating SealedVolume %s field %s to %s", sealedVolumeName, field, value))
|
||||
patch := fmt.Sprintf(`{"spec":{"attestation":{"%s":"%s"}}}`, field, value)
|
||||
cmd := exec.Command("kubectl", "patch", "sealedvolume", tpmHashParam, "--type=merge", "-p", patch)
|
||||
cmd := exec.Command("kubectl", "patch", "sealedvolume", sealedVolumeName, "--type=merge", "-p", patch)
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), string(out))
|
||||
}
|
||||
|
||||
// Helper to quarantine TPM
|
||||
func quarantineTPM(tpmHash string) {
|
||||
By(fmt.Sprintf("Quarantining TPM %s", tpmHash))
|
||||
sealedVolumeName := getSealedVolumeName(tpmHash)
|
||||
By(fmt.Sprintf("Quarantining TPM %s", sealedVolumeName))
|
||||
patch := `{"spec":{"quarantined":true}}`
|
||||
cmd := exec.Command("kubectl", "patch", "sealedvolume", tpmHash, "--type=merge", "-p", patch)
|
||||
cmd := exec.Command("kubectl", "patch", "sealedvolume", sealedVolumeName, "--type=merge", "-p", patch)
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), string(out))
|
||||
}
|
||||
|
||||
// Helper to unquarantine TPM
|
||||
func unquarantineTPM(tpmHashParam string) {
|
||||
By(fmt.Sprintf("Unquarantining TPM %s", tpmHashParam))
|
||||
sealedVolumeName := getSealedVolumeName(tpmHashParam)
|
||||
By(fmt.Sprintf("Unquarantining TPM %s", sealedVolumeName))
|
||||
patch := `{"spec":{"quarantined":false}}`
|
||||
cmd := exec.Command("kubectl", "patch", "sealedvolume", tpmHashParam, "--type=merge", "-p", patch)
|
||||
cmd := exec.Command("kubectl", "patch", "sealedvolume", sealedVolumeName, "--type=merge", "-p", patch)
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), string(out))
|
||||
}
|
||||
|
||||
// Helper to delete SealedVolume
|
||||
func deleteSealedVolume(tmpHashParam string) {
|
||||
By(fmt.Sprintf("Deleting SealedVolume %s", tmpHashParam))
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", tmpHashParam, "--ignore-not-found=true")
|
||||
func deleteSealedVolume(tpmHashParam string) {
|
||||
sealedVolumeName := getSealedVolumeName(tpmHashParam)
|
||||
By(fmt.Sprintf("Deleting SealedVolume %s", sealedVolumeName))
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", sealedVolumeName, "--ignore-not-found=true")
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), string(out))
|
||||
}
|
||||
|
||||
// Helper to delete SealedVolume from all namespaces
|
||||
func deleteSealedVolumeAllNamespaces(tpmHashParam string) {
|
||||
By(fmt.Sprintf("Deleting SealedVolume %s from all namespaces", tpmHashParam))
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", tpmHashParam, "--ignore-not-found=true", "--all-namespaces")
|
||||
sealedVolumeName := getSealedVolumeName(tpmHashParam)
|
||||
By(fmt.Sprintf("Deleting SealedVolume %s from all namespaces", sealedVolumeName))
|
||||
cmd := exec.Command("kubectl", "delete", "sealedvolume", sealedVolumeName, "--ignore-not-found=true", "--all-namespaces")
|
||||
out, err := cmd.CombinedOutput()
|
||||
Expect(err).ToNot(HaveOccurred(), string(out))
|
||||
}
|
||||
@@ -450,12 +463,15 @@ spec:
|
||||
|
||||
// Helper to create SealedVolume in specific namespace
|
||||
func createSealedVolumeInNamespace(tpmHash, namespace string) {
|
||||
// First create the namespace if it doesn't exist
|
||||
// First create the namespace if it doesn't exist with test labels
|
||||
kubectlApplyYaml(fmt.Sprintf(`---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: %s`, namespace))
|
||||
name: %s
|
||||
labels:
|
||||
test.kcrypt.kairos.io/type: test-namespace
|
||||
test.kcrypt.kairos.io/purpose: kcrypt-challenger-testing`, namespace))
|
||||
|
||||
sealedVolumeYaml := fmt.Sprintf(`---
|
||||
apiVersion: keyserver.kairos.io/v1alpha1
|
||||
@@ -478,18 +494,19 @@ func cleanupTestResources(tpmHash string) {
|
||||
if tpmHash != "" {
|
||||
deleteSealedVolumeAllNamespaces(tpmHash)
|
||||
|
||||
// Cleanup associated secrets in all namespaces
|
||||
cmd := exec.Command("kubectl", "delete", "secret", tpmHash, "--ignore-not-found=true", "--all-namespaces")
|
||||
// Cleanup associated secrets using labels
|
||||
// This will delete all secrets created by kcrypt-challenger for this TPM hash
|
||||
cmd := exec.Command("kubectl", "delete", "secret",
|
||||
"-l", fmt.Sprintf("kcrypt.kairos.io/tpm-hash=%s", tpmHash),
|
||||
"--ignore-not-found=true", "--all-namespaces")
|
||||
cmd.CombinedOutput()
|
||||
}
|
||||
}
|
||||
|
||||
cmd = exec.Command("kubectl", "delete", "secret", fmt.Sprintf("%s-cos-persistent", tpmHash), "--ignore-not-found=true", "--all-namespaces")
|
||||
cmd.CombinedOutput()
|
||||
|
||||
// Cleanup test namespaces
|
||||
cmd = exec.Command("kubectl", "delete", "namespace", "test-ns-1", "--ignore-not-found=true")
|
||||
cmd.CombinedOutput()
|
||||
|
||||
cmd = exec.Command("kubectl", "delete", "namespace", "test-ns-2", "--ignore-not-found=true")
|
||||
// Helper to delete specific test namespaces
|
||||
func deleteTestNamespaces(namespaces ...string) {
|
||||
for _, namespace := range namespaces {
|
||||
cmd := exec.Command("kubectl", "delete", "namespace", namespace, "--ignore-not-found=true")
|
||||
cmd.CombinedOutput()
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user