Skip to content

Resize encrypted partitions (SC-353)#1316

Merged
TheRealFalcon merged 18 commits into
canonical:mainfrom
TheRealFalcon:encrypted-growpart
Apr 27, 2022
Merged

Resize encrypted partitions (SC-353)#1316
TheRealFalcon merged 18 commits into
canonical:mainfrom
TheRealFalcon:encrypted-growpart

Conversation

@TheRealFalcon
Copy link
Copy Markdown
Contributor

@TheRealFalcon TheRealFalcon commented Mar 3, 2022

Take 2 of #1032

Proposed commit message

Allow growpart to resize encrypted partitions

Adds the ability for growpart to resize a LUKS formatted partition.
This involves resizing the underlying partition as well as the
filesystem. 'cryptsetup' is used for resizing.

This relies on a file present at /cc_growpart_keydata containing
json formatted 'key' and 'slot' keys, with the key being
base64 encoded. If resizing is successful, cloud-init will destroy
the luks slot used for resizing and remove the key file.

Testing

I tried to structure the unit tests such that they act more end-to-end, rather than testing individual functions at a time. This involved some additional mocking setup, but I think it's overall worth it.

As of right now, an in-tree integration test is impractical.

Following a Canonical internal guide, I setup an encrypted VM and manually verified the behavior. You can see parts of the logs and commands here:

# Normal VM with no encryption
2022-03-28 20:48:17,949 - stages.py[DEBUG]: Running module growpart (<module 'cloudinit.config.cc_growpart' from '/usr/lib/python3/dist-packages/cloudinit/config/cc_growpart.py'>) with frequency always
2022-03-28 20:48:17,949 - handlers.py[DEBUG]: start: init-network/config-growpart: running config-growpart with frequency always
2022-03-28 20:48:17,949 - helpers.py[DEBUG]: Running config-growpart using lock (<cloudinit.helpers.DummyLock object at 0x7f1c6220e5e0>)
2022-03-28 20:48:17,949 - cc_growpart.py[DEBUG]: No 'growpart' entry in cfg.  Using default: {'mode': 'auto', 'devices': ['/'], 'ignore_growroot_disabled': False}
2022-03-28 20:48:17,949 - subp.py[DEBUG]: Running command ['growpart', '--help'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 20:48:17,961 - util.py[DEBUG]: Reading from /proc/535/mountinfo (quiet=False)
2022-03-28 20:48:17,961 - util.py[DEBUG]: Read 3688 bytes from /proc/535/mountinfo
2022-03-28 20:48:17,961 - util.py[DEBUG]: Reading from /sys/class/block/sda1/partition (quiet=False)
2022-03-28 20:48:17,961 - util.py[DEBUG]: Read 2 bytes from /sys/class/block/sda1/partition
2022-03-28 20:48:17,961 - util.py[DEBUG]: Reading from /sys/devices/pci0000:00/0000:00:01.1/0000:02:00.0/virtio6/host0/target0:0:0/0:0:0:1/block/sda/dev (quiet=False)
2022-03-28 20:48:17,961 - util.py[DEBUG]: Read 4 bytes from /sys/devices/pci0000:00/0000:00:01.1/0000:02:00.0/virtio6/host0/target0:0:0/0:0:0:1/block/sda/dev
2022-03-28 20:48:17,962 - subp.py[DEBUG]: Running command ['growpart', '--dry-run', '/dev/sda', '1'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 20:48:17,985 - subp.py[DEBUG]: Running command ['growpart', '/dev/sda', '1'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 20:48:18,340 - util.py[DEBUG]: resize_devices took 0.379 seconds
2022-03-28 20:48:18,340 - cc_growpart.py[INFO]: '/' resized: changed (/dev/sda, 1) from 2244984320 to 10621009408
2022-03-28 20:48:18,340 - handlers.py[DEBUG]: finish: init-network/config-growpart: SUCCESS: config-growpart ran successfully


# First boot of encrypted VM WITHOUT updated cloud-init
2022-03-28 21:13:57,891 - stages.py[DEBUG]: Running module growpart (<module 'cloudinit.config.cc_growpart' from '/usr/lib/python3/dist-packages/cloudinit/config/cc_growpart.py'>) with frequency always
2022-03-28 21:13:57,891 - handlers.py[WARNING]: failed posting events to kvp, [Errno 2] No such file or directory: '/var/lib/hyperv/.kvp_pool_1'
2022-03-28 21:13:57,893 - handlers.py[DEBUG]: start: init-network/config-growpart: running config-growpart with frequency always
2022-03-28 21:13:57,894 - helpers.py[DEBUG]: Running config-growpart using lock (<cloudinit.helpers.DummyLock object at 0x7f9bf96c4e20>)
2022-03-28 21:13:57,894 - handlers.py[WARNING]: failed posting events to kvp, [Errno 2] No such file or directory: '/var/lib/hyperv/.kvp_pool_1'
2022-03-28 21:13:57,896 - cc_growpart.py[DEBUG]: No 'growpart' entry in cfg.  Using default: {'mode': 'auto', 'devices': ['/'], 'ignore_growroot_disabled': False}
2022-03-28 21:13:57,896 - subp.py[DEBUG]: Running command ['growpart', '--help'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:13:57,900 - util.py[DEBUG]: Reading from /proc/600/mountinfo (quiet=False)
2022-03-28 21:13:57,900 - util.py[DEBUG]: Read 3693 bytes from /proc/600/mountinfo
2022-03-28 21:13:57,900 - util.py[DEBUG]: resize_devices took 0.001 seconds
2022-03-28 21:13:57,900 - cc_growpart.py[DEBUG]: '/' SKIPPED: device_part_info(/dev/mapper/cloudimg-rootfs-91c13bcb-ec6c-4e27-a84c-364921d7dad6) failed: /dev/mapper/cloudimg-rootfs-91c13bcb-ec6c-4e27-a84c-364921d7dad6 not a partition
2022-03-28 21:13:57,901 - handlers.py[DEBUG]: finish: init-network/config-growpart: SUCCESS: config-growpart ran successfully

$ ls /
bin  boot  cc_growpart_keydata  dev  etc  home  lib  lib32  lib64  libx32  lost+found  media  mnt  opt  proc  root  run  sbin  snap  srv  sys  tmp  usr  var

$ blkid
/dev/loop0: TYPE="squashfs"
/dev/loop1: TYPE="squashfs"
/dev/loop2: TYPE="squashfs"
/dev/vda1: UUID="75e523a1-a78f-47fc-8947-e1271e1dc27e" LABEL="cloudimg-rootfs-enc" TYPE="crypto_LUKS" PARTUUID="fbfa67c7-6028-48c4-a76b-ed2e2503bdc0"
/dev/vda15: LABEL_FATBOOT="UEFI" LABEL="UEFI" UUID="5ABB-40A9" TYPE="vfat" PARTUUID="006c3e55-1fc2-41da-ad93-ae9f6278f6c8"
/dev/vdb: UUID="2022-03-28-16-13-19-00" LABEL="cidata" TYPE="iso9660"
/dev/mapper/cloudimg-rootfs-91c13bcb-ec6c-4e27-a84c-364921d7dad6: LABEL="cloudimg-rootfs" UUID="870790f6-4b16-4b58-ac30-4d0c36540bd0" TYPE="ext4"

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        956M     0  956M   0% /dev
tmpfs           974M     0  974M   0% /dev/shm
tmpfs           195M  3.5M  192M   2% /run
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           974M     0  974M   0% /sys/fs/cgroup
/dev/dm-0       1.9G  1.4G  559M  71% /
/dev/loop0       62M   62M     0 100% /snap/core20/1328
/dev/loop1       68M   68M     0 100% /snap/lxd/21835
/dev/loop2       44M   44M     0 100% /snap/snapd/14978
/dev/vda15     1022M   81M  942M   8% /boot/efi
tmpfs           195M     0  195M   0% /run/user/1000

# Then installed deb and did a cloud-init clean --logs --reboot

# First boot of encrypted VM WITH updated cloud-init
2022-03-28 21:19:20,800 - stages.py[DEBUG]: Running module growpart (<module 'cloudinit.config.cc_growpart' from '/usr/lib/python3/dist-packages/cloudinit/config/cc_growpart.py'>) with frequency always
2022-03-28 21:19:20,800 - handlers.py[WARNING]: failed posting events to kvp, [Errno 2] No such file or directory: '/var/lib/hyperv/.kvp_pool_1'
2022-03-28 21:19:20,800 - handlers.py[DEBUG]: start: init-network/config-growpart: running config-growpart with frequency always
2022-03-28 21:19:20,801 - helpers.py[DEBUG]: Running config-growpart using lock (<cloudinit.helpers.DummyLock object at 0x7fa509cacd00>)
2022-03-28 21:19:20,801 - handlers.py[WARNING]: failed posting events to kvp, [Errno 2] No such file or directory: '/var/lib/hyperv/.kvp_pool_1'
2022-03-28 21:19:20,801 - cc_growpart.py[DEBUG]: No 'growpart' entry in cfg.  Using default: {'mode': 'auto', 'devices': ['/'], 'ignore_growroot_disabled': False}
2022-03-28 21:19:20,801 - subp.py[DEBUG]: Running command ['growpart', '--help'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:19:20,809 - util.py[DEBUG]: Reading from /proc/610/mountinfo (quiet=False)
2022-03-28 21:19:20,810 - util.py[DEBUG]: Read 3693 bytes from /proc/610/mountinfo
2022-03-28 21:19:20,810 - cc_growpart.py[DEBUG]: /dev/mapper/cloudimg-rootfs-c3ac9cfb-1f50-4dbc-ac42-0d2fb0c637a1 is a mapped device pointing to /dev/dm-0
2022-03-28 21:19:20,810 - subp.py[DEBUG]: Running command ['cryptsetup', 'status', '/dev/dm-0'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:19:20,823 - cc_growpart.py[DEBUG]: Determined that /dev/dm-0 is encrypted
2022-03-28 21:19:20,823 - subp.py[DEBUG]: Running command ['dmsetup', 'deps', '--options=devname', '/dev/mapper/cloudimg-rootfs-c3ac9cfb-1f50-4dbc-ac42-0d2fb0c637a1'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:19:20,827 - util.py[DEBUG]: Reading from /sys/class/block/vda1/partition (quiet=False)
2022-03-28 21:19:20,827 - util.py[DEBUG]: Read 2 bytes from /sys/class/block/vda1/partition
2022-03-28 21:19:20,827 - util.py[DEBUG]: Reading from /sys/devices/pci0000:00/0000:00:03.0/virtio1/block/vda/dev (quiet=False)
2022-03-28 21:19:20,828 - util.py[DEBUG]: Read 6 bytes from /sys/devices/pci0000:00/0000:00:03.0/virtio1/block/vda/dev
2022-03-28 21:19:20,828 - subp.py[DEBUG]: Running command ['growpart', '--dry-run', '/dev/vda', '1'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:19:20,849 - subp.py[DEBUG]: Running command ['growpart', '/dev/vda', '1'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:19:21,150 - util.py[DEBUG]: Reading from /proc/610/mountinfo (quiet=False)
2022-03-28 21:19:21,150 - util.py[DEBUG]: Read 3693 bytes from /proc/610/mountinfo
2022-03-28 21:19:21,150 - cc_growpart.py[DEBUG]: /dev/mapper/cloudimg-rootfs-c3ac9cfb-1f50-4dbc-ac42-0d2fb0c637a1 is a mapped device pointing to /dev/dm-0
2022-03-28 21:19:21,151 - subp.py[DEBUG]: Running command ['cryptsetup', 'status', '/dev/dm-0'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:19:21,159 - cc_growpart.py[DEBUG]: Determined that /dev/dm-0 is encrypted
2022-03-28 21:19:21,159 - subp.py[DEBUG]: Running command ['dmsetup', 'deps', '--options=devname', '/dev/mapper/cloudimg-rootfs-c3ac9cfb-1f50-4dbc-ac42-0d2fb0c637a1'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:19:21,162 - subp.py[DEBUG]: Running command ['cryptsetup', '--key-file', '-', 'resize', '/dev/mapper/cloudimg-rootfs-c3ac9cfb-1f50-4dbc-ac42-0d2fb0c637a1'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:19:21,511 - subp.py[DEBUG]: Running command ['cryptsetup', 'luksKillSlot', '--batch-mode', '/dev/vda1', '10'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:19:21,588 - util.py[DEBUG]: resize_devices took 0.779 seconds
2022-03-28 21:19:21,589 - cc_growpart.py[INFO]: '/dev/vda1' resized: changed (/dev/vda, 1) from 2142223872 to 31134302208
2022-03-28 21:19:21,589 - cc_growpart.py[INFO]: '/' resized: Successfully resized encrypted volume '/dev/mapper/cloudimg-rootfs-c3ac9cfb-1f50-4dbc-ac42-0d2fb0c637a1'
2022-03-28 21:19:21,589 - handlers.py[DEBUG]: finish: init-network/config-growpart: SUCCESS: config-growpart ran successfully

$ ls /
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  lost+found  media  mnt  opt  proc  root  run  sbin  snap  srv  sys  tmp  usr  var


$ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        956M     0  956M   0% /dev
tmpfs           974M     0  974M   0% /dev/shm
tmpfs           195M  3.5M  192M   2% /run
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           974M     0  974M   0% /sys/fs/cgroup
/dev/dm-0        29G  1.4G   27G   5% /
/dev/loop0       68M   68M     0 100% /snap/lxd/21835
/dev/loop1       44M   44M     0 100% /snap/snapd/14978
/dev/loop2       62M   62M     0 100% /snap/core20/1328
/dev/vda15     1022M   81M  942M   8% /boot/efi
tmpfs           195M     0  195M   0% /run/user/1000

# Subsequent boot of encrypted VM WITH updated cloud-init
2022-03-28 21:22:55,132 - stages.py[DEBUG]: Running module growpart (<module 'cloudinit.config.cc_growpart' from '/usr/lib/python3/dist-packages/cloudinit/config/cc_growpart.py'>) with frequency always
2022-03-28 21:22:55,132 - handlers.py[WARNING]: failed posting events to kvp, [Errno 2] No such file or directory: '/var/lib/hyperv/.kvp_pool_1'
2022-03-28 21:22:55,133 - handlers.py[DEBUG]: start: init-network/config-growpart: running config-growpart with frequency always
2022-03-28 21:22:55,133 - helpers.py[DEBUG]: Running config-growpart using lock (<cloudinit.helpers.DummyLock object at 0x7fa8a098bbb0>)
2022-03-28 21:22:55,133 - handlers.py[WARNING]: failed posting events to kvp, [Errno 2] No such file or directory: '/var/lib/hyperv/.kvp_pool_1'
2022-03-28 21:22:55,134 - cc_growpart.py[DEBUG]: No 'growpart' entry in cfg.  Using default: {'mode': 'auto', 'devices': ['/'], 'ignore_growroot_disabled': False}
2022-03-28 21:22:55,134 - subp.py[DEBUG]: Running command ['growpart', '--help'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:22:55,141 - util.py[DEBUG]: Reading from /proc/585/mountinfo (quiet=False)
2022-03-28 21:22:55,141 - util.py[DEBUG]: Read 3534 bytes from /proc/585/mountinfo
2022-03-28 21:22:55,141 - cc_growpart.py[DEBUG]: /dev/mapper/cloudimg-rootfs-03de45d2-f355-457e-b8e8-87c48127d0a8 is a mapped device pointing to /dev/dm-0
2022-03-28 21:22:55,141 - subp.py[DEBUG]: Running command ['cryptsetup', 'status', '/dev/dm-0'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:22:55,155 - cc_growpart.py[DEBUG]: Determined that /dev/dm-0 is encrypted
2022-03-28 21:22:55,155 - subp.py[DEBUG]: Running command ['dmsetup', 'deps', '--options=devname', '/dev/mapper/cloudimg-rootfs-03de45d2-f355-457e-b8e8-87c48127d0a8'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:22:55,158 - util.py[DEBUG]: Reading from /sys/class/block/vda1/partition (quiet=False)
2022-03-28 21:22:55,158 - util.py[DEBUG]: Read 2 bytes from /sys/class/block/vda1/partition
2022-03-28 21:22:55,158 - util.py[DEBUG]: Reading from /sys/devices/pci0000:00/0000:00:03.0/virtio1/block/vda/dev (quiet=False)
2022-03-28 21:22:55,158 - util.py[DEBUG]: Read 6 bytes from /sys/devices/pci0000:00/0000:00:03.0/virtio1/block/vda/dev
2022-03-28 21:22:55,159 - subp.py[DEBUG]: Running command ['growpart', '--dry-run', '/dev/vda', '1'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:22:55,192 - util.py[DEBUG]: Reading from /proc/585/mountinfo (quiet=False)
2022-03-28 21:22:55,193 - util.py[DEBUG]: Read 3534 bytes from /proc/585/mountinfo
2022-03-28 21:22:55,193 - cc_growpart.py[DEBUG]: /dev/mapper/cloudimg-rootfs-03de45d2-f355-457e-b8e8-87c48127d0a8 is a mapped device pointing to /dev/dm-0
2022-03-28 21:22:55,193 - subp.py[DEBUG]: Running command ['cryptsetup', 'status', '/dev/dm-0'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:22:55,199 - cc_growpart.py[DEBUG]: Determined that /dev/dm-0 is encrypted
2022-03-28 21:22:55,199 - subp.py[DEBUG]: Running command ['dmsetup', 'deps', '--options=devname', '/dev/mapper/cloudimg-rootfs-03de45d2-f355-457e-b8e8-87c48127d0a8'] with allowed return codes [0] (shell=False, capture=True)
2022-03-28 21:22:55,202 - util.py[DEBUG]: resize_devices took 0.061 seconds
2022-03-28 21:22:55,202 - cc_growpart.py[DEBUG]: '/dev/vda1' NOCHANGE: no change necessary (/dev/vda, 1)
2022-03-28 21:22:55,202 - cc_growpart.py[DEBUG]: '/' FAILED: Resizing encrypted device (/dev/mapper/cloudimg-rootfs-03de45d2-f355-457e-b8e8-87c48127d0a8) failed: Could not load encryption key. This is expected if the volume has been previously resized.
2022-03-28 21:22:55,202 - handlers.py[DEBUG]: finish: init-network/config-growpart: SUCCESS: config-growpart ran successfully

@TheRealFalcon TheRealFalcon added the wip Work in progress, do not land label Mar 3, 2022
@TheRealFalcon
Copy link
Copy Markdown
Contributor Author

TheRealFalcon commented Mar 3, 2022

Still needs tests and some better error handling (assuming it works 😄 )
This is ready to review now.

@github-actions github-actions Bot added the stale-pr Pull request is stale; will be auto-closed soon label Mar 18, 2022
@TheRealFalcon TheRealFalcon removed stale-pr Pull request is stale; will be auto-closed soon wip Work in progress, do not land labels Mar 22, 2022
@TheRealFalcon TheRealFalcon changed the title WIP: Resize encrypted partitions Resize encrypted partitions Mar 28, 2022
@TheRealFalcon TheRealFalcon changed the title Resize encrypted partitions Resize encrypted partitions (SC-353) Mar 28, 2022
@canonical canonical deleted a comment from github-actions Bot Mar 30, 2022
@TheRealFalcon
Copy link
Copy Markdown
Contributor Author

This will also require adding pytest-mock to the build-depends of our packaging branches

@blackboxsw blackboxsw self-assigned this Apr 14, 2022
Comment thread cloudinit/config/cc_growpart.py Outdated
LOG.debug(
"Determined that %s is %sencrypted",
blockdev,
"" if is_encrypted else "not",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"" if is_encrypted else "not",
"" if is_encrypted else "not ",


def get_underlying_partition(blockdev):
command = ["dmsetup", "deps", "--options=devname", blockdev]
dep: str = subp.subp(command)[0] # type: ignore
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looking over cryptsetup dependencies, dmsetup is a Depends and we do an early check/exit in is_encrypted. If cryptsetup is absent, I don't think we need to handle CalledProcessErrrors here because we expect dmsetup will exist in order to get to this path.

Question about this in general, do we always expect any images which support encrypted disk resize to add cryptsetup package to the image above and beyond ensuring cloud-init is installed in the image?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Question about this in general, do we always expect any images which support encrypted disk resize to add cryptsetup package to the image above and beyond ensuring cloud-init is installed in the image?

I'm assuming so, but that'd be a question for CPC. Even if we were to fail here though, it'd get caught here with a slightly uglier error message: https://github.com/canonical/cloud-init/pull/1316/files#diff-326a9a2b453b20b3c3bb518217c1ffc2849e8ee82824ecfe359027b1e4237c77R466

Comment on lines +370 to +373
raise RuntimeError(
"Could not load encryption key. This is expected if "
"the volume has been previously resized."
) from e
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a way we can make this idempotent by introspecting that we have already resized and encrypted partition? I'm thinking in terms of if someone runs cloud-init clean --logs --reboot will be generate a Runtime error in this case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The actual effect is just an extra log (see my examples up top):

2022-03-28 21:22:55,202 - cc_growpart.py[DEBUG]: '/' FAILED: Resizing encrypted device (/dev/mapper/cloudimg-rootfs-03de45d2-f355-457e-b8e8-87c48127d0a8) failed: Could not load encryption key. This is expected if the volume has been previously resized.

Do you have any suggestions? We can't count on our normal semaphores, so cloud-init would have to store something more permanent which seems could be problematic if disks ever get hot-swapped or anything along those lines.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The only suggestion I wonder about is if we should really just early exit on absence of KEYDATA_PATH as mentioned above.

Comment thread test-requirements.txt
httpretty>=0.7.1
pytest
pytest-cov
pytest-mock
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's add this to build-depends in packages/pkg-deps.json to make sure we are injecting that dep during package build/tests during local package builds from cloud-init tooling.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As in add a new build-depends section to the debian section? Should we add other dependencies there too, like the reponses package we just added?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We looked over packages/bddeb (which sources information via the command ./tools/read-dependencies --distro debian during the local package build ./packages/bddeb. What I neglected to recall is that bddeb grew the ability to also read build dependencies from test-requirements.txt. So, I don't think we need to add those deps in pkg-deps.json too since you already added it to test-requirements.txt. You can validate that with python3-pytest-mock is listed in ./tools/read-dependencies --requirements-file test-requirements.txt --system-pkg-names

return False
is_encrypted = False
with suppress(subp.ProcessExecutionError):
subp.subp(["cryptsetup", "status", blockdev])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we want to make this catch more specific and log a warning in case there are other failure paths we might hit here which would force an is_encypted False value?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

cryptsetup should only return 0 if it's actually encrypted. Any other rc will raise the ProcessExecutionError.

Are you saying we should log the return code, just in case there's a different problem with running cryptsetup?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I guess what I'm thinking here two things:

  • Should we really only surpress a specific non-zero exit code and log any unexpected error messages and codes here
  • Also, running cryptsetup status on a thin volume group I created still returns 0 exit code, but it isn't an encrypted device, so a zero exit code may be a false positive in this case for any LVM setup?
csmith@downtown:~$ sudo cryptsetup status /dev/mapper/sru_lvmpool-LXDThinPool_tdata
/dev/mapper/sru_lvmpool-LXDThinPool_tdata is active and is in use.
  type:    n/a
csmith@downtown:~$ echo $?
0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm wondering if we can use dmsetup ls --target crypt here to list known crypt devices.
On my vm with luks enabled for the root FS I get

$ dmsetup ls --target crypt
dm_crypt-0     (253, 0)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The problem with dmsetup ls is that it's not easy to know how to tie values in that list to the specific device being resized.

In a previous iteration of this PR, I had a cryptsetup isLuks check. Based on a misunderstanding of a comment, I removed it. It looks like he was saying that we don't need to restrict resizing mapped devices to only Luks containers, but I want to specifically do that right now as we haven't had support for LVM sizing, and I'd rather not add it here without additional testing. So I added back the isLuks check.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

+1 this context makes sense. And crypsetup isLuks behaves as expected on my non-luks volume group. I'm +1 on restricting this behavior to just encrypted partitions at the moment given the artifacts which allow for resizing. We can iterate toward LVM resizing in separate efforts.

Comment thread cloudinit/config/cc_growpart.py Outdated
try:
KEYDATA_PATH.unlink()
except Exception:
util.logexc(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It'd be nice to avoid calling logexc where possible as it leaves a large tracebacks in the logs which doesn't generally add a whole lot of valuable content to the failure. Maybe just append the str(e) there with a LOG.warn?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree for the subp call, and I changed that one. For here though, if an unlink() call fails, something very strange is going on and I think a traceback would be extremely helpful.

Comment thread cloudinit/config/cc_growpart.py Outdated
) from e


def resize_encrypted(blockdev, partition):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can this function return a tuple of (RESIZE.SKIPPED|NOCHANGE|CHANGED) and message/details that can be consumed by the call-site?
It may provide us with a mechanism to early exit in the event that no KEYDATA_PATH.exists which could indicate that resize already happened or that the crypt support just doesn't exist for this device.

key is base64 encoded. Example:
{"key":"XFmCwX2FHIQp0LBWaLEMiHIyfxt1SGm16VvUAVledlY=","slot":5}
"""
try:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we pre-flight check existence of this KEYDATA_PATH and return SKIPPED in the event that it doesn't exist?

Then our try/except block below can only handle invalid data/format/decode errors if they exist and we avoid raising a FAILURE message in logs.

Suggested change
try:
if not KEYDATA_PATH.exists():
return (RESIZE.SKIPPED, f"No {KEYDATA_PATH} exists to decrypt device {blockdev}")
try:

Comment on lines +370 to +373
raise RuntimeError(
"Could not load encryption key. This is expected if "
"the volume has been previously resized."
) from e
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The only suggestion I wonder about is if we should really just early exit on absence of KEYDATA_PATH as mentioned above.

Comment thread cloudinit/config/cc_growpart.py Outdated
Comment thread cloudinit/config/cc_growpart.py
@TheRealFalcon
Copy link
Copy Markdown
Contributor Author

@blackboxsw , I believe I have addressed (or told you why I ignored 😜 ) all of your comments except the pkg-deps.json one as I'm still looking for feedback.

Comment thread cloudinit/config/cc_growpart.py
Copy link
Copy Markdown
Collaborator

@blackboxsw blackboxsw left a comment

Choose a reason for hiding this comment

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

Thanks @TheRealFalcon +1 from me on this work. Probably a good move to unlink the keyfile as you did in the event of unexpected errors resizing. The worst case scenario in unexpected failure case is that the encrypted disk isn't big enough.

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.

2 participants