Skip to content

Commit 674c1c5

Browse files
committed
Add persistent disk encryption
Add flags to encrypt persistent partition on install: * encrypt-persistent: flag to enable luks encryption on persistent partition. * enroll-passphrase: string to enroll as passphrase to unlock partition. * enroll-key-file: key-file to enroll as key to unlock partition. During install this will invoke cryptsetup to create the LUKS partition and during mount we use systemd-cryptsetup to attach the partition before mounting the contained filesystem. This also introduces some changes in the grub configuration, the encrypted_volumes variable can be set in grub_oem_env during install to configure which volumes are actually encrypted. Signed-off-by: Fredrik Lönnegren <[email protected]>
1 parent 5f996b5 commit 674c1c5

File tree

17 files changed

+375
-32
lines changed

17 files changed

+375
-32
lines changed

.github/workflows/build_and_test_x86.yaml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,18 @@ jobs:
164164
runs-on: ubuntu-latest
165165
outputs:
166166
tests: ${{ steps.detect.outputs.tests }}
167+
installertests: ${{ steps.detect.outputs.installertests }}
167168
steps:
168169
- id: detect
169170
env:
170171
FLAVOR: ${{ inputs.flavor }}
171172
run: |
172173
if [ "${FLAVOR}" == green ]; then
173174
echo "tests=['test-upgrade', 'test-downgrade', 'test-recovery', 'test-fallback', 'test-fsck', 'test-grubfallback']" >> $GITHUB_OUTPUT
175+
echo "installertests=['test-installer', 'test-encryption']" >> $GITHUB_OUTPUT
174176
else
175177
echo "tests=['test-active']" >> $GITHUB_OUTPUT
178+
echo "installertests=['test-installer']" >> $GITHUB_OUTPUT
176179
fi
177180
178181
tests-matrix:
@@ -255,6 +258,10 @@ jobs:
255258
- build-iso
256259
- detect
257260
runs-on: ubuntu-latest
261+
strategy:
262+
matrix:
263+
test: ${{ fromJson(needs.detect.outputs.installertests) }}
264+
fail-fast: false
258265
env:
259266
FLAVOR: ${{ inputs.flavor }}
260267
ARCH: x86_64
@@ -288,20 +295,20 @@ jobs:
288295
sudo udevadm trigger --name-match=kvm
289296
- name: Run installer test
290297
run: |
291-
make ISO=/tmp/elemental-${{ env.FLAVOR }}.${{ env.ARCH}}.iso ELMNTL_TARGETARCH=${{ env.ARCH }} ELMNTL_FIRMWARE=/usr/share/OVMF/OVMF_CODE.fd test-installer
298+
make ISO=/tmp/elemental-${{ env.FLAVOR }}.${{ env.ARCH}}.iso ELMNTL_TARGETARCH=${{ env.ARCH }} ELMNTL_FIRMWARE=/usr/share/OVMF/OVMF_CODE.fd ${{ matrix.test }}
292299
- name: Upload serial console for installer tests
293300
uses: actions/upload-artifact@v4
294301
if: always()
295302
with:
296-
name: serial-${{ env.ARCH }}-${{ env.FLAVOR }}-installer.log
303+
name: serial-${{ env.ARCH }}-${{ env.FLAVOR }}-${{ matrix.test }}.log
297304
path: tests/serial.log
298305
if-no-files-found: error
299306
overwrite: true
300307
- name: Upload qemu stdout for installer tests
301308
uses: actions/upload-artifact@v4
302309
if: failure()
303310
with:
304-
name: vmstdout-${{ env.ARCH }}-${{ env.FLAVOR }}-installer.log
311+
name: vmstdout-${{ env.ARCH }}-${{ env.FLAVOR }}-${{ matrix.test }}.log
305312
path: tests/vmstdout
306313
if-no-files-found: error
307314
overwrite: true

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ RUN ARCH=$(uname -m); \
5757
gptfdisk \
5858
patterns-microos-selinux \
5959
btrfsprogs \
60-
snapper \
60+
snapper \
61+
cryptsetup \
6162
lvm2 && \
6263
zypper cc -a
6364

cmd/config/config.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,19 @@ func applyKernelCmdline(r *types.RunConfig, mount *types.MountSpec) error {
351351
Options: []string{"rw", "defaults"},
352352
})
353353
}
354+
case "elemental.encrypted_volumes":
355+
vols := strings.Split(split[1], ",")
356+
357+
for _, vol := range vols {
358+
switch vol {
359+
case "persistent":
360+
mount.Persistent.Encrypted = true
361+
mount.Persistent.Volume.Device = constants.PersistentDeviceMapperPath
362+
default:
363+
r.Logger.Warnf("Unknown encrypted volume '%s', skipping", vol)
364+
}
365+
}
366+
354367
}
355368
}
356369

cmd/flags.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ func addPlatformFlags(cmd *cobra.Command) {
158158
cmd.Flags().String("platform", fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), "Platform to build the image for")
159159
}
160160

161+
// addEncryptionFlags adds the disk encryption flag for install command
162+
func addEncryptionFlags(cmd *cobra.Command) {
163+
cmd.Flags().Bool("encrypt-persistent", false, "Encrypt the persistent data partition on install")
164+
cmd.Flags().StringArray("enroll-passphrase", nil, "Clear text password to enroll as key for disk encryption")
165+
cmd.Flags().StringArray("enroll-key-file", nil, "Key-files to enroll as keys for disk encryption")
166+
}
167+
161168
type enum struct {
162169
Allowed []string
163170
Value string

cmd/install.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func NewInstallCmd(root *cobra.Command, addCheckRoot bool) *cobra.Command {
123123
addSharedInstallUpgradeFlags(c)
124124
addLocalImageFlag(c)
125125
addPlatformFlags(c)
126+
addEncryptionFlags(c)
126127
return c
127128
}
128129

examples/green/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ RUN ARCH=$(uname -m); \
6565
btrfsmaintenance \
6666
snapper \
6767
xterm-resize \
68+
cryptsetup \
6869
${ADD_PKGS} && \
6970
zypper clean --all
7071

make/Makefile.test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ test-installer: prepare-installer-test
5252
VM_PID=$$(scripts/run_vm.sh vmpid) go run $(GINKGO) $(GINKGO_ARGS) ./tests/installer
5353
VM_PID=$$(scripts/run_vm.sh vmpid) go run $(GINKGO) $(GINKGO_ARGS) ./tests/smoke
5454

55+
.PHONY: test-encryption
56+
test-encryption: prepare-installer-test
57+
VM_PID=$$(scripts/run_vm.sh vmpid) go run $(GINKGO) $(GINKGO_ARGS) ./tests/encryption
58+
5559
.PHONY: test-smoke
5660
test-smoke: test-active
5761
VM_PID=$$(scripts/run_vm.sh vmpid) go run $(GINKGO) $(GINKGO_ARGS) ./tests/smoke

pkg/action/mount.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,18 @@ func RunMount(cfg *types.RunConfig, spec *types.MountSpec) error {
5252
cfg.Logger.Info("Running mount command")
5353

5454
if spec.WriteFstab {
55-
cfg.Logger.Debug("Generating inital sysroot fstab lines")
55+
cfg.Logger.Debug("Generating initial sysroot fstab lines")
5656
fstabData, err = InitialFstabData(cfg.Runner, spec.Sysroot)
5757
if err != nil {
5858
cfg.Logger.Errorf("Error mounting volumes: %s", err.Error())
5959
return err
6060
}
61+
}
6162

63+
cfg.Logger.Debug("Mounting encrypted devices")
64+
if err = MountEncryptedVolumes(cfg, spec); err != nil {
65+
cfg.Logger.Errorf("Error mounting encrypted devices: %s", err.Error())
66+
return err
6267
}
6368

6469
cfg.Logger.Debug("Mounting volumes")
@@ -95,6 +100,19 @@ func RunMount(cfg *types.RunConfig, spec *types.MountSpec) error {
95100
return nil
96101
}
97102

103+
func MountEncryptedVolumes(cfg *types.RunConfig, spec *types.MountSpec) error {
104+
if !spec.Persistent.Encrypted {
105+
cfg.Logger.Debug("No encrypted devices specified")
106+
return nil
107+
}
108+
109+
data, err := cfg.Runner.Run("systemd-cryptsetup", "attach", "cr_persistent", "/dev/disk/by-partlabel/persistent")
110+
if err != nil {
111+
cfg.Logger.Errorf("Failed unlocking persistent partition: %s\nLogs: %s", err.Error(), string(data))
112+
}
113+
return err
114+
}
115+
98116
func MountVolumes(cfg *types.RunConfig, spec *types.MountSpec) error {
99117
var errs error
100118

pkg/constants/constants.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ const (
108108
PersistentStateDir = ".state"
109109
RunningStateDir = "/run/initramfs/elemental-state" // TODO: converge this constant with StateDir/RecoveryDir when moving to elemental-rootfs as default rootfs feature.
110110

111+
// Disk encryption constants
112+
PersistentDeviceMapperName = "cr_persistent"
113+
PersistentDeviceMapperPath = "/dev/mapper/" + PersistentDeviceMapperName
114+
111115
// Running mode sentinel files
112116
ActiveMode = "/run/elemental/active_mode"
113117
PassiveMode = "/run/elemental/passive_mode"

pkg/elemental/elemental.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,22 +79,37 @@ func createAndFormatPartition(c types.Config, disk *partitioner.Disk, part *type
7979
if err != nil {
8080
return err
8181
}
82+
83+
mappedDev := partDev
84+
if part.Encryption != nil {
85+
c.Logger.Debugf("Encrypting partition %s into %s, using %v slots", partDev, part.Encryption.MappedDeviceName, len(part.Encryption.KeySlots))
86+
err := partitioner.EncryptDevice(c.Runner, partDev, part.Encryption.MappedDeviceName, part.Encryption.KeySlots)
87+
if err != nil {
88+
c.Logger.Errorf("Failed encrypting %s partition", partDev)
89+
return err
90+
}
91+
92+
mappedDev = fmt.Sprintf("/dev/mapper/%s", part.Encryption.MappedDeviceName)
93+
}
94+
95+
c.Logger.Debugf("Using device %s", mappedDev)
96+
8297
if part.FS != "" {
8398
c.Logger.Debugf("Formatting partition with label %s", part.FilesystemLabel)
84-
err = partitioner.FormatDevice(c.Runner, partDev, part.FS, part.FilesystemLabel)
99+
err = partitioner.FormatDevice(c.Runner, mappedDev, part.FS, part.FilesystemLabel)
85100
if err != nil {
86101
c.Logger.Errorf("Failed formatting partition %s", part.Name)
87102
return err
88103
}
89104
} else {
90105
c.Logger.Debugf("Wipe file system on %s", part.Name)
91-
err = disk.WipeFsOnPartition(partDev)
106+
err = disk.WipeFsOnPartition(mappedDev)
92107
if err != nil {
93108
c.Logger.Errorf("Failed to wipe filesystem of partition %s", partDev)
94109
return err
95110
}
96111
}
97-
part.Path = partDev
112+
part.Path = mappedDev
98113
return nil
99114
}
100115

0 commit comments

Comments
 (0)