Skip to content

Commit

Permalink
image: add new ContainerBuildable flag to OSTreeDiskImage
Browse files Browse the repository at this point in the history
One objective for bifrost images is that it should be possible
to run osbuild inside a container. This can interfere with the
selinux policies of the buildroot.

Inside the container everything is labeled
`system_u:object_r:container_files_t`. Labeling /usr/bin/osbuild as
`osbuild_exec_t` is not possible in the general case because
the host may not have `osbuild-selinux` installed that contains
this type. The workaround is that the container labels osbuild
itself as `install_exec_t`. This works fine however there is
a selinux denial warning when the `{,u}mount` binary is called
because the transition from `install_t`->`mount_t` is not allowed.
The warning is "harmless" because `install_t` has enough privs
to allow the `{,u}mount` binaries to work.

To silence this warning we can label `{,u}mount` in the buildroot
as `install_exec_t` directly.

This commit allows to control this now via the `ContainerBuildable`
flag that can be set on `manifest.Build` to enable this behavior.
  • Loading branch information
mvo5 authored and achilleas-k committed Dec 7, 2023
1 parent 4362ad0 commit dea1af4
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 1 deletion.
15 changes: 15 additions & 0 deletions pkg/image/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package image

import (
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/images/pkg/runner"
)

func MockManifestNewBuild(new func(m *manifest.Manifest, runner runner.Runner, repos []rpmmd.RepoConfig) *manifest.Build) (restore func()) {
saved := manifestNewBuild
manifestNewBuild = new
return func() {
manifestNewBuild = saved
}
}
10 changes: 9 additions & 1 deletion pkg/image/ostree_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ type OSTreeDiskImage struct {
// Lock the root account in the deployment unless the user defined root
// user options in the build configuration.
LockRoot bool

// Container buildable tweaks the buildroot to be container friendly,
// i.e. to not rely on an installed osbuild-selinux
ContainerBuildable bool
}

func NewOSTreeDiskImageFromCommit(commit ostree.SourceSpec) *OSTreeDiskImage {
Expand Down Expand Up @@ -102,11 +106,15 @@ func baseRawOstreeImage(img *OSTreeDiskImage, m *manifest.Manifest, buildPipelin
return manifest.NewRawOStreeImage(buildPipeline, osPipeline, img.Platform)
}

// replaced in testing
var manifestNewBuild = manifest.NewBuild

func (img *OSTreeDiskImage) InstantiateManifest(m *manifest.Manifest,
repos []rpmmd.RepoConfig,
runner runner.Runner,
rng *rand.Rand) (*artifact.Artifact, error) {
buildPipeline := manifest.NewBuild(m, runner, repos)
buildPipeline := manifestNewBuild(m, runner, repos)
buildPipeline.ContainerBuildable = img.ContainerBuildable
buildPipeline.Checkpoint()

// don't support compressing non-raw images
Expand Down
59 changes: 59 additions & 0 deletions pkg/image/ostree_disk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package image_test

import (
"math/rand"
"testing"

"github.com/stretchr/testify/require"

"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/image"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/images/pkg/runner"
)

func TestOSTreeDiskImageManifestSetsContainerBuildable(t *testing.T) {
rng := rand.New(rand.NewSource(0)) // nolint:gosec

repos := []rpmmd.RepoConfig{}
r := &runner.Fedora{Version: 39}

ref := "ostree/1/1/0"
containerSource := container.SourceSpec{
Source: "source-spec",
Name: "name",
}

var buildPipeline *manifest.Build
restore := image.MockManifestNewBuild(func(m *manifest.Manifest, r runner.Runner, repos []rpmmd.RepoConfig) *manifest.Build {
buildPipeline = manifest.NewBuild(m, r, repos)
return buildPipeline
})
defer restore()

for _, containerBuildable := range []bool{true, false} {
mf := manifest.New()
img := image.NewOSTreeDiskImageFromContainer(containerSource, ref)
require.NotNil(t, img)
img.Platform = &platform.X86{
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
},
BIOS: true,
UEFIVendor: "fedora",
}
img.Workload = &workload.BaseWorkload{}
img.OSName = "osname"
img.ContainerBuildable = containerBuildable

_, err := img.InstantiateManifest(&mf, repos, r, rng)
require.Nil(t, err)
require.NotNil(t, img)
require.NotNil(t, buildPipeline)

require.Equal(t, buildPipeline.ContainerBuildable, containerBuildable)
}
}
9 changes: 9 additions & 0 deletions pkg/manifest/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ type Build struct {
dependents []Pipeline
repos []rpmmd.RepoConfig
packageSpecs []rpmmd.PackageSpec

// TODO: make private?
// Container buildable tweaks the buildroot to be container friendly,
// i.e. to not rely on an installed osbuild-selinux
ContainerBuildable bool
}

// NewBuild creates a new build pipeline from the repositories in repos
Expand Down Expand Up @@ -109,6 +114,10 @@ func (p *Build) getSELinuxLabels() map[string]string {
switch pkg.Name {
case "coreutils":
labels["/usr/bin/cp"] = "system_u:object_r:install_exec_t:s0"
if p.ContainerBuildable {
labels["/usr/bin/mount"] = "system_u:object_r:install_exec_t:s0"
labels["/usr/bin/umount"] = "system_u:object_r:install_exec_t:s0"
}
case "tar":
labels["/usr/bin/tar"] = "system_u:object_r:install_exec_t:s0"
}
Expand Down
84 changes: 84 additions & 0 deletions pkg/manifest/build_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package manifest

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/images/pkg/runner"
)

func TestBuildContainerBuildableNo(t *testing.T) {
repos := []rpmmd.RepoConfig{}
mf := New()
runner := &runner.Fedora{Version: 39}

build := NewBuild(&mf, runner, repos)
require.NotNil(t, build)

for _, tc := range []struct {
packageSpec []rpmmd.PackageSpec
containerBuildable bool
expectedSELinuxLabels map[string]string
}{
// no pkgs means no selinux labels (container build or not)
{
[]rpmmd.PackageSpec{},
false,
map[string]string{},
},
{
[]rpmmd.PackageSpec{},
true,
map[string]string{},
},
{
[]rpmmd.PackageSpec{{Name: "coreutils"}},
false,
map[string]string{
"/usr/bin/cp": "system_u:object_r:install_exec_t:s0",
},
},
{
[]rpmmd.PackageSpec{{Name: "tar"}},
false,
map[string]string{
"/usr/bin/tar": "system_u:object_r:install_exec_t:s0",
},
},
{
[]rpmmd.PackageSpec{{Name: "coreutils"}, {Name: "tar"}},
false,
map[string]string{
"/usr/bin/cp": "system_u:object_r:install_exec_t:s0",
"/usr/bin/tar": "system_u:object_r:install_exec_t:s0",
},
},
{
[]rpmmd.PackageSpec{{Name: "coreutils"}},
true,
map[string]string{
"/usr/bin/cp": "system_u:object_r:install_exec_t:s0",
"/usr/bin/mount": "system_u:object_r:install_exec_t:s0",
"/usr/bin/umount": "system_u:object_r:install_exec_t:s0",
},
},
{
[]rpmmd.PackageSpec{{Name: "coreutils"}, {Name: "tar"}},
true,
map[string]string{
"/usr/bin/cp": "system_u:object_r:install_exec_t:s0",
"/usr/bin/mount": "system_u:object_r:install_exec_t:s0",
"/usr/bin/umount": "system_u:object_r:install_exec_t:s0",
"/usr/bin/tar": "system_u:object_r:install_exec_t:s0",
},
},
} {
build.packageSpecs = tc.packageSpec
build.ContainerBuildable = tc.containerBuildable

labels := build.getSELinuxLabels()
require.Equal(t, labels, tc.expectedSELinuxLabels)
}
}

0 comments on commit dea1af4

Please sign in to comment.