From 01d95167cdd95c26a986748c171ba2370f2c4731 Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Wed, 13 May 2026 09:45:38 -0400 Subject: [PATCH] internal/exec/stages/files: add x-initrd.attach to LUKS crypttab entries Add x-initrd.attach option unconditionally to all LUKS crypttab entries. This prevents systemd-cryptsetup-generator from adding Conflicts=umount.target, which is necessary for soft-reboot to work correctly with LUKS. --- docs/release-notes.md | 2 + .../exec/stages/files/filesystemEntries.go | 21 ++++- .../stages/files/filesystemEntries_test.go | 93 +++++++++++++++++++ 3 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 internal/exec/stages/files/filesystemEntries_test.go diff --git a/docs/release-notes.md b/docs/release-notes.md index 15d42e50d..6687cf445 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -14,6 +14,8 @@ nav_order: 9 ### Bug fixes +- Add `x-initrd.attach` to crypttab entries to fix soft-reboot with LUKS ([#2219](https://github.com/coreos/ignition/pull/2219)) + ## Ignition 2.26.0 (2026-02-17) diff --git a/internal/exec/stages/files/filesystemEntries.go b/internal/exec/stages/files/filesystemEntries.go index d01554a16..d4f5e151a 100644 --- a/internal/exec/stages/files/filesystemEntries.go +++ b/internal/exec/stages/files/filesystemEntries.go @@ -33,6 +33,19 @@ import ( "github.com/vincent-petithory/dataurl" ) +// buildCrypttabOptions constructs the options suffix for a crypttab entry. +// It always adds x-initrd.attach and adds _netdev if network is needed. +// The x-initrd.attach option prevents systemd-cryptsetup-generator from +// adding Conflicts=umount.target, which is necessary for soft-reboot to +// work correctly with LUKS. +func buildCrypttabOptions(hasNetworkDev bool) string { + options := "x-initrd.attach" + if hasNetworkDev { + options = "_netdev," + options + } + return "," + options +} + // 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 { @@ -62,10 +75,7 @@ func (s *stage) createCrypttabEntries(config types.Config) error { return fmt.Errorf("gathering luks uuid: %s: %v", out, err) } uuid := strings.TrimSpace(string(out)) - netdev := "" - if len(luks.Clevis.Tang) > 0 || cutil.NotEmpty(luks.Clevis.Custom.Pin) && cutil.IsTrue(luks.Clevis.Custom.NeedsNetwork) { - netdev = ",_netdev" - } + hasNetworkDev := len(luks.Clevis.Tang) > 0 || cutil.NotEmpty(luks.Clevis.Custom.Pin) && cutil.IsTrue(luks.Clevis.Custom.NeedsNetwork) keyfile := "none" if !luks.Clevis.IsPresent() { keyfile = filepath.Join(distro.LuksRealRootKeyFilePath(), luks.Name) @@ -91,7 +101,8 @@ func (s *stage) createCrypttabEntries(config types.Config) error { }, }) } - uri := dataurl.EncodeBytes([]byte(fmt.Sprintf("%s UUID=%s %s luks%s\n", luks.Name, uuid, keyfile, netdev))) + options := buildCrypttabOptions(hasNetworkDev) + uri := dataurl.EncodeBytes([]byte(fmt.Sprintf("%s UUID=%s %s luks%s\n", luks.Name, uuid, keyfile, options))) crypttab.Append = append(crypttab.Append, types.Resource{ Source: &uri, }) diff --git a/internal/exec/stages/files/filesystemEntries_test.go b/internal/exec/stages/files/filesystemEntries_test.go new file mode 100644 index 000000000..c25c21832 --- /dev/null +++ b/internal/exec/stages/files/filesystemEntries_test.go @@ -0,0 +1,93 @@ +// Copyright 2026 CoreOS, 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 files + +import ( + "testing" +) + +func TestBuildCrypttabOptions(t *testing.T) { + tests := []struct { + name string + hasNetworkDev bool + expectedResult string + expectedFormat string + }{ + { + name: "without network", + hasNetworkDev: false, + expectedResult: ",x-initrd.attach", + expectedFormat: "testdev UUID=test-uuid /path/to/keyfile luks,x-initrd.attach\n", + }, + { + name: "with network", + hasNetworkDev: true, + expectedResult: ",_netdev,x-initrd.attach", + expectedFormat: "testdev UUID=test-uuid /path/to/keyfile luks,_netdev,x-initrd.attach\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + options := buildCrypttabOptions(tt.hasNetworkDev) + + if options != tt.expectedResult { + t.Errorf("got options %q, want %q", options, tt.expectedResult) + } + + crypttabLine := "testdev UUID=test-uuid /path/to/keyfile luks" + options + "\n" + if crypttabLine != tt.expectedFormat { + t.Errorf("got line %q, want %q", crypttabLine, tt.expectedFormat) + } + }) + } +} + +func TestBuildCrypttabOptionsWithClevis(t *testing.T) { + tests := []struct { + name string + hasNetworkDev bool + expectedOptions string + expectedLine string + }{ + { + name: "clevis without network", + hasNetworkDev: false, + expectedOptions: ",x-initrd.attach", + expectedLine: "testdev UUID=test-uuid none luks,x-initrd.attach\n", + }, + { + name: "clevis with network", + hasNetworkDev: true, + expectedOptions: ",_netdev,x-initrd.attach", + expectedLine: "testdev UUID=test-uuid none luks,_netdev,x-initrd.attach\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + options := buildCrypttabOptions(tt.hasNetworkDev) + + if options != tt.expectedOptions { + t.Errorf("got options %q, want %q", options, tt.expectedOptions) + } + + crypttabLine := "testdev UUID=test-uuid none luks" + options + "\n" + if crypttabLine != tt.expectedLine { + t.Errorf("got line %q, want %q", crypttabLine, tt.expectedLine) + } + }) + } +}