kcrypt-challenger/tests/encryption_test.go

390 lines
9.7 KiB
Go
Raw Normal View History

package e2e_test
import (
"fmt"
"os"
"os/exec"
"path"
"strconv"
"strings"
"syscall"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/spectrocloud/peg/matcher"
"gopkg.in/yaml.v3"
client "github.com/kairos-io/kairos-challenger/cmd/discovery/client"
)
var installationOutput string
var vm VM
var _ = Describe("local encrypted passphrase", func() {
var config string
BeforeEach(func() {
RegisterFailHandler(printInstallationOutput)
vm = startVM()
vm.EventuallyConnects(1200)
})
JustBeforeEach(func() {
configFile, err := os.CreateTemp("", "")
Expect(err).ToNot(HaveOccurred())
defer os.Remove(configFile.Name())
err = os.WriteFile(configFile.Name(), []byte(config), 0744)
Expect(err).ToNot(HaveOccurred())
err = vm.Scp(configFile.Name(), "config.yaml", "0744")
Expect(err).ToNot(HaveOccurred())
installationOutput, err = vm.Sudo("set -o pipefail && kairos-agent manual-install --device auto config.yaml 2>&1 | tee manual-install.txt")
Expect(err).ToNot(HaveOccurred(), installationOutput)
})
AfterEach(func() {
err := vm.Destroy(func(vm VM) {
// Stop TPM emulator
tpmPID, err := os.ReadFile(path.Join(vm.StateDir, "tpm", "pid"))
Expect(err).ToNot(HaveOccurred())
if len(tpmPID) != 0 {
pid, err := strconv.Atoi(string(tpmPID))
Expect(err).ToNot(HaveOccurred())
syscall.Kill(pid, syscall.SIGKILL)
}
})
Expect(err).ToNot(HaveOccurred())
})
// https://kairos.io/docs/advanced/partition_encryption/#offline-mode
When("doing local encryption", func() {
BeforeEach(func() {
config = `#cloud-config
install:
encrypted_partitions:
- COS_PERSISTENT
reboot: false # we will reboot manually
hostname: metal-{{ trunc 4 .MachineID }}
users:
- name: kairos
passwd: kairos
`
})
It("boots and has an encrypted partition", func() {
vm.Reboot()
vm.EventuallyConnects(1200)
out, err := vm.Sudo("blkid")
Expect(err).ToNot(HaveOccurred(), out)
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"persistent\""), out)
})
})
//https://kairos.io/docs/advanced/partition_encryption/#online-mode
When("using a remote key management server (automated passphrase generation)", func() {
var tpmHash string
var err error
BeforeEach(func() {
tpmHash, err = vm.Sudo("/system/discovery/kcrypt-discovery-challenger")
Expect(err).ToNot(HaveOccurred(), tpmHash)
kubectlApplyYaml(fmt.Sprintf(`---
apiVersion: keyserver.kairos.io/v1alpha1
kind: SealedVolume
metadata:
name: "%[1]s"
namespace: default
spec:
TPMHash: "%[1]s"
partitions:
- label: COS_PERSISTENT
quarantined: false
`, strings.TrimSpace(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 # we will reboot manually
kcrypt:
challenger:
challenger_server: "http://%s"
nv_index: ""
c_index: ""
tpm_device: ""
`, os.Getenv("KMS_ADDRESS"))
})
AfterEach(func() {
cmd := exec.Command("kubectl", "delete", "sealedvolume", tpmHash)
out, err := cmd.CombinedOutput()
Expect(err).ToNot(HaveOccurred(), out)
})
It("creates a passphrase and a key/pair to decrypt it", func() {
// Expect a LUKS partition
vm.Reboot()
vm.EventuallyConnects(1200)
out, err := vm.Sudo("blkid")
Expect(err).ToNot(HaveOccurred(), out)
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"persistent\""), out)
// Expect a secret to be created
cmd := exec.Command("kubectl", "get", "secrets",
fmt.Sprintf("%s-cos-persistent", tpmHash),
"-o=go-template='{{.data.generated_by|base64decode}}'",
)
secretOut, err := cmd.CombinedOutput()
Expect(err).ToNot(HaveOccurred(), secretOut)
Expect(string(secretOut)).To(MatchRegexp("tpm"))
})
})
// https://kairos.io/docs/advanced/partition_encryption/#scenario-static-keys
When("using a remote key management server (static keys)", func() {
var tpmHash string
var err error
BeforeEach(func() {
tpmHash, err = vm.Sudo("/system/discovery/kcrypt-discovery-challenger")
Expect(err).ToNot(HaveOccurred(), tpmHash)
kubectlApplyYaml(fmt.Sprintf(`---
apiVersion: v1
kind: Secret
metadata:
name: %[1]s
namespace: default
type: Opaque
stringData:
pass: "awesome-plaintext-passphrase"
`, tpmHash))
kubectlApplyYaml(fmt.Sprintf(`---
apiVersion: keyserver.kairos.io/v1alpha1
kind: SealedVolume
metadata:
name: %[1]s
namespace: default
spec:
TPMHash: "%[1]s"
partitions:
- label: COS_PERSISTENT
secret:
name: %[1]s
path: pass
quarantined: false
`, 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 # we will reboot manually
kcrypt:
challenger:
challenger_server: "http://%s"
nv_index: ""
c_index: ""
tpm_device: ""
`, os.Getenv("KMS_ADDRESS"))
})
AfterEach(func() {
cmd := exec.Command("kubectl", "delete", "sealedvolume", tpmHash)
out, err := cmd.CombinedOutput()
Expect(err).ToNot(HaveOccurred(), out)
cmd = exec.Command("kubectl", "delete", "secret", tpmHash)
out, err = cmd.CombinedOutput()
Expect(err).ToNot(HaveOccurred(), out)
})
It("creates uses the existing passphrase to decrypt it", func() {
// Expect a LUKS partition
vm.Reboot()
vm.EventuallyConnects(1200)
out, err := vm.Sudo("blkid")
Expect(err).ToNot(HaveOccurred(), out)
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"persistent\""), out)
Expect(out).To(MatchRegexp("/dev/mapper.*LABEL=\"COS_PERSISTENT\""), out)
})
})
When("the key management server is listening on https", func() {
var tpmHash string
var err error
BeforeEach(func() {
tpmHash, err = vm.Sudo("/system/discovery/kcrypt-discovery-challenger")
Expect(err).ToNot(HaveOccurred(), tpmHash)
kubectlApplyYaml(fmt.Sprintf(`---
apiVersion: keyserver.kairos.io/v1alpha1
kind: SealedVolume
metadata:
name: "%[1]s"
namespace: default
spec:
TPMHash: "%[1]s"
partitions:
- label: COS_PERSISTENT
quarantined: false
`, strings.TrimSpace(tpmHash)))
})
When("the certificate is pinned on the configuration", func() {
BeforeEach(func() {
cert := getChallengerServerCert()
kcryptConfig := createConfigWithCert(fmt.Sprintf("https://%s", os.Getenv("KMS_ADDRESS")), cert)
kcryptConfigBytes, err := yaml.Marshal(kcryptConfig)
Expect(err).ToNot(HaveOccurred())
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 # we will reboot manually
%s
`, string(kcryptConfigBytes))
})
It("successfully talks to the server", func() {
vm.Reboot()
vm.EventuallyConnects(1200)
out, err := vm.Sudo("blkid")
Expect(err).ToNot(HaveOccurred(), out)
Expect(out).To(MatchRegexp("TYPE=\"crypto_LUKS\" PARTLABEL=\"persistent\""), out)
Expect(out).To(MatchRegexp("/dev/mapper.*LABEL=\"COS_PERSISTENT\""), out)
})
})
When("the no certificate is set in the configuration", func() {
BeforeEach(func() {
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 # we will reboot manually
kcrypt:
challenger:
challenger_server: "https://%s"
nv_index: ""
c_index: ""
tpm_device: ""
`, os.Getenv("KMS_ADDRESS"))
})
It("fails to talk to the server", func() {
out, err := vm.Sudo("cat manual-install.txt")
Expect(err).ToNot(HaveOccurred(), out)
Expect(out).To(MatchRegexp("could not encrypt partition.*x509: certificate signed by unknown authority"))
})
})
})
})
func printInstallationOutput(message string, callerSkip ...int) {
fmt.Printf("This is the installation output in case it's useful:\n%s\n", installationOutput)
// Ensures the correct line numbers are reported
Fail(message, callerSkip[0]+1)
}
func kubectlApplyYaml(yamlData string) {
yamlFile, err := os.CreateTemp("", "")
Expect(err).ToNot(HaveOccurred())
defer os.Remove(yamlFile.Name())
err = os.WriteFile(yamlFile.Name(), []byte(yamlData), 0744)
Expect(err).ToNot(HaveOccurred())
cmd := exec.Command("kubectl", "apply", "-f", yamlFile.Name())
out, err := cmd.CombinedOutput()
Expect(err).ToNot(HaveOccurred(), out)
}
func getChallengerServerCert() string {
cmd := exec.Command(
"kubectl", "get", "secret", "-n", "default", "kms-tls",
"-o", `go-template={{ index .data "ca.crt" | base64decode }}`)
out, err := cmd.CombinedOutput()
Expect(err).ToNot(HaveOccurred(), string(out))
return string(out)
}
func createConfigWithCert(server, cert string) client.Config {
return client.Config{
Kcrypt: struct {
Challenger struct {
Server string "yaml:\"challenger_server,omitempty\""
NVIndex string "yaml:\"nv_index,omitempty\""
CIndex string "yaml:\"c_index,omitempty\""
TPMDevice string "yaml:\"tpm_device,omitempty\""
Certificate string "yaml:\"certificate,omitempty\""
}
}{
Challenger: struct {
Server string "yaml:\"challenger_server,omitempty\""
NVIndex string "yaml:\"nv_index,omitempty\""
CIndex string "yaml:\"c_index,omitempty\""
TPMDevice string "yaml:\"tpm_device,omitempty\""
Certificate string "yaml:\"certificate,omitempty\""
}{
Server: server,
NVIndex: "",
CIndex: "",
TPMDevice: "",
Certificate: cert,
},
},
}
}