Files
kcrypt-challenger/pkg/challenger/challenger_test.go
Dimitris Karakasilis b123339d19 Refactor wall-of-text method to one with better narrative
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2025-09-25 13:16:15 +03:00

643 lines
21 KiB
Go

// [✓] Setup a cluster
// [✓] install crds on it
// - run the server locally
// - make requests to the server to see if we can get passphrases back
package challenger
import (
"net/http"
"net/http/httptest"
"github.com/go-logr/logr"
"github.com/google/go-attestation/attest"
keyserverv1alpha1 "github.com/kairos-io/kairos-challenger/api/v1alpha1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("challenger", func() {
Describe("findSecretFor", func() {
var requestData PassphraseRequestData
var volumeList *keyserverv1alpha1.SealedVolumeList
BeforeEach(func() {
requestData = PassphraseRequestData{
TPMHash: "1234",
DeviceName: "/dev/sda1",
UUID: "sda1_uuid",
Label: "COS_PERSISTENT",
}
})
When("a sealedvolume matching the label exists", func() {
BeforeEach(func() {
volumeList = volumeListWithPartitionSpec(
keyserverv1alpha1.PartitionSpec{
Label: requestData.Label,
DeviceName: "not_matching",
UUID: "not_matching",
Secret: &keyserverv1alpha1.SecretSpec{
Name: "the_secret",
Path: "the_path",
}})
})
It("returns the sealed volume data", func() {
volumeData, _ := findVolumeFor(requestData, volumeList)
Expect(volumeData).ToNot(BeNil())
Expect(volumeData.Quarantined).To(BeFalse())
Expect(volumeData.SecretName).To(Equal("the_secret"))
Expect(volumeData.SecretPath).To(Equal("the_path"))
})
})
When("a sealedvolume with empty field exists", func() {
BeforeEach(func() {
volumeList = volumeListWithPartitionSpec(
keyserverv1alpha1.PartitionSpec{
Label: "",
DeviceName: "not_matching",
UUID: "not_matching",
Secret: &keyserverv1alpha1.SecretSpec{
Name: "the_secret",
Path: "the_path",
}})
requestData = PassphraseRequestData{
TPMHash: "1234",
Label: "",
DeviceName: "/dev/sda1",
UUID: "sda1_uuid",
}
})
It("doesn't match a request with an empty field", func() {
volumeData, _ := findVolumeFor(requestData, volumeList)
Expect(volumeData).To(BeNil())
})
})
When("a sealedvolume matching the device name exists", func() {
BeforeEach(func() {
volumeList = volumeListWithPartitionSpec(
keyserverv1alpha1.PartitionSpec{
Label: "not_matching",
DeviceName: requestData.DeviceName,
UUID: "not_matching",
Secret: &keyserverv1alpha1.SecretSpec{
Name: "the_secret",
Path: "the_path",
}})
})
It("returns the sealed volume data", func() {
volumeData, _ := findVolumeFor(requestData, volumeList)
Expect(volumeData).ToNot(BeNil())
Expect(volumeData.Quarantined).To(BeFalse())
Expect(volumeData.SecretName).To(Equal("the_secret"))
Expect(volumeData.SecretPath).To(Equal("the_path"))
})
})
When("a sealedvolume matching the UUID exists", func() {
BeforeEach(func() {
volumeList = volumeListWithPartitionSpec(
keyserverv1alpha1.PartitionSpec{
Label: "not_matching",
DeviceName: "not_matching",
UUID: requestData.UUID,
Secret: &keyserverv1alpha1.SecretSpec{
Name: "the_secret",
Path: "the_path",
}})
})
It("returns the sealed volume data", func() {
volumeData, _ := findVolumeFor(requestData, volumeList)
Expect(volumeData).ToNot(BeNil())
Expect(volumeData.Quarantined).To(BeFalse())
Expect(volumeData.SecretName).To(Equal("the_secret"))
Expect(volumeData.SecretPath).To(Equal("the_path"))
})
})
When("a matching sealedvolume doesn't exist", func() {
BeforeEach(func() {
volumeList = volumeListWithPartitionSpec(
keyserverv1alpha1.PartitionSpec{
Label: "not_matching",
DeviceName: "not_matching",
UUID: "not_matching",
Secret: &keyserverv1alpha1.SecretSpec{
Name: "the_secret",
Path: "the_path",
}})
})
It("returns nil sealedVolumeData", func() {
volumeData, _ := findVolumeFor(requestData, volumeList)
Expect(volumeData).To(BeNil())
})
})
})
Describe("Selective Enrollment Mode", func() {
var logger logr.Logger
BeforeEach(func() {
logger = logr.Discard()
})
Describe("verifyAKMatch with selective enrollment", func() {
var currentAK *attest.AttestationParameters
var expectedAKPEM string
const mockAKPublicKey = "mock-ak-public-key"
BeforeEach(func() {
// Mock current AK parameters - in real implementation this would come from TPM
currentAK = &attest.AttestationParameters{
Public: []byte(mockAKPublicKey),
UseTCSDActivationFormat: false,
CreateData: []byte("mock-create-data"),
CreateAttestation: []byte("mock-create-attestation"),
CreateSignature: []byte("mock-create-signature"),
}
// Generate the expected PEM encoding from the plain text constant
var err error
expectedAKPEM, err = encodeAKToPEM(currentAK)
Expect(err).To(BeNil())
})
When("stored AK is empty (re-enrollment mode)", func() {
It("should store the current AK value during re-enrollment", func() {
attestation := &keyserverv1alpha1.AttestationSpec{
AKPublicKey: "", // Empty = re-enrollment mode
}
// Before re-enrollment: AK should be empty
Expect(attestation.AKPublicKey).To(Equal(""))
// Re-enrollment should store the current AK
err := updateAttestationDataSelective(attestation, currentAK, nil, logger)
Expect(err).To(BeNil())
// After re-enrollment: AK should contain the exact expected PEM value
Expect(attestation.AKPublicKey).To(Equal(expectedAKPEM))
})
It("should accept any AK, store it during re-enrollment, then enforce exact match", func() {
attestation := &keyserverv1alpha1.AttestationSpec{
AKPublicKey: "", // Start in re-enrollment mode
}
sealedVolume := &keyserverv1alpha1.SealedVolume{
Spec: keyserverv1alpha1.SealedVolumeSpec{
Attestation: attestation,
},
}
// Step 1: Verification should pass with any AK (re-enrollment mode)
err := verifyAKMatchSelective(sealedVolume, currentAK, logger)
Expect(err).To(BeNil())
// Step 2: Re-enroll - store the AK
err = updateAttestationDataSelective(attestation, currentAK, nil, logger)
Expect(err).To(BeNil())
// Step 3: Now we should be in enforcement mode - same AK should pass
err = verifyAKMatchSelective(sealedVolume, currentAK, logger)
Expect(err).To(BeNil())
// Step 4: Different AK should now fail (enforcement mode)
differentAK := &attest.AttestationParameters{
Public: []byte("different-ak-key"),
}
err = verifyAKMatchSelective(sealedVolume, differentAK, logger)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("AK public key does not match"))
})
})
When("stored AK is set (enforcement mode)", func() {
It("should enforce exact match", func() {
// Create a specific AK PEM that won't match our mock
storedAKPEM := "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtest\n-----END PUBLIC KEY-----"
attestation := &keyserverv1alpha1.AttestationSpec{
AKPublicKey: storedAKPEM,
}
sealedVolume := &keyserverv1alpha1.SealedVolume{
Spec: keyserverv1alpha1.SealedVolumeSpec{
Attestation: attestation,
},
}
err := verifyAKMatchSelective(sealedVolume, currentAK, logger)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("AK public key does not match"))
})
})
When("no attestation data exists", func() {
It("should return error", func() {
sealedVolume := &keyserverv1alpha1.SealedVolume{
Spec: keyserverv1alpha1.SealedVolumeSpec{
Attestation: nil,
},
}
err := verifyAKMatchSelective(sealedVolume, currentAK, logger)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("no attestation data"))
})
})
})
Describe("verifyPCRValuesSelective", func() {
var currentPCRs *keyserverv1alpha1.PCRValues
const expectedPCR0 = "abc123def456"
const expectedPCR7 = "ghi789jkl012"
const expectedPCR11 = "mno345pqr678"
BeforeEach(func() {
currentPCRs = &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": expectedPCR0,
"7": expectedPCR7,
"11": expectedPCR11,
},
}
})
When("stored PCR values are empty (re-enrollment mode)", func() {
It("should accept any PCR values during verification", func() {
storedPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "", // Empty = re-enrollment mode
"7": "", // Empty = re-enrollment mode
"11": "", // Empty = re-enrollment mode
},
}
err := verifyPCRValuesSelective(storedPCRs, currentPCRs, logger)
Expect(err).To(BeNil())
})
It("should store the current PCR values during re-enrollment", func() {
attestation := &keyserverv1alpha1.AttestationSpec{
PCRValues: &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "", // Empty = re-enrollment mode
"7": "", // Empty = re-enrollment mode
"11": "", // Empty = re-enrollment mode
},
},
}
// Before re-enrollment: PCRs should be empty
Expect(attestation.PCRValues.PCRs["0"]).To(Equal(""))
Expect(attestation.PCRValues.PCRs["7"]).To(Equal(""))
Expect(attestation.PCRValues.PCRs["11"]).To(Equal(""))
// Re-enrollment should store the current PCR values
err := updateAttestationDataSelective(attestation, nil, currentPCRs, logger)
Expect(err).To(BeNil())
// After re-enrollment: PCRs should be stored with exact expected values
Expect(attestation.PCRValues.PCRs["0"]).To(Equal(expectedPCR0))
Expect(attestation.PCRValues.PCRs["7"]).To(Equal(expectedPCR7))
Expect(attestation.PCRValues.PCRs["11"]).To(Equal(expectedPCR11))
})
It("should transition from re-enrollment mode to enforcement mode", func() {
storedPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "", // Start in re-enrollment mode
},
}
// Create a limited current PCR set (only PCR0) to test selective enrollment
limitedCurrentPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": expectedPCR0, // Only provide PCR0
},
}
// Step 1: Should accept any PCR values (re-enrollment mode)
err := verifyPCRValuesSelective(storedPCRs, limitedCurrentPCRs, logger)
Expect(err).To(BeNil())
// Step 2: Re-enroll - store the PCR value (should only update the empty PCR0)
attestation := &keyserverv1alpha1.AttestationSpec{
PCRValues: storedPCRs,
}
err = updateAttestationDataSelective(attestation, nil, limitedCurrentPCRs, logger)
Expect(err).To(BeNil())
// Verify PCR0 was enrolled and no other PCRs were added
Expect(storedPCRs.PCRs["0"]).To(Equal(expectedPCR0))
Expect(storedPCRs.PCRs).To(HaveLen(1)) // Should still only have PCR0
// Step 3: Now should be in enforcement mode - same PCR should pass
err = verifyPCRValuesSelective(storedPCRs, limitedCurrentPCRs, logger)
Expect(err).To(BeNil())
// Step 4: Different PCR should now fail (enforcement mode)
differentPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "different_value",
},
}
err = verifyPCRValuesSelective(storedPCRs, differentPCRs, logger)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("PCR0 changed"))
})
})
When("stored PCR values are set (enforcement mode)", func() {
It("should enforce exact match for set values", func() {
storedPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "abc123def456", // Matches current
"7": "different_value", // Different from current
"11": "mno345pqr678", // Matches current
},
}
err := verifyPCRValuesSelective(storedPCRs, currentPCRs, logger)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("PCR7 changed"))
})
It("should pass when all set values match", func() {
storedPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "abc123def456", // Matches current
"7": "ghi789jkl012", // Matches current
"11": "mno345pqr678", // Matches current
},
}
err := verifyPCRValuesSelective(storedPCRs, currentPCRs, logger)
Expect(err).To(BeNil())
})
})
When("PCR fields are omitted (skip verification)", func() {
It("should skip verification for omitted PCRs entirely", func() {
storedPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "abc123def456", // Present and matches
"7": "ghi789jkl012", // Present and matches
// "11" is omitted entirely = skip verification
},
}
err := verifyPCRValuesSelective(storedPCRs, currentPCRs, logger)
Expect(err).To(BeNil())
})
})
When("mixed selective and enforcement mode", func() {
It("should handle combination of empty, set, and omitted PCRs", func() {
storedPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "", // Empty = re-enrollment mode
"7": "ghi789jkl012", // Set = enforcement mode (matches)
"14": "any_value", // Set but PCR14 not in current (should fail)
// "11" omitted = skip verification
},
}
err := verifyPCRValuesSelective(storedPCRs, currentPCRs, logger)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("PCR14"))
})
})
When("no stored PCR values exist", func() {
It("should accept any current PCR values", func() {
err := verifyPCRValuesSelective(nil, currentPCRs, logger)
Expect(err).To(BeNil())
})
})
When("no current PCR values provided", func() {
It("should pass if no stored values either", func() {
err := verifyPCRValuesSelective(nil, nil, logger)
Expect(err).To(BeNil())
})
It("should fail if stored values expect specific PCRs", func() {
storedPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "abc123def456",
},
}
err := verifyPCRValuesSelective(storedPCRs, nil, logger)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("no current PCR values"))
})
})
})
Describe("updateAttestationData for selective enrollment", func() {
It("should update empty fields with current values", func() {
currentAK := &attest.AttestationParameters{
Public: []byte("new-ak-public-key"),
}
currentPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "new_pcr0_value",
"7": "new_pcr7_value",
"11": "new_pcr11_value",
},
}
attestation := &keyserverv1alpha1.AttestationSpec{
AKPublicKey: "", // Empty = should be updated
PCRValues: &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "", // Empty = should be updated
"7": "fixed_pcr7_value", // Set = should NOT be updated
"11": "", // Empty = should be updated
},
},
}
err := updateAttestationDataSelective(attestation, currentAK, currentPCRs, logger)
Expect(err).To(BeNil())
// AK should be updated
Expect(attestation.AKPublicKey).ToNot(BeEmpty())
// PCR0 should be updated (was empty)
Expect(attestation.PCRValues.PCRs["0"]).To(Equal("new_pcr0_value"))
// PCR7 should NOT be updated (was set)
Expect(attestation.PCRValues.PCRs["7"]).To(Equal("fixed_pcr7_value"))
// PCR11 should be updated (was empty)
Expect(attestation.PCRValues.PCRs["11"]).To(Equal("new_pcr11_value"))
})
It("should demonstrate AK re-enrollment workflow", func() {
// Step 1: Start with empty AK (re-enrollment mode)
originalAK := ""
attestation := &keyserverv1alpha1.AttestationSpec{
AKPublicKey: originalAK, // Empty = re-enrollment mode
}
// Step 2: Current AK from client
currentAK := &attest.AttestationParameters{
Public: []byte("client-provided-ak-key"),
}
// Step 3: Verification should pass (empty stored AK accepts any)
sealedVolume := &keyserverv1alpha1.SealedVolume{
Spec: keyserverv1alpha1.SealedVolumeSpec{
Attestation: attestation,
},
}
err := verifyAKMatchSelective(sealedVolume, currentAK, logger)
Expect(err).To(BeNil())
// Step 4: Update should store the new AK (this is the re-enrollment)
err = updateAttestationDataSelective(attestation, currentAK, nil, logger)
Expect(err).To(BeNil())
// Step 5: Verify the AK was actually enrolled (stored)
Expect(attestation.AKPublicKey).ToNot(BeEmpty())
Expect(attestation.AKPublicKey).ToNot(Equal(originalAK))
// Step 6: Future verification should now require exact match
err = verifyAKMatchSelective(sealedVolume, currentAK, logger)
Expect(err).To(BeNil()) // Should still pass with same AK
// Step 7: Different AK should now fail (enforcement mode)
differentAK := &attest.AttestationParameters{
Public: []byte("different-ak-key"),
}
err = verifyAKMatchSelective(sealedVolume, differentAK, logger)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("AK public key does not match"))
})
})
Describe("Initial TOFU Enrollment behavior", func() {
It("should store ALL provided PCRs during initial enrollment", func() {
clientPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"0": "pcr0_value",
"1": "pcr1_value",
"2": "pcr2_value",
"7": "pcr7_value",
"11": "pcr11_value",
"14": "pcr14_value",
},
}
attestation := createInitialTOFUAttestation(nil, clientPCRs, logger)
// All provided PCRs should be stored
Expect(attestation.PCRValues).ToNot(BeNil())
Expect(attestation.PCRValues.PCRs).To(HaveLen(6))
Expect(attestation.PCRValues.PCRs["0"]).To(Equal("pcr0_value"))
Expect(attestation.PCRValues.PCRs["1"]).To(Equal("pcr1_value"))
Expect(attestation.PCRValues.PCRs["2"]).To(Equal("pcr2_value"))
Expect(attestation.PCRValues.PCRs["7"]).To(Equal("pcr7_value"))
Expect(attestation.PCRValues.PCRs["11"]).To(Equal("pcr11_value"))
Expect(attestation.PCRValues.PCRs["14"]).To(Equal("pcr14_value"))
})
It("should not filter or omit any PCRs during TOFU", func() {
// Test that even "sensitive" PCRs like PCR11 are stored
clientPCRs := &keyserverv1alpha1.PCRValues{
PCRs: map[string]string{
"11": "kernel_pcr_value", // Previously filtered out
"12": "other_pcr_value",
},
}
attestation := createInitialTOFUAttestation(nil, clientPCRs, logger)
Expect(attestation.PCRValues.PCRs).To(HaveKey("11"))
Expect(attestation.PCRValues.PCRs).To(HaveKey("12"))
Expect(attestation.PCRValues.PCRs["11"]).To(Equal("kernel_pcr_value"))
})
})
})
Describe("handleTPMAttestation functions", func() {
Describe("establishAttestationConnection", func() {
var mockResponseWriter *httptest.ResponseRecorder
var mockRequest *http.Request
var logger logr.Logger
BeforeEach(func() {
logger = logr.Discard()
mockResponseWriter = httptest.NewRecorder()
mockRequest = httptest.NewRequest("GET", "/test", nil)
// Set partition headers
mockRequest.Header.Set("label", "COS_PERSISTENT")
mockRequest.Header.Set("name", "/dev/sda1")
mockRequest.Header.Set("uuid", "test-uuid-123")
})
It("should return error when WebSocket upgrade fails", func() {
// This test checks the error behavior when WebSocket upgrade fails
conn, partition, err := establishAttestationConnection(mockResponseWriter, mockRequest, logger)
// WebSocket upgrade should fail with regular HTTP request
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("upgrade"))
Expect(conn).To(BeNil())
// When upgrade fails, partition info is not extracted (function returns early)
Expect(partition.Label).To(Equal(""))
Expect(partition.DeviceName).To(Equal(""))
Expect(partition.UUID).To(Equal(""))
})
})
})
})
func volumeListWithPartitionSpec(partitionSpec keyserverv1alpha1.PartitionSpec) *keyserverv1alpha1.SealedVolumeList {
return &keyserverv1alpha1.SealedVolumeList{
Items: []keyserverv1alpha1.SealedVolume{
{Spec: keyserverv1alpha1.SealedVolumeSpec{
TPMHash: "1234",
Partitions: []keyserverv1alpha1.PartitionSpec{
partitionSpec,
},
Quarantined: false,
},
},
},
}
}
func volumeListWithAttestationSpec(tpmHash string, attestation *keyserverv1alpha1.AttestationSpec) *keyserverv1alpha1.SealedVolumeList {
return &keyserverv1alpha1.SealedVolumeList{
Items: []keyserverv1alpha1.SealedVolume{
{Spec: keyserverv1alpha1.SealedVolumeSpec{
TPMHash: tpmHash,
Partitions: []keyserverv1alpha1.PartitionSpec{
{
Label: "COS_PERSISTENT",
Secret: &keyserverv1alpha1.SecretSpec{
Name: "test-secret",
Path: "pass",
},
},
},
Quarantined: false,
Attestation: attestation,
},
},
},
}
}