From a3df62df031c2dc18f8fa72a70347058d770191f Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Tue, 8 Nov 2022 16:57:19 +0200 Subject: [PATCH 1/5] [WIP] Send more data over to the escrow server in order to identify the partition. The label is not available before the filesystem is descrypted (post-install). In that case the server can look up the partition in the configuration using the name or the mountpoint. Signed-off-by: Dimitris Karakasilis --- cmd/discovery/main.go | 25 ++++++++++++------------- pkg/challenger/challenger.go | 3 +++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cmd/discovery/main.go b/cmd/discovery/main.go index 35f1543..c8f4a21 100644 --- a/cmd/discovery/main.go +++ b/cmd/discovery/main.go @@ -48,7 +48,7 @@ func readServer() string { return server } -func waitPass(label string, attempts int) (pass string, err error) { +func waitPass(p *block.Partition, attempts int) (pass string, err error) { for tries := 0; tries < attempts; tries++ { server := readServer() if server == "" { @@ -56,7 +56,7 @@ func waitPass(label string, attempts int) (pass string, err error) { continue } - pass, err = getPass(server, label) + pass, err = getPass(server, p) if pass != "" || err == nil { return pass, err } @@ -65,8 +65,11 @@ func waitPass(label string, attempts int) (pass string, err error) { return } -func getPass(server, label string) (string, error) { - msg, err := tpm.Get(server, tpm.WithAdditionalHeader("label", label)) +func getPass(server string, partition *block.Partition) (string, error) { + msg, err := tpm.Get(server, + tpm.WithAdditionalHeader("label", partition.Label), + tpm.WithAdditionalHeader("name", partition.Name), + tpm.WithAdditionalHeader("uuid", partition.UUID)) if err != nil { return "", err } @@ -79,7 +82,7 @@ func getPass(server, label string) (string, error) { if ok { return fmt.Sprint(p), nil } - return "", fmt.Errorf("pass for label not found") + return "", fmt.Errorf("pass for partition not found") } type config struct { @@ -102,14 +105,10 @@ func start() error { } } - // TODO: This should be 1 call, send both name and label to controller - pass, err := waitPass(b.Label, 30) - if err != nil || pass == "" { - pass, err = waitPass(b.Name, 30) - if err != nil { - return pluggable.EventResponse{ - Error: fmt.Sprintf("failed getting pass: %s", err.Error()), - } + pass, err := waitPass(b, 30) + if err != nil { + return pluggable.EventResponse{ + Error: fmt.Sprintf("failed getting pass: %s", err.Error()), } } diff --git a/pkg/challenger/challenger.go b/pkg/challenger/challenger.go index 33262e5..c3f6a7e 100644 --- a/pkg/challenger/challenger.go +++ b/pkg/challenger/challenger.go @@ -71,6 +71,8 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr token := r.Header.Get("Authorization") label := r.Header.Get("label") + name := r.Header.Get("name") + uuid := r.Header.Get("uuid") ek, at, err := tpm.GetAttestationData(token) if err != nil { fmt.Println("Failed getting tpm token") @@ -91,6 +93,7 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr for _, v := range volumeList.Items { if hashEncoded == v.Spec.TPMHash { for l, secretRef := range v.Spec.Passphrase { + // TODO: Try the rest of the data (name, mountpoint) if label is not found if l == label { found = true volume = v From 7a07d5c45b09ab8ee26ed7402e23371bb2c2536b Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Wed, 9 Nov 2022 12:45:02 +0200 Subject: [PATCH 2/5] Change sealedvolume CRD to add more fields to the partition We use those field to identify which partition is requested. On the client side, the label is not available when the partition is encrypted. We allow the client to request the passphrase for a partition using the partition name (e.g. /dev/sdb1) or the UUID (as returned by blkid). Signed-off-by: Dimitris Karakasilis --- api/v1alpha1/sealedvolume_types.go | 16 +++++++-- api/v1alpha1/zz_generated.deepcopy.go | 34 +++++++++++-------- .../keyserver.kairos.io_sealedvolumes.yaml | 27 ++++++++++++--- pkg/challenger/challenger.go | 7 ++-- 4 files changed, 58 insertions(+), 26 deletions(-) diff --git a/api/v1alpha1/sealedvolume_types.go b/api/v1alpha1/sealedvolume_types.go index b077ac1..d95d754 100644 --- a/api/v1alpha1/sealedvolume_types.go +++ b/api/v1alpha1/sealedvolume_types.go @@ -25,9 +25,19 @@ import ( // SealedVolumeSpec defines the desired state of SealedVolume type SealedVolumeSpec struct { - TPMHash string `json:"TPMHash,omitempty"` - Passphrase map[string]*SecretSpec `json:"partitionSecrets,omitempty"` - Quarantined bool `json:"quarantined,omitempty"` + TPMHash string `json:"TPMHash,omitempty"` + Partitions []PartitionSpec `json:"partitions,omitempty"` + Quarantined bool `json:"quarantined,omitempty"` +} + +// PartitionSpec defines a Partition. A partition can be identified using +// any of the fields: Label, DeviceName, UUID. The Secret defines the secret +// which decrypts the partition. +type PartitionSpec struct { + Label string `json:"label"` + DeviceName string `json:"deviceName"` + UUID string `json:"uuid"` + Secret *SecretSpec `json:"secret"` } type SecretSpec struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fec9629..f1d2f6b 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -25,6 +25,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PartitionSpec) DeepCopyInto(out *PartitionSpec) { + *out = *in + out.Secret = in.Secret +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartitionSpec. +func (in *PartitionSpec) DeepCopy() *PartitionSpec { + if in == nil { + return nil + } + out := new(PartitionSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SealedVolume) DeepCopyInto(out *SealedVolume) { *out = *in @@ -87,20 +103,10 @@ func (in *SealedVolumeList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SealedVolumeSpec) DeepCopyInto(out *SealedVolumeSpec) { *out = *in - if in.Passphrase != nil { - in, out := &in.Passphrase, &out.Passphrase - *out = make(map[string]*SecretSpec, len(*in)) - for key, val := range *in { - var outVal *SecretSpec - if val == nil { - (*out)[key] = nil - } else { - in, out := &val, &outVal - *out = new(SecretSpec) - **out = **in - } - (*out)[key] = outVal - } + if in.Partitions != nil { + in, out := &in.Partitions, &out.Partitions + *out = make([]PartitionSpec, len(*in)) + copy(*out, *in) } } diff --git a/config/crd/bases/keyserver.kairos.io_sealedvolumes.yaml b/config/crd/bases/keyserver.kairos.io_sealedvolumes.yaml index 6f91639..0506445 100644 --- a/config/crd/bases/keyserver.kairos.io_sealedvolumes.yaml +++ b/config/crd/bases/keyserver.kairos.io_sealedvolumes.yaml @@ -37,15 +37,32 @@ spec: properties: TPMHash: type: string - partitionSecrets: - additionalProperties: + partitions: + items: + description: 'PartitionSpec defines a Partition. A partition can + be identified using any of the fields: Label, DeviceName, UUID. + The Secret defines the secret which decrypts the partition.' properties: - name: + deviceName: type: string - path: + label: type: string + secret: + properties: + name: + type: string + path: + type: string + type: object + uuid: + type: string + required: + - deviceName + - label + - secret + - uuid type: object - type: object + type: array quarantined: type: boolean type: object diff --git a/pkg/challenger/challenger.go b/pkg/challenger/challenger.go index c3f6a7e..81a467b 100644 --- a/pkg/challenger/challenger.go +++ b/pkg/challenger/challenger.go @@ -92,12 +92,11 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr var passsecret *keyserverv1alpha1.SecretSpec for _, v := range volumeList.Items { if hashEncoded == v.Spec.TPMHash { - for l, secretRef := range v.Spec.Passphrase { - // TODO: Try the rest of the data (name, mountpoint) if label is not found - if l == label { + for _, p := range v.Spec.Partitions { + if p.Label == label || p.DeviceName == name || p.UUID == uuid { found = true volume = v - passsecret = secretRef + passsecret = p.Secret } } } From aa736211af485f6ef037f9bbf6ffafc380afeb78 Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Wed, 9 Nov 2022 12:51:49 +0200 Subject: [PATCH 3/5] Don't go frenzy when a TPM is not found. Just return. Because there is no guarantee that a TPM will eventually be found. Signed-off-by: Dimitris Karakasilis --- pkg/challenger/challenger.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/challenger/challenger.go b/pkg/challenger/challenger.go index 81a467b..12f9908 100644 --- a/pkg/challenger/challenger.go +++ b/pkg/challenger/challenger.go @@ -105,10 +105,7 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr if !found { fmt.Println("No TPM Hash found for", hashEncoded) conn.Close() - // conn.Close() - // return - continue //will iterate until a TPM is available - + return } secret, challenge, err := tpm.GenerateChallenge(ek, at) From 3b9477b6ea92e00c3a444299996c30d856332656 Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Thu, 10 Nov 2022 09:57:28 +0200 Subject: [PATCH 4/5] Add `omitempty` on PartitionSpec fields to make the optional Signed-off-by: Dimitris Karakasilis --- api/v1alpha1/sealedvolume_types.go | 8 ++++---- config/crd/bases/keyserver.kairos.io_sealedvolumes.yaml | 5 ----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/api/v1alpha1/sealedvolume_types.go b/api/v1alpha1/sealedvolume_types.go index d95d754..358bbe6 100644 --- a/api/v1alpha1/sealedvolume_types.go +++ b/api/v1alpha1/sealedvolume_types.go @@ -34,10 +34,10 @@ type SealedVolumeSpec struct { // any of the fields: Label, DeviceName, UUID. The Secret defines the secret // which decrypts the partition. type PartitionSpec struct { - Label string `json:"label"` - DeviceName string `json:"deviceName"` - UUID string `json:"uuid"` - Secret *SecretSpec `json:"secret"` + Label string `json:"label,omitempty"` + DeviceName string `json:"deviceName,omitempty"` + UUID string `json:"uuid,omitempty"` + Secret *SecretSpec `json:"secret,omitempty"` } type SecretSpec struct { diff --git a/config/crd/bases/keyserver.kairos.io_sealedvolumes.yaml b/config/crd/bases/keyserver.kairos.io_sealedvolumes.yaml index 0506445..37bb4e7 100644 --- a/config/crd/bases/keyserver.kairos.io_sealedvolumes.yaml +++ b/config/crd/bases/keyserver.kairos.io_sealedvolumes.yaml @@ -56,11 +56,6 @@ spec: type: object uuid: type: string - required: - - deviceName - - label - - secret - - uuid type: object type: array quarantined: From 83bba2f0cfa36ef04e80083f3e7ba97e9dba5f7d Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Wed, 9 Nov 2022 16:51:31 +0200 Subject: [PATCH 5/5] Introduce a test suite and an earthly target to run it Signed-off-by: Dimitris Karakasilis --- .github/workflows/unit-tests.yml | 18 +++++ .gitignore | 3 +- Earthfile | 19 ++++- earthly.sh | 3 + go.mod | 4 + go.sum | 5 ++ pkg/challenger/challenger.go | 63 ++++++++++----- pkg/challenger/challenger_test.go | 127 ++++++++++++++++++++++++++++++ pkg/challenger/suite_test.go | 13 +++ 9 files changed, 233 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/unit-tests.yml create mode 100755 earthly.sh create mode 100644 pkg/challenger/challenger_test.go create mode 100644 pkg/challenger/suite_test.go diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..17f8c14 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,18 @@ +name: Unit tests +on: + push: + branches: + - master + pull_request: + +jobs: + unit-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Run tests + run: | + ./earthly.sh +test diff --git a/.gitignore b/.gitignore index 0b66eef..938e380 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - # Binaries for programs and plugins *.exe *.exe~ @@ -24,4 +23,4 @@ testbin/* *.swo *~ -/helm-chart \ No newline at end of file +/helm-chart diff --git a/Earthfile b/Earthfile index 0c5a3b5..0d720d9 100644 --- a/Earthfile +++ b/Earthfile @@ -1,7 +1,7 @@ VERSION 0.6 ARG BASE_IMAGE=quay.io/kairos/core-opensuse:latest ARG OSBUILDER_IMAGE=quay.io/kairos/osbuilder-tools - +ARG GO_VERSION=1.18 build-challenger: FROM golang:alpine @@ -29,3 +29,20 @@ iso: RUN sha256sum $ISO_NAME.iso > $ISO_NAME.iso.sha256 SAVE ARTIFACT /build/$ISO_NAME.iso kairos.iso AS LOCAL build/$ISO_NAME.iso SAVE ARTIFACT /build/$ISO_NAME.iso.sha256 kairos.iso.sha256 AS LOCAL build/$ISO_NAME.iso.sha256 + +test: + ARG GO_VERSION + FROM golang:$GO_VERSION + ENV CGO_ENABLED=0 + + WORKDIR /work + + # Cache layer for modules + COPY go.mod go.sum ./ + RUN go mod download && go mod verify + + RUN go install github.com/onsi/ginkgo/v2/ginkgo + + COPY . /work + RUN PATH=$PATH:$GOPATH/bin ginkgo run --covermode=atomic --coverprofile=coverage.out -p -r pkg/challenger + SAVE ARTIFACT coverage.out AS LOCAL coverage.out diff --git a/earthly.sh b/earthly.sh new file mode 100755 index 0000000..12b82a9 --- /dev/null +++ b/earthly.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker run --privileged -v /var/run/docker.sock:/var/run/docker.sock --rm -t -v $(pwd):/workspace -v earthly-tmp:/tmp/earthly:rw earthly/earthly:v0.6.21 --allow-privileged $@ \ No newline at end of file diff --git a/go.mod b/go.mod index 8939004..edbe420 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/kairos-io/kcrypt v0.0.0-20221006145351-cabc24dc37a7 github.com/mudler/go-pluggable v0.0.0-20220716112424-189d463e3ff3 github.com/onsi/ginkgo v1.16.5 + github.com/onsi/ginkgo/v2 v2.1.4 github.com/onsi/gomega v1.20.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.24.2 @@ -46,6 +47,7 @@ require ( github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -57,6 +59,7 @@ require ( github.com/google/go-tpm-tools v0.3.2 // indirect github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad // indirect github.com/google/gofuzz v1.1.0 // indirect + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.1.2 // indirect github.com/gookit/color v1.5.0 // indirect @@ -98,6 +101,7 @@ require ( golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + golang.org/x/tools v0.1.10 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.0 // indirect diff --git a/go.sum b/go.sum index af58547..71adbd6 100644 --- a/go.sum +++ b/go.sum @@ -326,6 +326,7 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -448,6 +449,7 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= @@ -719,6 +721,7 @@ github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1352,6 +1355,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/challenger/challenger.go b/pkg/challenger/challenger.go index 12f9908..65030a6 100644 --- a/pkg/challenger/challenger.go +++ b/pkg/challenger/challenger.go @@ -19,6 +19,21 @@ import ( "github.com/gorilla/websocket" ) +// PassphraseRequestData is a struct that holds all the information needed in +// order to lookup a passphrase for a specific tpm hash. +type PassphraseRequestData struct { + TPMHash string + Label string + DeviceName string + UUID string +} + +type SealedVolumeData struct { + Quarantined bool + SecretName string + SecretPath string +} + var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, @@ -87,22 +102,14 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr return } - found := false - var volume keyserverv1alpha1.SealedVolume - var passsecret *keyserverv1alpha1.SecretSpec - for _, v := range volumeList.Items { - if hashEncoded == v.Spec.TPMHash { - for _, p := range v.Spec.Partitions { - if p.Label == label || p.DeviceName == name || p.UUID == uuid { - found = true - volume = v - passsecret = p.Secret - } - } - } - } + sealedVolumeData := findSecretFor(PassphraseRequestData{ + TPMHash: hashEncoded, + Label: label, + DeviceName: name, + UUID: uuid, + }, volumeList) - if !found { + if sealedVolumeData == nil { fmt.Println("No TPM Hash found for", hashEncoded) conn.Close() return @@ -123,10 +130,10 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr writer, _ := conn.NextWriter(websocket.BinaryMessage) - if !volume.Spec.Quarantined { - secret, err := kclient.CoreV1().Secrets(namespace).Get(ctx, passsecret.Name, v1.GetOptions{}) + if !sealedVolumeData.Quarantined { + secret, err := kclient.CoreV1().Secrets(namespace).Get(ctx, sealedVolumeData.SecretName, v1.GetOptions{}) if err == nil { - passphrase := secret.Data[passsecret.Path] + passphrase := secret.Data[sealedVolumeData.SecretPath] json.NewEncoder(writer).Encode(map[string]string{"passphrase": string(passphrase)}) } } else { @@ -141,7 +148,7 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr go func() { err := s.ListenAndServe() - if err != nil { + if err != nil && err != http.ErrServerClosed { panic(err) } }() @@ -151,3 +158,21 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr s.Shutdown(ctx) }() } + +func findSecretFor(requestData PassphraseRequestData, volumeList *keyserverv1alpha1.SealedVolumeList) *SealedVolumeData { + for _, v := range volumeList.Items { + if requestData.TPMHash == v.Spec.TPMHash { + for _, p := range v.Spec.Partitions { + if p.Label == requestData.Label || p.DeviceName == requestData.DeviceName || p.UUID == requestData.UUID { + return &SealedVolumeData{ + Quarantined: v.Spec.Quarantined, + SecretName: p.Secret.Name, + SecretPath: p.Secret.Path, + } + } + } + } + } + + return nil +} diff --git a/pkg/challenger/challenger_test.go b/pkg/challenger/challenger_test.go new file mode 100644 index 0000000..199d896 --- /dev/null +++ b/pkg/challenger/challenger_test.go @@ -0,0 +1,127 @@ +// [✓] 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 ( + 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 := findSecretFor(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 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 := findSecretFor(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 := findSecretFor(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 := findSecretFor(requestData, volumeList) + Expect(volumeData).To(BeNil()) + }) + }) + }) +}) + +func volumeListWithPartitionSpec(partitionSpec keyserverv1alpha1.PartitionSpec) *keyserverv1alpha1.SealedVolumeList { + return &keyserverv1alpha1.SealedVolumeList{ + Items: []keyserverv1alpha1.SealedVolume{ + {Spec: keyserverv1alpha1.SealedVolumeSpec{ + TPMHash: "1234", + Partitions: []keyserverv1alpha1.PartitionSpec{ + partitionSpec, + }, + Quarantined: false, + }, + }, + }, + } +} diff --git a/pkg/challenger/suite_test.go b/pkg/challenger/suite_test.go new file mode 100644 index 0000000..3a34fab --- /dev/null +++ b/pkg/challenger/suite_test.go @@ -0,0 +1,13 @@ +package challenger_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestEpinio(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Kcrypt challenger suite") +}