From d84b1ea2c28ce9595f0139b0e09dad61708966a6 Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Thu, 10 Nov 2022 16:20:47 +0200 Subject: [PATCH 1/6] Make `kcrypt encrypt` return useful lable/uuid mapping data part of: https://github.com/kairos-io/kairos/issues/380 Signed-off-by: Dimitris Karakasilis --- main.go | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index bd27240..7924858 100644 --- a/main.go +++ b/main.go @@ -109,45 +109,50 @@ func createDiskImage() (*os.File, error) { // 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 { +// On success, it returns a machine parseable string with the partition information +// (label:name:uuid) so that it can be stored by the caller for later use. +// This is because the label of the encrypted partition is not accessible unless +// the partition is decrypted first and the uuid changed after encryption so +// any stored information needs to be updated (by the caller). +func luksify(label string) (string, error) { // blkid persistent, b, err := findPartition(label) if err != nil { - return err + return "", err } pass, err := getPassword(b) if err != nil { - return err + return "", err } persistent = fmt.Sprintf("/dev/%s", persistent) devMapper := fmt.Sprintf("/dev/mapper/%s", b.Name) if err := createLuks(persistent, pass, "luks1"); err != nil { - return err + return "", err } if err := luksUnlock(persistent, b.Name, pass); err != nil { - return err + return "", err } if err := waitdevice(devMapper, 10); err != nil { - return err + return "", err } out, err := sh(fmt.Sprintf("mkfs.ext4 -L %s %s", label, devMapper)) if err != nil { - return fmt.Errorf("err: %w, out: %s", err, out) + return "", fmt.Errorf("err: %w, out: %s", err, out) } out2, err := sh(fmt.Sprintf("cryptsetup close %s", b.Name)) if err != nil { - return fmt.Errorf("err: %w, out: %s", err, out2) + return "", fmt.Errorf("err: %w, out: %s", err, out2) } - return nil + return fmt.Sprintf("%s:%s:%s", b.Label, b.Name, b.UUID), nil } func findPartition(label string) (string, *block.Partition, error) { @@ -318,7 +323,12 @@ func main() { if c.NArg() != 1 { return fmt.Errorf("requires 1 arg, the partition label") } - return luksify(c.Args().First()) + out, err := luksify(c.Args().First()) + if err != nil { + return err + } + fmt.Println(out) + return nil }, }, { From 82c6e8fcd092bd05aeda8fd5ef292965957ee95f Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Fri, 11 Nov 2022 10:54:33 +0200 Subject: [PATCH 2/6] Create parition info parsing library to be used both here (when trying to find the partition UUID using a label) and on the kairos side when updating the file after calling kcrypt to encrypt a partition (which causes the UUID to change). Signed-off-by: Dimitris Karakasilis --- go.mod | 12 ++- go.sum | 24 +++-- pkg/partition_info/partition_info.go | 59 +++++++++++++ pkg/partition_info/partition_info_test.go | 102 ++++++++++++++++++++++ pkg/partition_info/suite_test.go | 13 +++ tests/assets/partition_info.yaml | 2 + 6 files changed, 202 insertions(+), 10 deletions(-) create mode 100644 pkg/partition_info/partition_info.go create mode 100644 pkg/partition_info/partition_info_test.go create mode 100644 pkg/partition_info/suite_test.go create mode 100644 tests/assets/partition_info.yaml diff --git a/go.mod b/go.mod index a1723ff..a8580ef 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,12 @@ require ( 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/onsi/ginkgo/v2 v2.5.0 + github.com/onsi/gomega v1.24.0 github.com/otiai10/copy v1.7.0 + github.com/pkg/errors v0.9.1 github.com/urfave/cli v1.22.9 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -17,16 +21,18 @@ require ( 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-logr/logr v1.2.3 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/go-cmp v0.5.9 // 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 + golang.org/x/net v0.1.0 // indirect + golang.org/x/sys v0.1.0 // indirect + golang.org/x/text v0.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect howett.net/plist v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index 975385d..193c695 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo 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-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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= @@ -26,9 +28,12 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU 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/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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= @@ -57,10 +62,13 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W 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/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= 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/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= 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= @@ -90,7 +98,8 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r 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/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 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= @@ -103,15 +112,14 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w 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/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/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/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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= @@ -119,6 +127,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ 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= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 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= @@ -132,5 +141,6 @@ 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= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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/pkg/partition_info/partition_info.go b/pkg/partition_info/partition_info.go new file mode 100644 index 0000000..212ddc8 --- /dev/null +++ b/pkg/partition_info/partition_info.go @@ -0,0 +1,59 @@ +package partition_info + +import ( + "io/ioutil" + "strings" + + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +// PartitionInfo maps a partition label to a partition UUID. +// It's used in order to be able to ask the kcrypt-challenger for the passphrase +// using the partition label, even when the label is not accessible (e.g. before +// decrypting the partition). The UUID can be used to lookup the partition label +// and make the request. +type PartitionInfo map[string]string + +// UpdatePartitionLabelMapping takes partition information as a string argument +// the the form: `label:name:uuid` (that's what the `kcrypt encrypt` command returns +// on success. This function stores it in the PartitionInfoFile yaml file for +// later use. +func UpdatePartitionLabelMapping(partitionData, file string) error { + partitionInfo, err := ParsePartitionInfoFile(file) + if err != nil { + return err + } + + parts := strings.Split(partitionData, ":") + partitionInfo[parts[0]] = parts[2] + + return UpdatePartitionInfoFile(partitionInfo, file) +} + +func ParsePartitionInfoFile(file string) (PartitionInfo, error) { + var result PartitionInfo + + yamlFile, err := ioutil.ReadFile(file) + if err != nil { + return result, errors.Wrap(err, "reading the partition info file") + } + err = yaml.Unmarshal(yamlFile, &result) + if err != nil { + return result, errors.Wrap(err, "unmarshalling partition info file") + } + + return result, nil +} + +func UpdatePartitionInfoFile(partitionInfo PartitionInfo, file string) error { + data, err := yaml.Marshal(&partitionInfo) + if err != nil { + return errors.Wrap(err, "marshalling the new partition info to yaml") + } + err = ioutil.WriteFile(file, data, 0) + if err != nil { + return errors.Wrap(err, "writing back the partition info file") + } + return nil +} diff --git a/pkg/partition_info/partition_info_test.go b/pkg/partition_info/partition_info_test.go new file mode 100644 index 0000000..bd213ff --- /dev/null +++ b/pkg/partition_info/partition_info_test.go @@ -0,0 +1,102 @@ +package partition_info_test + +import ( + "io/ioutil" + "os" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + pi "github.com/kairos-io/kcrypt/pkg/partition_info" +) + +var _ = Describe("Partition Info file parsing", func() { + Describe("ParsePartitionInfoFile", func() { + var file string + + BeforeEach(func() { + file = "../../tests/assets/partition_info.yaml" + }) + + It("parses the file correctly", func() { + info, err := pi.ParsePartitionInfoFile(file) + Expect(err).ToNot(HaveOccurred()) + Expect(len(info)).To(Equal(2)) + Expect(info["COS_PERSISTENT"]).To(Equal("some_uuid_1")) + Expect(info["COS_OTHER"]).To(Equal("some_uuid_2")) + }) + }) + + Describe("UpdatePartitionInfoFile", func() { + var file *os.File + var err error + + BeforeEach(func() { + file, err = ioutil.TempFile("", "parition-info.*.yaml") + Expect(err).ToNot(HaveOccurred()) + + fileContents := ` +- label: TO_DELETE + uuid: old_uuid_1 +- label: TO_BE_UPDATED + uuid: old_uuid_2 +` + _, err = file.Write([]byte(fileContents)) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + os.Remove(file.Name()) + }) + + It("Updates the partition file correctly", func() { + partitionInfo := pi.PartitionInfo{ + "TO_BE_UPDATED": "new_uuid_1", + "NEW_PARTITION": "new_uuid_2", + } + + err = pi.UpdatePartitionInfoFile(partitionInfo, file.Name()) + Expect(err).ToNot(HaveOccurred()) + + dat, err := os.ReadFile(file.Name()) + Expect(err).ToNot(HaveOccurred()) + + expectedContent := `NEW_PARTITION: new_uuid_2 +TO_BE_UPDATED: new_uuid_1 +` + Expect(string(dat)).To(Equal(expectedContent)) + }) + }) + + Describe("UpdatePartitionLabelMapping", func() { + var file *os.File + var err error + + BeforeEach(func() { + file, err = ioutil.TempFile("", "parition-info.*.yaml") + Expect(err).ToNot(HaveOccurred()) + + _, err = file.Write([]byte("TO_KEEP: old_uuid_1")) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + os.Remove(file.Name()) + }) + + It("Updates the file correctly from a `kcrypt encrypt` return value", func() { + partitionData := "TO_BE_ADDED:some_name:new_uuid" + + err = pi.UpdatePartitionLabelMapping(partitionData, file.Name()) + Expect(err).ToNot(HaveOccurred()) + + dat, err := os.ReadFile(file.Name()) + Expect(err).ToNot(HaveOccurred()) + + expectedContent := `TO_BE_ADDED: new_uuid +TO_KEEP: old_uuid_1 +` + Expect(string(dat)).To(Equal(expectedContent)) + }) + }) +}) diff --git a/pkg/partition_info/suite_test.go b/pkg/partition_info/suite_test.go new file mode 100644 index 0000000..e3e910b --- /dev/null +++ b/pkg/partition_info/suite_test.go @@ -0,0 +1,13 @@ +package partition_info + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestPartitionINfo(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "PartitionInfo file parser test suite") +} diff --git a/tests/assets/partition_info.yaml b/tests/assets/partition_info.yaml new file mode 100644 index 0000000..4f630ab --- /dev/null +++ b/tests/assets/partition_info.yaml @@ -0,0 +1,2 @@ +COS_PERSISTENT: some_uuid_1 +COS_OTHER: some_uuid_2 From c0d6b81b5d83b791d1c4c900de045dbd3d52251a Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Fri, 11 Nov 2022 11:46:18 +0200 Subject: [PATCH 3/6] Add github action pipeline to run unit tests Signed-off-by: Dimitris Karakasilis --- .github/workflows/unit-tests.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/unit-tests.yml diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..0189eb3 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,26 @@ +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: Install Go + uses: actions/setup-go@v3 + with: + go-version: '^1.18' + + - name: Install Ginkgo + run: go install github.com/onsi/ginkgo/v2/ginkgo@v2.5.0 + + - name: Run tests + run: | + ginkgo run ./... From 8ca95e953b513ef1c3d72361ef495259c66e05e4 Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Fri, 11 Nov 2022 12:11:43 +0200 Subject: [PATCH 4/6] Add functions to produce a string representation of a partition so that we encapsulate all the logic in the same package Signed-off-by: Dimitris Karakasilis --- main.go | 4 ++- pkg/partition_info/partition_info.go | 18 +++++++++++-- pkg/partition_info/partition_info_test.go | 33 +++++++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 7924858..debc97c 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,8 @@ import ( "github.com/mudler/go-pluggable" cp "github.com/otiai10/copy" "github.com/urfave/cli" + + pi "github.com/kairos-io/kcrypt/pkg/partition_info" ) func waitdevice(device string, attempts int) error { @@ -152,7 +154,7 @@ func luksify(label string) (string, error) { return "", fmt.Errorf("err: %w, out: %s", err, out2) } - return fmt.Sprintf("%s:%s:%s", b.Label, b.Name, b.UUID), nil + return pi.PartitionToString(b), nil } func findPartition(label string) (string, *block.Partition, error) { diff --git a/pkg/partition_info/partition_info.go b/pkg/partition_info/partition_info.go index 212ddc8..10b04ae 100644 --- a/pkg/partition_info/partition_info.go +++ b/pkg/partition_info/partition_info.go @@ -1,9 +1,11 @@ package partition_info import ( + "fmt" "io/ioutil" "strings" + "github.com/jaypipes/ghw/pkg/block" "github.com/pkg/errors" "gopkg.in/yaml.v3" ) @@ -15,6 +17,18 @@ import ( // and make the request. type PartitionInfo map[string]string +func PartitionToString(p *block.Partition) string { + return fmt.Sprintf("%s:%s:%s", p.Label, p.Name, p.UUID) +} + +// Takes a partition info string (as returned by PartitionToString) and return +// the partition label and the UUID +func PartitionDataFromString(partitionStr string) (string, string) { + parts := strings.Split(partitionStr, ":") + + return parts[0], parts[2] +} + // UpdatePartitionLabelMapping takes partition information as a string argument // the the form: `label:name:uuid` (that's what the `kcrypt encrypt` command returns // on success. This function stores it in the PartitionInfoFile yaml file for @@ -25,8 +39,8 @@ func UpdatePartitionLabelMapping(partitionData, file string) error { return err } - parts := strings.Split(partitionData, ":") - partitionInfo[parts[0]] = parts[2] + label, uuid := PartitionDataFromString(partitionData) + partitionInfo[label] = uuid return UpdatePartitionInfoFile(partitionInfo, file) } diff --git a/pkg/partition_info/partition_info_test.go b/pkg/partition_info/partition_info_test.go index bd213ff..eced9d8 100644 --- a/pkg/partition_info/partition_info_test.go +++ b/pkg/partition_info/partition_info_test.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "os" + "github.com/jaypipes/ghw/pkg/block" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -99,4 +100,36 @@ TO_KEEP: old_uuid_1 Expect(string(dat)).To(Equal(expectedContent)) }) }) + + Describe("PartitionToString", func() { + var partition *block.Partition + + BeforeEach(func() { + partition = &block.Partition{ + Disk: nil, + Name: "sda1", + Label: "COS_PERSISTENT", + MountPoint: "/mnt/sda1", + UUID: "some_uuid_here", + } + }) + + It("returns a string representation of the partition data", func() { + Expect(pi.PartitionToString(partition)).To(Equal("COS_PERSISTENT:sda1:some_uuid_here")) + }) + }) + + Describe("PartitionDataFromString", func() { + var partitionData string + + BeforeEach(func() { + partitionData = "THE_LABEL:the_name:the_uuid" + }) + + It("returns the label and the uuid", func() { + label, uuid := pi.PartitionDataFromString(partitionData) + Expect(label).To(Equal("THE_LABEL")) + Expect(uuid).To(Equal("the_uuid")) + }) + }) }) From 4a6c79f6a638a4281bc802ce1654def139b3080a Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Fri, 11 Nov 2022 13:04:17 +0200 Subject: [PATCH 5/6] Change to a more object-oriented approach Now the code can simply initialize a PartitionInfo from a file and then call LookupUUIDForLabel on it. Signed-off-by: Dimitris Karakasilis --- pkg/partition_info/partition_info.go | 79 +++++++----- pkg/partition_info/partition_info_test.go | 140 +++++++++++----------- 2 files changed, 115 insertions(+), 104 deletions(-) diff --git a/pkg/partition_info/partition_info.go b/pkg/partition_info/partition_info.go index 10b04ae..3bbf986 100644 --- a/pkg/partition_info/partition_info.go +++ b/pkg/partition_info/partition_info.go @@ -10,12 +10,56 @@ import ( "gopkg.in/yaml.v3" ) +const DefaultPartitionInfoFile = "/oem/partition_info.yaml" + // PartitionInfo maps a partition label to a partition UUID. // It's used in order to be able to ask the kcrypt-challenger for the passphrase // using the partition label, even when the label is not accessible (e.g. before // decrypting the partition). The UUID can be used to lookup the partition label // and make the request. -type PartitionInfo map[string]string +type PartitionInfo struct { + file string + mapping map[string]string +} + +func NewPartitionInfoFromFile(file string) (*PartitionInfo, error) { + mapping, err := ParsePartitionInfoFile(file) + if err != nil { + return nil, err + } + + return &PartitionInfo{ + file: file, + mapping: mapping, + }, nil +} + +func (pi PartitionInfo) LookupUUIDForLabel(l string) string { + return pi.mapping[l] +} + +// UpdatePartitionLabelMapping takes partition information as a string argument +// the the form: `label:name:uuid` (that's what the `kcrypt encrypt` command returns +// on success. This function stores it in the PartitionInfoFile yaml file for +// later use. +func (pi PartitionInfo) UpdateMapping(partitionData string) error { + label, uuid := PartitionDataFromString(partitionData) + pi.mapping[label] = uuid + + return pi.save() +} + +func (pi PartitionInfo) save() error { + data, err := yaml.Marshal(&pi.mapping) + if err != nil { + return errors.Wrap(err, "marshalling the new partition info to yaml") + } + err = ioutil.WriteFile(pi.file, data, 0) + if err != nil { + return errors.Wrap(err, "writing back the partition info file") + } + return nil +} func PartitionToString(p *block.Partition) string { return fmt.Sprintf("%s:%s:%s", p.Label, p.Name, p.UUID) @@ -29,29 +73,14 @@ func PartitionDataFromString(partitionStr string) (string, string) { return parts[0], parts[2] } -// UpdatePartitionLabelMapping takes partition information as a string argument -// the the form: `label:name:uuid` (that's what the `kcrypt encrypt` command returns -// on success. This function stores it in the PartitionInfoFile yaml file for -// later use. -func UpdatePartitionLabelMapping(partitionData, file string) error { - partitionInfo, err := ParsePartitionInfoFile(file) - if err != nil { - return err - } - - label, uuid := PartitionDataFromString(partitionData) - partitionInfo[label] = uuid - - return UpdatePartitionInfoFile(partitionInfo, file) -} - -func ParsePartitionInfoFile(file string) (PartitionInfo, error) { - var result PartitionInfo +func ParsePartitionInfoFile(file string) (map[string]string, error) { + var result map[string]string yamlFile, err := ioutil.ReadFile(file) if err != nil { return result, errors.Wrap(err, "reading the partition info file") } + err = yaml.Unmarshal(yamlFile, &result) if err != nil { return result, errors.Wrap(err, "unmarshalling partition info file") @@ -59,15 +88,3 @@ func ParsePartitionInfoFile(file string) (PartitionInfo, error) { return result, nil } - -func UpdatePartitionInfoFile(partitionInfo PartitionInfo, file string) error { - data, err := yaml.Marshal(&partitionInfo) - if err != nil { - return errors.Wrap(err, "marshalling the new partition info to yaml") - } - err = ioutil.WriteFile(file, data, 0) - if err != nil { - return errors.Wrap(err, "writing back the partition info file") - } - return nil -} diff --git a/pkg/partition_info/partition_info_test.go b/pkg/partition_info/partition_info_test.go index eced9d8..1a1e22d 100644 --- a/pkg/partition_info/partition_info_test.go +++ b/pkg/partition_info/partition_info_test.go @@ -12,6 +12,20 @@ import ( ) var _ = Describe("Partition Info file parsing", func() { + Describe("NewPartitionInfoFromFile", func() { + var file string + + BeforeEach(func() { + file = "../../tests/assets/partition_info.yaml" + }) + + It("returns a PartitionInfo", func() { + result, err := pi.NewPartitionInfoFromFile(file) + Expect(err).ToNot(HaveOccurred()) + Expect(result).ToNot(BeNil()) + }) + }) + Describe("ParsePartitionInfoFile", func() { var file string @@ -28,79 +42,6 @@ var _ = Describe("Partition Info file parsing", func() { }) }) - Describe("UpdatePartitionInfoFile", func() { - var file *os.File - var err error - - BeforeEach(func() { - file, err = ioutil.TempFile("", "parition-info.*.yaml") - Expect(err).ToNot(HaveOccurred()) - - fileContents := ` -- label: TO_DELETE - uuid: old_uuid_1 -- label: TO_BE_UPDATED - uuid: old_uuid_2 -` - _, err = file.Write([]byte(fileContents)) - Expect(err).ToNot(HaveOccurred()) - }) - - AfterEach(func() { - os.Remove(file.Name()) - }) - - It("Updates the partition file correctly", func() { - partitionInfo := pi.PartitionInfo{ - "TO_BE_UPDATED": "new_uuid_1", - "NEW_PARTITION": "new_uuid_2", - } - - err = pi.UpdatePartitionInfoFile(partitionInfo, file.Name()) - Expect(err).ToNot(HaveOccurred()) - - dat, err := os.ReadFile(file.Name()) - Expect(err).ToNot(HaveOccurred()) - - expectedContent := `NEW_PARTITION: new_uuid_2 -TO_BE_UPDATED: new_uuid_1 -` - Expect(string(dat)).To(Equal(expectedContent)) - }) - }) - - Describe("UpdatePartitionLabelMapping", func() { - var file *os.File - var err error - - BeforeEach(func() { - file, err = ioutil.TempFile("", "parition-info.*.yaml") - Expect(err).ToNot(HaveOccurred()) - - _, err = file.Write([]byte("TO_KEEP: old_uuid_1")) - Expect(err).ToNot(HaveOccurred()) - }) - - AfterEach(func() { - os.Remove(file.Name()) - }) - - It("Updates the file correctly from a `kcrypt encrypt` return value", func() { - partitionData := "TO_BE_ADDED:some_name:new_uuid" - - err = pi.UpdatePartitionLabelMapping(partitionData, file.Name()) - Expect(err).ToNot(HaveOccurred()) - - dat, err := os.ReadFile(file.Name()) - Expect(err).ToNot(HaveOccurred()) - - expectedContent := `TO_BE_ADDED: new_uuid -TO_KEEP: old_uuid_1 -` - Expect(string(dat)).To(Equal(expectedContent)) - }) - }) - Describe("PartitionToString", func() { var partition *block.Partition @@ -132,4 +73,57 @@ TO_KEEP: old_uuid_1 Expect(uuid).To(Equal("the_uuid")) }) }) + + Describe("UpdateMapping", func() { + var file *os.File + var err error + var partitionInfo *pi.PartitionInfo + + BeforeEach(func() { + file, err = ioutil.TempFile("", "partition-info.*.yaml") + Expect(err).ToNot(HaveOccurred()) + + _, err = file.Write([]byte("TO_KEEP: old_uuid_1")) + Expect(err).ToNot(HaveOccurred()) + + partitionInfo, err = pi.NewPartitionInfoFromFile(file.Name()) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + os.Remove(file.Name()) + }) + + It("Updates the file correctly from a `kcrypt encrypt` return value", func() { + partitionData := "TO_BE_ADDED:some_name:new_uuid" + + err = partitionInfo.UpdateMapping(partitionData) + Expect(err).ToNot(HaveOccurred()) + + dat, err := os.ReadFile(file.Name()) + Expect(err).ToNot(HaveOccurred()) + + expectedContent := `TO_BE_ADDED: new_uuid +TO_KEEP: old_uuid_1 +` + Expect(string(dat)).To(Equal(expectedContent)) + }) + }) + + Describe("LookupUUIDForLabel", func() { + var file string + var partitionInfo *pi.PartitionInfo + var err error + + BeforeEach(func() { + file = "../../tests/assets/partition_info.yaml" + partitionInfo, err = pi.NewPartitionInfoFromFile(file) + Expect(err).ToNot(HaveOccurred()) + }) + + It("parses the file correctly", func() { + uuid := partitionInfo.LookupUUIDForLabel("COS_PERSISTENT") + Expect(uuid).To(Equal("some_uuid_1")) + }) + }) }) From 29f22e7f925c3b3007b072f1a88c623bcf1e8e87 Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Fri, 11 Nov 2022 13:14:31 +0200 Subject: [PATCH 6/6] Update partition label with the store one before asking for a passphrase Signed-off-by: Dimitris Karakasilis --- main.go | 6 +++++ pkg/partition_info/partition_info.go | 10 ++++++++ pkg/partition_info/partition_info_test.go | 29 ++++++++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index debc97c..3cb8837 100644 --- a/main.go +++ b/main.go @@ -279,11 +279,17 @@ func injectInitrd(initrd string, file, dst string) error { func unlockAll() error { bus.Manager.Initialize() + partitionInfo, err := pi.NewPartitionInfoFromFile(pi.DefaultPartitionInfoFile) + if err != nil { + return err + } + block, err := ghw.Block() if err == nil { for _, disk := range block.Disks { for _, p := range disk.Partitions { if p.Type == "crypto_LUKS" { + p.Label = partitionInfo.LookupLabelForUUID(p.UUID) fmt.Printf("Unmounted Luks found at '%s' LABEL '%s' \n", p.Name, p.Label) err = multierror.Append(err, unlockDisk(p)) if err != nil { diff --git a/pkg/partition_info/partition_info.go b/pkg/partition_info/partition_info.go index 3bbf986..304f4c1 100644 --- a/pkg/partition_info/partition_info.go +++ b/pkg/partition_info/partition_info.go @@ -38,6 +38,16 @@ func (pi PartitionInfo) LookupUUIDForLabel(l string) string { return pi.mapping[l] } +func (pi PartitionInfo) LookupLabelForUUID(uuid string) string { + for k, v := range pi.mapping { + if v == uuid { + return k + } + } + + return "" +} + // UpdatePartitionLabelMapping takes partition information as a string argument // the the form: `label:name:uuid` (that's what the `kcrypt encrypt` command returns // on success. This function stores it in the PartitionInfoFile yaml file for diff --git a/pkg/partition_info/partition_info_test.go b/pkg/partition_info/partition_info_test.go index 1a1e22d..b77483b 100644 --- a/pkg/partition_info/partition_info_test.go +++ b/pkg/partition_info/partition_info_test.go @@ -121,9 +121,36 @@ TO_KEEP: old_uuid_1 Expect(err).ToNot(HaveOccurred()) }) - It("parses the file correctly", func() { + It("returns the correct UUID", func() { uuid := partitionInfo.LookupUUIDForLabel("COS_PERSISTENT") Expect(uuid).To(Equal("some_uuid_1")) }) + + It("returns an empty UUID when the label is not found", func() { + uuid := partitionInfo.LookupUUIDForLabel("DOESNT_EXIST") + Expect(uuid).To(Equal("")) + }) + }) + + Describe("LookupLabelForUUID", func() { + var file string + var partitionInfo *pi.PartitionInfo + var err error + + BeforeEach(func() { + file = "../../tests/assets/partition_info.yaml" + partitionInfo, err = pi.NewPartitionInfoFromFile(file) + Expect(err).ToNot(HaveOccurred()) + }) + + It("returns the correct label", func() { + uuid := partitionInfo.LookupLabelForUUID("some_uuid_1") + Expect(uuid).To(Equal("COS_PERSISTENT")) + }) + + It("returns an empty label when UUID doesn't exist", func() { + uuid := partitionInfo.LookupLabelForUUID("doesnt_exist") + Expect(uuid).To(Equal("")) + }) }) })