Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion crates/lib/src/bootc_composefs/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ use crate::{
use crate::{bootc_composefs::status::get_sorted_grub_uki_boot_entries, install::PostFetchState};
use crate::{
composefs_consts::{
BOOT_LOADER_ENTRIES, STAGED_BOOT_LOADER_ENTRIES, UKI_NAME_PREFIX, USER_CFG, USER_CFG_STAGED,
BOOT_LOADER_ENTRIES, STAGED_BOOT_LOADER_ENTRIES, STATE_DIR_RELATIVE, UKI_NAME_PREFIX,
USER_CFG, USER_CFG_STAGED,
},
spec::{Bootloader, Host},
};
Expand Down Expand Up @@ -2261,6 +2262,19 @@ pub(crate) async fn setup_composefs_boot(
)
.await?;

if let Some(contents) = state.root_ssh_authorized_keys.as_deref() {
let deployment = root_setup
.physical_root
.open_dir(format!("{STATE_DIR_RELATIVE}/{}", deploy_id.to_hex()))
.context("Opening deployment state")?;
crate::install::osconfig::inject_root_ssh_authorized_keys(
mounted_root.dir(),
&deployment,
state.load_policy()?.as_ref(),
contents,
)?;
}

Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion crates/lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1324,7 +1324,7 @@ async fn install_container(
}

if let Some(contents) = state.root_ssh_authorized_keys.as_deref() {
osconfig::inject_root_ssh_authorized_keys(&root, sepolicy, contents)?;
osconfig::inject_root_ssh_authorized_keys(&root, &root, sepolicy, contents)?;

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.

For followup, I think this should be moved out into a common place that's "post image layout"

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.

Makes sense, I'll move it in a follow-up once this lands.

}

let aleph = InstallAleph::new(
Expand Down
33 changes: 29 additions & 4 deletions crates/lib/src/install/osconfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ use ostree_ext::ostree;
const ETC_TMPFILES: &str = "etc/tmpfiles.d";
const ROOT_SSH_TMPFILE: &str = "bootc-root-ssh.conf";

/// Resolve /root in `root` and write the tmpfiles.d drop-in under `dest`.
/// With ostree `dest` is the deployment root itself; a composefs deployment
/// keeps its /etc apart from the image.
#[context("Injecting root authorized_keys")]
pub(crate) fn inject_root_ssh_authorized_keys(
root: &Dir,
dest: &Dir,
sepolicy: Option<&ostree::SePolicy>,
contents: &str,
) -> Result<()> {
Expand All @@ -36,8 +40,8 @@ pub(crate) fn inject_root_ssh_authorized_keys(
let tmpfiles_content =
format!("f~ /{root_path}/.ssh/authorized_keys 600 root root - {b64_encoded}\n");

crate::lsm::ensure_dir_labeled(root, ETC_TMPFILES, None, 0o755.into(), sepolicy)?;
let tmpfiles_dir = root.open_dir(ETC_TMPFILES)?;
crate::lsm::ensure_dir_labeled(dest, ETC_TMPFILES, None, 0o755.into(), sepolicy)?;
let tmpfiles_dir = dest.open_dir(ETC_TMPFILES)?;
crate::lsm::atomic_replace_labeled(
&tmpfiles_dir,
ROOT_SSH_TMPFILE,
Expand All @@ -62,7 +66,8 @@ mod tests {
root.create_dir("etc")?;
// Test with a symlink
root.symlink("var/roothome", "root")?;
inject_root_ssh_authorized_keys(root, None, "ssh-ed25519 ABCDE example@demo\n").unwrap();
inject_root_ssh_authorized_keys(root, root, None, "ssh-ed25519 ABCDE example@demo\n")
.unwrap();

let content = root.read_to_string(format!("etc/tmpfiles.d/{ROOT_SSH_TMPFILE}"))?;
assert_eq!(
Expand All @@ -79,7 +84,8 @@ mod tests {

root.create_dir("etc")?;
root.create_dir("root")?;
inject_root_ssh_authorized_keys(root, None, "ssh-ed25519 ABCDE example@demo\n").unwrap();
inject_root_ssh_authorized_keys(root, root, None, "ssh-ed25519 ABCDE example@demo\n")
.unwrap();

let content = root.read_to_string(format!("etc/tmpfiles.d/{ROOT_SSH_TMPFILE}"))?;
assert_eq!(
Expand All @@ -88,4 +94,23 @@ mod tests {
);
Ok(())
}

#[test]
fn test_inject_root_ssh_separate_dest() -> Result<()> {
let root = &cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
let dest = &cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;

root.symlink("var/roothome", "root")?;
dest.create_dir("etc")?;
inject_root_ssh_authorized_keys(root, dest, None, "ssh-ed25519 ABCDE example@demo\n")
.unwrap();

let content = dest.read_to_string(format!("etc/tmpfiles.d/{ROOT_SSH_TMPFILE}"))?;
assert_eq!(
content,
"f~ /var/roothome/.ssh/authorized_keys 600 root root - c3NoLWVkMjU1MTkgQUJDREUgZXhhbXBsZUBkZW1vCg==\n"
);
assert!(!root.exists("etc"));
Ok(())
}
}
11 changes: 10 additions & 1 deletion tmt/tests/booted/test-install-composefs-native.nu
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const NATIVE = "localhost/bootc-composefs-native"
const BOTH = "localhost/bootc-composefs-both"
const DISK = "/var/tmp/composefs-native.img"
const MNT = "/var/mnt/composefs-native"
const KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITEST test@example.com"
const KEYS = "/var/tmp/composefs-native-keys"

def build [image: string, extra: string] {
let td = mktemp -d
Expand All @@ -46,7 +48,8 @@ def install [image: string, ...args: string] {
--security-opt label=type:unconfined_t
-v /dev:/dev -v /var/lib/containers:/var/lib/containers -v /var/tmp:/var/tmp
$image
bootc install to-disk --disable-selinux --via-loopback ...$args $DISK)
bootc install to-disk --disable-selinux --via-loopback
--root-ssh-authorized-keys $KEYS ...$args $DISK)

# Inspect the root partition of the installed disk
let parts = sfdisk --json $DISK | from json | get partitiontable
Expand All @@ -56,8 +59,12 @@ def install [image: string, ...args: string] {
mount -o $"ro,loop,offset=($offset)" $DISK $MNT
let composefs = ($"($MNT)/composefs" | path exists) and ((ls $"($MNT)/state/deploy" | length) == 1)
let ostree = ($"($MNT)/ostree/deploy" | path exists)
let dropin = glob $"($MNT)/state/deploy/*/etc/tmpfiles.d/bootc-root-ssh.conf"
| append (glob $"($MNT)/ostree/deploy/*/deploy/*/etc/tmpfiles.d/bootc-root-ssh.conf")
| each {|p| {content: (open --raw $p | decode utf-8), mode: (stat -c %a $p | str trim)}}
umount $MNT
rm -f $DISK
assert equal $dropin [{content: $"f~ /var/roothome/.ssh/authorized_keys 600 root root - ($KEY | encode base64)\n", mode: "644"}] "root ssh drop-in"
match [$composefs $ostree] {
[true false] => "composefs",
[false true] => "ostree",
Expand All @@ -69,6 +76,7 @@ def main [] {
tap begin "composefs-native images default to the composefs backend"

bootc image copy-to-storage
$KEY | save -f $KEYS
build $NATIVE "RUN rm -f /usr/lib/ostree/prepare-root.conf /etc/ostree/prepare-root.conf"
# On the composefs variant, localhost/bootc is itself composefs-native: it
# has no prepare-root.conf, and configures its composefs bootloader.
Expand All @@ -83,5 +91,6 @@ def main [] {
}

podman rmi $NATIVE $BOTH
rm -f $KEYS
tap ok
}
Loading