From 0831da3115311db823cd8a98929d1af004b33224 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 2c12016b58..43043f3d24 100644 --- a/pkg/image/bootc_disk.go +++ b/pkg/image/bootc_disk.go @@ -5,6 +5,7 @@ import ( "math/rand" "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" @@ -18,6 +19,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 @@ -48,6 +53,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 // In BIB, we export multiple images from the same pipeline so we use the // filename as the basename for each export and set the extensions based on diff --git a/pkg/manifest/raw_bootc.go b/pkg/manifest/raw_bootc.go index d3c6ba2996..452679b52e 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 = []string{*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..06be586016 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,omitempty"` + // options for --karg + Kargs []string `json:"kernel-args,omitempty"` +} + +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"