Skip to content

updates to ephemeral encryption keys - #1024

Open
piyush-jena wants to merge 4 commits into
bottlerocket-os:developfrom
piyush-jena:rottweiler-update/core-kit-1
Open

updates to ephemeral encryption keys#1024
piyush-jena wants to merge 4 commits into
bottlerocket-os:developfrom
piyush-jena:rottweiler-update/core-kit-1

Conversation

@piyush-jena

@piyush-jena piyush-jena commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Description of changes:

  • Adds documentation for this preview feature
  • use plain mode device encryption (which is headerless) when ephemeral encryption keys are used
  • encrypt and unlock in the same service to avoid storing keys in any storage (except datastore encryption)

Testing done:
Containers run with containerd:

  1. Tested nvidia-smoke-test - both nvidia-smi and CUDA Workload
  2. Tested dotnet-test workload.

FIPS test:

[ssm-user@control]$ apiclient report fips
Benchmark name:  FIPS Security Policy
Version:         v1.0.0
Reference:       https://csrc.nist.gov/
Benchmark level: 1
Start time:      2026-08-27T06:49:34.743345588Z

[PASS] 1.0       FIPS mode is enabled. (Automatic)
[PASS] 1.1       FIPS module is Amazon Linux 2023 Kernel Cryptographic API. (Automatic)
[PASS] 1.2       FIPS self-tests passed. (Automatic)

Passed:          3
Failed:          0
Skipped:         0
Total checks:    3

Compliance check result: PASS

lsblk output

bash-5.2# lsblk
NAME                     MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
nvme1n1                  259:0    0    20G  0 disk
`-nvme1n1p1              259:12   0    20G  0 part
  `-BOTTLEROCKET-DATA    252:2    0    20G  0 crypt /var
                                                    /opt
                                                    /mnt
                                                    /local
nvme0n1                  259:1    0     2G  0 disk
|-nvme0n1p1              259:4    0     4M  0 part
|-nvme0n1p2              259:5    0    10M  0 part
|-nvme0n1p3              259:6    0    80M  0 part  /boot
|-nvme0n1p4              259:7    0   1.8G  0 part
|-nvme0n1p5              259:8    0    20M  0 part
|-nvme0n1p6              259:9    0    50M  0 part
|-nvme0n1p7              259:10   0    41M  0 part
| `-BOTTLEROCKET-PRIVATE 252:1    0    41M  0 crypt /var/lib/bottlerocket
|                                                   /.bottlerocket
`-nvme0n1p8              259:11   0     1M  0 part
nvme2n1                  259:2    0 109.9G  0 disk

keystore for datastore encryption is correctly labeled and the key is ephemeral

bash-5.2# ls -lZ /run/ | grep rottweiler
drwx------.  2 root   root   system_u:object_r:private_t:s0     40 Aug 27 06:31 rottweiler
bash-5.2# ls -lZ /run/rottweiler
total 0

systemctl status output

bash-5.2# systemctl status
● ip-172-31-16-76.us-west-2.compute.internal
    State: running
    Units: 417 loaded (incl. loaded aliases)
     Jobs: 0 queued
   Failed: 0 units
    Since: Thu 2026-08-27 06:29:39 UTC; 22min ago
  systemd: 257.13

older services were removed

bash-5.2# find . -name unlock-datastore.service
bash-5.2# find . -name encrypt-unlock-datastore.service
./etc/systemd/system/storewolf.service.requires/encrypt-unlock-datastore.service
./etc/systemd/system/migrator.service.requires/encrypt-unlock-datastore.service
./x86_64-bottlerocket-linux-gnu/sys-root/usr/lib/systemd/system/encrypt-unlock-datastore.service
bash-5.2# find . -name unlock-datastore.service
bash-5.2# find . -name encrypt-datastore.service
bash-5.2# find . -name encrypt-local-fs.service
bash-5.2# find . -name unlock-local-fs.service
bash-5.2# find . -name encrypt-unlock-local-fs.service
./etc/systemd/system/local-fs.target.requires/encrypt-unlock-local-fs.service
./x86_64-bottlerocket-linux-gnu/sys-root/usr/lib/systemd/system/encrypt-unlock-local-fs.service
bash-5.2# find . -name encrypt-unlock-private-fs.service
./etc/systemd/system/prepare-private-fs.service.requires/encrypt-unlock-private-fs.service
./x86_64-bottlerocket-linux-gnu/sys-root/usr/lib/systemd/system/encrypt-unlock-private-fs.service
bash-5.2# find . -name encrypt-private-fs.service
bash-5.2# find . -name unlock-private-fs.service

Stress test:
Ran reboot loop on 3 nvidia instances with bootstrap command setting up ephemeral storage with a large data partition. Didn't get any bricked device across 300 boots.

Terms of contribution:

By submitting this pull request, I agree that this contribution is dual-licensed under the terms of both the Apache License, version 2.0, and the MIT license.

@piyush-jena
piyush-jena force-pushed the rottweiler-update/core-kit-1 branch 4 times, most recently from 8856f70 to 6956570 Compare August 27, 2026 02:44
Comment thread packages/release/encrypt-unlock-datastore.service
%package crypt-luks
Summary: Bottlerocket release, with LUKS block-device encryption
Requires: (%{_cross_os}image-feature(encrypted-storage) and %{name}-crypt)
Conflicts: %{_cross_os}image-feature(ephemeral-encryption-keys)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this conflict with ephemeral-crypt?

@piyush-jena
piyush-jena force-pushed the rottweiler-update/core-kit-1 branch 4 times, most recently from 99b8a83 to fd8cdf9 Compare August 28, 2026 04:36
@piyush-jena

Copy link
Copy Markdown
Contributor Author

Force push removes the requirement of a udev settle by reordering the commands in encrypt-attach-local-fs.service. This is achieved by doing the cheaper operations first - encrypt, unlock, systemd-makefs (on the smaller filesystem) followed by systemd-repart and rottweiler resize. We run systemd-growfs in a separate service because this takes the most time and is heavily dependent on the size of the block device and requires the data partition to be mounted as local.

@piyush-jena
piyush-jena marked this pull request as ready for review August 28, 2026 05:25
@piyush-jena
piyush-jena force-pushed the rottweiler-update/core-kit-1 branch 3 times, most recently from 2148f81 to ab6597b Compare August 28, 2026 20:34
Signed-off-by: Piyush Jena <jepiyush@amazon.com>
Signed-off-by: Piyush Jena <jepiyush@amazon.com>
Signed-off-by: Piyush Jena <jepiyush@amazon.com>
Signed-off-by: Piyush Jena <jepiyush@amazon.com>
@piyush-jena
piyush-jena force-pushed the rottweiler-update/core-kit-1 branch from ab6597b to 53d5f9e Compare August 28, 2026 20:44
Comment thread docs/ENCRYPTED_STORAGE.md
Comment on lines +225 to +227
- The `bottlerocket-data`, `bottlerocket-private`, and ephemeral-storage keys are generated, used, and deleted within a single service (an `ExecStartPost=rottweiler delete-key` removes each one)
- Keys live in the `/run/rottweiler` tmpfs keystore, never on `/.bottlerocket` and never on a persistent disk
- Data does not survive a reboot: the next boot generates a new key, so prior contents are unreadable and the filesystem is recreated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above is enough, you can remove this.

Comment thread docs/ENCRYPTED_STORAGE.md
Comment on lines +237 to +238
the key. Opening the mapper before growing the partition avoids waiting on the by-partlabel
device node to be recreated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
the key. Opening the mapper before growing the partition avoids waiting on the by-partlabel
device node to be recreated.
the key.

Comment thread docs/ENCRYPTED_STORAGE.md
- Mounts `/dev/mapper/BOTTLEROCKET-PRIVATE` instead of the raw partition
- Requires `prepare-private-fs.service`

`repart-local.service` gets a `10-ephemeral.conf` drop-in in this mode that masks the base unit's `ExecStart` (the `systemd-repart` grow), because the partition grow and mapper resize are performed inside `encrypt-unlock-local-fs.service` so the key can be generated and deleted within that single service. The base unit's `systemd-growfs /local` still runs to grow the filesystem to fill the resized mapper.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not following the convention already established for all other services?

Comment on lines +115 to +120
// Without `--key-file=-`, cryptsetup treats stdin as an interactive *passphrase* and stops
// reading at the first newline; `--keyfile-size` is what makes it read exactly the key even
// from stdin. Together they are the only reason the mapper gets all of the key bytes: a
// 0x0A anywhere in a random 64-byte key would otherwise silently truncate it (and a leading
// 0x0A would yield an all-zero key, accepted as-is on a non-FIPS kernel and rejected by
// xts_verify_key() as -EINVAL on a FIPS one).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document this in the function, not here. And less verbose: prefer key-file over stdin for reasons...

Comment on lines +314 to +320
/// The key is 64 raw bytes from /dev/random handed to cryptsetup on stdin. Without
/// `--key-file=-` cryptsetup reads stdin as an interactive passphrase and stops at the first
/// newline, and `--hash plain` then zero-pads whatever it got to `--key-size`. Observed live on
/// aws-mantle-1{,-fips} x86_64: a key whose first byte was 0x0A produced an all-zero dm-crypt
/// key, silently accepted on the non-FIPS kernel and rejected on the FIPS kernel as
/// `crypt: Error decoding and setting key (-EINVAL)`, which failed
/// `encrypt-unlock-local-fs.service` and took `preconfigured.target` with it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The key is 64 raw bytes from /dev/random handed to cryptsetup on stdin. Without
/// `--key-file=-` cryptsetup reads stdin as an interactive passphrase and stops at the first
/// newline, and `--hash plain` then zero-pads whatever it got to `--key-size`. Observed live on
/// aws-mantle-1{,-fips} x86_64: a key whose first byte was 0x0A produced an all-zero dm-crypt
/// key, silently accepted on the non-FIPS kernel and rejected on the FIPS kernel as
/// `crypt: Error decoding and setting key (-EINVAL)`, which failed
/// `encrypt-unlock-local-fs.service` and took `preconfigured.target` with it.
/// The key is 64 raw bytes from /dev/random handed to cryptsetup on stdin. Without
/// `--key-file=-` cryptsetup reads stdin as an interactive passphrase and stops at the first
/// newline, and `--hash plain` then zero-pads whatever it got to `--key-size`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants