From fafa0263db48351261fbeb799b9eb9f817cd38a4 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 12 Mar 2024 10:05:53 +0100 Subject: [PATCH] image,manifest: add support for user customization (well, not really) This adds support for being able to add user customization. In practise we can only handle adding root user key(s) for now until we have more discussion about how to support adding users in a bootc supported way. This support for keys is essential to allow testing the images without play gustfish or similar tricks (which is hard on a bootc deploy because bootc will bind mount the deploy `etc` over the `sysroot/etc` on first boot so anything we do on the root of the disk will not work for /etc (/root/.authorized_keys might work actually maybe?). This also adds support for kernel-args to the bootc install-to-fs stage. --- pkg/image/bootc_disk.go | 6 ++++++ pkg/manifest/raw_bootc.go | 20 ++++++++++++++++++- .../bootc_install_to_filesystem_stage.go | 12 ++++++++++- .../bootc_install_to_filesystem_stage_test.go | 12 ++++++----- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/pkg/image/bootc_disk.go b/pkg/image/bootc_disk.go index 2596e7a6d0..b68db92112 100644 --- a/pkg/image/bootc_disk.go +++ b/pkg/image/bootc_disk.go @@ -8,6 +8,7 @@ import ( "github.com/osbuild/images/pkg/artifact" "github.com/osbuild/images/pkg/container" + "github.com/osbuild/images/pkg/customizations/users" "github.com/osbuild/images/pkg/disk" "github.com/osbuild/images/pkg/manifest" "github.com/osbuild/images/pkg/osbuild" @@ -21,6 +22,10 @@ type BootcDiskImage struct { Platform platform.Platform PartitionTable *disk.PartitionTable + // This is a bit of a lie, only root and it's ssh key is supported + // today because that is all that bootc gives us by default. + Users []users.User + Filename string ContainerSource *container.SourceSpec @@ -61,6 +66,7 @@ func (img *BootcDiskImage) InstantiateManifestFromContainers(m *manifest.Manifes // and very basic user (root only?) should be supported baseImage := manifest.NewRawBootcImage(buildPipeline, containers, img.Platform) baseImage.PartitionTable = img.PartitionTable + baseImage.Users = img.Users switch imgFormat { case platform.FORMAT_QCOW2: diff --git a/pkg/manifest/raw_bootc.go b/pkg/manifest/raw_bootc.go index d3c6ba2996..22c6a71136 100644 --- a/pkg/manifest/raw_bootc.go +++ b/pkg/manifest/raw_bootc.go @@ -1,8 +1,11 @@ package manifest import ( + "fmt" + "github.com/osbuild/images/pkg/artifact" "github.com/osbuild/images/pkg/container" + "github.com/osbuild/images/pkg/customizations/users" "github.com/osbuild/images/pkg/disk" "github.com/osbuild/images/pkg/osbuild" "github.com/osbuild/images/pkg/ostree" @@ -25,6 +28,10 @@ type RawBootcImage struct { // tree, with `bootc install to-filesystem` we can only work // with the image itself PartitionTable *disk.PartitionTable + + // This is a bit of a lie, only root and it's ssh key is supported + // today because that is all that bootc gives us by default. + Users []users.User } func (p RawBootcImage) Filename() string { @@ -77,10 +84,21 @@ func (p *RawBootcImage) serialize() osbuild.Pipeline { panic("no partition table in live image") } + if len(p.Users) > 1 { + panic(fmt.Sprintf("raw bootc image only supports a single root key for user customization, got %v", p.Users)) + } + if len(p.Users) == 1 && p.Users[0].Name != "root" { + panic(fmt.Sprintf("raw bootc image only supports the root user, got %v", p.Users)) + } + for _, stage := range osbuild.GenImagePrepareStages(pt, p.filename, osbuild.PTSfdisk) { pipeline.AddStage(stage) } + opts := &osbuild.BootcInstallToFilesystemOptions{} + if len(p.Users) == 1 && p.Users[0].Key != nil { + opts.RootSSHAuthorizedKeys = *p.Users[0].Key + } inputs := osbuild.ContainerDeployInputs{ Images: osbuild.NewContainersInputForSources(p.containerSpecs), } @@ -88,7 +106,7 @@ func (p *RawBootcImage) serialize() osbuild.Pipeline { if err != nil { panic(err) } - st, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + st, err := osbuild.NewBootcInstallToFilesystemStage(opts, inputs, devices, mounts) if err != nil { panic(err) } diff --git a/pkg/osbuild/bootc_install_to_filesystem_stage.go b/pkg/osbuild/bootc_install_to_filesystem_stage.go index d3a4876d85..b9e4475e1c 100644 --- a/pkg/osbuild/bootc_install_to_filesystem_stage.go +++ b/pkg/osbuild/bootc_install_to_filesystem_stage.go @@ -4,6 +4,15 @@ import ( "fmt" ) +type BootcInstallToFilesystemOptions struct { + // options for --root-ssh-authorized-keys + RootSSHAuthorizedKeys string `json:"root-ssh-authorized-keys"` + // options for --karg + Kargs []string `json:"kernel-args"` +} + +func (BootcInstallToFilesystemOptions) isStageOptions() {} + // NewBootcInstallToFilesystem creates a new stage for the // org.osbuild.bootc.install-to-filesystem stage. // @@ -12,7 +21,7 @@ import ( // bootc/bootupd find and install all required bootloader bits. // // The mounts input should be generated with GenBootupdDevicesMounts. -func NewBootcInstallToFilesystemStage(inputs ContainerDeployInputs, devices map[string]Device, mounts []Mount) (*Stage, error) { +func NewBootcInstallToFilesystemStage(options *BootcInstallToFilesystemOptions, inputs ContainerDeployInputs, devices map[string]Device, mounts []Mount) (*Stage, error) { if err := validateBootupdMounts(mounts); err != nil { return nil, err } @@ -23,6 +32,7 @@ func NewBootcInstallToFilesystemStage(inputs ContainerDeployInputs, devices map[ return &Stage{ Type: "org.osbuild.bootc.install-to-filesystem", + Options: options, Inputs: inputs, Devices: devices, Mounts: mounts, diff --git a/pkg/osbuild/bootc_install_to_filesystem_stage_test.go b/pkg/osbuild/bootc_install_to_filesystem_stage_test.go index 8c036d9104..3f6abf1a19 100644 --- a/pkg/osbuild/bootc_install_to_filesystem_stage_test.go +++ b/pkg/osbuild/bootc_install_to_filesystem_stage_test.go @@ -31,11 +31,12 @@ func TestBootcInstallToFilesystemStageNewHappy(t *testing.T) { expectedStage := &osbuild.Stage{ Type: "org.osbuild.bootc.install-to-filesystem", + Options: (*osbuild.BootcInstallToFilesystemOptions)(nil), Inputs: inputs, Devices: devices, Mounts: mounts, } - stage, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + stage, err := osbuild.NewBootcInstallToFilesystemStage(nil, inputs, devices, mounts) require.Nil(t, err) assert.Equal(t, stage, expectedStage) } @@ -45,7 +46,7 @@ func TestBootcInstallToFilesystemStageNewNoContainers(t *testing.T) { mounts := makeOsbuildMounts("/", "/boot", "/boot/efi") inputs := osbuild.ContainerDeployInputs{} - _, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + _, err := osbuild.NewBootcInstallToFilesystemStage(nil, inputs, devices, mounts) assert.EqualError(t, err, "expected exactly one container input but got: 0 (map[])") } @@ -61,7 +62,7 @@ func TestBootcInstallToFilesystemStageNewTwoContainers(t *testing.T) { }, } - _, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + _, err := osbuild.NewBootcInstallToFilesystemStage(nil, inputs, devices, mounts) assert.EqualError(t, err, "expected exactly one container input but got: 2 (map[1:{} 2:{}])") } @@ -70,7 +71,7 @@ func TestBootcInstallToFilesystemStageMissingMounts(t *testing.T) { mounts := makeOsbuildMounts("/") inputs := makeFakeContainerInputs() - stage, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + stage, err := osbuild.NewBootcInstallToFilesystemStage(nil, inputs, devices, mounts) // XXX: rename error assert.ErrorContains(t, err, "required mounts for bootupd stage [/boot /boot/efi] missing") require.Nil(t, stage) @@ -81,7 +82,7 @@ func TestBootcInstallToFilesystemStageJsonHappy(t *testing.T) { mounts := makeOsbuildMounts("/", "/boot", "/boot/efi") inputs := makeFakeContainerInputs() - stage, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + stage, err := osbuild.NewBootcInstallToFilesystemStage(nil, inputs, devices, mounts) require.Nil(t, err) stageJson, err := json.MarshalIndent(stage, "", " ") require.Nil(t, err) @@ -98,6 +99,7 @@ func TestBootcInstallToFilesystemStageJsonHappy(t *testing.T) { } } }, + "options": null, "devices": { "dev-for-/": { "type": "org.osbuild.loopback"