diff --git a/config/shared/errors/errors.go b/config/shared/errors/errors.go index c29a2dcb4..f18a5e6ae 100644 --- a/config/shared/errors/errors.go +++ b/config/shared/errors/errors.go @@ -52,6 +52,10 @@ var ( ErrXfsLabelTooLong = errors.New("filesystem labels cannot be longer than 12 characters when using xfs") ErrSwapLabelTooLong = errors.New("filesystem labels cannot be longer than 15 characters when using swap") ErrVfatLabelTooLong = errors.New("filesystem labels cannot be longer than 11 characters when using vfat") + ErrLuksLabelTooLong = errors.New("luks device labels cannot be longer than 47 characters") + ErrLuksNameContainsSlash = errors.New("device names cannot contain slashes") + ErrInvalidLuksVolume = errors.New("a key-file or clevis configuration must be specified") + ErrTangThumbprintRequired = errors.New("thumbprint is required") ErrFileIllegalMode = errors.New("illegal file mode") ErrBothIDAndNameSet = errors.New("cannot set both id and name") ErrLabelTooLong = errors.New("partition labels may not exceed 36 characters") diff --git a/config/v3_2_experimental/schema/ignition.json b/config/v3_2_experimental/schema/ignition.json index 495b040f8..e7d5aaca0 100644 --- a/config/v3_2_experimental/schema/ignition.json +++ b/config/v3_2_experimental/schema/ignition.json @@ -155,6 +155,12 @@ "$ref": "#/definitions/storage/definitions/raid" } }, + "luks": { + "type": "array", + "items": { + "$ref": "#/definitions/storage/definitions/luks" + } + }, "filesystems": { "type": "array", "items": { @@ -232,6 +238,63 @@ "devices" ] }, + "luks": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "label": { + "type": ["string", "null"] + }, + "uuid": { + "type": ["string", "null"] + }, + "device": { + "type": ["string", "null"] + }, + "keyFile": { + "$ref": "#/definitions/resource" + }, + "clevis": { + "type": ["object", "null"], + "properties": { + "tpm2": { + "type": ["boolean", "null"] + }, + "tang": { + "type": "array", + "items": { + "$ref": "#/definitions/storage/definitions/tang" + } + }, + "threshold": { + "type": ["integer", "null"] + } + } + }, + "options": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "name" + ] + }, + "tang": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "thumbprint": { + "type": ["string", "null"] + } + } + }, "filesystem": { "type": "object", "properties": { diff --git a/config/v3_2_experimental/translate/translate.go b/config/v3_2_experimental/translate/translate.go index 0cb33f770..7ad715bae 100644 --- a/config/v3_2_experimental/translate/translate.go +++ b/config/v3_2_experimental/translate/translate.go @@ -27,9 +27,21 @@ func translateIgnition(old old_types.Ignition) (ret types.Ignition) { return } +func translateStorage(old old_types.Storage) (ret types.Storage) { + tr := translate.NewTranslator() + tr.Translate(&old.Directories, &ret.Directories) + tr.Translate(&old.Disks, &ret.Disks) + tr.Translate(&old.Files, &ret.Files) + tr.Translate(&old.Filesystems, &ret.Filesystems) + tr.Translate(&old.Links, &ret.Links) + tr.Translate(&old.Raid, &ret.Raid) + return +} + func Translate(old old_types.Config) (ret types.Config) { tr := translate.NewTranslator() tr.AddCustomTranslator(translateIgnition) + tr.AddCustomTranslator(translateStorage) tr.Translate(&old, &ret) return } diff --git a/config/v3_2_experimental/types/luks.go b/config/v3_2_experimental/types/luks.go new file mode 100644 index 000000000..8b8537ce6 --- /dev/null +++ b/config/v3_2_experimental/types/luks.go @@ -0,0 +1,75 @@ +// Copyright 2020 Red Hat, Inc. +// +// 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. + +package types + +import ( + "strings" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (l Luks) Key() string { + return l.Name +} + +func (l Luks) IgnoreDuplicates() map[string]struct{} { + return map[string]struct{}{ + "Options": {}, + } +} + +func (l Luks) Validate(c path.ContextPath) (r report.Report) { + if strings.Contains(l.Name, "/") { + r.AddOnError(c.Append("name"), errors.ErrLuksNameContainsSlash) + } + r.AddOnError(c.Append("label"), l.validateLabel()) + if util.NilOrEmpty(l.Device) { + r.AddOnError(c.Append("device"), errors.ErrDiskDeviceRequired) + } else { + r.AddOnError(c.Append("device"), validatePath(*l.Device)) + } + + // fail if there is no valid keyfile & no clevis entries + if err := l.KeyFile.validateRequiredSource(); err != nil && l.emptyClevis() { + r.AddOnError(c.Append("keys"), errors.ErrInvalidLuksVolume) + } + return +} + +func (l Luks) emptyClevis() bool { + if l.Clevis == nil { + return true + } + + return len(l.Clevis.Tang) == 0 && (l.Clevis.Tpm2 == nil || !*l.Clevis.Tpm2) +} + +func (l Luks) validateLabel() error { + if util.NilOrEmpty(l.Label) { + return nil + } + + if len(*l.Label) > 47 { + // LUKS2_LABEL_L has a maximum length of 48 (including the null terminator) + // https://gitlab.com/cryptsetup/cryptsetup/-/blob/1633f030e89ad2f11ae649ba9600997a41abd3fc/lib/luks2/luks2.h#L86 + return errors.ErrLuksLabelTooLong + } + + return nil +} diff --git a/config/v3_2_experimental/types/schema.go b/config/v3_2_experimental/types/schema.go index fe2d3fdb4..5907b5fdf 100644 --- a/config/v3_2_experimental/types/schema.go +++ b/config/v3_2_experimental/types/schema.go @@ -2,6 +2,12 @@ package types // generated by "schematyper --package=types config/v3_2_experimental/schema/ignition.json -o config/v3_2_experimental/types/schema.go --root-type=Config" -- DO NOT EDIT +type Clevis struct { + Tang []Tang `json:"tang,omitempty"` + Threshold *int `json:"threshold,omitempty"` + Tpm2 *bool `json:"tpm2,omitempty"` +} + type Config struct { Ignition Ignition `json:"ignition"` Passwd Passwd `json:"passwd,omitempty"` @@ -87,6 +93,18 @@ type LinkEmbedded1 struct { Target string `json:"target"` } +type Luks struct { + Clevis *Clevis `json:"clevis,omitempty"` + Device *string `json:"device,omitempty"` + KeyFile Resource `json:"keyFile,omitempty"` + Label *string `json:"label,omitempty"` + Name string `json:"name"` + Options []LuksOption `json:"options,omitempty"` + UUID *string `json:"uuid,omitempty"` +} + +type LuksOption string + type MountOption string type NoProxyItem string @@ -182,6 +200,7 @@ type Storage struct { Files []File `json:"files,omitempty"` Filesystems []Filesystem `json:"filesystems,omitempty"` Links []Link `json:"links,omitempty"` + Luks []Luks `json:"luks,omitempty"` Raid []Raid `json:"raid,omitempty"` } @@ -193,6 +212,11 @@ type TLS struct { CertificateAuthorities []Resource `json:"certificateAuthorities,omitempty"` } +type Tang struct { + Thumbprint *string `json:"thumbprint,omitempty"` + URL string `json:"url,omitempty"` +} + type Timeouts struct { HTTPResponseHeaders *int `json:"httpResponseHeaders,omitempty"` HTTPTotal *int `json:"httpTotal,omitempty"` diff --git a/config/v3_2_experimental/types/tang.go b/config/v3_2_experimental/types/tang.go new file mode 100644 index 000000000..86ab79c9e --- /dev/null +++ b/config/v3_2_experimental/types/tang.go @@ -0,0 +1,51 @@ +// Copyright 2020 Red Hat, Inc. +// +// 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. + +package types + +import ( + "net/url" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (t Tang) Key() string { + return t.URL +} + +func (t Tang) Validate(c path.ContextPath) (r report.Report) { + r.AddOnError(c.Append("url"), validateTangURL(t.URL)) + if util.NilOrEmpty(t.Thumbprint) { + r.AddOnError(c.Append("thumbprint"), errors.ErrTangThumbprintRequired) + } + return +} + +func validateTangURL(s string) error { + u, err := url.Parse(s) + if err != nil { + return errors.ErrInvalidUrl + } + + switch u.Scheme { + case "http", "https": + return nil + default: + return errors.ErrInvalidScheme + } +} diff --git a/doc/configuration-v3_2_experimental.md b/doc/configuration-v3_2_experimental.md index 626eac750..fee5fb66e 100644 --- a/doc/configuration-v3_2_experimental.md +++ b/doc/configuration-v3_2_experimental.md @@ -115,6 +115,26 @@ The Ignition configuration is a JSON document conforming to the following specif * **_name_** (string): the group name of the owner. * **target** (string): the target path of the link * **_hard_** (boolean): a symbolic link is created if this is false, a hard one if this is true. + * **_luks_** (list of objects): the list of luks devices to be created. Every device must have a unique `name`. + * **name** (string): the name of the luks device. + * **device** (string): the absolute path to the device. Devices are typically referenced by the `/dev/disk/by-*` symlinks. + * **_keyFile_** (string): options related to the contents of the key file. + * **_compression_** (string): the type of compression used on the contents (null or gzip). Compression cannot be used with S3. + * **_source_** (string): the URL of the contents to append. Supported schemes are `http`, `https`, `tftp`, `s3`, `gs`, and [`data`][rfc2397]. When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. + * **_httpHeaders_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only. + * **name** (string): the header name. + * **_value_** (string): the header contents. + * **_verification_** (object): options related to the verification of the key file. + * **_hash_** (string): the hash of the contents, in the form `-` where type is either `sha512` or `sha256`. + * **_label_** (string): the label of the luks device. + * **_uuid_** (string): the uuid of the luks device. + * **_options_** (list of strings): any additional options to be passed to the cryptsetup utility. + * **_clevis_** (object): describes the clevis configuration for the luks device. + * **_tang_** (list of objects): describes a tang server. Every server must have a unique `url`. + * **url** (string): url of the tang server. + * **thumbprint** (string): thumbprint of a trusted signing key. + * **_tpm2_** (bool): whether or not to use a tpm2 device. + * **_threshold_** (int): sets the minimum number of pieces required to decrypt the device. * **_systemd_** (object): describes the desired state of the systemd units. * **_units_** (list of objects): the list of systemd units. * **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service"). Every unit must have a unique `name`. diff --git a/internal/distro/distro.go b/internal/distro/distro.go index 9c21a1506..336471575 100644 --- a/internal/distro/distro.go +++ b/internal/distro/distro.go @@ -56,6 +56,10 @@ var ( chccwdevCmd = "chccwdev" cioIgnoreCmd = "cio_ignore" + // LUKS programs + clevisCmd = "clevis" + cryptsetupCmd = "cryptsetup" + // Flags selinuxRelabel = "true" blackboxTesting = "false" @@ -64,6 +68,9 @@ var ( // ".ssh/authorized_keys.d/ignition" ("true"), or to // ".ssh/authorized_keys" ("false"). writeAuthorizedKeysFragment = "true" + + luksInitramfsKeyFilePath = "/run/ignition/luks-keyfiles/" + luksRealRootKeyFilePath = "/etc/luks/" ) func DiskByIDDir() string { return diskByIDDir } @@ -94,6 +101,12 @@ func VmurCmd() string { return vmurCmd } func ChccwdevCmd() string { return chccwdevCmd } func CioIgnoreCmd() string { return cioIgnoreCmd } +func ClevisCmd() string { return clevisCmd } +func CryptsetupCmd() string { return cryptsetupCmd } + +func LuksInitramfsKeyFilePath() string { return luksInitramfsKeyFilePath } +func LuksRealRootKeyFilePath() string { return luksRealRootKeyFilePath } + func SelinuxRelabel() bool { return bakedStringToBool(selinuxRelabel) && !BlackboxTesting() } func BlackboxTesting() bool { return bakedStringToBool(blackboxTesting) } func WriteAuthorizedKeysFragment() bool { diff --git a/internal/exec/stages/disks/disks.go b/internal/exec/stages/disks/disks.go index a953fe67c..6d29a7c49 100644 --- a/internal/exec/stages/disks/disks.go +++ b/internal/exec/stages/disks/disks.go @@ -71,7 +71,8 @@ func (s stage) Run(config types.Config) error { // do the udevadm settle and can just return here. if len(config.Storage.Disks) == 0 && len(config.Storage.Raid) == 0 && - len(config.Storage.Filesystems) == 0 { + len(config.Storage.Filesystems) == 0 && + len(config.Storage.Luks) == 0 { return nil } @@ -83,6 +84,10 @@ func (s stage) Run(config types.Config) error { return fmt.Errorf("failed to create raids: %v", err) } + if err := s.createLuks(config); err != nil { + return fmt.Errorf("failed to create luks: %v", err) + } + if err := s.createFilesystems(config); err != nil { return fmt.Errorf("failed to create filesystems: %v", err) } diff --git a/internal/exec/stages/disks/luks.go b/internal/exec/stages/disks/luks.go new file mode 100644 index 000000000..ffc986a16 --- /dev/null +++ b/internal/exec/stages/disks/luks.go @@ -0,0 +1,226 @@ +// Copyright 2020 Red Hat +// +// 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. + +package disks + +import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/ignition/v2/config/v3_2_experimental/types" + "github.com/coreos/ignition/v2/internal/distro" +) + +// https://github.com/latchset/clevis/blob/master/src/pins/tang/clevis-encrypt-tang.1.adoc#config +type Tang struct { + URL string `json:"url"` + Thumbprint string `json:"thp,omitempty"` +} + +// https://github.com/latchset/clevis/blob/master/README.md#pin-shamir-secret-sharing +type Pin struct { + Tpm bool + Tang []Tang +} + +func (p Pin) MarshalJSON() ([]byte, error) { + if p.Tpm { + return json.Marshal(&struct { + Tang []Tang `json:"tang,omitempty"` + Tpm struct{} `json:"tpm2"` + }{ + Tang: p.Tang, + Tpm: struct{}{}, + }) + } else { + return json.Marshal(&struct { + Tang []Tang `json:"tang"` + }{ + Tang: p.Tang, + }) + } +} + +type Clevis struct { + Pins Pin `json:"pins"` + Threshold int `json:"t"` +} + +// Initially tested generating keyfiles via dd'ing to a file from /dev/urandom +// however while cryptsetup had no problem with these keyfiles clevis seemed to +// die on them while keyfiles generated via openssl rand -hex would work... +func randHex(length int) (string, error) { + bytes := make([]byte, length) + // On older kernels this could block indefinitely but there's nothing + // that we can do about it; we don't want to use earlyrand + // https://lwn.net/Articles/802360/ + if _, err := rand.Read(bytes); err != nil { + return "", err + } + return hex.EncodeToString(bytes), nil +} + +func (s *stage) createLuks(config types.Config) error { + if len(config.Storage.Luks) == 0 { + return nil + } + s.Logger.PushPrefix("createLuks") + defer s.Logger.PopPrefix() + + for _, luks := range config.Storage.Luks { + // TODO: allow Ignition generated KeyFiles for + // non-clevis devices that can be persisted. + // TODO: create devices in parallel. + // track whether Ignition creates the KeyFile + // so that it can be removed + var ignitionCreatedKeyFile bool + // create keyfile inside of tmpfs, it will be copied to the + // sysroot by the files stage + os.MkdirAll(distro.LuksInitramfsKeyFilePath(), 0700) + keyFilePath := filepath.Join(distro.LuksInitramfsKeyFilePath(), luks.Name) + if util.NilOrEmpty(luks.KeyFile.Source) { + // create a keyfile + key, err := randHex(4096) + if err != nil { + return fmt.Errorf("generating keyfile: %v", err) + } + if err := ioutil.WriteFile(keyFilePath, []byte(key), 0400); err != nil { + return fmt.Errorf("creating keyfile: %v", err) + } + ignitionCreatedKeyFile = true + } else { + f := types.File{ + Node: types.Node{ + Path: keyFilePath, + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: luks.KeyFile, + }, + } + fetchOps, err := s.Util.PrepareFetches(s.Util.Logger, f) + if err != nil { + return fmt.Errorf("failed to resolve keyfile %q: %v", f.Path, err) + } + for _, op := range fetchOps { + if err := s.Util.Logger.LogOp( + func() error { + return s.Util.PerformFetch(op) + }, "writing file %q", f.Path, + ); err != nil { + return fmt.Errorf("failed to create keyfile %q: %v", op.Node.Path, err) + } + } + } + args := []string{ + "luksFormat", + "--type", "luks2", + "--key-file", keyFilePath, + } + + if !util.NilOrEmpty(luks.Label) { + args = append(args, "--label", *luks.Label) + } + + if !util.NilOrEmpty(luks.UUID) { + args = append(args, "--uuid", *luks.UUID) + } + + if len(luks.Options) > 0 { + // golang's a really great language... + for _, option := range luks.Options { + args = append(args, string(option)) + } + } + + args = append(args, *luks.Device) + + if _, err := s.Logger.LogCmd( + exec.Command(distro.CryptsetupCmd(), args...), + "creating %q", luks.Name, + ); err != nil { + return fmt.Errorf("cryptsetup failed: %v", err) + } + + // open the device + if _, err := s.Logger.LogCmd( + exec.Command(distro.CryptsetupCmd(), "luksOpen", *luks.Device, luks.Name, "--key-file", keyFilePath), + "opening luks device %v", luks.Name, + ); err != nil { + return fmt.Errorf("opening luks device: %v", err) + } + + if luks.Clevis != nil { + c := Clevis{ + Threshold: 1, + } + if luks.Clevis.Threshold != nil { + c.Threshold = *luks.Clevis.Threshold + } + for _, tang := range luks.Clevis.Tang { + c.Pins.Tang = append(c.Pins.Tang, Tang{ + URL: tang.URL, + Thumbprint: *tang.Thumbprint, + }) + } + if luks.Clevis.Tpm2 != nil { + c.Pins.Tpm = *luks.Clevis.Tpm2 + } + clevisJson, err := json.Marshal(c) + if err != nil { + return fmt.Errorf("creating clevis json: %v", err) + } + if _, err := s.Logger.LogCmd( + exec.Command(distro.ClevisCmd(), "luks", "bind", "-f", "-k", keyFilePath, "-d", *luks.Device, "sss", string(clevisJson)), "Clevis bind", + ); err != nil { + return fmt.Errorf("binding clevis device: %v", err) + } + + // close & re-open Clevis devices to make sure that we can unlock them + if _, err := s.Logger.LogCmd( + exec.Command(distro.CryptsetupCmd(), "luksClose", luks.Name), + "closing clevis luks device %v", luks.Name, + ); err != nil { + return fmt.Errorf("closing luks device: %v", err) + } + if _, err := s.Logger.LogCmd( + exec.Command(distro.ClevisCmd(), "luks", "unlock", "-d", *luks.Device, "-n", luks.Name), + "reopening clevis luks device %s", luks.Name, + ); err != nil { + return fmt.Errorf("reopening luks device %s: %v", luks.Name, err) + } + } + + // assume the user does not want a key file, remove it + if ignitionCreatedKeyFile { + if _, err := s.Logger.LogCmd( + exec.Command(distro.CryptsetupCmd(), "luksRemoveKey", *luks.Device, keyFilePath), + "removing key file for %v", luks.Name, + ); err != nil { + return fmt.Errorf("removing key file from luks device: %v", err) + } + if err := os.Remove(keyFilePath); err != nil { + return fmt.Errorf("removing key file: %v", err) + } + } + } + + return nil +} diff --git a/internal/exec/stages/files/files.go b/internal/exec/stages/files/files.go index 4ba7a68a4..5b0eceebf 100644 --- a/internal/exec/stages/files/files.go +++ b/internal/exec/stages/files/files.go @@ -81,6 +81,10 @@ func (s stage) Run(config types.Config) error { return fmt.Errorf("failed to create units: %v", err) } + if err := s.createCrypttabEntries(config); err != nil { + return fmt.Errorf("creating crypttab entries: %v", err) + } + if err := s.relabelFiles(); err != nil { return fmt.Errorf("failed to handle relabeling: %v", err) } diff --git a/internal/exec/stages/files/filesystemEntries.go b/internal/exec/stages/files/filesystemEntries.go index 1f53e93a4..af7726ab8 100644 --- a/internal/exec/stages/files/filesystemEntries.go +++ b/internal/exec/stages/files/filesystemEntries.go @@ -16,16 +16,99 @@ package files import ( "fmt" + "io/ioutil" "os" + "os/exec" "path/filepath" "sort" "strings" "github.com/coreos/ignition/v2/config/v3_2_experimental/types" + "github.com/coreos/ignition/v2/internal/distro" "github.com/coreos/ignition/v2/internal/exec/util" "github.com/coreos/ignition/v2/internal/log" + + "github.com/vincent-petithory/dataurl" ) +// createCrypttabEntries creates entries inside of /etc/crypttab for LUKS volumes, +// as well as copying keyfiles to the sysroot. +func (s *stage) createCrypttabEntries(config types.Config) error { + if len(config.Storage.Luks) == 0 { + return nil + } + + s.Logger.PushPrefix("createCrypttabEntries") + defer s.Logger.PopPrefix() + + mode := 0600 + path, err := s.JoinPath("/etc/crypttab") + if err != nil { + return fmt.Errorf("building crypttab filepath: %v", err) + } + crypttab := fileEntry{ + types.Node{ + Path: path, + }, + types.FileEmbedded1{ + Mode: &mode, + }, + } + extrafiles := []filesystemEntry{} + for _, luks := range config.Storage.Luks { + out, err := exec.Command(distro.CryptsetupCmd(), "luksUUID", *luks.Device).CombinedOutput() + if err != nil { + return fmt.Errorf("gathering luks uuid: %s: %v", out, err) + } + uuid := strings.TrimSpace(string(out)) + netdev := "" + if luks.Clevis != nil && len(luks.Clevis.Tang) > 0 { + netdev = ",_netdev" + } + keyfile := "none" + if luks.Clevis == nil { + keyfile = filepath.Join(distro.LuksRealRootKeyFilePath(), luks.Name) + + // Copy keyfile from /run to sysroot + contents, err := ioutil.ReadFile(filepath.Join(distro.LuksInitramfsKeyFilePath(), luks.Name)) + if err != nil { + return fmt.Errorf("reading keyfile for %s: %v", luks.Name, err) + } + contentsUri := dataurl.EncodeBytes(contents) + keyfilePath, err := s.JoinPath(keyfile) + if err != nil { + return fmt.Errorf("building keyfile path: %v", err) + } + // TODO: add directory with mode 0700 + extrafiles = append(extrafiles, fileEntry{ + types.Node{ + Path: keyfilePath, + }, + types.FileEmbedded1{ + Contents: types.Resource{ + Source: &contentsUri, + }, + Mode: &mode, + }, + }) + } + uri := dataurl.EncodeBytes([]byte(fmt.Sprintf("%s UUID=%s %s luks%s\n", luks.Name, uuid, keyfile, netdev))) + crypttab.Append = append(crypttab.Append, types.Resource{ + Source: &uri, + }) + } + extrafiles = append(extrafiles, crypttab) + if err := s.createEntries(extrafiles); err != nil { + return fmt.Errorf("adding luks related files: %v", err) + } + // delete the entire keyfiles folder in /run/ so that the keyfiles are stored on + // only the root device which can be encrypted + if err := os.RemoveAll(distro.LuksInitramfsKeyFilePath()); err != nil { + return fmt.Errorf("removing initramfs keyfiles: %v", err) + } + return nil +} + // createFilesystemsEntries creates the files described in config.Storage.{Files,Directories}. func (s *stage) createFilesystemsEntries(config types.Config) error { s.Logger.PushPrefix("createFilesystemsFiles")