diff --git a/pkg/image/bootc_disk.go b/pkg/image/bootc_disk.go index 44ce306ee2..2c277a027a 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,11 @@ 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 but + // that will most likely change over time. + Users []users.User + Filename string ContainerSource *container.SourceSpec @@ -42,10 +48,11 @@ func (img *BootcDiskImage) InstantiateManifestFromContainers(m *manifest.Manifes // this is signified by passing nil to the below pipelines. var hostPipeline manifest.Build - // XXX: no support for customization right now, at least /etc/fstab - // and very basic user (root only?) should be supported + // TODO: no support for customization right now but minimal support + // for root ssh keys is 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"