Compare commits

..

30 Commits

Author SHA1 Message Date
Dimitris Karakasilis
317c6d87b4 Merge pull request #10 from kairos-io/local_encryption
🌱 Enable local encryption, remote now partially uses TPM
2023-01-19 16:27:52 +02:00
Dimitris Karakasilis
8898eb8ae9 Small refactorings (renaming vars, create constants etc)
Signed-off-by: Ettore Di Giacinto <ettore@spectrocloud.com>
2023-01-19 16:24:39 +02:00
Ettore Di Giacinto
91c24586ea Improve naming of functions and add comments
Signed-off-by: Dimitris Karakasilis <dimitris@spectrocloud.com>
2023-01-19 16:06:53 +02:00
Dimitris Karakasilis
eefd5f2c2c Extract method and simplify "if" logic
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2023-01-19 15:46:35 +02:00
mudler
83f529b53d 🌱 Small fixups
Signed-off-by: mudler <mudler@c3os.io>
2023-01-19 14:24:33 +01:00
mudler
2c8a589906 Enable local encryption, remote now partially uses TPM
Signed-off-by: mudler <mudler@c3os.io>
2023-01-18 23:32:27 +01:00
Dimitris Karakasilis
9f7abe321a Merge pull request #9 from kairos-io/use_tpm_helpers
Use tpm helpers
2023-01-18 17:26:15 +02:00
mudler
2603757f2c Simplify challenge
Signed-off-by: mudler <mudler@c3os.io>
2023-01-18 16:09:52 +01:00
mudler
df0fb4a341 ⬆️ Point to tpm-helpers
Signed-off-by: mudler <mudler@c3os.io>
2023-01-18 16:02:17 +01:00
Dimitris Karakasilis
12edf4d4cf Merge pull request #8 from kairos-io/399-configuration-from-file
399 configuration from file
2023-01-18 16:58:38 +02:00
Dimitris Karakasilis
b3ca9687c6 Implement test and remove TODOs
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2023-01-18 16:56:49 +02:00
Dimitris Karakasilis
72829108df Extract client code to separate package and test it
- add new suite to the pipeline and fix Earthly to run tests
- read configuration from file
- the "kcrypt" section is our configuration now
- move configuration logic in `kcrypt` repository

Part of #399

Signed-off-by: Mauro Morales <mauro.morales@spectrocloud.com>
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2023-01-18 15:25:04 +02:00
Dimitris Karakasilis
a49495e47a Merge pull request #7 from kairos-io/380-traceback-partition
380 traceback partition
2022-11-17 15:06:04 +02:00
Dimitris Karakasilis
83bba2f0cf Introduce a test suite and an earthly target to run it
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2022-11-17 12:57:09 +02:00
Dimitris Karakasilis
3b9477b6ea Add omitempty on PartitionSpec fields to make the optional
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2022-11-17 12:56:59 +02:00
Dimitris Karakasilis
aa736211af 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 <dimitris@karakasilis.me>
2022-11-11 09:54:19 +02:00
Dimitris Karakasilis
7a07d5c45b 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 <dimitris@karakasilis.me>
2022-11-11 09:54:19 +02:00
Dimitris Karakasilis
a3df62df03 [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 <dimitris@karakasilis.me>
2022-11-11 09:54:19 +02:00
mudler
9e8249c730 Minor fixups 2022-10-18 17:04:48 +02:00
mudler
4236420ed5 📖 Update README 2022-10-18 15:44:09 +02:00
mudler
21681a58fd Dial-in re-attempts 2022-10-18 15:43:58 +02:00
Ettore Di Giacinto
a2cb5d95fb Skip errors when evaluating cmdline (best-effort) 2022-10-18 12:27:48 +00:00
Ettore Di Giacinto
06b8dc9c58 🐛 Fixup unmarshal to anonymous struct 2022-10-17 22:27:39 +02:00
Ettore Di Giacinto
d9da1b4090 Merge pull request #1 from kairos-io/tests
Add Earthfile
2022-10-17 22:10:26 +02:00
Ettore Di Giacinto
770814996b Attempt to get also from part name 2022-10-17 19:08:45 +00:00
Ettore Di Giacinto
a00353fda1 Add Earthfile 2022-10-17 16:44:25 +02:00
Ettore Di Giacinto
673bfcbd56 Slightly change spec 2022-10-13 22:21:06 +00:00
Ettore Di Giacinto
7c6fa7df06 🎨 Small fixups 2022-10-13 21:35:26 +00:00
Ettore Di Giacinto
6124f9aec9 🤖 Fixup workflow 2022-10-13 20:35:58 +00:00
Ettore Di Giacinto
05d48347d7 Initial import 2022-10-13 20:34:44 +00:00
20 changed files with 1138 additions and 621 deletions

63
.github/workflows/image.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
---
name: 'build container images'
on:
push:
branches:
- main
tags:
- '*'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Prepare
id: prep
run: |
DOCKER_IMAGE=quay.io/kairos/kcrypt-challenger
VERSION=latest
SHORTREF=${GITHUB_SHA::8}
# If this is git tag, use the tag name as a docker tag
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
fi
TAGS="${DOCKER_IMAGE}:${VERSION},${DOCKER_IMAGE}:${SHORTREF}"
# If the VERSION looks like a version number, assume that
# this is the most recent version of the image and also
# tag it 'latest'.
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
fi
# Set output parameters.
echo ::set-output name=tags::${TAGS}
echo ::set-output name=docker_image::${DOCKER_IMAGE}
- name: Set up QEMU
uses: docker/setup-qemu-action@master
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@master
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
- name: Build
uses: docker/build-push-action@v2
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.prep.outputs.tags }}

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

52
Earthfile Normal file
View File

@@ -0,0 +1,52 @@
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
COPY . /work
WORKDIR /work
RUN CGO_ENABLED=0 go build -o kcrypt-discovery-challenger ./cmd/discovery
SAVE ARTIFACT /work/kcrypt-discovery-challenger AS LOCAL kcrypt-discovery-challenger
image:
FROM $BASE_IMAGE
ARG IMAGE
COPY +build-challenger/kcrypt-discovery-challenger /system/discovery/kcrypt-discovery-challenger
SAVE IMAGE $IMAGE
iso:
ARG OSBUILDER_IMAGE
ARG ISO_NAME=challenger
FROM $OSBUILDER_IMAGE
RUN zypper in -y jq docker
WORKDIR /build
WITH DOCKER --allow-privileged --load $IMAGE=(+image --IMAGE=test)
RUN /entrypoint.sh --name $ISO_NAME --debug build-iso --date=false --local test --output /build/
END
# See: https://github.com/rancher/elemental-cli/issues/228
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 get github.com/onsi/gomega/...
RUN go get github.com/onsi/ginkgo/v2/ginkgo/internal@v2.1.4
RUN go get github.com/onsi/ginkgo/v2/ginkgo/generators@v2.1.4
RUN go get github.com/onsi/ginkgo/v2/ginkgo/labels@v2.1.4
RUN go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo
COPY . /work
RUN PATH=$PATH:$GOPATH/bin ginkgo run --covermode=atomic --coverprofile=coverage.out -p -r pkg/challenger cmd/discovery/client
SAVE ARTIFACT coverage.out AS LOCAL coverage.out

110
README.md
View File

@@ -1,94 +1,30 @@
# kcrypt-controller
// TODO(user): Add simple overview of use/purpose
# kcrypt-challenger
## Description
// TODO(user): An in-depth paragraph about your project and overview of use
| :exclamation: | This is experimental! |
|-|:-|
## Getting Started
Youll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster.
**Note:** Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster `kubectl cluster-info` shows).
This is the Kairos kcrypt-challenger Kubernetes Native Extension.
### Running on the cluster
1. Install Instances of Custom Resources:
To install, use helm:
```sh
kubectl apply -f config/samples/
```
# Adds the kairos repo to helm
$ helm repo add kairos https://kairos-io.github.io/helm-charts
"kairos" has been added to your repositories
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "kairos" chart repository
Update Complete. ⎈Happy Helming!⎈
2. Build and push your image to the location specified by `IMG`:
```sh
make docker-build docker-push IMG=<some-registry>/kcrypt-controller:tag
# Install the CRD chart
$ helm install kairos-crd kairos/kairos-crds
NAME: kairos-crd
LAST DEPLOYED: Tue Sep 6 20:35:34 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
# Installs challenger
$ helm install kairos-challenger kairos/kcrypt-challenger
```
3. Deploy the controller to the cluster with the image specified by `IMG`:
```sh
make deploy IMG=<some-registry>/kcrypt-controller:tag
```
### Uninstall CRDs
To delete the CRDs from the cluster:
```sh
make uninstall
```
### Undeploy controller
UnDeploy the controller to the cluster:
```sh
make undeploy
```
## Contributing
// TODO(user): Add detailed information on how you would like others to contribute to this project
### How it works
This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/)
It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/)
which provides a reconcile function responsible for synchronizing resources untile the desired state is reached on the cluster
### Test It Out
1. Install the CRDs into the cluster:
```sh
make install
```
2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running):
```sh
make run
```
**NOTE:** You can also run this in one step by running: `make install run`
### Modifying the API definitions
If you are editing the API definitions, generate the manifests such as CRs or CRDs using:
```sh
make manifests
```
**NOTE:** Run `make --help` for more information on all potential `make` targets
More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
## License
Copyright 2022.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -25,10 +25,19 @@ import (
// SealedVolumeSpec defines the desired state of SealedVolume
type SealedVolumeSpec struct {
TPMHash string `json:"TPMHash,omitempty"`
Label string `json:"label,omitempty"`
Passphrase *SecretSpec `json:"passphraseRef,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,26 @@ 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
if in.Secret != nil {
in, out := &in.Secret, &out.Secret
*out = new(SecretSpec)
**out = **in
}
}
// 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,10 +107,12 @@ 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 = new(SecretSpec)
**out = **in
if in.Partitions != nil {
in, out := &in.Partitions, &out.Partitions
*out = make([]PartitionSpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}

View File

@@ -0,0 +1,129 @@
package client
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"time"
"github.com/jaypipes/ghw/pkg/block"
"github.com/kairos-io/kairos-challenger/pkg/constants"
"github.com/kairos-io/kcrypt/pkg/bus"
"github.com/kairos-io/tpm-helpers"
"github.com/mudler/go-pluggable"
"github.com/mudler/yip/pkg/utils"
)
var errPartNotFound error = fmt.Errorf("pass for partition not found")
func NewClient() (*Client, error) {
conf, err := unmarshalConfig()
if err != nil {
return nil, err
}
return &Client{Config: conf}, nil
}
// echo '{ "data": "{ \\"label\\": \\"LABEL\\" }"}' | sudo -E WSS_SERVER="http://localhost:8082/challenge" ./challenger "discovery.password"
func (c *Client) Start() error {
factory := pluggable.NewPluginFactory()
// Input: bus.EventInstallPayload
// Expected output: map[string]string{}
factory.Add(bus.EventDiscoveryPassword, func(e *pluggable.Event) pluggable.EventResponse {
b := &block.Partition{}
err := json.Unmarshal([]byte(e.Data), b)
if err != nil {
return pluggable.EventResponse{
Error: fmt.Sprintf("failed reading partitions: %s", err.Error()),
}
}
pass, err := c.waitPass(b, 30)
if err != nil {
return pluggable.EventResponse{
Error: fmt.Sprintf("failed getting pass: %s", err.Error()),
}
}
return pluggable.EventResponse{
Data: pass,
}
})
return factory.Run(pluggable.EventType(os.Args[1]), os.Stdin, os.Stdout)
}
func (c *Client) waitPass(p *block.Partition, attempts int) (pass string, err error) {
// IF we don't have any server configured, just do local
if c.Config.Kcrypt.Challenger.Server == "" {
return localPass(c.Config)
}
challengeEndpoint := fmt.Sprintf("%s/getPass", c.Config.Kcrypt.Challenger.Server)
postEndpoint := fmt.Sprintf("%s/postPass", c.Config.Kcrypt.Challenger.Server)
// IF server doesn't have a pass for us, then we generate one and we set it
if _, _, err := getPass(challengeEndpoint, p); err == errPartNotFound {
rand := utils.RandomString(32)
pass, err := tpm.EncryptBlob([]byte(rand))
if err != nil {
return "", err
}
bpass := base64.RawURLEncoding.EncodeToString(pass)
opts := []tpm.Option{
tpm.WithAdditionalHeader("label", p.Label),
tpm.WithAdditionalHeader("name", p.Name),
tpm.WithAdditionalHeader("uuid", p.UUID),
}
conn, err := tpm.Connection(postEndpoint, opts...)
if err != nil {
return "", err
}
err = conn.WriteJSON(map[string]string{"passphrase": bpass, constants.GeneratedByKey: constants.TPMSecret})
if err != nil {
return rand, err
}
}
for tries := 0; tries < attempts; tries++ {
var generated bool
pass, generated, err = getPass(challengeEndpoint, p)
if generated { // passphrase is encrypted
return c.decryptPassphrase(pass)
}
if err == nil || err == errPartNotFound { // passphrase not encrypted or not available
return
}
time.Sleep(1 * time.Second) // network errors? retry
}
return
}
// decryptPassphrase decodes (base64) and decrypts the passphrase returned
// by the challenger server.
func (c *Client) decryptPassphrase(pass string) (string, error) {
blob, err := base64.RawURLEncoding.DecodeString(pass)
if err != nil {
return "", err
}
// Decrypt and return it to unseal the LUKS volume
opts := []tpm.TPMOption{}
if c.Config.Kcrypt.Challenger.CIndex != "" {
opts = append(opts, tpm.WithIndex(c.Config.Kcrypt.Challenger.CIndex))
}
if c.Config.Kcrypt.Challenger.TPMDevice != "" {
opts = append(opts, tpm.WithDevice(c.Config.Kcrypt.Challenger.TPMDevice))
}
passBytes, err := tpm.DecryptBlob(blob, opts...)
return string(passBytes), err
}

View File

@@ -0,0 +1,38 @@
package client
import (
"github.com/kairos-io/kairos/pkg/config"
kconfig "github.com/kairos-io/kcrypt/pkg/config"
)
type Client struct {
Config Config
}
type Config struct {
Kcrypt struct {
Challenger struct {
Server string `yaml:"challenger_server,omitempty"`
// Non-volatile index memory: where we store the encrypted passphrase (offline mode)
NVIndex string `yaml:"nv_index,omitempty"`
// Certificate index: this is where the rsa pair that decrypts the passphrase lives
CIndex string `yaml:"c_index,omitempty"`
TPMDevice string `yaml:"tpm_device,omitempty"`
}
}
}
func unmarshalConfig() (Config, error) {
var result Config
c, err := config.Scan(config.Directories(kconfig.ConfigScanDirs...), config.NoLogs)
if err != nil {
return result, err
}
if err = c.Unmarshal(&result); err != nil {
return result, err
}
return result, nil
}

View File

@@ -0,0 +1,86 @@
package client
import (
"encoding/json"
"fmt"
"github.com/kairos-io/kairos-challenger/pkg/constants"
"github.com/jaypipes/ghw/pkg/block"
"github.com/kairos-io/tpm-helpers"
"github.com/mudler/yip/pkg/utils"
"github.com/pkg/errors"
)
const DefaultNVIndex = "0x1500000"
func getPass(server string, partition *block.Partition) (string, bool, 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 "", false, err
}
result := map[string]interface{}{}
err = json.Unmarshal(msg, &result)
if err != nil {
return "", false, errors.Wrap(err, string(msg))
}
generatedBy, generated := result[constants.GeneratedByKey]
p, ok := result["passphrase"]
if ok {
return fmt.Sprint(p), generated && generatedBy == constants.TPMSecret, nil
}
return "", false, errPartNotFound
}
func genAndStore(k Config) (string, error) {
opts := []tpm.TPMOption{}
if k.Kcrypt.Challenger.TPMDevice != "" {
opts = append(opts, tpm.WithDevice(k.Kcrypt.Challenger.TPMDevice))
}
if k.Kcrypt.Challenger.CIndex != "" {
opts = append(opts, tpm.WithIndex(k.Kcrypt.Challenger.CIndex))
}
// Generate a new one, and return it to luks
rand := utils.RandomString(32)
blob, err := tpm.EncryptBlob([]byte(rand))
if err != nil {
return "", err
}
nvindex := DefaultNVIndex
if k.Kcrypt.Challenger.NVIndex != "" {
nvindex = k.Kcrypt.Challenger.NVIndex
}
opts = append(opts, tpm.WithIndex(nvindex))
return rand, tpm.StoreBlob(blob, opts...)
}
func localPass(k Config) (string, error) {
index := DefaultNVIndex
if k.Kcrypt.Challenger.NVIndex != "" {
index = k.Kcrypt.Challenger.NVIndex
}
opts := []tpm.TPMOption{tpm.WithIndex(index)}
if k.Kcrypt.Challenger.TPMDevice != "" {
opts = append(opts, tpm.WithDevice(k.Kcrypt.Challenger.TPMDevice))
}
encodedPass, err := tpm.ReadBlob(opts...)
if err != nil {
// Generate if we fail to read from the assigned blob
return genAndStore(k)
}
// Decode and give it back
opts = []tpm.TPMOption{}
if k.Kcrypt.Challenger.CIndex != "" {
opts = append(opts, tpm.WithIndex(k.Kcrypt.Challenger.CIndex))
}
if k.Kcrypt.Challenger.TPMDevice != "" {
opts = append(opts, tpm.WithDevice(k.Kcrypt.Challenger.TPMDevice))
}
pass, err := tpm.DecryptBlob(encodedPass, opts...)
return string(pass), err
}

View File

@@ -1,25 +1,24 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/jaypipes/ghw/pkg/block"
"github.com/kairos-io/go-tpm"
"github.com/kairos-io/kairos/pkg/machine"
"github.com/kairos-io/kairos-challenger/cmd/discovery/client"
"github.com/kairos-io/kcrypt/pkg/bus"
"gopkg.in/yaml.v3"
"github.com/mudler/go-pluggable"
"github.com/kairos-io/tpm-helpers"
)
func main() {
if len(os.Args) >= 2 && bus.IsEventDefined(os.Args[1]) {
checkErr(start())
c, err := client.NewClient()
checkErr(err)
checkErr(c.Start())
return
}
pubhash, _ := tpm.GetPubHash()
pubhash, err := tpm.GetPubHash()
checkErr(err)
fmt.Print(pubhash)
}
@@ -28,62 +27,4 @@ func checkErr(err error) {
fmt.Println(err)
os.Exit(1)
}
os.Exit(0)
}
// echo '{ "data": "{ \\"label\\": \\"LABEL\\" }"}' | sudo -E WSS_SERVER="http://localhost:8082/challenge" ./challenger "discovery.password"
func start() error {
factory := pluggable.NewPluginFactory()
connectionDetails := &struct {
Server string
}{}
var server string
d, err := machine.DotToYAML("/proc/cmdline")
if err == nil { // best-effort
yaml.Unmarshal(d, connectionDetails) //nolint:errcheck
}
server = connectionDetails.Server
if os.Getenv("WSS_SERVER") != "" {
server = os.Getenv("WSS_SERVER")
}
// Input: bus.EventInstallPayload
// Expected output: map[string]string{}
factory.Add(bus.EventDiscoveryPassword, func(e *pluggable.Event) pluggable.EventResponse {
b := &block.Partition{}
err := json.Unmarshal([]byte(e.Data), b)
if err != nil {
return pluggable.EventResponse{
Error: fmt.Sprintf("failed reading partitions: %s", err.Error()),
}
}
msg, err := tpm.Get(server, tpm.WithAdditionalHeader("label", b.Label))
if err != nil {
return pluggable.EventResponse{
Error: fmt.Sprintf("failed contacting from wss server: %s", err.Error()),
}
}
result := map[string]interface{}{}
err = json.Unmarshal(msg, &result)
if err != nil {
return pluggable.EventResponse{
Error: fmt.Sprintf("failed reading from wss server: %s", err.Error()),
}
}
p, ok := result["passphrase"]
if !ok {
return pluggable.EventResponse{
Error: "not found",
}
}
return pluggable.EventResponse{
Data: fmt.Sprint(p),
}
})
return factory.Run(pluggable.EventType(os.Args[1]), os.Stdin, os.Stdout)
}

View File

@@ -37,15 +37,27 @@ spec:
properties:
TPMHash:
type: string
label:
type: string
passphraseRef:
properties:
name:
type: string
path:
type: string
type: object
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:
deviceName:
type: string
label:
type: string
secret:
properties:
name:
type: string
path:
type: string
type: object
uuid:
type: string
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 $@

View File

@@ -15,8 +15,8 @@ metadata:
namespace: default
spec:
TPMHash: "something"
label: "label"
passphraseRef:
name: mysecret
path: pass
partitionSecrets:
LABEL:
name: mysecret
path: pass
quarantined: false

95
go.mod
View File

@@ -5,19 +5,24 @@ go 1.18
require (
github.com/gorilla/websocket v1.5.0
github.com/jaypipes/ghw v0.9.0
github.com/kairos-io/go-tpm v0.0.0-20221007215323-700d855876c5
github.com/kairos-io/kairos v1.1.2
github.com/kairos-io/kcrypt v0.0.0-20221006145351-cabc24dc37a7
github.com/kairos-io/kairos v1.24.3-56.0.20230118103822-e3dbd41dddd1
github.com/kairos-io/kcrypt v0.4.5-0.20230118125949-27183fbce7ea
github.com/kairos-io/tpm-helpers v0.0.0-20230119140150-3fa97128ef6b
github.com/mudler/go-pluggable v0.0.0-20220716112424-189d463e3ff3
github.com/mudler/yip v0.11.4
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.20.0
gopkg.in/yaml.v3 v3.0.1
github.com/onsi/ginkgo/v2 v2.7.0
github.com/onsi/gomega v1.25.0
github.com/pkg/errors v0.9.1
k8s.io/api v0.24.2
k8s.io/apimachinery v0.24.2
k8s.io/client-go v0.24.2
sigs.k8s.io/controller-runtime v0.12.2
)
require (
atomicgo.dev/cursor v0.1.1 // indirect
atomicgo.dev/keyboard v0.2.9 // indirect
cloud.google.com/go v0.93.3 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
@@ -25,22 +30,27 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/atomicgo/cursor v0.0.1 // indirect
github.com/avast/retry-go v3.0.0+incompatible // indirect
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denisbrodbeck/machineid v1.0.1 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/folbricht/tpmk v0.1.2-0.20230104073416-f20b20c289d7 // indirect
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-logr/logr v1.2.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/zapr v1.2.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
@@ -49,68 +59,73 @@ require (
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
github.com/google/certificate-transparency-go v1.1.2 // indirect
github.com/google/certificate-transparency-go v1.1.4 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-attestation v0.4.3 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/go-attestation v0.4.4-0.20220404204839-8820d49b18d9 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-tpm v0.3.3 // indirect
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/go-tpm-tools v0.3.10 // indirect
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/gofuzz v1.1.0 // 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
github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/itchyny/gojq v0.12.8 // indirect
github.com/itchyny/timefmt-go v0.1.3 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/itchyny/gojq v0.12.11 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/joho/godotenv v1.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lithammer/fuzzysearch v1.1.5 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/pterm/pterm v0.12.41 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/pterm/pterm v0.12.53 // indirect
github.com/qeesung/image2ascii v1.0.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/twpayne/go-vfs v1.7.2 // indirect
github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
go.uber.org/atomic v1.9.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 // indirect
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
go.uber.org/zap v1.23.0 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/oauth2 v0.4.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/term v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // 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
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect
k8s.io/api v0.24.2 // indirect
k8s.io/apiextensions-apiserver v0.24.2 // indirect
k8s.io/component-base v0.24.2 // indirect
k8s.io/klog/v2 v2.60.1 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect

593
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -9,16 +9,37 @@ import (
"time"
keyserverv1alpha1 "github.com/kairos-io/kairos-challenger/api/v1alpha1"
"github.com/kairos-io/kairos-challenger/pkg/constants"
"sigs.k8s.io/controller-runtime/pkg/client"
tpm "github.com/kairos-io/go-tpm"
"github.com/kairos-io/kairos-challenger/controllers"
tpm "github.com/kairos-io/tpm-helpers"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"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
PartitionLabel string
VolumeName string
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
@@ -46,6 +67,15 @@ func writeRead(conn *websocket.Conn, input []byte) ([]byte, error) {
return ioutil.ReadAll(reader)
}
func getPubHash(token string) (string, error) {
ek, _, err := tpm.GetAttestationData(token)
if err != nil {
return "", err
}
return tpm.DecodePubHash(ek)
}
func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *controllers.SealedVolumeReconciler, namespace, address string) {
fmt.Println("Challenger started at", address)
s := http.Server{
@@ -56,11 +86,101 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr
m := http.NewServeMux()
m.HandleFunc("/challenge", func(w http.ResponseWriter, r *http.Request) {
m.HandleFunc("/postPass", func(w http.ResponseWriter, r *http.Request) {
conn, _ := upgrader.Upgrade(w, r, nil) // error ignored for sake of simplicity
for {
if err := tpm.AuthRequest(r, conn); err != nil {
fmt.Println("error", err.Error())
return
}
defer conn.Close()
token := r.Header.Get("Authorization")
hashEncoded, err := getPubHash(token)
if err != nil {
fmt.Println("error decoding pubhash", err.Error())
return
}
label := r.Header.Get("label")
name := r.Header.Get("name")
uuid := r.Header.Get("uuid")
v := map[string]string{}
volumeList := &keyserverv1alpha1.SealedVolumeList{}
if err := reconciler.List(ctx, volumeList, &client.ListOptions{Namespace: namespace}); err != nil {
fmt.Println("Failed listing volumes")
fmt.Println(err)
continue
}
sealedVolumeData := findVolumeFor(PassphraseRequestData{
TPMHash: hashEncoded,
Label: label,
DeviceName: name,
UUID: uuid,
}, volumeList)
if sealedVolumeData == nil {
fmt.Println("No TPM Hash found for", hashEncoded)
conn.Close()
return
}
if err := conn.ReadJSON(&v); err != nil {
fmt.Println("error", err.Error())
return
}
pass, ok := v["passphrase"]
if ok {
secretName := fmt.Sprintf("%s-%s", sealedVolumeData.VolumeName, sealedVolumeData.PartitionLabel)
secretPath := "passphrase"
if sealedVolumeData.SecretName != "" {
secretName = sealedVolumeData.SecretName
}
if sealedVolumeData.SecretPath != "" {
secretPath = sealedVolumeData.SecretPath
}
_, err := kclient.CoreV1().Secrets(namespace).Get(ctx, secretName, v1.GetOptions{})
if err != nil {
if !apierrors.IsNotFound(err) {
fmt.Printf("Failed getting secret: %s\n", err.Error())
continue
}
secret := corev1.Secret{
TypeMeta: v1.TypeMeta{
Kind: "Secret",
APIVersion: "apps/v1",
},
ObjectMeta: v1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
Data: map[string][]byte{
secretPath: []byte(pass),
constants.GeneratedByKey: []byte(v[constants.GeneratedByKey]),
},
Type: "Opaque",
}
_, err := kclient.CoreV1().Secrets(namespace).Create(ctx, &secret, v1.CreateOptions{})
if err != nil {
fmt.Println("failed during secret creation")
}
} else {
fmt.Println("Posted for already existing secret - ignoring")
}
} else {
fmt.Println("Invalid answer from client: doesn't contain any passphrase")
}
}
})
m.HandleFunc("/getPass", func(w http.ResponseWriter, r *http.Request) {
conn, _ := upgrader.Upgrade(w, r, nil) // error ignored for sake of simplicity
for {
fmt.Println("Received connection")
volumeList := &keyserverv1alpha1.SealedVolumeList{}
if err := reconciler.List(ctx, volumeList, &client.ListOptions{Namespace: namespace}); err != nil {
@@ -71,62 +191,77 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr
token := r.Header.Get("Authorization")
label := r.Header.Get("label")
ek, at, err := tpm.GetAttestationData(token)
if err != nil {
fmt.Println("Failed getting tpm token")
name := r.Header.Get("name")
uuid := r.Header.Get("uuid")
fmt.Println("error", err.Error())
if err := tpm.AuthRequest(r, conn); err != nil {
fmt.Println("error validating challenge", err.Error())
return
}
hashEncoded, err := tpm.DecodePubHash(ek)
hashEncoded, err := getPubHash(token)
if err != nil {
fmt.Println("error decoding pubhash", err.Error())
return
}
found := false
var volume keyserverv1alpha1.SealedVolume
for _, v := range volumeList.Items {
if hashEncoded == v.Spec.TPMHash && v.Spec.Label == label {
found = true
volume = v
}
}
sealedVolumeData := findVolumeFor(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
}
secret, challenge, err := tpm.GenerateChallenge(ek, at)
if err != nil {
fmt.Println("error", err.Error())
return
}
resp, _ := writeRead(conn, challenge)
if err := tpm.ValidateChallenge(secret, resp); err != nil {
fmt.Println("error validating challenge", err.Error(), string(resp))
return
}
writer, _ := conn.NextWriter(websocket.BinaryMessage)
if !sealedVolumeData.Quarantined {
secretName := fmt.Sprintf("%s-%s", sealedVolumeData.VolumeName, sealedVolumeData.PartitionLabel)
secretPath := "passphrase"
if sealedVolumeData.SecretName != "" {
secretName = sealedVolumeData.SecretName
}
if sealedVolumeData.SecretPath != "" {
secretPath = sealedVolumeData.SecretPath
}
if !volume.Spec.Quarantined {
secret, err := kclient.CoreV1().Secrets(namespace).Get(ctx, volume.Spec.Passphrase.Name, v1.GetOptions{})
// 1. The admin sets a specific cleartext password from Kube manager
// SealedVolume -> with a secret .
// 2. The admin just adds a SealedVolume associated with a TPM Hash ( you don't provide any passphrase )
// 3. There is no challenger server at all (offline mode)
//
secret, err := kclient.CoreV1().Secrets(namespace).Get(ctx, secretName, v1.GetOptions{})
if err == nil {
passphrase := secret.Data[volume.Spec.Passphrase.Path]
json.NewEncoder(writer).Encode(map[string]string{"passphrase": string(passphrase)})
passphrase := secret.Data[secretPath]
generatedBy, generated := secret.Data[constants.GeneratedByKey]
result := map[string]string{"passphrase": string(passphrase)}
if generated {
result[constants.GeneratedByKey] = string(generatedBy)
}
err = json.NewEncoder(writer).Encode(result)
if err != nil {
fmt.Println("error encoding the passphrase to json", err.Error(), string(passphrase))
}
if err = writer.Close(); err != nil {
fmt.Println("error closing the writer", err.Error())
return
}
if err = conn.Close(); err != nil {
fmt.Println("error closing the connection", err.Error())
return
}
return
}
} else {
conn.Close()
fmt.Println("error getting the secret", err.Error())
if err = conn.Close(); err != nil {
fmt.Println("error closing the connection", err.Error())
return
}
return
}
}
@@ -137,7 +272,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)
}
}()
@@ -147,3 +282,27 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr
s.Shutdown(ctx)
}()
}
func findVolumeFor(requestData PassphraseRequestData, volumeList *keyserverv1alpha1.SealedVolumeList) *SealedVolumeData {
for _, v := range volumeList.Items {
if requestData.TPMHash == v.Spec.TPMHash {
for _, p := range v.Spec.Partitions {
deviceNameMatches := requestData.DeviceName != "" && p.DeviceName == requestData.DeviceName
uuidMatches := requestData.UUID != "" && p.UUID == requestData.UUID
labelMatches := requestData.Label != "" && p.Label == requestData.Label
if labelMatches || uuidMatches || deviceNameMatches {
return &SealedVolumeData{
Quarantined: v.Spec.Quarantined,
SecretName: p.Secret.Name,
SecretPath: p.Secret.Path,
VolumeName: v.Name,
PartitionLabel: p.Label,
}
}
}
}
}
return nil
}

View File

@@ -0,0 +1,153 @@
// [✓] 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 := 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())
})
})
})
})
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")
}

4
pkg/constants/secret.go Normal file
View File

@@ -0,0 +1,4 @@
package constants
const TPMSecret = "tpm"
const GeneratedByKey = "generated_by"