From ccc3f270d7ef532d93412afbdb3ad4b7f9dfa6ad Mon Sep 17 00:00:00 2001 From: Madhu Pillai Date: Wed, 20 Nov 2024 17:07:51 +0100 Subject: [PATCH] Support LUKS encryption using IBM CEX secure keys on s390x For fcos 1.6.0-exp & openshift 4.18.0-exp specs, expected to be based on stable 3.5.0 spec. See: https://github.com/coreos/ignition/issues/1693 See: https://github.com/coreos/fedora-coreos-tracker/issues/1708 --- base/v0_6_exp/schema.go | 5 ++ config/common/errors.go | 2 + config/fcos/v1_6_exp/schema.go | 1 + config/fcos/v1_6_exp/translate.go | 73 +++++++++++++++++++------- config/fcos/v1_6_exp/validate.go | 15 ++++++ config/fcos/v1_6_exp/validate_test.go | 74 +++++++++++++++++++++++++++ config/flatcar/v1_2_exp/translate.go | 8 ++- docs/config-fcos-v1_6-exp.md | 4 ++ docs/config-openshift-v4_18-exp.md | 4 ++ docs/examples.md | 14 +++++ docs/release-notes.md | 2 + internal/doc/butane.yaml | 5 ++ 12 files changed, 187 insertions(+), 20 deletions(-) diff --git a/base/v0_6_exp/schema.go b/base/v0_6_exp/schema.go index 67e7b95c..1104198f 100644 --- a/base/v0_6_exp/schema.go +++ b/base/v0_6_exp/schema.go @@ -14,6 +14,10 @@ package v0_6_exp +type Cex struct { + Enabled *bool `yaml:"enabled"` +} + type Clevis struct { Custom ClevisCustom `yaml:"custom"` Tang []Tang `yaml:"tang"` @@ -119,6 +123,7 @@ type Link struct { } type Luks struct { + Cex Cex `yaml:"cex"` Clevis Clevis `yaml:"clevis"` Device *string `yaml:"device"` Discard *bool `yaml:"discard"` diff --git a/config/common/errors.go b/config/common/errors.go index 922111ab..71f3c0d2 100644 --- a/config/common/errors.go +++ b/config/common/errors.go @@ -59,6 +59,8 @@ var ( ErrNoLuksBootDevice = errors.New("device is required for layouts: s390x-eckd, s390x-zfcp") ErrMirrorNotSupport = errors.New("mirroring not supported on layouts: s390x-eckd, s390x-zfcp, s390x-virt") ErrLuksBootDeviceBadName = errors.New("device name must start with /dev/dasd on s390x-eckd layout or /dev/sd on s390x-zfcp layout") + ErrCexArchitectureMismatch = errors.New("when using cex the targeted architecture must match s390x") + ErrCexNotSupported = errors.New("cex is not currently supported on the target platform") // partition ErrReuseByLabel = errors.New("partitions cannot be reused by label; number must be specified except on boot disk (/dev/disk/by-id/coreos-boot-disk) or when wipe_table is true") diff --git a/config/fcos/v1_6_exp/schema.go b/config/fcos/v1_6_exp/schema.go index 52cdfb43..c72795ca 100644 --- a/config/fcos/v1_6_exp/schema.go +++ b/config/fcos/v1_6_exp/schema.go @@ -31,6 +31,7 @@ type BootDevice struct { } type BootDeviceLuks struct { + Cex base.Cex `yaml:"cex"` Discard *bool `yaml:"discard"` Device *string `yaml:"device"` Tang []base.Tang `yaml:"tang"` diff --git a/config/fcos/v1_6_exp/translate.go b/config/fcos/v1_6_exp/translate.go index a7c0a679..e9a7ae2e 100644 --- a/config/fcos/v1_6_exp/translate.go +++ b/config/fcos/v1_6_exp/translate.go @@ -114,7 +114,7 @@ func (c Config) processBootDevice(config *types.Config, ts *translate.Translatio var r report.Report // check for high-level features - wantLuks := util.IsTrue(c.BootDevice.Luks.Tpm2) || len(c.BootDevice.Luks.Tang) > 0 + wantLuks := util.IsTrue(c.BootDevice.Luks.Tpm2) || len(c.BootDevice.Luks.Tang) > 0 || util.IsTrue(c.BootDevice.Luks.Cex.Enabled) wantMirror := len(c.BootDevice.Mirror.Devices) > 0 if !wantLuks && !wantMirror { return r @@ -252,25 +252,47 @@ func (c Config) processBootDevice(config *types.Config, ts *translate.Translatio default: luksDevice = "/dev/disk/by-partlabel/root" } - clevis, ts2, r2 := translateBootDeviceLuks(c.BootDevice.Luks, options) - rendered.Storage.Luks = []types.Luks{{ - Clevis: clevis, - Device: &luksDevice, - Discard: c.BootDevice.Luks.Discard, - Label: util.StrToPtr("luks-root"), - Name: "root", - WipeVolume: util.BoolToPtr(true), - }} - lpath := path.New("yaml", "boot_device", "luks") - rpath := path.New("json", "storage", "luks", 0) - renderedTranslations.Merge(ts2.PrefixPaths(lpath, rpath.Append("clevis"))) - renderedTranslations.AddTranslation(lpath.Append("discard"), rpath.Append("discard")) - for _, f := range []string{"device", "label", "name", "wipeVolume"} { - renderedTranslations.AddTranslation(lpath, rpath.Append(f)) + if util.IsTrue(c.BootDevice.Luks.Cex.Enabled) { + cex, ts2, r2 := translateBootDeviceLuksCex(c.BootDevice.Luks, options) + rendered.Storage.Luks = []types.Luks{{ + Cex: cex, + Device: &luksDevice, + Discard: c.BootDevice.Luks.Discard, + Label: util.StrToPtr("luks-root"), + Name: "root", + WipeVolume: util.BoolToPtr(true), + }} + lpath := path.New("yaml", "boot_device", "luks") + rpath := path.New("json", "storage", "luks", 0) + renderedTranslations.Merge(ts2.PrefixPaths(lpath, rpath.Append("cex"))) + renderedTranslations.AddTranslation(lpath.Append("discard"), rpath.Append("discard")) + for _, f := range []string{"device", "label", "name", "wipeVolume"} { + renderedTranslations.AddTranslation(lpath, rpath.Append(f)) + } + renderedTranslations.AddTranslation(lpath, rpath) + renderedTranslations.AddTranslation(lpath, path.New("json", "storage", "luks")) + r.Merge(r2) + } else { + clevis, ts2, r2 := translateBootDeviceLuks(c.BootDevice.Luks, options) + rendered.Storage.Luks = []types.Luks{{ + Clevis: clevis, + Device: &luksDevice, + Discard: c.BootDevice.Luks.Discard, + Label: util.StrToPtr("luks-root"), + Name: "root", + WipeVolume: util.BoolToPtr(true), + }} + lpath := path.New("yaml", "boot_device", "luks") + rpath := path.New("json", "storage", "luks", 0) + renderedTranslations.Merge(ts2.PrefixPaths(lpath, rpath.Append("clevis"))) + renderedTranslations.AddTranslation(lpath.Append("discard"), rpath.Append("discard")) + for _, f := range []string{"device", "label", "name", "wipeVolume"} { + renderedTranslations.AddTranslation(lpath, rpath.Append(f)) + } + renderedTranslations.AddTranslation(lpath, rpath) + renderedTranslations.AddTranslation(lpath, path.New("json", "storage", "luks")) + r.Merge(r2) } - renderedTranslations.AddTranslation(lpath, rpath) - renderedTranslations.AddTranslation(lpath, path.New("json", "storage", "luks")) - r.Merge(r2) } // create root filesystem @@ -317,6 +339,19 @@ func translateBootDeviceLuks(from BootDeviceLuks, options common.TranslateOption return } +func translateBootDeviceLuksCex(from BootDeviceLuks, options common.TranslateOptions) (to types.Cex, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + // Discard field is handled by the caller because it doesn't go + // into types.Cex + tm, r = translate.Prefixed(tr, "enabled", &from.Cex.Enabled, &to.Enabled) + translate.MergeP(tr, tm, &r, "enabled", &from.Cex.Enabled, &to.Enabled) + // we're being called manually, not via the translate package's + // custom translator mechanism, so we have to add the base + // translation ourselves + tm.AddTranslation(path.New("yaml"), path.New("json")) + return +} + func (c Config) handleUserGrubCfg(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { rendered := types.Config{} ts := translate.NewTranslationSet("yaml", "json") diff --git a/config/fcos/v1_6_exp/validate.go b/config/fcos/v1_6_exp/validate.go index ed705607..443f6523 100644 --- a/config/fcos/v1_6_exp/validate.go +++ b/config/fcos/v1_6_exp/validate.go @@ -19,6 +19,7 @@ import ( "strings" "github.com/coreos/butane/config/common" + "github.com/coreos/ignition/v2/config/shared/errors" "github.com/coreos/ignition/v2/config/util" "github.com/coreos/vcontext/path" @@ -55,6 +56,7 @@ func (d BootDevice) Validate(c path.ContextPath) (r report.Report) { if d.Layout != nil { switch *d.Layout { case "aarch64", "ppc64le", "x86_64": + // Nothing to do case "s390x-eckd": if util.NilOrEmpty(d.Luks.Device) { r.AddOnError(c.Append("layout"), common.ErrNoLuksBootDevice) @@ -77,6 +79,19 @@ func (d BootDevice) Validate(c path.ContextPath) (r report.Report) { r.AddOnError(c.Append("layout"), common.ErrMirrorNotSupport) } } + + // CEX is only valid on s390x and incompatible with Clevis + if util.IsTrue(d.Luks.Cex.Enabled) { + if d.Layout == nil { + r.AddOnError(c.Append("luks", "cex"), common.ErrCexArchitectureMismatch) + } else if !strings.HasPrefix(*d.Layout, "s390x") { + r.AddOnError(c.Append("layout"), common.ErrCexArchitectureMismatch) + } + if len(d.Luks.Tang) > 0 || util.IsTrue(d.Luks.Tpm2) { + r.AddOnError(c.Append("luks"), errors.ErrCexWithClevis) + } + } + r.Merge(d.Mirror.Validate(c.Append("mirror"))) return } diff --git a/config/fcos/v1_6_exp/validate_test.go b/config/fcos/v1_6_exp/validate_test.go index acd302d9..22ad6d91 100644 --- a/config/fcos/v1_6_exp/validate_test.go +++ b/config/fcos/v1_6_exp/validate_test.go @@ -161,6 +161,80 @@ func TestValidateBootDevice(t *testing.T) { nil, path.New("yaml"), }, + // complete config with cex + { + BootDevice{ + Layout: util.StrToPtr("s390x-eckd"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/dasda"), + Cex: base.Cex{ + Enabled: util.BoolToPtr(true), + }, + }, + }, + nil, + path.New("yaml"), + }, + // can not use both cex & tang + { + BootDevice{ + Layout: util.StrToPtr("s390x-eckd"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/dasda"), + Cex: base.Cex{ + Enabled: util.BoolToPtr(true), + }, + Tang: []base.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("x"), + }}, + }, + }, + errors.ErrCexWithClevis, + path.New("yaml", "luks"), + }, + // can not use both cex & tpm2 + { + BootDevice{ + Layout: util.StrToPtr("s390x-eckd"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/dasda"), + Cex: base.Cex{ + Enabled: util.BoolToPtr(true), + }, + Tpm2: util.BoolToPtr(true), + }, + }, + errors.ErrCexWithClevis, + path.New("yaml", "luks"), + }, + // can not use cex on non s390x + { + BootDevice{ + Layout: util.StrToPtr("x86_64"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/sda"), + Cex: base.Cex{ + Enabled: util.BoolToPtr(true), + }, + }, + }, + common.ErrCexArchitectureMismatch, + path.New("yaml", "layout"), + }, + // must set s390X layout with cex + { + BootDevice{ + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/sda"), + Cex: base.Cex{ + Enabled: util.BoolToPtr(true), + }, + }, + }, + common.ErrCexArchitectureMismatch, + path.New("yaml", "luks", "cex"), + }, // invalid layout { BootDevice{ diff --git a/config/flatcar/v1_2_exp/translate.go b/config/flatcar/v1_2_exp/translate.go index e74b6f40..e712d00d 100644 --- a/config/flatcar/v1_2_exp/translate.go +++ b/config/flatcar/v1_2_exp/translate.go @@ -22,9 +22,15 @@ import ( "github.com/coreos/vcontext/report" ) +var ( + fieldFilters = cutil.NewFilters(types.Config{}, cutil.FilterMap{ + "storage.luks.cex": common.ErrCexNotSupported, + }) +) + // Return FieldFilters for this spec. func (c Config) FieldFilters() *cutil.FieldFilters { - return nil + return &fieldFilters } // ToIgn3_5 translates the config to an Ignition config. It returns a diff --git a/docs/config-fcos-v1_6-exp.md b/docs/config-fcos-v1_6-exp.md index 6f8a4d1c..41c695c2 100644 --- a/docs/config-fcos-v1_6-exp.md +++ b/docs/config-fcos-v1_6-exp.md @@ -168,6 +168,8 @@ The Fedora CoreOS configuration is a YAML document conforming to the following s * **pin** (string): the clevis pin. * **config** (string): the clevis configuration JSON. * **_needs_network_** (boolean): whether or not the device requires networking. + * **_cex_** (object): describes the IBM Crypto Express (CEX) card configuration for the luks device. + * **_enabled_** (boolean): whether or not to use a CEX secure key to encrypt the luks device. * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Ownership is not preserved. File modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. * **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument. * **_path_** (string): the path of the tree within the target system. Defaults to `/`. @@ -219,6 +221,8 @@ The Fedora CoreOS configuration is a YAML document conforming to the following s * **_tpm2_** (boolean): whether or not to use a tpm2 device. * **_threshold_** (integer): sets the minimum number of pieces required to decrypt the device. Default is 1. * **_discard_** (boolean): whether to issue discard commands to the underlying block device when blocks are freed. Enabling this improves performance and device longevity on SSDs and space utilization on thinly provisioned SAN devices, but leaks information about which disk blocks contain data. If omitted, it defaults to false. + * **_cex_** (object): describes the IBM Crypto Express (CEX) card configuration for the luks device. + * **_enabled_** (boolean): whether or not to enable cex compatibility for luks. If omitted, defaults to false. * **_mirror_** (object): describes mirroring of the boot disk for fault tolerance. * **_devices_** (list of strings): the list of whole-disk devices (not partitions) to include in the disk array, referenced by their absolute path. At least two devices must be specified. * **_grub_** (object): describes the desired GRUB bootloader configuration. diff --git a/docs/config-openshift-v4_18-exp.md b/docs/config-openshift-v4_18-exp.md index 07addc6c..0da369aa 100644 --- a/docs/config-openshift-v4_18-exp.md +++ b/docs/config-openshift-v4_18-exp.md @@ -137,6 +137,8 @@ The OpenShift configuration is a YAML document conforming to the following speci * **pin** (string): the clevis pin. * **config** (string): the clevis configuration JSON. * **_needs_network_** (boolean): whether or not the device requires networking. + * **_cex_** (object): describes the IBM Crypto Express (CEX) card configuration for the luks device. + * **_enabled_** (boolean): whether or not to use a CEX secure key to encrypt the luks device. * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Symlinks must not be present. Ownership is not preserved. File modes are set to 0755 if the local file is executable or 0644 otherwise. File attributes can be overridden by creating a corresponding entry in the `files` section; such entries must omit `contents`. * **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument. * **_path_** (string): the path of the tree within the target system. Defaults to `/`. @@ -168,6 +170,8 @@ The OpenShift configuration is a YAML document conforming to the following speci * **_tpm2_** (boolean): whether or not to use a tpm2 device. * **_threshold_** (integer): sets the minimum number of pieces required to decrypt the device. Default is 1. * **_discard_** (boolean): whether to issue discard commands to the underlying block device when blocks are freed. Enabling this improves performance and device longevity on SSDs and space utilization on thinly provisioned SAN devices, but leaks information about which disk blocks contain data. If omitted, it defaults to false. + * **_cex_** (object): describes the IBM Crypto Express (CEX) card configuration for the luks device. + * **_enabled_** (boolean): whether or not to enable cex compatibility for luks. If omitted, defaults to false. * **_mirror_** (object): describes mirroring of the boot disk for fault tolerance. * **_devices_** (list of strings): the list of whole-disk devices (not partitions) to include in the disk array, referenced by their absolute path. At least two devices must be specified. * **_grub_** (object): describes the desired GRUB bootloader configuration. diff --git a/docs/examples.md b/docs/examples.md index ee62c41c..b6061633 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -340,6 +340,20 @@ boot_device: thumbprint: REPLACE-THIS-WITH-YOUR-TANG-THUMBPRINT ``` +This example uses the shortcut `boot_device` syntax to configure an encrypted root filesystem in s390x on the `dasda` DASD device unlocked with a CEX card. + + +```yaml +variant: fcos +version: 1.6.0-experimental +boot_device: + layout: s390x-eckd + luks: + device: /dev/dasda + cex: + enabled: true +``` + ### Mirrored boot disk This example replicates all default partitions on the boot disk across multiple disks, allowing the system to survive disk failure. diff --git a/docs/release-notes.md b/docs/release-notes.md index 115f99b0..dd0f092a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,8 @@ nav_order: 9 ### Features +- Support LUKS encryption using IBM CEX secure keys on s390x _(fcos 1.6-exp)_ _(openshift 4.18.0-exp)_ + ### Bug fixes ### Misc. changes diff --git a/internal/doc/butane.yaml b/internal/doc/butane.yaml index c8b3da5d..c05b8b9d 100644 --- a/internal/doc/butane.yaml +++ b/internal/doc/butane.yaml @@ -344,6 +344,11 @@ root: desc: sets the minimum number of pieces required to decrypt the device. Default is 1. - name: discard desc: whether to issue discard commands to the underlying block device when blocks are freed. Enabling this improves performance and device longevity on SSDs and space utilization on thinly provisioned SAN devices, but leaks information about which disk blocks contain data. If omitted, it defaults to false. + - name: cex + desc: describes the IBM Crypto Express (CEX) card configuration for the luks device. + children: + - name: enabled + desc: whether or not to enable cex compatibility for luks. If omitted, defaults to false. - name: mirror desc: describes mirroring of the boot disk for fault tolerance. children: