Skip to content

Fix crypt detection: read PART_CRYPT instead of grepping the PART line - #2

Open
lraphael wants to merge 1 commit into
zytedata:patch-1from
lraphael:fix/crypt-marker-consistency
Open

lraphael wants to merge 1 commit into
zytedata:patch-1from
lraphael:fix/crypt-marker-consistency

Conversation

@lraphael

Copy link
Copy Markdown

Hi @cyberplant — I tested hetzneronline#85 because I want exactly the layout I asked about in that thread
back in 2024 (/var/lib/longhorn on its own RAID level). The good news first: the RAID part
works.
Against current upstream HEAD (e15b5ea) the patch still applies cleanly, and driving
the real read_vars → create_partitions → make_swraid chain against two loop devices gives
exactly what it promises:

md2 : active raid0 loop1p3[1] loop0p3[0]      <- PART lvm vg1 all no array_raidlevel=0
md1 : active raid1 loop1p2[1] loop0p2[0]      <- PART lvm vg0 2G
md0 : active raid1 loop1p1[1] loop0p1[0]      <- PART /boot ext3 512M

The # HZ-CFG: marker lands only on the intended fstab line, and the override is correctly
scoped per array.

The problem this PR fixes

Making the 5th column authoritative (yes instead of "any non-empty value") was necessary —
you need a placeholder there to reach the 6th column, and under the old test every placeholder
would have switched LUKS on. But create_partitions still decides the fstab crypted marker by
grepping the raw PART line for the string crypt, and that half wasn't moved along.

Since encrypt_partitions() iterates the fstab looking for crypted, the two halves no
longer meet:

config $CRYPT gate encrypt_partitions crypted lines result
hetzneronline#85 as-is + yes 1 runs 0 plaintext
hetzneronline#85 + this PR + yes 1 runs 1 encrypted
hetzneronline#85 + this PR + crypt 0 not called 0 plaintext (intended)
upstream + crypt 1 runs 2 encrypted

Row 1 is the bad one: the installer prints "Encrypt partitions and create /etc/crypttab", the
function runs, and nothing gets encrypted. No error, no non-zero exit. With hetzneronline#85 as it stands
there is no 5th-column value that satisfies both paths.

The fix

One line — read the same field both halves should have been reading:

-   if [ "${PART_MOUNT[$i]}" = "lvm" ]; then
-     crypt_part=$(grep "${PART_FS[$i]}" /tmp/part_lines.tmp | grep "crypt" | awk '{print $3}')
-   else
-     crypt_part=$(grep "${PART_MOUNT[$i]}" /tmp/part_lines.tmp | grep "crypt" | awk '{print $3}')
-   fi
-   [ -n "$crypt_part" ] && is_crypted='crypt' || is_crypted='no'
+   [ "${PART_CRYPT[$i]}" = "yes" ] && is_crypted='crypt' || is_crypted='no'

Side effect: it also fixes a pre-existing upstream bug

That grep matched by substring against the whole part-lines file. With

PART /var     ext4 1G
PART /var/log ext4 all crypt

upstream produces:

/dev/loop32 /var ext4 defaults 0 0 # crypted        <- never asked for
/dev/loop33 /var/log ext4 defaults 0 0 # crypted

/var gets encrypted because grep "/var" matches the /var/log line. With this patch only
/var/log is marked. That bug predates hetzneronline#85; the fix removes it as a by-product.

Note on the description

The PR description says SWRAIDLEVEL=0, but the variable that actually matters is
array_raidlevel (upstream already sets local array_raidlevel="$SWRAIDLEVEL" per iteration,
and the override replaces that local). Only the tested example in your first comment is correct —
might be worth fixing in the description so nobody copies the wrong one.

How I tested this (repro script)

Ubuntu 24.04 VM, two loop devices as DRIVE1/DRIVE2. Two environment stubs were needed, neither
touching the logic under test: getdrives() (it scans /sys/block for nvme*/sd*, which
loop devices never match) and symlinks /dev/loopNM -> /dev/loopNpM, because the partition-name
helper only emits the p separator for nvme and by-id paths.

export FOLD=/tmp/fold; mkdir -p $FOLD
source ./config.sh; source ./functions.sh
debug() { :; }; debugoutput() { cat >/dev/null; }
graph_error() { :; }; status_busy() { :; }; status_done() { :; }; status_failed() { :; }
wait_for_udev() { udevadm settle; }
getdrives() { DRIVE1="$L0"; DRIVE2="$L1"; COUNT_DRIVES=2; return 0; }
export GPT=1

read_vars /tmp/test.conf
for d in "$L0" "$L1"; do create_partitions "$d"; done
partprobe "$L0" "$L1"; udevadm settle
for d in "$L0" "$L1"; do for p in 1 2 3; do ln -sf "${d}p${p}" "${d}${p}"; done; done
make_swraid "$FOLD/fstab"
[ "$CRYPT" = "1" ] && encrypt_partitions "$FOLD/fstab" "$CRYPTPASSWORD"

cat /proc/mdstat
cryptsetup isLuks /dev/md/2 && echo ENCRYPTED || echo PLAINTEXT

The control group carries the proof: the same run against unpatched upstream with crypt
encrypts, so a "not encrypted" result isn't just the test measuring itself.

There's no test suite in the repo to extend, so this is a manual repro. If you'd like it as a
committed script (something runnable against loop devices, no hardware needed), say the word and
I'll add it as a second commit — happy to keep this PR to the one-liner otherwise.

The 6th-parameter change made the 5th column authoritative for enabling
encryption ("yes" instead of any non-empty value), but create_partitions
still decided the fstab "crypted" marker by grepping the raw PART line for
the string "crypt". The two are now inconsistent, and since encrypt_partitions
iterates the fstab looking for "crypted", the documented syntax

    PART /mnt ext4 all yes

passes the CRYPT gate in install.sh, runs encrypt_partitions, and installs
in plaintext -- no error, no non-zero exit.

Reading PART_CRYPT[$i] makes both paths use the same field. It also fixes a
pre-existing defect: the grep matched by substring against the whole
part_lines file, so with

    PART /var     ext4 1G
    PART /var/log ext4 all crypt

/var was marked crypted as well, encrypting a partition that was never
configured for it.

Verified on Ubuntu 24.04 against two loop devices, driving read_vars ->
create_partitions -> make_swraid -> encrypt_partitions:

  patch as-is   + 'yes'    CRYPT=1  0 crypted lines  -> plaintext
  patch + this  + 'yes'    CRYPT=1  1 crypted line   -> encrypted
  patch + this  + 'crypt'  CRYPT=0  0 crypted lines  -> plaintext (intended)
  upstream      + 'crypt'  CRYPT=1  2 crypted lines  -> encrypted (+ the /var overmatch)

Mixed RAID levels are unaffected: md0 and md1 raid1, md2 raid0 with
array_raidlevel=0 on the last PART line.
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.

1 participant