diff --git a/cmd/buildah/commit.go b/cmd/buildah/commit.go index 9ea0c3704..c6def14df 100644 --- a/cmd/buildah/commit.go +++ b/cmd/buildah/commit.go @@ -35,6 +35,8 @@ type commitInputOptions struct { signBy string squash bool tlsVerify bool + encryptionKeys []string + encryptLayers []int } func init() { @@ -59,6 +61,8 @@ func init() { flags.StringVar(&opts.authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&opts.blobCache, "blob-cache", "", "assume image blobs in the specified directory will be available for pushing") + flags.StringSliceVar(&opts.encryptionKeys, "encryption-key", nil, "key with the encryption protocol to use needed to encrypt the image (e.g. jwe:/path/to/key.pem)") + flags.IntSliceVar(&opts.encryptLayers, "encrypt-layer", nil, "layers to encrypt, 0-indexed layer indices with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer). If not defined, will encrypt all layers if encryption-key flag is specified") if err := flags.MarkHidden("blob-cache"); err != nil { panic(fmt.Sprintf("error marking blob-cache as hidden: %v", err)) @@ -168,6 +172,11 @@ func commitCmd(c *cobra.Command, args []string, iopts commitInputOptions) error // Add builder identity information. builder.SetLabel(buildah.BuilderIdentityAnnotation, buildah.Version) + encConfig, encLayers, err := getEncryptConfig(iopts.encryptionKeys, iopts.encryptLayers) + if err != nil { + return errors.Wrapf(err, "unable to obtain encryption config") + } + options := buildah.CommitOptions{ PreferredManifestType: format, Compression: compress, @@ -179,6 +188,8 @@ func commitCmd(c *cobra.Command, args []string, iopts commitInputOptions) error BlobDirectory: iopts.blobCache, OmitTimestamp: iopts.omitTimestamp, SignBy: iopts.signBy, + OciEncryptConfig: encConfig, + OciEncryptLayers: encLayers, } if !iopts.quiet { options.ReportWriter = os.Stderr diff --git a/commit.go b/commit.go index b919ba7fa..5e08b8345 100644 --- a/commit.go +++ b/commit.go @@ -89,6 +89,17 @@ type CommitOptions struct { // RetryDelay is how long to wait before retrying a commit attempt to a // registry. RetryDelay time.Duration + // OciEncryptConfig when non-nil indicates that an image should be encrypted. + // The encryption options is derived from the construction of EncryptConfig object. + // Note: During initial encryption process of a layer, the resultant digest is not known + // during creation, so newDigestingReader has to be set with validateDigest = false + OciEncryptConfig *encconfig.EncryptConfig + // OciEncryptLayers represents the list of layers to encrypt. + // If nil, don't encrypt any layers. + // If non-nil and len==0, denotes encrypt all layers. + // integers in the slice represent 0-indexed layer indices, with support for negative + // indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer. + OciEncryptLayers *[]int } // PushOptions can be used to alter how an image is copied somewhere. @@ -284,7 +295,9 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options // Check if the base image is already in the destination and it's some kind of local // storage. If so, we can skip recompressing any layers that come from the base image. exportBaseLayers := true - if transport, destIsStorage := dest.Transport().(is.StoreTransport); destIsStorage && b.FromImageID != "" { + if transport, destIsStorage := dest.Transport().(is.StoreTransport); destIsStorage && options.OciEncryptConfig != nil { + return imgID, nil, "", errors.New("unable to use local storage with image encryption") + } else if destIsStorage && b.FromImageID != "" { if baseref, err := transport.ParseReference(b.FromImageID); baseref != nil && err == nil { if img, err := transport.GetImage(baseref); img != nil && err == nil { logrus.Debugf("base image %q is already present in local storage, no need to copy its layers", b.FromImageID) @@ -333,7 +346,7 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options } var manifestBytes []byte - if manifestBytes, err = retryCopyImage(ctx, policyContext, maybeCachedDest, maybeCachedSrc, dest, "push", getCopyOptions(b.store, options.ReportWriter, nil, systemContext, "", false, options.SignBy, nil, nil, nil), options.MaxRetries, options.RetryDelay); err != nil { + if manifestBytes, err = retryCopyImage(ctx, policyContext, maybeCachedDest, maybeCachedSrc, dest, "push", getCopyOptions(b.store, options.ReportWriter, nil, systemContext, "", false, options.SignBy, options.OciEncryptLayers, options.OciEncryptConfig, nil), options.MaxRetries, options.RetryDelay); err != nil { return imgID, nil, "", errors.Wrapf(err, "error copying layers and metadata for container %q", b.ContainerID) } // If we've got more names to attach, and we know how to do that for diff --git a/tests/commit.bats b/tests/commit.bats index 6bf6010c5..d03da42b7 100644 --- a/tests/commit.bats +++ b/tests/commit.bats @@ -174,3 +174,29 @@ load helpers run_buildah commit --signature-policy ${TESTSDIR}/policy.json --tls-verify=false --creds testuser:testpassword busyboxc docker://localhost:5000/commit/busybox run_buildah from --signature-policy ${TESTSDIR}/policy.json --name fromdocker --tls-verify=false --creds testuser:testpassword docker://localhost:5000/commit/busybox } + +@test "commit encrypted local oci image" { + _prefetch busybox + mkdir ${TESTDIR}/tmp + openssl genrsa -out ${TESTDIR}/tmp/mykey.pem 1024 + openssl rsa -in ${TESTDIR}/tmp/mykey.pem -pubout > ${TESTDIR}/tmp/mykey.pub + run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json busybox + cid=$output + run_buildah commit --iidfile /dev/null --signature-policy ${TESTSDIR}/policy.json --encryption-key jwe:${TESTDIR}/tmp/mykey.pub -q $cid oci:${TESTDIR}/tmp/busybox_enc + imgtype -show-manifest oci:${TESTDIR}/tmp/busybox_enc | grep "+encrypted" + rm -rf ${TESTDIR}/tmp +} + + +@test "commit oci encrypt to registry" { + _prefetch busybox + mkdir ${TESTDIR}/tmp + openssl genrsa -out ${TESTDIR}/tmp/mykey.pem 1024 + openssl rsa -in ${TESTDIR}/tmp/mykey.pem -pubout > ${TESTDIR}/tmp/mykey.pub + run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json busybox + cid=$output + run_buildah commit --iidfile /dev/null --tls-verify=false --creds testuser:testpassword --signature-policy ${TESTSDIR}/policy.json --encryption-key jwe:${TESTDIR}/tmp/mykey.pub -q $cid docker://localhost:5000/buildah/busybox_encrypted:latest + # this test, just checks the ability to commit an image to a registry + # there is no good way to test the details of the image unless with ./buildah pull, test will be in pull.bats + rm -rf ${TESTDIR}/tmp +} diff --git a/tests/pull.bats b/tests/pull.bats index 51f179f7f..dc0ce69ce 100644 --- a/tests/pull.bats +++ b/tests/pull.bats @@ -204,3 +204,23 @@ load helpers rm -rf ${TESTDIR}/tmp } + +@test "pull encrypted registry image from commit" { + _prefetch busybox + mkdir ${TESTDIR}/tmp + openssl genrsa -out ${TESTDIR}/tmp/mykey.pem 1024 + openssl rsa -in ${TESTDIR}/tmp/mykey.pem -pubout > ${TESTDIR}/tmp/mykey.pub + run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json busybox + cid=$output + run_buildah commit --iidfile /dev/null --tls-verify=false --creds testuser:testpassword --signature-policy ${TESTSDIR}/policy.json --encryption-key jwe:${TESTDIR}/tmp/mykey.pub -q $cid docker://localhost:5000/buildah/busybox_encrypted:latest + + # Try to pull encrypted image without key should fail + run_buildah 1 pull --signature-policy ${TESTSDIR}/policy.json --tls-verify=false --creds testuser:testpassword docker://localhost:5000/buildah/busybox_encrypted:latest + # Try to pull encrypted image with wrong key should fail + run_buildah 1 pull --signature-policy ${TESTSDIR}/policy.json --tls-verify=false --creds testuser:testpassword --decryption-key ${TESTDIR}/tmp/mykey2.pem docker://localhost:5000/buildah/busybox_encrypted:latest + # Providing the right key should succeed + run_buildah pull --signature-policy ${TESTSDIR}/policy.json --tls-verify=false --creds testuser:testpassword --decryption-key ${TESTDIR}/tmp/mykey.pem docker://localhost:5000/buildah/busybox_encrypted:latest + run_buildah rmi localhost:5000/buildah/busybox_encrypted:latest + + rm -rf ${TESTDIR}/tmp +}