Skip to content

Commit

Permalink
Merge pull request #5252 from nalind/mkcw-add-files
Browse files Browse the repository at this point in the history
internal/mkcw.Archive(): handle extra image content
  • Loading branch information
openshift-merge-bot[bot] committed Jan 17, 2024
2 parents e676f85 + de7c1e1 commit 8098bf2
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 25 deletions.
20 changes: 20 additions & 0 deletions cmd/buildah/mkcw.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"os"
"strings"

"github.com/containers/buildah"
"github.com/containers/buildah/pkg/parse"
Expand Down Expand Up @@ -38,6 +39,7 @@ func mkcwCmd(c *cobra.Command, args []string, options buildah.CWConvertImageOpti

func init() {
var teeType string
var addFile []string
var options buildah.CWConvertImageOptions
mkcwDescription := `Convert a conventional image to a confidential workload image.`
mkcwCommand := &cobra.Command{
Expand All @@ -46,6 +48,23 @@ func init() {
Long: mkcwDescription,
RunE: func(cmd *cobra.Command, args []string) error {
options.TeeType = parse.TeeType(teeType)
if len(addFile) > 0 {
options.ExtraImageContent = make(map[string]string)
for _, spec := range addFile {
source, dest, haveDest := strings.Cut(spec, ":")
if !haveDest {
dest = source
}
st, err := os.Stat(source)
if err != nil {
return fmt.Errorf("parsing add-file argument %q: source %q: %w", spec, source, err)
}
if st.IsDir() {
return fmt.Errorf("parsing add-file argument %q: source %q is not a regular file", spec, source)
}
options.ExtraImageContent[dest] = source
}
}
return mkcwCmd(cmd, args, options)
},
Example: `buildah mkcw localhost/repository:typical localhost/repository:cw`,
Expand All @@ -57,6 +76,7 @@ func init() {
flags.SetInterspersed(false)

flags.StringVarP(&teeType, "type", "t", "", "TEE (trusted execution environment) type: SEV,SNP (default: SNP)")
flags.StringArrayVar(&addFile, "add-file", nil, "add contents of a file to the image at a specified path (`source:destination`)")
flags.StringVarP(&options.AttestationURL, "attestation-url", "u", "", "attestation server URL")
flags.StringVarP(&options.BaseImage, "base-image", "b", "", "alternate base image (default: scratch)")
flags.StringVarP(&options.DiskEncryptionPassphrase, "passphrase", "p", "", "disk encryption passphrase")
Expand Down
2 changes: 2 additions & 0 deletions convertcw.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type CWConvertImageOptions struct {
FirmwareLibrary string
BaseImage string
Logger *logrus.Logger
ExtraImageContent map[string]string

// Passed through to BuilderOptions. Most settings won't make
// sense to be made available here because we don't launch a process.
Expand Down Expand Up @@ -172,6 +173,7 @@ func CWConvertImage(ctx context.Context, systemContext *types.SystemContext, sto
FirmwareLibrary: options.FirmwareLibrary,
Logger: logger,
GraphOptions: store.GraphOptions(),
ExtraImageContent: options.ExtraImageContent,
}
rc, workloadConfig, err := mkcw.Archive(sourceDir, &source.OCIv1, archiveOptions)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion docs/buildah-build.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ process completes successfully.
If _imageName_ does not include a registry name component, the registry name *localhost* will be prepended to the image name.

The **--tag** option supports all transports from `containers-transports(5)`.
If no transport is specified, the `container-storage` (i.e., local storage) transport is used.
If no transport is specified, the `containers-storage` (i.e., local storage) transport is used.

__buildah build --tag=oci-archive:./foo.ociarchive .__

Expand Down
8 changes: 4 additions & 4 deletions docs/buildah-commit.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ with a registry name component, `localhost` will be added to the name. If
name, the `buildah images` command will display `<none>` in the `REPOSITORY` and
`TAG` columns.

The *Image* value supports all transports from `containers-transports(5)`. If no transport is specified, the `container-storage` (i.e., local storage) transport is used.
The *image* value supports all transports from `containers-transports(5)`. If no transport is specified, the `containers-storage` (i.e., local storage) transport is used.

## RETURN VALUE
The image ID of the image that was created. On error, 1 is returned and errno is returned.
Expand Down Expand Up @@ -202,11 +202,11 @@ Unset environment variables from the final image.
This example saves an image based on the container.
`buildah commit containerID newImageName`

This example saves an image named newImageName based on the container.
This example saves an image named newImageName based on the container and removes the working container.
`buildah commit --rm containerID newImageName`

This example commits to an OCI Directory named /tmp/newImageName based on the container.
`buildah commit --rm containerID oci-archive:/tmp/newImageName`
This example commits to an OCI archive file named /tmp/newImageName based on the container.
`buildah commit containerID oci-archive:/tmp/newImageName`

This example saves an image with no name, removes the working container, and creates a new container using the image's ID.
`buildah from $(buildah commit --rm containerID)`
Expand Down
8 changes: 8 additions & 0 deletions docs/buildah-mkcw.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ A container image, stored locally or in a registry

## OPTIONS

**--add-file** *source[:destination]*

Read the contents of the file `source` and add it to the committed image as a
file at `destination`. If `destination` is not specified, the path of `source`
will be used. The new file will be owned by UID 0, GID 0, have 0644
permissions, and be given a current timestamp. This option can be specified
multiple times.

**--attestation-url**, **-u** *url*
The location of a key broker / attestation server.
If a value is specified, the new image's workload ID, along with the passphrase
Expand Down
4 changes: 1 addition & 3 deletions image.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,7 @@ func (i *containerImageRef) extractConfidentialWorkloadFS(options ConfidentialWo
Slop: options.Slop,
FirmwareLibrary: options.FirmwareLibrary,
GraphOptions: i.store.GraphOptions(),
}
if len(i.extraImageContent) > 0 {
logrus.Warnf("ignoring extra requested content %v, not implemented (yet)", i.extraImageContent)
ExtraImageContent: i.extraImageContent,
}
rc, _, err := mkcw.Archive(mountPoint, &image, archiveOptions)
if err != nil {
Expand Down
57 changes: 44 additions & 13 deletions internal/mkcw/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type ArchiveOptions struct {
FirmwareLibrary string
Logger *logrus.Logger
GraphOptions []string // passed in from a storage Store, probably
ExtraImageContent map[string]string
}

type chainRetrievalError struct {
Expand All @@ -70,17 +71,15 @@ func (c chainRetrievalError) Error() string {

// Archive generates a WorkloadConfig for a specified directory and produces a
// tar archive of a container image's rootfs with the expected contents.
// The input directory will have a ".krun_config.json" file added to it while
// this function is running, but it will be removed on completion.
func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadCloser, WorkloadConfig, error) {
func Archive(rootfsPath string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadCloser, WorkloadConfig, error) {
const (
teeDefaultCPUs = 2
teeDefaultMemory = 512
teeDefaultFilesystem = "ext4"
teeDefaultTeeType = SNP
)

if path == "" {
if rootfsPath == "" {
return nil, WorkloadConfig{}, fmt.Errorf("required path not specified")
}
logger := options.Logger
Expand All @@ -103,7 +102,7 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
filesystem := teeDefaultFilesystem
workloadID := options.WorkloadID
if workloadID == "" {
digestInput := path + filesystem + time.Now().String()
digestInput := rootfsPath + filesystem + time.Now().String()
workloadID = digest.Canonical.FromString(digestInput).Encoded()
}
workloadConfig := WorkloadConfig{
Expand Down Expand Up @@ -176,7 +175,7 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC

// We're going to want to add some content to the rootfs, so set up an
// overlay that uses it as a lower layer so that we can write to it.
st, err := system.Stat(path)
st, err := system.Stat(rootfsPath)
if err != nil {
return nil, WorkloadConfig{}, fmt.Errorf("reading information about the container root filesystem: %w", err)
}
Expand Down Expand Up @@ -220,7 +219,7 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
}
}()
// Create a mount point using that working state.
rootfsMount, err := overlay.Mount(overlayTempDir, path, rootfsDir, 0, 0, options.GraphOptions)
rootfsMount, err := overlay.Mount(overlayTempDir, rootfsPath, rootfsDir, 0, 0, options.GraphOptions)
if err != nil {
return nil, WorkloadConfig{}, fmt.Errorf("setting up support for overlay of container root filesystem: %w", err)
}
Expand All @@ -243,14 +242,46 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
}
}()
// Pretend that we didn't have to do any of the preceding.
path = rootfsDir
rootfsPath = rootfsDir

// Write extra content to the rootfs, creating intermediate directories if necessary.
for location, content := range options.ExtraImageContent {
err := func() error {
if err := idtools.MkdirAllAndChownNew(filepath.Dir(filepath.Join(rootfsPath, location)), 0o755, idtools.IDPair{UID: int(st.UID()), GID: int(st.GID())}); err != nil {
return fmt.Errorf("ensuring %q is present in container root filesystem: %w", filepath.Dir(location), err)
}
output, err := os.OpenFile(filepath.Join(rootfsPath, location), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
if err != nil {
return fmt.Errorf("preparing to write %q to container root filesystem: %w", location, err)
}
defer output.Close()
input, err := os.Open(content)
if err != nil {
return err
}
defer input.Close()
if _, err := io.Copy(output, input); err != nil {
return fmt.Errorf("copying contents of %q to %q in container root filesystem: %w", content, location, err)
}
if err := output.Chown(int(st.UID()), int(st.GID())); err != nil {
return fmt.Errorf("setting owner of %q in the container root filesystem: %w", location, err)
}
if err := output.Chmod(0o644); err != nil {
return fmt.Errorf("setting permissions on %q in the container root filesystem: %w", location, err)
}
return nil
}()
if err != nil {
return nil, WorkloadConfig{}, err
}
}

// Write part of the config blob where the krun init process will be
// looking for it. The oci2cw tool used `buildah inspect` output, but
// init is just looking for fields that have the right names in any
// object, and the image's config will have that, so let's try encoding
// it directly.
krunConfigPath := filepath.Join(path, ".krun_config.json")
krunConfigPath := filepath.Join(rootfsPath, ".krun_config.json")
krunConfigBytes, err := json.Marshal(ociConfig)
if err != nil {
return nil, WorkloadConfig{}, fmt.Errorf("creating .krun_config from image configuration: %w", err)
Expand Down Expand Up @@ -288,7 +319,7 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
imageSize := slop(options.ImageSize, options.Slop)
if imageSize == 0 {
var sourceSize int64
if err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
if err := filepath.WalkDir(rootfsPath, func(path string, d fs.DirEntry, err error) error {
if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) {
return err
}
Expand Down Expand Up @@ -336,7 +367,7 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
}

// Format the disk image with the filesystem contents.
if _, stderr, err := MakeFS(path, plain.Name(), filesystem); err != nil {
if _, stderr, err := MakeFS(rootfsPath, plain.Name(), filesystem); err != nil {
if strings.TrimSpace(stderr) != "" {
return nil, WorkloadConfig{}, fmt.Errorf("%s: %w", strings.TrimSpace(stderr), err)
}
Expand Down Expand Up @@ -456,8 +487,8 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
tmpHeader.Name = "tmp/"
tmpHeader.Typeflag = tar.TypeDir
tmpHeader.Mode = 0o1777
tmpHeader.Uname, workloadConfigHeader.Gname = "", ""
tmpHeader.Uid, workloadConfigHeader.Gid = 0, 0
tmpHeader.Uname, tmpHeader.Gname = "", ""
tmpHeader.Uid, tmpHeader.Gid = 0, 0
tmpHeader.Size = 0
if err = tw.WriteHeader(tmpHeader); err != nil {
logrus.Errorf("writing header for %q: %v", tmpHeader.Name, err)
Expand Down
19 changes: 15 additions & 4 deletions tests/mkcw.bats
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ function mkcw_check_image() {
test -d "$TEST_SCRATCH_DIR"/mount/tmp
# Should have a /bin/sh file from the base image, at least.
test -s "$TEST_SCRATCH_DIR"/mount/bin/sh || test -L "$TEST_SCRATCH_DIR"/mount/bin/sh
if shift ; then
if shift ; then
for pair in "$@" ; do
inner=${pair##*:}
outer=${pair%%:*}
cmp ${outer} "$TEST_SCRATCH_DIR"/mount/${inner}
done
fi
fi

# Clean up.
umount "$TEST_SCRATCH_DIR"/mount
Expand All @@ -50,14 +59,16 @@ function mkcw_check_image() {
fi
_prefetch busybox
_prefetch bash
createrandom randomfile1
createrandom randomfile2

echo -n mkcw-convert > "$TEST_SCRATCH_DIR"/key
# image has one layer, check with all-lower-case TEE type name
run_buildah mkcw --ignore-attestation-errors --type snp --passphrase=mkcw-convert busybox busybox-cw
mkcw_check_image busybox-cw
run_buildah mkcw --ignore-attestation-errors --type snp --passphrase=mkcw-convert --add-file randomfile1:/in-a-subdir/rnd1 busybox busybox-cw
mkcw_check_image busybox-cw "" randomfile1:in-a-subdir/rnd1
# image has multiple layers, check with all-upper-case TEE type name
run_buildah mkcw --ignore-attestation-errors --type SNP --passphrase=mkcw-convert bash bash-cw
mkcw_check_image bash-cw
run_buildah mkcw --ignore-attestation-errors --type SNP --passphrase=mkcw-convert --add-file randomfile2:rnd2 bash bash-cw
mkcw_check_image bash-cw "" randomfile2:/rnd2
}

@test "mkcw-commit" {
Expand Down

0 comments on commit 8098bf2

Please sign in to comment.