commit b05d3c1e54246d4040691b8bf8243f8736665505 Author: mudler <mudler@mocaccino.org> Date: Mon Oct 3 11:03:48 2022 +0200 Initial import diff --git a/Earthfile b/Earthfile new file mode 100644 index 0000000..c6c8edf --- /dev/null +++ b/Earthfile @@ -0,0 +1,64 @@ +VERSION 0.6 +# Note the base image needs to have dracut. +# TODO: This needs to come from pre-built kernels in c3os repos, kcrypt included. +# Framework images should use our initrd +ARG BASE_IMAGE=quay.io/c3os/core-opensuse + +build-kcrypt: + FROM golang:alpine + COPY . /work + WORKDIR /work + RUN CGO_ENABLED=0 go build -o kcrypt + SAVE ARTIFACT /work/kcrypt AS LOCAL kcrypt + +build-dracut: + FROM $BASE_IMAGE + COPY . /work + COPY +build-kcrypt/kcrypt /usr/bin/kcrypt + WORKDIR /work + RUN cp -r dracut/* /usr/lib/dracut/modules.d + RUN cp dracut.conf /etc/dracut.conf.d/10-kcrypt.conf + RUN kernel=$(ls /lib/modules | head -n1) && \ + dracut -f "/boot/initrd-${kernel}" "${kernel}" && \ + ln -sf "initrd-${kernel}" /boot/initrd + ARG INITRD=$(readlink -f /boot/initrd) + SAVE ARTIFACT $INITRD AS LOCAL initrd + +image: + FROM $BASE_IMAGE + ARG IMAGE=dracut + ARG INITRD=$(readlink -f /boot/initrd) + ARG NAME=$(basename $INITRD) + COPY +build-dracut/$NAME $INITRD + COPY +build-kcrypt/kcrypt /usr/bin/kcrypt + # This is the discovery plugin that needs to be replaced! + # TODO: After install, copy any discovery plugin found to /oem - this is weak - another way is to inject the binary into the initrd, but a the moment it is not working properly. + COPY +dummy-discovery/dummy-discovery /system/discovery/kcrypt-discovery-dummy + # XXX: this is not working properly, but avoids the /oem copy. + #RUN kcrypt inject-initrd $INITRD /system/discovery/kcrypt-discovery-dummy /system/discovery/kcrypt-discovery-dummy + SAVE IMAGE $IMAGE + +dummy-discovery: + FROM golang:alpine + COPY . /work + WORKDIR /work + RUN CGO_ENABLED=0 go build -o dummy-discovery ./examples/dummy-discovery + SAVE ARTIFACT /work/dummy-discovery AS LOCAL kcrypt-discovery-dummy + + +iso: + ARG ISO_NAME=test + ARG IMAGE + FROM quay.io/c3os/osbuilder-tools + + WORKDIR /build + RUN zypper in -y jq docker wget + RUN mkdir -p files-iso/boot/grub2 + RUN wget https://raw.githubusercontent.com/c3os-io/c3os/master/overlay/files-iso/boot/grub2/grub.cfg -O files-iso/boot/grub2/grub.cfg + WITH DOCKER --allow-privileged --load $IMAGE=(+image) + RUN /entrypoint.sh --name $ISO_NAME --debug build-iso --date=false --local --overlay-iso /build/files-iso $IMAGE --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 iso AS LOCAL build/$ISO_NAME.iso + SAVE ARTIFACT /build/$ISO_NAME.iso.sha256 sha256 AS LOCAL build/$ISO_NAME.iso.sha256 diff --git a/dracut.conf b/dracut.conf new file mode 100644 index 0000000..448ac5e --- /dev/null +++ b/dracut.conf @@ -0,0 +1 @@ +add_dracutmodules+=" kcrypt " \ No newline at end of file diff --git a/dracut/29kcrypt/kcrypt.service b/dracut/29kcrypt/kcrypt.service new file mode 100644 index 0000000..c0d4dc9 --- /dev/null +++ b/dracut/29kcrypt/kcrypt.service @@ -0,0 +1,9 @@ +[Unit] +Description=kcrypt mount +DefaultDependencies=no +Before=cos-immutable-rootfs.service + +[Service] +Type=oneshot +RemainAfterExit=no +ExecStart=/sbin/kcrypt-mount-local diff --git a/dracut/29kcrypt/module-setup.sh b/dracut/29kcrypt/module-setup.sh new file mode 100644 index 0000000..aa308fe --- /dev/null +++ b/dracut/29kcrypt/module-setup.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# called by dracut +check() { + require_binaries "$systemdutildir"/systemd || return 1 + return 255 +} + +# called by dracut +depends() { + echo systemd rootfs-block dm fs-lib + #tpm2-tss + return 0 +} + +# called by dracut +installkernel() { + instmods overlay +} + +# called by dracut +install() { + declare moddir=${moddir} + declare systemdutildir=${systemdutildir} + declare systemdsystemunitdir=${systemdsystemunitdir} + declare initdir="${initdir}" + + inst_multiple \ + kcrypt + inst_script "${moddir}/mount-local.sh" "/sbin/kcrypt-mount-local" + #inst_hook pre-trigger 10 "$moddir/mount-local.sh" + inst_simple "${moddir}/kcrypt.service" \ + "${systemdsystemunitdir}/kcrypt.service" + mkdir -p "${initdir}/${systemdsystemunitdir}/initrd-fs.target.requires" + ln_r "../kcrypt.service" \ + "${systemdsystemunitdir}/initrd-fs.target.requires/kcrypt.service" + dracut_need_initqueue +} \ No newline at end of file diff --git a/dracut/29kcrypt/mount-local.sh b/dracut/29kcrypt/mount-local.sh new file mode 100755 index 0000000..827402d --- /dev/null +++ b/dracut/29kcrypt/mount-local.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh + +OEM=$(blkid -L COS_OEM) +if [ "$OEM" != "" ]; then + mkdir /oem + mount $OEM /oem +fi + +kcrypt unlock-all + +if [ "$OEM" != "" ]; then +umount /oem +fi diff --git a/examples/dummy-discovery/main.go b/examples/dummy-discovery/main.go new file mode 100644 index 0000000..84fecd9 --- /dev/null +++ b/examples/dummy-discovery/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/jaypipes/ghw/pkg/block" + "github.com/keirros-io/kcrypt/pkg/bus" + + "github.com/mudler/go-pluggable" +) + +func main() { + if len(os.Args) >= 2 && bus.IsEventDefined(os.Args[1]) { + checkErr(start()) + } +} + +func checkErr(err error) { + if err != nil { + fmt.Println(err) + os.Exit(1) + } + os.Exit(0) +} + +func 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{} + var errString string + err := json.Unmarshal([]byte(e.Data), b) + if err != nil { + errString = err.Error() + } + + return pluggable.EventResponse{ + Data: "hardcoded password", + Error: errString, + } + }) + + return factory.Run(pluggable.EventType(os.Args[1]), os.Stdin, os.Stdout) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..770cbcd --- /dev/null +++ b/go.mod @@ -0,0 +1,32 @@ +module github.com/keirros-io/kcrypt + +go 1.18 + +require ( + github.com/anatol/luks.go v0.0.0-20220803222236-155595903818 + github.com/hashicorp/go-multierror v1.1.1 + github.com/jaypipes/ghw v0.9.0 + github.com/mudler/go-pluggable v0.0.0-20220716112424-189d463e3ff3 + github.com/otiai10/copy v1.7.0 + github.com/urfave/cli v1.22.9 +) + +require ( + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/anatol/devmapper.go v0.0.0-20220716012224-693a1447fc15 // indirect + github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/jaypipes/pcidb v1.0.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect + golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + howett.net/plist v1.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..975385d --- /dev/null +++ b/go.sum @@ -0,0 +1,136 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/anatol/devmapper.go v0.0.0-20220716012224-693a1447fc15 h1:741Z9HQjbLXe4SnZ6liQTYINeD559oFl/vOtzA21KM0= +github.com/anatol/devmapper.go v0.0.0-20220716012224-693a1447fc15/go.mod h1:Ow7/kdG1m4K6UDTOwJeYJv9bkSLoL44qSu7S/Bxxi4Y= +github.com/anatol/luks.go v0.0.0-20220803222236-155595903818 h1:IyYgXFMhnSIbkDnDrTnMrPDYlc/uYhG5UcGX6NVkO58= +github.com/anatol/luks.go v0.0.0-20220803222236-155595903818/go.mod h1:1lE8PgTi0cS+3YsxbWcpfvGfjiPrRdcKJZc9j70OOTs= +github.com/anatol/vmtest v0.0.0-20220413190228-7a42f1f6d7b8 h1:t4JGeY9oaF5LB4Rdx9e2wARRRPAYt8Ow4eCf5SwO3fA= +github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4= +github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jaypipes/ghw v0.9.0 h1:TWF4wNIGtZcgDJaiNcFgby5BR8s2ixcUe0ydxNO2McY= +github.com/jaypipes/ghw v0.9.0/go.mod h1:dXMo19735vXOjpIBDyDYSp31sB2u4hrtRCMxInqQ64k= +github.com/jaypipes/pcidb v1.0.0 h1:vtZIfkiCUE42oYbJS0TAq9XSfSmcsgo9IdxSm9qzYU8= +github.com/jaypipes/pcidb v1.0.0/go.mod h1:TnYUvqhPBzCKnH34KrIX22kAeEbDCSRJ9cqLRCuNDfk= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mudler/go-pluggable v0.0.0-20220716112424-189d463e3ff3 h1:t4X6t8WisUy5mExfS58RBOkzaEGmuor5kOUMQS8lT2g= +github.com/mudler/go-pluggable v0.0.0-20220716112424-189d463e3ff3/go.mod h1:WmKcT8ONmhDQIqQ+HxU+tkGWjzBEyY/KFO8LTGCu4AI= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= +github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= +github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI= +github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef h1:7D6Nm4D6f0ci9yttWaKjM1TMAXrH5Su72dojqYGntFY= +github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw= +github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 h1:Y7NOhdqIOU8kYI7BxsgL38d0ot0raxvcW+EMQU2QrT4= +golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/main.go b/main.go new file mode 100644 index 0000000..1b28a60 --- /dev/null +++ b/main.go @@ -0,0 +1,342 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + luks "github.com/anatol/luks.go" + multierror "github.com/hashicorp/go-multierror" + "github.com/jaypipes/ghw" + "github.com/jaypipes/ghw/pkg/block" + "github.com/keirros-io/kcrypt/pkg/bus" + "github.com/mudler/go-pluggable" + cp "github.com/otiai10/copy" + "github.com/urfave/cli" +) + +// TODO: Ask to discovery a pass to unlock. keep waiting until we get it and a timeout is exhausted with retrials (exp backoff) +func getPassword(b *block.Partition) (password string, err error) { + bus.Reload() + + bus.Manager.Response(bus.EventDiscoveryPassword, func(p *pluggable.Plugin, r *pluggable.EventResponse) { + password = r.Data + if r.Errored() { + err = fmt.Errorf("failed discovery: %s", r.Error) + } + }) + bus.Manager.Publish(bus.EventDiscoveryPassword, b) + + if password == "" { + return password, fmt.Errorf("received empty password") + } + + return +} + +func luksUnlock(device, mapper, password string) error { + dev, err := luks.Open(device) + if err != nil { + // handle error + return err + } + defer dev.Close() + + // set LUKS flags before unlocking the volume + if err := dev.FlagsAdd(luks.FlagAllowDiscards); err != nil { + log.Print(err) + } + + // UnsealVolume+SetupMapper is equivalent of `cryptsetup open /dev/sda1 volumename` + volume, err := dev.UnsealVolume(0, []byte(password)) + if err == luks.ErrPassphraseDoesNotMatch { + return fmt.Errorf("incorrect password") + } else if err != nil { + return err + } + return volume.SetupMapper(mapper) +} + +func unlockDisk(b *block.Partition) error { + pass, err := getPassword(b) + if err != nil { + return fmt.Errorf("error retreiving password remotely: %w", err) + } + + return luksUnlock(fmt.Sprintf("/dev/%s", b.Name), b.UUID, pass) +} + +func createLuks(dev, password, version string, cryptsetupArgs ...string) error { + if version == "" { + version = "luks2" + } + args := []string{"luksFormat", "--type", version, "--iter-time", "5", "-q", dev} + args = append(args, cryptsetupArgs...) + cmd := exec.Command("cryptsetup", args...) + cmd.Stdin = strings.NewReader(password) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return err + } + + return nil +} + +func createDiskImage() (*os.File, error) { + disk, err := ioutil.TempFile("", "luksv2.go.disk") + if err != nil { + return nil, err + } + + if err := disk.Truncate(24 * 1024 * 1024); err != nil { + return nil, err + } + + return disk, err +} + +// TODO: A crypt disk utility to call after install, that with discovery discoveries the password that should be used +// this function should delete COS_PERSISTENT. delete the partition and create a luks+type in place. + +// Take a part label, and recreates it with LUKS. IT OVERWRITES DATA! +func luksify(label string) error { + // blkid + persistent, b, err := findPartition(label) + if err != nil { + return err + } + + pass, err := getPassword(b) + if err != nil { + return err + } + + if err := createLuks(persistent, pass, "luks2"); err != nil { + return err + } + + if err := luksUnlock(persistent, b.UUID, pass); err != nil { + return err + } + + out, err := sh(fmt.Sprintf("mkfs.ext4 %s -n %s", b.UUID, label)) + + if err != nil { + return fmt.Errorf("err: %w, out: %s", err, out) + } + + return nil +} + +func findPartition(label string) (string, *block.Partition, error) { + block, err := ghw.Block() + if err == nil { + for _, disk := range block.Disks { + for _, p := range disk.Partitions { + if p.Label == label { + return p.Name, p, nil + } + + } + } + } else { + return "", nil, err + } + + return "", nil, fmt.Errorf("not found") +} + +func sh(c string) (string, error) { + o, err := exec.Command("/bin/sh", "-c", c).CombinedOutput() + return string(o), err +} + +const ( + GZType = "gz" + XZType = "xz" + LZMAType = "lzma" +) + +// TODO: replace with golang native code +func detect(archive string) (string, error) { + out, err := sh(fmt.Sprintf("file %s", archive)) + if err != nil { + return "", err + } + out = strings.ToLower(out) + if strings.Contains(out, "xz") { + return XZType, nil + + } else if strings.Contains(out, "lzma") { + return LZMAType, nil + + } else if strings.Contains(out, "gz") { + return GZType, nil + + } + + return "", fmt.Errorf("Unknown") +} + +// TODO: replace with golang native code +func extractInitrd(initrd string, dst string) error { + os.MkdirAll(dst, os.ModePerm) + var out string + var err error + format, err := detect(initrd) + if err != nil { + return err + } + if format == XZType || format == LZMAType { + out, err = sh(fmt.Sprintf("cd %s && xz -dc < %s | cpio -idmv", dst, initrd)) + } else if format == GZType { + out, err = sh(fmt.Sprintf("cd %s && zcat %s | cpio -idmv", dst, initrd)) + } + fmt.Println(out) + + return err +} + +func createInitrd(initrd string, src string, format string) error { + fmt.Printf("Creating '%s' from '%s' as '%s'\n",initrd, src,format) + + if _, err := os.Stat(src); err != nil { + return err + } + var err error + var out string + if format == XZType { + out, err = sh(fmt.Sprintf("cd %s && find . 2>/dev/null | cpio -H newc --quiet --null -o -R root:root | xz -0 --check=crc32 > %s", src, initrd)) + } else if format == GZType { + out, err = sh(fmt.Sprintf("cd %s && find . | cpio -H newc -o -R root:root | gzip -9 > %s", src, initrd)) + } else if format == LZMAType { + out, err = sh(fmt.Sprintf("cd %s && find . 2>/dev/null | cpio -H newc -o -R root:root | xz -9 --format=lzma > %s", src, initrd)) + } + fmt.Println(out) + + return err +} + +// TODO: A inject initramfs command to add the discovery e.g. to use inside Dockerfiles + +func injectInitrd(initrd string, file, dst string) error { + + fmt.Printf("Injecting '%s' as '%s' into '%s'\n",file, dst,initrd) + format, err := detect(initrd) + if err != nil { + return err + } + tmp, err := ioutil.TempDir("", "kcrypt") + if err != nil { + return fmt.Errorf("cannot create tempdir, %s", err) + } + defer os.RemoveAll(tmp) + + fmt.Printf("Extracting '%s' in '%s' ...\n",initrd, tmp) + if err := extractInitrd(initrd, tmp); err != nil { + return fmt.Errorf("cannot extract initrd, %s", err) + } + + d:=filepath.Join(tmp, dst) + fmt.Printf("Copying '%s' in '%s' ...\n",file, d) + if err := cp.Copy(file, d); err != nil { + return fmt.Errorf("cannot copy file, %s", err) + } + + return createInitrd(initrd, tmp, format) +} + +// TODO: a custom toolkit version, to build out initrd pre-built with this component +func unlockAll() error { + bus.Manager.Initialize() + + block, err := ghw.Block() + if err == nil { + for _, disk := range block.Disks { + for _, p := range disk.Partitions { + if p.Type == "crypto_LUKS" { + fmt.Printf("Unmounted Luks found at '%s' LABEL '%s' \n", p.Name, p.Label) + err = multierror.Append(err, unlockDisk(p)) + if err != nil { + fmt.Printf("Unlocking failed: '%s'\n", err.Error()) + } + time.Sleep(10 * time.Second) + } + } + } + } + return err +} + +func main() { + app := &cli.App{ + Name: "keiros-kcrypt", + Version: "0.1", + Author: "Ettore Di Giacinto", + Usage: "keiros escrow key agent component", + Description: ``, + UsageText: ``, + Copyright: "Ettore Di Giacinto", + Commands: []cli.Command{ + { + + Name: "extract-initrd", + Action: func(c *cli.Context) error { + if c.NArg() != 2 { + return fmt.Errorf("requires 3 args. initrd,, dst") + } + return extractInitrd(c.Args()[0], c.Args()[1]) + }, + }, + { + + Name: "encrypt", + Description: "Encrypts a partition", + Action: func(c *cli.Context) error { + if c.NArg() != 1 { + return fmt.Errorf("requires 1 arg, the partition label") + } + return luksify(c.Args().First()) + }, + }, + { + + Name: "inject-initrd", + Action: func(c *cli.Context) error { + if c.NArg() != 3 { + return fmt.Errorf("requires 3 args. initrd, srcfile, dst") + } + return injectInitrd(c.Args()[0], c.Args()[1], c.Args()[2]) + }, + }, + { + Name: "unlock-all", + UsageText: "unlock-all", + Usage: "Try to unlock all LUKS partitions", + Description: ` +Typically run during initrd to unlock all the LUKS partitions found + `, + ArgsUsage: "kcrypt unlock-all", + Flags: []cli.Flag{ + + &cli.StringFlag{}, + }, + Action: func(c *cli.Context) error { + return unlockAll() + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } +} diff --git a/pkg/bus/bus.go b/pkg/bus/bus.go new file mode 100644 index 0000000..f3b5a2b --- /dev/null +++ b/pkg/bus/bus.go @@ -0,0 +1,65 @@ +package bus + +import ( + "fmt" + "os" + + "github.com/mudler/go-pluggable" +) + +// Manager is the bus instance manager, which subscribes plugins to events emitted. +var Manager = NewBus() + +func NewBus() *Bus { + return &Bus{ + Manager: pluggable.NewManager(AllEvents), + } +} + +func Reload() { + Manager = NewBus() + Manager.Initialize() +} + +type Bus struct { + *pluggable.Manager + registered bool +} + +func (b *Bus) LoadProviders() { + wd, _ := os.Getwd() + b.Manager.Autoload("kcrypt-discovery", "/system/discovery", "/oem/kcrypt", wd).Register() +} + +func (b *Bus) Initialize() { + if b.registered { + return + } + + b.LoadProviders() + for i := range b.Manager.Events { + e := b.Manager.Events[i] + b.Manager.Response(e, func(p *pluggable.Plugin, r *pluggable.EventResponse) { + if os.Getenv("BUS_DEBUG") == "true" { + fmt.Println( + fmt.Sprintf("[provider event: %s]", e), + "received from", + p.Name, + "at", + p.Executable, + r, + ) + } + if r.Errored() { + err := fmt.Sprintf("Provider %s at %s had an error: %s", p.Name, p.Executable, r.Error) + fmt.Println(err) + os.Exit(1) + } else { + if r.State != "" { + fmt.Println(fmt.Sprintf("[provider event: %s]", e), r.State) + } + } + }) + } + b.registered = true +} diff --git a/pkg/bus/events.go b/pkg/bus/events.go new file mode 100644 index 0000000..b39b5ac --- /dev/null +++ b/pkg/bus/events.go @@ -0,0 +1,45 @@ +package bus + +import ( + "github.com/mudler/go-pluggable" +) + +const ( + // Package events. + + // EventChallenge is issued before installation begins to gather information about how the device should be provisioned. + EventDiscoveryPassword pluggable.EventType = "discovery.password" +) + +// AllEvents is a convenience list of all the events streamed from the bus. +var AllEvents = []pluggable.EventType{ + EventDiscoveryPassword, +} + +// IsEventDefined checks wether an event is defined in the bus. +// It accepts strings or EventType, returns a boolean indicating that +// the event was defined among the events emitted by the bus. +func IsEventDefined(i interface{}) bool { + checkEvent := func(e pluggable.EventType) bool { + for _, ee := range AllEvents { + if ee == e { + return true + } + } + + return false + } + + switch f := i.(type) { + case string: + return checkEvent(pluggable.EventType(f)) + case pluggable.EventType: + return checkEvent(f) + default: + return false + } +} + +func EventError(err error) pluggable.EventResponse { + return pluggable.EventResponse{Error: err.Error()} +}