Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal/mkcw.Archive(): handle extra image content #5252

Merged
merged 2 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment was no longer correct as of #5224, but I forgot to drop it during that one.

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