Skip to content

Commit 8098bf2

Browse files
Merge pull request #5252 from nalind/mkcw-add-files
internal/mkcw.Archive(): handle extra image content
2 parents e676f85 + de7c1e1 commit 8098bf2

File tree

8 files changed

+95
-25
lines changed

8 files changed

+95
-25
lines changed

cmd/buildah/mkcw.go

+20
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"fmt"
55
"os"
6+
"strings"
67

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

3940
func init() {
4041
var teeType string
42+
var addFile []string
4143
var options buildah.CWConvertImageOptions
4244
mkcwDescription := `Convert a conventional image to a confidential workload image.`
4345
mkcwCommand := &cobra.Command{
@@ -46,6 +48,23 @@ func init() {
4648
Long: mkcwDescription,
4749
RunE: func(cmd *cobra.Command, args []string) error {
4850
options.TeeType = parse.TeeType(teeType)
51+
if len(addFile) > 0 {
52+
options.ExtraImageContent = make(map[string]string)
53+
for _, spec := range addFile {
54+
source, dest, haveDest := strings.Cut(spec, ":")
55+
if !haveDest {
56+
dest = source
57+
}
58+
st, err := os.Stat(source)
59+
if err != nil {
60+
return fmt.Errorf("parsing add-file argument %q: source %q: %w", spec, source, err)
61+
}
62+
if st.IsDir() {
63+
return fmt.Errorf("parsing add-file argument %q: source %q is not a regular file", spec, source)
64+
}
65+
options.ExtraImageContent[dest] = source
66+
}
67+
}
4968
return mkcwCmd(cmd, args, options)
5069
},
5170
Example: `buildah mkcw localhost/repository:typical localhost/repository:cw`,
@@ -57,6 +76,7 @@ func init() {
5776
flags.SetInterspersed(false)
5877

5978
flags.StringVarP(&teeType, "type", "t", "", "TEE (trusted execution environment) type: SEV,SNP (default: SNP)")
79+
flags.StringArrayVar(&addFile, "add-file", nil, "add contents of a file to the image at a specified path (`source:destination`)")
6080
flags.StringVarP(&options.AttestationURL, "attestation-url", "u", "", "attestation server URL")
6181
flags.StringVarP(&options.BaseImage, "base-image", "b", "", "alternate base image (default: scratch)")
6282
flags.StringVarP(&options.DiskEncryptionPassphrase, "passphrase", "p", "", "disk encryption passphrase")

convertcw.go

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type CWConvertImageOptions struct {
4545
FirmwareLibrary string
4646
BaseImage string
4747
Logger *logrus.Logger
48+
ExtraImageContent map[string]string
4849

4950
// Passed through to BuilderOptions. Most settings won't make
5051
// sense to be made available here because we don't launch a process.
@@ -172,6 +173,7 @@ func CWConvertImage(ctx context.Context, systemContext *types.SystemContext, sto
172173
FirmwareLibrary: options.FirmwareLibrary,
173174
Logger: logger,
174175
GraphOptions: store.GraphOptions(),
176+
ExtraImageContent: options.ExtraImageContent,
175177
}
176178
rc, workloadConfig, err := mkcw.Archive(sourceDir, &source.OCIv1, archiveOptions)
177179
if err != nil {

docs/buildah-build.1.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,7 @@ process completes successfully.
875875
If _imageName_ does not include a registry name component, the registry name *localhost* will be prepended to the image name.
876876

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

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

docs/buildah-commit.1.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ with a registry name component, `localhost` will be added to the name. If
1414
name, the `buildah images` command will display `<none>` in the `REPOSITORY` and
1515
`TAG` columns.
1616

17-
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.
17+
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.
1818

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

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

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

211211
This example saves an image with no name, removes the working container, and creates a new container using the image's ID.
212212
`buildah from $(buildah commit --rm containerID)`

docs/buildah-mkcw.1.md

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ A container image, stored locally or in a registry
2121

2222
## OPTIONS
2323

24+
**--add-file** *source[:destination]*
25+
26+
Read the contents of the file `source` and add it to the committed image as a
27+
file at `destination`. If `destination` is not specified, the path of `source`
28+
will be used. The new file will be owned by UID 0, GID 0, have 0644
29+
permissions, and be given a current timestamp. This option can be specified
30+
multiple times.
31+
2432
**--attestation-url**, **-u** *url*
2533
The location of a key broker / attestation server.
2634
If a value is specified, the new image's workload ID, along with the passphrase

image.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,7 @@ func (i *containerImageRef) extractConfidentialWorkloadFS(options ConfidentialWo
204204
Slop: options.Slop,
205205
FirmwareLibrary: options.FirmwareLibrary,
206206
GraphOptions: i.store.GraphOptions(),
207-
}
208-
if len(i.extraImageContent) > 0 {
209-
logrus.Warnf("ignoring extra requested content %v, not implemented (yet)", i.extraImageContent)
207+
ExtraImageContent: i.extraImageContent,
210208
}
211209
rc, _, err := mkcw.Archive(mountPoint, &image, archiveOptions)
212210
if err != nil {

internal/mkcw/archive.go

+44-13
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type ArchiveOptions struct {
5454
FirmwareLibrary string
5555
Logger *logrus.Logger
5656
GraphOptions []string // passed in from a storage Store, probably
57+
ExtraImageContent map[string]string
5758
}
5859

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

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

83-
if path == "" {
82+
if rootfsPath == "" {
8483
return nil, WorkloadConfig{}, fmt.Errorf("required path not specified")
8584
}
8685
logger := options.Logger
@@ -103,7 +102,7 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
103102
filesystem := teeDefaultFilesystem
104103
workloadID := options.WorkloadID
105104
if workloadID == "" {
106-
digestInput := path + filesystem + time.Now().String()
105+
digestInput := rootfsPath + filesystem + time.Now().String()
107106
workloadID = digest.Canonical.FromString(digestInput).Encoded()
108107
}
109108
workloadConfig := WorkloadConfig{
@@ -176,7 +175,7 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
176175

177176
// We're going to want to add some content to the rootfs, so set up an
178177
// overlay that uses it as a lower layer so that we can write to it.
179-
st, err := system.Stat(path)
178+
st, err := system.Stat(rootfsPath)
180179
if err != nil {
181180
return nil, WorkloadConfig{}, fmt.Errorf("reading information about the container root filesystem: %w", err)
182181
}
@@ -220,7 +219,7 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
220219
}
221220
}()
222221
// Create a mount point using that working state.
223-
rootfsMount, err := overlay.Mount(overlayTempDir, path, rootfsDir, 0, 0, options.GraphOptions)
222+
rootfsMount, err := overlay.Mount(overlayTempDir, rootfsPath, rootfsDir, 0, 0, options.GraphOptions)
224223
if err != nil {
225224
return nil, WorkloadConfig{}, fmt.Errorf("setting up support for overlay of container root filesystem: %w", err)
226225
}
@@ -243,14 +242,46 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
243242
}
244243
}()
245244
// Pretend that we didn't have to do any of the preceding.
246-
path = rootfsDir
245+
rootfsPath = rootfsDir
246+
247+
// Write extra content to the rootfs, creating intermediate directories if necessary.
248+
for location, content := range options.ExtraImageContent {
249+
err := func() error {
250+
if err := idtools.MkdirAllAndChownNew(filepath.Dir(filepath.Join(rootfsPath, location)), 0o755, idtools.IDPair{UID: int(st.UID()), GID: int(st.GID())}); err != nil {
251+
return fmt.Errorf("ensuring %q is present in container root filesystem: %w", filepath.Dir(location), err)
252+
}
253+
output, err := os.OpenFile(filepath.Join(rootfsPath, location), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
254+
if err != nil {
255+
return fmt.Errorf("preparing to write %q to container root filesystem: %w", location, err)
256+
}
257+
defer output.Close()
258+
input, err := os.Open(content)
259+
if err != nil {
260+
return err
261+
}
262+
defer input.Close()
263+
if _, err := io.Copy(output, input); err != nil {
264+
return fmt.Errorf("copying contents of %q to %q in container root filesystem: %w", content, location, err)
265+
}
266+
if err := output.Chown(int(st.UID()), int(st.GID())); err != nil {
267+
return fmt.Errorf("setting owner of %q in the container root filesystem: %w", location, err)
268+
}
269+
if err := output.Chmod(0o644); err != nil {
270+
return fmt.Errorf("setting permissions on %q in the container root filesystem: %w", location, err)
271+
}
272+
return nil
273+
}()
274+
if err != nil {
275+
return nil, WorkloadConfig{}, err
276+
}
277+
}
247278

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

338369
// Format the disk image with the filesystem contents.
339-
if _, stderr, err := MakeFS(path, plain.Name(), filesystem); err != nil {
370+
if _, stderr, err := MakeFS(rootfsPath, plain.Name(), filesystem); err != nil {
340371
if strings.TrimSpace(stderr) != "" {
341372
return nil, WorkloadConfig{}, fmt.Errorf("%s: %w", strings.TrimSpace(stderr), err)
342373
}
@@ -456,8 +487,8 @@ func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadC
456487
tmpHeader.Name = "tmp/"
457488
tmpHeader.Typeflag = tar.TypeDir
458489
tmpHeader.Mode = 0o1777
459-
tmpHeader.Uname, workloadConfigHeader.Gname = "", ""
460-
tmpHeader.Uid, workloadConfigHeader.Gid = 0, 0
490+
tmpHeader.Uname, tmpHeader.Gname = "", ""
491+
tmpHeader.Uid, tmpHeader.Gid = 0, 0
461492
tmpHeader.Size = 0
462493
if err = tw.WriteHeader(tmpHeader); err != nil {
463494
logrus.Errorf("writing header for %q: %v", tmpHeader.Name, err)

tests/mkcw.bats

+15-4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ function mkcw_check_image() {
3535
test -d "$TEST_SCRATCH_DIR"/mount/tmp
3636
# Should have a /bin/sh file from the base image, at least.
3737
test -s "$TEST_SCRATCH_DIR"/mount/bin/sh || test -L "$TEST_SCRATCH_DIR"/mount/bin/sh
38+
if shift ; then
39+
if shift ; then
40+
for pair in "$@" ; do
41+
inner=${pair##*:}
42+
outer=${pair%%:*}
43+
cmp ${outer} "$TEST_SCRATCH_DIR"/mount/${inner}
44+
done
45+
fi
46+
fi
3847

3948
# Clean up.
4049
umount "$TEST_SCRATCH_DIR"/mount
@@ -50,14 +59,16 @@ function mkcw_check_image() {
5059
fi
5160
_prefetch busybox
5261
_prefetch bash
62+
createrandom randomfile1
63+
createrandom randomfile2
5364

5465
echo -n mkcw-convert > "$TEST_SCRATCH_DIR"/key
5566
# image has one layer, check with all-lower-case TEE type name
56-
run_buildah mkcw --ignore-attestation-errors --type snp --passphrase=mkcw-convert busybox busybox-cw
57-
mkcw_check_image busybox-cw
67+
run_buildah mkcw --ignore-attestation-errors --type snp --passphrase=mkcw-convert --add-file randomfile1:/in-a-subdir/rnd1 busybox busybox-cw
68+
mkcw_check_image busybox-cw "" randomfile1:in-a-subdir/rnd1
5869
# image has multiple layers, check with all-upper-case TEE type name
59-
run_buildah mkcw --ignore-attestation-errors --type SNP --passphrase=mkcw-convert bash bash-cw
60-
mkcw_check_image bash-cw
70+
run_buildah mkcw --ignore-attestation-errors --type SNP --passphrase=mkcw-convert --add-file randomfile2:rnd2 bash bash-cw
71+
mkcw_check_image bash-cw "" randomfile2:/rnd2
6172
}
6273

6374
@test "mkcw-commit" {

0 commit comments

Comments
 (0)