Merge pull request #7 from kairos-io/380-traceback-partition

380 traceback partition
This commit is contained in:
Dimitris Karakasilis 2022-11-17 15:06:04 +02:00 committed by GitHub
commit a49495e47a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 298 additions and 61 deletions

18
.github/workflows/unit-tests.yml vendored Normal file
View File

@ -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

3
.gitignore vendored
View File

@ -1,4 +1,3 @@
# Binaries for programs and plugins
*.exe
*.exe~
@ -24,4 +23,4 @@ testbin/*
*.swo
*~
/helm-chart
/helm-chart

View File

@ -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

View File

@ -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,omitempty"`
DeviceName string `json:"deviceName,omitempty"`
UUID string `json:"uuid,omitempty"`
Secret *SecretSpec `json:"secret,omitempty"`
}
type SecretSpec struct {

View File

@ -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)
}
}

View File

@ -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()),
}
}

View File

@ -37,15 +37,27 @@ 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
type: object
type: object
type: array
quarantined:
type: boolean
type: object

3
earthly.sh Executable file
View File

@ -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 $@

4
go.mod
View File

@ -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

5
go.sum
View File

@ -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=

View File

@ -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,
@ -71,6 +86,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")
@ -85,28 +102,17 @@ 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 l, secretRef := range v.Spec.Passphrase {
if l == label {
found = true
volume = v
passsecret = secretRef
}
}
}
}
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()
// conn.Close()
// return
continue //will iterate until a TPM is available
return
}
secret, challenge, err := tpm.GenerateChallenge(ek, at)
@ -124,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 {
@ -142,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)
}
}()
@ -152,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
}

View File

@ -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,
},
},
},
}
}

View File

@ -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")
}