From dde436e65bd9319209d0a113f27078f2a07cd2d9 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 16 Jul 2019 14:36:42 -0700 Subject: [PATCH] Crypto library movement and changes to content helper interfaces Signed-off-by: Derek McGowan --- cmd/ctr/commands/images/crypt_utils.go | 13 +- cmd/ctr/commands/images/decrypt.go | 6 +- cmd/ctr/commands/images/encrypt.go | 2 +- cmd/ctr/commands/images/layerinfo.go | 2 +- content/helpers.go | 62 +- image_enc_test.go | 9 +- images/enc_utils.go | 451 -------------- images/encryption/encryption.go | 583 ++++++++++++------ images/image.go | 21 +- .../encryption/blockcipher/blockcipher.go | 0 .../encryption/blockcipher/blockcipher_siv.go | 0 .../blockcipher/blockcipher_siv_test.go | 0 .../blockcipher/blockcipher_test.go | 0 {images => pkg}/encryption/config/config.go | 0 pkg/encryption/encryption.go | 244 ++++++++ {images => pkg}/encryption/encryption_test.go | 2 +- {images => pkg}/encryption/gpg.go | 0 {images => pkg}/encryption/gpgvault.go | 0 .../encryption/keywrap/jwe/keywrapper_jwe.go | 6 +- .../keywrap/jwe/keywrapper_jwe_test.go | 4 +- {images => pkg}/encryption/keywrap/keywrap.go | 2 +- .../encryption/keywrap/pgp/keywrapper_gpg.go | 4 +- .../keywrap/pgp/keywrapper_gpg_test.go | 2 +- .../keywrap/pgp/testingkeys_test.go | 0 .../keywrap/pkcs7/keywrapper_pkcs7.go | 6 +- .../keywrap/pkcs7/keywrapper_pkcs7_test.go | 4 +- {images => pkg}/encryption/reader.go | 0 {images => pkg}/encryption/utils/testing.go | 0 {images => pkg}/encryption/utils/utils.go | 0 29 files changed, 713 insertions(+), 710 deletions(-) delete mode 100644 images/enc_utils.go rename {images => pkg}/encryption/blockcipher/blockcipher.go (100%) rename {images => pkg}/encryption/blockcipher/blockcipher_siv.go (100%) rename {images => pkg}/encryption/blockcipher/blockcipher_siv_test.go (100%) rename {images => pkg}/encryption/blockcipher/blockcipher_test.go (100%) rename {images => pkg}/encryption/config/config.go (100%) create mode 100644 pkg/encryption/encryption.go rename {images => pkg}/encryption/encryption_test.go (98%) rename {images => pkg}/encryption/gpg.go (100%) rename {images => pkg}/encryption/gpgvault.go (100%) rename {images => pkg}/encryption/keywrap/jwe/keywrapper_jwe.go (95%) rename {images => pkg}/encryption/keywrap/jwe/keywrapper_jwe_test.go (98%) rename {images => pkg}/encryption/keywrap/keywrap.go (96%) rename {images => pkg}/encryption/keywrap/pgp/keywrapper_gpg.go (98%) rename {images => pkg}/encryption/keywrap/pgp/keywrapper_gpg_test.go (98%) rename {images => pkg}/encryption/keywrap/pgp/testingkeys_test.go (100%) rename {images => pkg}/encryption/keywrap/pkcs7/keywrapper_pkcs7.go (95%) rename {images => pkg}/encryption/keywrap/pkcs7/keywrapper_pkcs7_test.go (98%) rename {images => pkg}/encryption/reader.go (100%) rename {images => pkg}/encryption/utils/testing.go (100%) rename {images => pkg}/encryption/utils/utils.go (100%) diff --git a/cmd/ctr/commands/images/crypt_utils.go b/cmd/ctr/commands/images/crypt_utils.go index 08917382e..09b9f3b94 100644 --- a/cmd/ctr/commands/images/crypt_utils.go +++ b/cmd/ctr/commands/images/crypt_utils.go @@ -28,10 +28,11 @@ import ( "github.com/containerd/containerd" "github.com/containerd/containerd/images" - "github.com/containerd/containerd/images/encryption" - encconfig "github.com/containerd/containerd/images/encryption/config" - encutils "github.com/containerd/containerd/images/encryption/utils" + imgenc "github.com/containerd/containerd/images/encryption" "github.com/containerd/containerd/leases" + "github.com/containerd/containerd/pkg/encryption" + encconfig "github.com/containerd/containerd/pkg/encryption/config" + encutils "github.com/containerd/containerd/pkg/encryption/utils" "github.com/containerd/containerd/platforms" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -209,7 +210,7 @@ func getGPGPrivateKeys(context *cli.Context, gpgSecretKeyRingFiles [][]byte, des return encryption.GPGGetPrivateKey(descs, gpgClient, gpgVault, mustFindKey, dcparameters) } -func createLayerFilter(client *containerd.Client, ctx gocontext.Context, desc ocispec.Descriptor, layers []int32, platformList []ocispec.Platform) (images.LayerFilter, error) { +func createLayerFilter(client *containerd.Client, ctx gocontext.Context, desc ocispec.Descriptor, layers []int32, platformList []ocispec.Platform) (imgenc.LayerFilter, error) { alldescs, err := images.GetImageLayerDescriptors(ctx, client.ContentStore(), desc) if err != nil { return nil, err @@ -261,9 +262,9 @@ func cryptImage(client *containerd.Client, ctx gocontext.Context, name, newName defer ls.Delete(ctx, l, leases.SynchronousDelete) if encrypt { - newSpec, modified, err = images.EncryptImage(ctx, client.ContentStore(), ls, l, image.Target, cc, lf) + newSpec, modified, err = imgenc.EncryptImage(ctx, client.ContentStore(), ls, l, image.Target, cc, lf) } else { - newSpec, modified, err = images.DecryptImage(ctx, client.ContentStore(), ls, l, image.Target, cc, lf) + newSpec, modified, err = imgenc.DecryptImage(ctx, client.ContentStore(), ls, l, image.Target, cc, lf) } if err != nil { return image, err diff --git a/cmd/ctr/commands/images/decrypt.go b/cmd/ctr/commands/images/decrypt.go index fa30036a9..c3f3caca5 100644 --- a/cmd/ctr/commands/images/decrypt.go +++ b/cmd/ctr/commands/images/decrypt.go @@ -20,8 +20,8 @@ import ( "fmt" "github.com/containerd/containerd/cmd/ctr/commands" - "github.com/containerd/containerd/images" - encconfig "github.com/containerd/containerd/images/encryption/config" + imgenc "github.com/containerd/containerd/images/encryption" + encconfig "github.com/containerd/containerd/pkg/encryption/config" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -77,7 +77,7 @@ var decryptCommand = cli.Command{ return err } - isEncrypted := images.HasEncryptedLayer(ctx, descs) + isEncrypted := imgenc.HasEncryptedLayer(ctx, descs) if !isEncrypted { fmt.Printf("Nothing to decrypted.\n") return nil diff --git a/cmd/ctr/commands/images/encrypt.go b/cmd/ctr/commands/images/encrypt.go index f6269f242..09a5356e6 100644 --- a/cmd/ctr/commands/images/encrypt.go +++ b/cmd/ctr/commands/images/encrypt.go @@ -20,7 +20,7 @@ import ( "fmt" "github.com/containerd/containerd/cmd/ctr/commands" - encconfig "github.com/containerd/containerd/images/encryption/config" + encconfig "github.com/containerd/containerd/pkg/encryption/config" "github.com/pkg/errors" "github.com/urfave/cli" ) diff --git a/cmd/ctr/commands/images/layerinfo.go b/cmd/ctr/commands/images/layerinfo.go index 56571307e..4c34639b5 100644 --- a/cmd/ctr/commands/images/layerinfo.go +++ b/cmd/ctr/commands/images/layerinfo.go @@ -24,7 +24,7 @@ import ( "text/tabwriter" "github.com/containerd/containerd/cmd/ctr/commands" - "github.com/containerd/containerd/images/encryption" + "github.com/containerd/containerd/pkg/encryption" "github.com/containerd/containerd/platforms" "github.com/pkg/errors" diff --git a/content/helpers.go b/content/helpers.go index ceb9a6761..c1c204618 100644 --- a/content/helpers.go +++ b/content/helpers.go @@ -87,46 +87,6 @@ func WriteBlob(ctx context.Context, cs Ingester, ref string, r io.Reader, desc o return Copy(ctx, cw, r, desc.Size, desc.Digest, opts...) } -// WriteBlobBlind writes data without expected digest into the content store. If -// expected already exists, the method returns immediately and the reader will -// not be consumed. -// -// This is useful when the digest and size are NOT known beforehand. -// -// Copy is buffered, so no need to wrap reader in buffered io. -func WriteBlobBlind(ctx context.Context, cs Ingester, ref string, r io.Reader, opts ...Opt) (digest.Digest, int64, error) { - cw, err := OpenWriter(ctx, cs, WithRef(ref)) - if err != nil { - return "", 0, errors.Wrap(err, "failed to open writer") - } - defer cw.Close() - - ws, err := cw.Status() - if err != nil { - return "", 0, errors.Wrap(err, "failed to get status") - } - - if ws.Offset > 0 { - // not needed - return "", 0, errors.New("ws.Offset > 0 is not supported") - } - - size, err := copyWithBuffer(cw, r) - if err != nil { - return "", 0, errors.Wrap(err, "failed to copy") - } - - digest := cw.Digest() - - if err := cw.Commit(ctx, size, digest); err != nil { - if !errdefs.IsAlreadyExists(err) { - return "", 0, errors.Wrapf(err, "failed commit block") - } - } - - return digest, size, err -} - // OpenWriter opens a new writer for the given reference, retrying if the writer // is locked until the reference is available or returns an error. func OpenWriter(ctx context.Context, cs Ingester, opts ...WriterOpt) (Writer, error) { @@ -209,6 +169,28 @@ func CopyReaderAt(cw Writer, ra ReaderAt, n int64) error { return err } +// CopyReader copies to a writer from a given reader, returning +// the number of bytes copied. +// Note: if the writer has a non-zero offset, the total number +// of bytes read may be greater than those copied if the reader +// is not an io.Seeker. +// This copy does not commit the writer. +func CopyReader(cw Writer, r io.Reader) (int64, error) { + ws, err := cw.Status() + if err != nil { + return 0, errors.Wrap(err, "failed to get status") + } + + if ws.Offset > 0 { + r, err = seekReader(r, ws.Offset, 0) + if err != nil { + return 0, errors.Wrapf(err, "unable to resume write to %v", ws.Ref) + } + } + + return copyWithBuffer(cw, r) +} + // seekReader attempts to seek the reader to the given offset, either by // resolving `io.Seeker`, by detecting `io.ReaderAt`, or discarding // up to the given offset. diff --git a/image_enc_test.go b/image_enc_test.go index caa4d8b65..bce5dad4a 100644 --- a/image_enc_test.go +++ b/image_enc_test.go @@ -25,9 +25,10 @@ import ( "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" - encconfig "github.com/containerd/containerd/images/encryption/config" - "github.com/containerd/containerd/images/encryption/utils" + imgenc "github.com/containerd/containerd/images/encryption" "github.com/containerd/containerd/leases" + encconfig "github.com/containerd/containerd/pkg/encryption/config" + "github.com/containerd/containerd/pkg/encryption/utils" "github.com/containerd/containerd/platforms" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -142,7 +143,7 @@ func TestImageEncryption(t *testing.T) { defer ls.Delete(ctx, l, leases.SynchronousDelete) // Perform encryption of image - encSpec, modified, err := images.EncryptImage(ctx, client.ContentStore(), ls, l, image.Target, cc, lf) + encSpec, modified, err := imgenc.EncryptImage(ctx, client.ContentStore(), ls, l, image.Target, cc, lf) if err != nil { t.Fatal(err) } @@ -180,7 +181,7 @@ func TestImageEncryption(t *testing.T) { } defer ls.Delete(ctx, l, leases.SynchronousDelete) - decSpec, modified, err := images.DecryptImage(ctx, client.ContentStore(), ls, l, encSpec, cc, lf) + decSpec, modified, err := imgenc.DecryptImage(ctx, client.ContentStore(), ls, l, encSpec, cc, lf) if err != nil { t.Fatal(err) } diff --git a/images/enc_utils.go b/images/enc_utils.go deleted file mode 100644 index 46fc1212d..000000000 --- a/images/enc_utils.go +++ /dev/null @@ -1,451 +0,0 @@ -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package images - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "math/rand" - - "github.com/containerd/containerd/images/encryption" - encconfig "github.com/containerd/containerd/images/encryption/config" - - "github.com/containerd/containerd/content" - "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/leases" - "github.com/containerd/containerd/platforms" - digest "github.com/opencontainers/go-digest" - specs "github.com/opencontainers/image-spec/specs-go" - "github.com/pkg/errors" - - ocispec "github.com/opencontainers/image-spec/specs-go/v1" -) - -// IsEncryptedDiff returns true if mediaType is a known encrypted media type. -func IsEncryptedDiff(ctx context.Context, mediaType string) bool { - switch mediaType { - case MediaTypeDockerSchema2LayerGzipEnc, MediaTypeDockerSchema2LayerEnc: - return true - } - return false -} - -// HasEncryptedLayer returns true if any LayerInfo indicates that the layer is encrypted -func HasEncryptedLayer(ctx context.Context, layerInfos []ocispec.Descriptor) bool { - for i := 0; i < len(layerInfos); i++ { - if IsEncryptedDiff(ctx, layerInfos[i].MediaType) { - return true - } - } - return false -} - -// encryptLayer encrypts the layer using the CryptoConfig and creates a new OCI Descriptor. -// A call to this function may also only manipulate the wrapped keys list. -// The caller is expected to store the returned encrypted data and OCI Descriptor -func encryptLayer(cc *encconfig.CryptoConfig, dataReader content.ReaderAt, desc ocispec.Descriptor) (ocispec.Descriptor, io.Reader, error) { - var ( - size int64 - d digest.Digest - err error - ) - - encLayerReader, annotations, err := encryption.EncryptLayer(cc.EncryptConfig, encryption.ReaderFromReaderAt(dataReader), desc) - if err != nil { - return ocispec.Descriptor{}, nil, err - } - - // were data touched ? - if encLayerReader != nil { - size = 0 - d = "" - } else { - size = desc.Size - d = desc.Digest - } - - newDesc := ocispec.Descriptor{ - Digest: d, - Size: size, - Platform: desc.Platform, - Annotations: annotations, - } - - switch desc.MediaType { - case MediaTypeDockerSchema2LayerGzip: - newDesc.MediaType = MediaTypeDockerSchema2LayerGzipEnc - case MediaTypeDockerSchema2Layer: - newDesc.MediaType = MediaTypeDockerSchema2LayerEnc - case MediaTypeDockerSchema2LayerGzipEnc: - newDesc.MediaType = MediaTypeDockerSchema2LayerGzipEnc - case MediaTypeDockerSchema2LayerEnc: - newDesc.MediaType = MediaTypeDockerSchema2LayerEnc - - // TODO: Mediatypes to be added in ocispec - case ocispec.MediaTypeImageLayerGzip: - newDesc.MediaType = MediaTypeDockerSchema2LayerGzipEnc - case ocispec.MediaTypeImageLayer: - newDesc.MediaType = MediaTypeDockerSchema2LayerEnc - - default: - return ocispec.Descriptor{}, nil, errors.Errorf("Encryption: unsupporter layer MediaType: %s\n", desc.MediaType) - } - - return newDesc, encLayerReader, nil -} - -// decryptLayer decrypts the layer using the CryptoConfig and creates a new OCI Descriptor. -// The caller is expected to store the returned plain data and OCI Descriptor -func decryptLayer(cc *encconfig.CryptoConfig, dataReader content.ReaderAt, desc ocispec.Descriptor, unwrapOnly bool) (ocispec.Descriptor, io.Reader, error) { - resultReader, d, err := encryption.DecryptLayer(cc.DecryptConfig, encryption.ReaderFromReaderAt(dataReader), desc, unwrapOnly) - if err != nil || unwrapOnly { - return ocispec.Descriptor{}, nil, err - } - - newDesc := ocispec.Descriptor{ - Digest: d, - Size: 0, - Platform: desc.Platform, - } - - switch desc.MediaType { - case MediaTypeDockerSchema2LayerGzipEnc: - newDesc.MediaType = MediaTypeDockerSchema2LayerGzip - case MediaTypeDockerSchema2LayerEnc: - newDesc.MediaType = MediaTypeDockerSchema2Layer - default: - return ocispec.Descriptor{}, nil, errors.Errorf("Decryption: unsupporter layer MediaType: %s\n", desc.MediaType) - } - return newDesc, resultReader, nil -} - -// cryptLayer handles the changes due to encryption or decryption of a layer -func cryptLayer(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, cryptoOp cryptoOp) (ocispec.Descriptor, error) { - var ( - resultReader io.Reader - newDesc ocispec.Descriptor - ) - - dataReader, err := cs.ReaderAt(ctx, desc) - if err != nil { - return ocispec.Descriptor{}, err - } - defer dataReader.Close() - - if cryptoOp == cryptoOpEncrypt { - newDesc, resultReader, err = encryptLayer(cc, dataReader, desc) - } else { - newDesc, resultReader, err = decryptLayer(cc, dataReader, desc, cryptoOp == cryptoOpUnwrapOnly) - } - if err != nil || cryptoOp == cryptoOpUnwrapOnly { - return ocispec.Descriptor{}, err - } - // some operations, such as changing recipients, may not touch the layer at all - if resultReader != nil { - if ls == nil { - return ocispec.Descriptor{}, errors.New("Unexpected write to object without lease") - } - - var rsrc leases.Resource - var ref string - - // If we have the digest, write blob with checks - haveDigest := newDesc.Digest.String() != "" - if haveDigest { - ref = fmt.Sprintf("layer-%s", newDesc.Digest.String()) - rsrc = leases.Resource{ - ID: newDesc.Digest.String(), - Type: "content", - } - } else { - ref = fmt.Sprintf("blob-%d-%d", rand.Int(), rand.Int()) - rsrc = leases.Resource{ - ID: ref, - Type: "ingests", - } - - } - - // Add resource to lease and write blob - if err := ls.AddResource(ctx, l, rsrc); err != nil { - return ocispec.Descriptor{}, errors.Wrap(err, "Unable to add resource to lease") - } - - if haveDigest { - if err := content.WriteBlob(ctx, cs, ref, resultReader, newDesc); err != nil { - return ocispec.Descriptor{}, errors.Wrap(err, "failed to write config") - } - } else { - newDesc.Digest, newDesc.Size, err = content.WriteBlobBlind(ctx, cs, ref, resultReader) - if err != nil { - return ocispec.Descriptor{}, err - } - } - } - return newDesc, err -} - -// isDecriptorALayer determines whether the given Descriptor describes an image layer -func isDescriptorALayer(desc ocispec.Descriptor) bool { - switch desc.MediaType { - case MediaTypeDockerSchema2LayerGzip, MediaTypeDockerSchema2Layer, - ocispec.MediaTypeImageLayerGzip, ocispec.MediaTypeImageLayer, - MediaTypeDockerSchema2LayerGzipEnc, MediaTypeDockerSchema2LayerEnc: - return true - } - return false -} - -// Encrypt or decrypt all the Children of a given descriptor -func cryptChildren(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp, thisPlatform *ocispec.Platform) (ocispec.Descriptor, bool, error) { - children, err := Children(ctx, cs, desc) - if err != nil { - if errdefs.IsNotFound(err) { - return desc, false, nil - } - return ocispec.Descriptor{}, false, err - } - - var newLayers []ocispec.Descriptor - var config ocispec.Descriptor - modified := false - - for _, child := range children { - // we only encrypt child layers and have to update their parents if encryption happened - switch child.MediaType { - case MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig: - config = child - case MediaTypeDockerSchema2LayerGzip, MediaTypeDockerSchema2Layer, - ocispec.MediaTypeImageLayerGzip, ocispec.MediaTypeImageLayer: - if cryptoOp == cryptoOpEncrypt && lf(child) { - nl, err := cryptLayer(ctx, cs, ls, l, child, cc, cryptoOp) - if err != nil { - return ocispec.Descriptor{}, false, err - } - modified = true - newLayers = append(newLayers, nl) - } else { - newLayers = append(newLayers, child) - } - case MediaTypeDockerSchema2LayerGzipEnc, MediaTypeDockerSchema2LayerEnc: - // this one can be decrypted but also its recipients list changed - if lf(child) { - nl, err := cryptLayer(ctx, cs, ls, l, child, cc, cryptoOp) - if err != nil || cryptoOp == cryptoOpUnwrapOnly { - return ocispec.Descriptor{}, false, err - } - modified = true - newLayers = append(newLayers, nl) - } else { - newLayers = append(newLayers, child) - } - case MediaTypeDockerSchema2LayerForeign, MediaTypeDockerSchema2LayerForeignGzip: - // never encrypt/decrypt - newLayers = append(newLayers, child) - default: - return ocispec.Descriptor{}, false, errors.Errorf("bad/unhandled MediaType %s in encryptChildren\n", child.MediaType) - } - } - - if modified && len(newLayers) > 0 { - newManifest := ocispec.Manifest{ - Versioned: specs.Versioned{ - SchemaVersion: 2, - }, - Config: config, - Layers: newLayers, - } - - mb, err := json.MarshalIndent(newManifest, "", " ") - if err != nil { - return ocispec.Descriptor{}, false, errors.Wrap(err, "failed to marshal image") - } - - newDesc := ocispec.Descriptor{ - MediaType: ocispec.MediaTypeImageManifest, - Size: int64(len(mb)), - Digest: digest.Canonical.FromBytes(mb), - Platform: desc.Platform, - } - - labels := map[string]string{} - labels["containerd.io/gc.ref.content.0"] = newManifest.Config.Digest.String() - for i, ch := range newManifest.Layers { - labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = ch.Digest.String() - } - - ref := fmt.Sprintf("manifest-%s", newDesc.Digest.String()) - - if ls == nil { - return ocispec.Descriptor{}, false, errors.New("Unexpected write to object without lease") - } - - rsrc := leases.Resource{ - ID: desc.Digest.String(), - Type: "content", - } - - if err := ls.AddResource(ctx, l, rsrc); err != nil { - return ocispec.Descriptor{}, false, errors.Wrap(err, "Unable to add resource to lease") - } - - if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(mb), newDesc, content.WithLabels(labels)); err != nil { - return ocispec.Descriptor{}, false, errors.Wrap(err, "failed to write config") - } - return newDesc, true, nil - } - - return desc, modified, nil -} - -// cryptManifest encrypts or decrypts the children of a top level manifest -func cryptManifest(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp) (ocispec.Descriptor, bool, error) { - p, err := content.ReadBlob(ctx, cs, desc) - if err != nil { - return ocispec.Descriptor{}, false, err - } - var manifest ocispec.Manifest - if err := json.Unmarshal(p, &manifest); err != nil { - return ocispec.Descriptor{}, false, err - } - platform := platforms.DefaultSpec() - newDesc, modified, err := cryptChildren(ctx, cs, ls, l, desc, cc, lf, cryptoOp, &platform) - if err != nil || cryptoOp == cryptoOpUnwrapOnly { - return ocispec.Descriptor{}, false, err - } - return newDesc, modified, nil -} - -// cryptManifestList encrypts or decrypts the children of a top level manifest list -func cryptManifestList(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp) (ocispec.Descriptor, bool, error) { - // read the index; if any layer is encrypted and any manifests change we will need to rewrite it - b, err := content.ReadBlob(ctx, cs, desc) - if err != nil { - return ocispec.Descriptor{}, false, err - } - - var index ocispec.Index - if err := json.Unmarshal(b, &index); err != nil { - return ocispec.Descriptor{}, false, err - } - - var newManifests []ocispec.Descriptor - modified := false - for _, manifest := range index.Manifests { - newManifest, m, err := cryptChildren(ctx, cs, ls, l, manifest, cc, lf, cryptoOp, manifest.Platform) - if err != nil || cryptoOp == cryptoOpUnwrapOnly { - return ocispec.Descriptor{}, false, err - } - if m { - modified = true - } - newManifests = append(newManifests, newManifest) - } - - if modified { - // we need to update the index - newIndex := ocispec.Index{ - Versioned: index.Versioned, - Manifests: newManifests, - } - - mb, err := json.MarshalIndent(newIndex, "", " ") - if err != nil { - return ocispec.Descriptor{}, false, errors.Wrap(err, "failed to marshal index") - } - - newDesc := ocispec.Descriptor{ - MediaType: ocispec.MediaTypeImageIndex, - Size: int64(len(mb)), - Digest: digest.Canonical.FromBytes(mb), - } - - labels := map[string]string{} - for i, m := range newIndex.Manifests { - labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = m.Digest.String() - } - - ref := fmt.Sprintf("index-%s", newDesc.Digest.String()) - - if ls == nil { - return ocispec.Descriptor{}, false, errors.New("Unexpected write to object without lease") - } - - rsrc := leases.Resource{ - ID: desc.Digest.String(), - Type: "content", - } - - if err := ls.AddResource(ctx, l, rsrc); err != nil { - return ocispec.Descriptor{}, false, errors.Wrap(err, "Unable to add resource to lease") - } - - if err = content.WriteBlob(ctx, cs, ref, bytes.NewReader(mb), newDesc, content.WithLabels(labels)); err != nil { - return ocispec.Descriptor{}, false, errors.Wrap(err, "failed to write index") - } - return newDesc, true, nil - } - - return desc, false, nil -} - -// cryptImage is the dispatcher to encrypt/decrypt an image; it accepts either an OCI descriptor -// representing a manifest list or a single manifest -func cryptImage(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp) (ocispec.Descriptor, bool, error) { - if cc == nil { - return ocispec.Descriptor{}, false, errors.Wrapf(errdefs.ErrInvalidArgument, "CryptoConfig must not be nil") - } - switch desc.MediaType { - case ocispec.MediaTypeImageIndex, MediaTypeDockerSchema2ManifestList: - return cryptManifestList(ctx, cs, ls, l, desc, cc, lf, cryptoOp) - case ocispec.MediaTypeImageManifest, MediaTypeDockerSchema2Manifest: - return cryptManifest(ctx, cs, ls, l, desc, cc, lf, cryptoOp) - default: - return ocispec.Descriptor{}, false, errors.Errorf("CryptImage: Unhandled media type: %s", desc.MediaType) - } -} - -// EncryptImage encrypts an image; it accepts either an OCI descriptor representing a manifest list or a single manifest -func EncryptImage(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter) (ocispec.Descriptor, bool, error) { - return cryptImage(ctx, cs, ls, l, desc, cc, lf, cryptoOpEncrypt) -} - -// DecryptImage decrypts an image; it accepts either an OCI descriptor representing a manifest list or a single manifest -func DecryptImage(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter) (ocispec.Descriptor, bool, error) { - return cryptImage(ctx, cs, ls, l, desc, cc, lf, cryptoOpDecrypt) -} - -// CheckAuthorization checks whether a user has the right keys to be allowed to access an image (every layer) -// It takes decrypting of the layers only as far as decrypting the asymmetrically encrypted data -// The decryption is only done for the current platform -func CheckAuthorization(ctx context.Context, cs content.Store, desc ocispec.Descriptor, dc *encconfig.DecryptConfig) error { - cc := encconfig.CryptoConfig{ - DecryptConfig: dc, - } - lf := func(desc ocispec.Descriptor) bool { - return true - } - // We shouldn't need to create any objects in CheckAuthorization, so no lease required. - _, _, err := cryptImage(ctx, cs, nil, leases.Lease{}, desc, &cc, lf, cryptoOpUnwrapOnly) - if err != nil { - return errors.Wrapf(err, "you are not authorized to use this image") - } - return nil -} diff --git a/images/encryption/encryption.go b/images/encryption/encryption.go index 438b7001c..9a9e8c4b7 100644 --- a/images/encryption/encryption.go +++ b/images/encryption/encryption.go @@ -17,228 +17,461 @@ package encryption import ( - "encoding/base64" + "bytes" + "context" "encoding/json" + "fmt" "io" - "strings" + "math/rand" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/pkg/encryption" + encconfig "github.com/containerd/containerd/pkg/encryption/config" + + "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/images/encryption/blockcipher" - "github.com/containerd/containerd/images/encryption/config" - "github.com/containerd/containerd/images/encryption/keywrap" - "github.com/containerd/containerd/images/encryption/keywrap/jwe" - "github.com/containerd/containerd/images/encryption/keywrap/pgp" - "github.com/containerd/containerd/images/encryption/keywrap/pkcs7" - "github.com/opencontainers/go-digest" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/containerd/containerd/leases" + "github.com/containerd/containerd/platforms" + digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go" "github.com/pkg/errors" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) -func init() { - keyWrappers = make(map[string]keywrap.KeyWrapper) - keyWrapperAnnotations = make(map[string]string) - RegisterKeyWrapper("pgp", pgp.NewKeyWrapper()) - RegisterKeyWrapper("jwe", jwe.NewKeyWrapper()) - RegisterKeyWrapper("pkcs7", pkcs7.NewKeyWrapper()) +type cryptoOp int + +const ( + cryptoOpEncrypt cryptoOp = iota + cryptoOpDecrypt = iota + cryptoOpUnwrapOnly = iota +) + +// LayerFilter allows to select Layers by certain criteria +type LayerFilter func(desc ocispec.Descriptor) bool + +// IsEncryptedDiff returns true if mediaType is a known encrypted media type. +func IsEncryptedDiff(ctx context.Context, mediaType string) bool { + switch mediaType { + case images.MediaTypeDockerSchema2LayerGzipEnc, images.MediaTypeDockerSchema2LayerEnc: + return true + } + return false } -var keyWrappers map[string]keywrap.KeyWrapper -var keyWrapperAnnotations map[string]string - -// RegisterKeyWrapper allows to register key wrappers by their encryption scheme -func RegisterKeyWrapper(scheme string, iface keywrap.KeyWrapper) { - keyWrappers[scheme] = iface - keyWrapperAnnotations[iface.GetAnnotationID()] = scheme -} - -// GetKeyWrapper looks up the encryptor interface given an encryption scheme (gpg, jwe) -func GetKeyWrapper(scheme string) keywrap.KeyWrapper { - return keyWrappers[scheme] -} - -// GetWrappedKeysMap returns a map of wrappedKeys as values in a -// map with the encryption scheme(s) as the key(s) -func GetWrappedKeysMap(desc ocispec.Descriptor) map[string]string { - wrappedKeysMap := make(map[string]string) - - for annotationsID, scheme := range keyWrapperAnnotations { - if annotation, ok := desc.Annotations[annotationsID]; ok { - wrappedKeysMap[scheme] = annotation +// HasEncryptedLayer returns true if any LayerInfo indicates that the layer is encrypted +func HasEncryptedLayer(ctx context.Context, layerInfos []ocispec.Descriptor) bool { + for i := 0; i < len(layerInfos); i++ { + if IsEncryptedDiff(ctx, layerInfos[i].MediaType) { + return true } } - return wrappedKeysMap + return false } -// EncryptLayer encrypts the layer by running one encryptor after the other -func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, desc ocispec.Descriptor) (io.Reader, map[string]string, error) { +// encryptLayer encrypts the layer using the CryptoConfig and creates a new OCI Descriptor. +// A call to this function may also only manipulate the wrapped keys list. +// The caller is expected to store the returned encrypted data and OCI Descriptor +func encryptLayer(cc *encconfig.CryptoConfig, dataReader content.ReaderAt, desc ocispec.Descriptor) (ocispec.Descriptor, io.Reader, error) { var ( - encLayerReader io.Reader - err error - optsData []byte + size int64 + d digest.Digest + err error ) - if ec == nil { - return nil, nil, errors.Wrapf(errdefs.ErrInvalidArgument, "EncryptConfig must not be nil") + encLayerReader, annotations, err := encryption.EncryptLayer(cc.EncryptConfig, encryption.ReaderFromReaderAt(dataReader), desc) + if err != nil { + return ocispec.Descriptor{}, nil, err } - for annotationsID := range keyWrapperAnnotations { - annotation := desc.Annotations[annotationsID] - if annotation != "" { - optsData, err = decryptLayerKeyOptsData(&ec.DecryptConfig, desc) - if err != nil { - return nil, nil, err - } - // already encrypted! - } + // were data touched ? + if encLayerReader != nil { + size = 0 + d = "" + } else { + size = desc.Size + d = desc.Digest } - newAnnotations := make(map[string]string) + newDesc := ocispec.Descriptor{ + Digest: d, + Size: size, + Platform: desc.Platform, + Annotations: annotations, + } - for annotationsID, scheme := range keyWrapperAnnotations { - b64Annotations := desc.Annotations[annotationsID] - if b64Annotations == "" && optsData == nil { - encLayerReader, optsData, err = commonEncryptLayer(encOrPlainLayerReader, desc.Digest, blockcipher.AESSIVCMAC512) - if err != nil { - return nil, nil, err - } - } - keywrapper := GetKeyWrapper(scheme) - b64Annotations, err = preWrapKeys(keywrapper, ec, b64Annotations, optsData) - if err != nil { - return nil, nil, err - } - if b64Annotations != "" { - newAnnotations[annotationsID] = b64Annotations - } + switch desc.MediaType { + case images.MediaTypeDockerSchema2LayerGzip: + newDesc.MediaType = images.MediaTypeDockerSchema2LayerGzipEnc + case images.MediaTypeDockerSchema2Layer: + newDesc.MediaType = images.MediaTypeDockerSchema2LayerEnc + case images.MediaTypeDockerSchema2LayerGzipEnc: + newDesc.MediaType = images.MediaTypeDockerSchema2LayerGzipEnc + case images.MediaTypeDockerSchema2LayerEnc: + newDesc.MediaType = images.MediaTypeDockerSchema2LayerEnc + + // TODO: Mediatypes to be added in ocispec + case ocispec.MediaTypeImageLayerGzip: + newDesc.MediaType = images.MediaTypeDockerSchema2LayerGzipEnc + case ocispec.MediaTypeImageLayer: + newDesc.MediaType = images.MediaTypeDockerSchema2LayerEnc + + default: + return ocispec.Descriptor{}, nil, errors.Errorf("Encryption: unsupporter layer MediaType: %s\n", desc.MediaType) } - if len(newAnnotations) == 0 { - err = errors.Errorf("no encryptor found to handle encryption") - } - // if nothing was encrypted, we just return encLayer = nil - return encLayerReader, newAnnotations, err + + return newDesc, encLayerReader, nil } -// preWrapKeys calls WrapKeys and handles the base64 encoding and concatenation of the -// annotation data -func preWrapKeys(keywrapper keywrap.KeyWrapper, ec *config.EncryptConfig, b64Annotations string, optsData []byte) (string, error) { - newAnnotation, err := keywrapper.WrapKeys(ec, optsData) - if err != nil || len(newAnnotation) == 0 { - return b64Annotations, err - } - b64newAnnotation := base64.StdEncoding.EncodeToString(newAnnotation) - if b64Annotations == "" { - return b64newAnnotation, nil - } - return b64Annotations + "," + b64newAnnotation, nil -} - -// DecryptLayer decrypts a layer trying one keywrap.KeyWrapper after the other to see whether it -// can apply the provided private key -// If unwrapOnly is set we will only try to decrypt the layer encryption key and return -func DecryptLayer(dc *config.DecryptConfig, encLayerReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool) (io.Reader, digest.Digest, error) { - if dc == nil { - return nil, "", errors.Wrapf(errdefs.ErrInvalidArgument, "DecryptConfig must not be nil") - } - optsData, err := decryptLayerKeyOptsData(dc, desc) +// decryptLayer decrypts the layer using the CryptoConfig and creates a new OCI Descriptor. +// The caller is expected to store the returned plain data and OCI Descriptor +func decryptLayer(cc *encconfig.CryptoConfig, dataReader content.ReaderAt, desc ocispec.Descriptor, unwrapOnly bool) (ocispec.Descriptor, io.Reader, error) { + resultReader, d, err := encryption.DecryptLayer(cc.DecryptConfig, encryption.ReaderFromReaderAt(dataReader), desc, unwrapOnly) if err != nil || unwrapOnly { - return nil, "", err + return ocispec.Descriptor{}, nil, err } - return commonDecryptLayer(encLayerReader, optsData) + newDesc := ocispec.Descriptor{ + Digest: d, + Size: 0, + Platform: desc.Platform, + } + + switch desc.MediaType { + case images.MediaTypeDockerSchema2LayerGzipEnc: + newDesc.MediaType = images.MediaTypeDockerSchema2LayerGzip + case images.MediaTypeDockerSchema2LayerEnc: + newDesc.MediaType = images.MediaTypeDockerSchema2Layer + default: + return ocispec.Descriptor{}, nil, errors.Errorf("Decryption: unsupporter layer MediaType: %s\n", desc.MediaType) + } + return newDesc, resultReader, nil } -func decryptLayerKeyOptsData(dc *config.DecryptConfig, desc ocispec.Descriptor) ([]byte, error) { - privKeyGiven := false - for annotationsID, scheme := range keyWrapperAnnotations { - b64Annotation := desc.Annotations[annotationsID] - if b64Annotation != "" { - keywrapper := GetKeyWrapper(scheme) +// cryptLayer handles the changes due to encryption or decryption of a layer +func cryptLayer(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, cryptoOp cryptoOp) (ocispec.Descriptor, error) { + var ( + resultReader io.Reader + newDesc ocispec.Descriptor + ) - if len(keywrapper.GetPrivateKeys(dc.Parameters)) == 0 { - continue + dataReader, err := cs.ReaderAt(ctx, desc) + if err != nil { + return ocispec.Descriptor{}, err + } + defer dataReader.Close() + + if cryptoOp == cryptoOpEncrypt { + newDesc, resultReader, err = encryptLayer(cc, dataReader, desc) + } else { + newDesc, resultReader, err = decryptLayer(cc, dataReader, desc, cryptoOp == cryptoOpUnwrapOnly) + } + if err != nil || cryptoOp == cryptoOpUnwrapOnly { + return ocispec.Descriptor{}, err + } + // some operations, such as changing recipients, may not touch the layer at all + if resultReader != nil { + if ls == nil { + return ocispec.Descriptor{}, errors.New("Unexpected write to object without lease") + } + + var rsrc leases.Resource + var ref string + + // If we have the digest, write blob with checks + haveDigest := newDesc.Digest.String() != "" + if haveDigest { + ref = fmt.Sprintf("layer-%s", newDesc.Digest.String()) + rsrc = leases.Resource{ + ID: newDesc.Digest.String(), + Type: "content", + } + } else { + ref = fmt.Sprintf("blob-%d-%d", rand.Int(), rand.Int()) + rsrc = leases.Resource{ + ID: ref, + Type: "ingests", } - privKeyGiven = true - optsData, err := preUnwrapKey(keywrapper, dc, b64Annotation) + } + + // Add resource to lease and write blob + if err := ls.AddResource(ctx, l, rsrc); err != nil { + return ocispec.Descriptor{}, errors.Wrap(err, "Unable to add resource to lease") + } + + if haveDigest { + if err := content.WriteBlob(ctx, cs, ref, resultReader, newDesc); err != nil { + return ocispec.Descriptor{}, errors.Wrap(err, "failed to write config") + } + } else { + newDesc.Digest, newDesc.Size, err = ingestReader(ctx, cs, ref, resultReader) if err != nil { - // try next keywrap.KeyWrapper - continue + return ocispec.Descriptor{}, err } - if optsData == nil { - // try next keywrap.KeyWrapper - continue + } + } + return newDesc, err +} + +func ingestReader(ctx context.Context, cs content.Ingester, ref string, r io.Reader) (digest.Digest, int64, error) { + cw, err := content.OpenWriter(ctx, cs, content.WithRef(ref)) + if err != nil { + return "", 0, errors.Wrap(err, "failed to open writer") + } + defer cw.Close() + + if _, err := content.CopyReader(cw, r); err != nil { + return "", 0, errors.Wrap(err, "copy failed") + } + + st, err := cw.Status() + if err != nil { + return "", 0, errors.Wrap(err, "failed to get state") + } + + if err := cw.Commit(ctx, st.Offset, ""); err != nil { + if !errdefs.IsAlreadyExists(err) { + return "", 0, errors.Wrapf(err, "failed commit on ref %q", ref) + } + } + + return cw.Digest(), st.Offset, nil +} + +// Encrypt or decrypt all the Children of a given descriptor +func cryptChildren(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp, thisPlatform *ocispec.Platform) (ocispec.Descriptor, bool, error) { + children, err := images.Children(ctx, cs, desc) + if err != nil { + if errdefs.IsNotFound(err) { + return desc, false, nil + } + return ocispec.Descriptor{}, false, err + } + + var newLayers []ocispec.Descriptor + var config ocispec.Descriptor + modified := false + + for _, child := range children { + // we only encrypt child layers and have to update their parents if encryption happened + switch child.MediaType { + case images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig: + config = child + case images.MediaTypeDockerSchema2LayerGzip, images.MediaTypeDockerSchema2Layer, + ocispec.MediaTypeImageLayerGzip, ocispec.MediaTypeImageLayer: + if cryptoOp == cryptoOpEncrypt && lf(child) { + nl, err := cryptLayer(ctx, cs, ls, l, child, cc, cryptoOp) + if err != nil { + return ocispec.Descriptor{}, false, err + } + modified = true + newLayers = append(newLayers, nl) + } else { + newLayers = append(newLayers, child) } - return optsData, nil + case images.MediaTypeDockerSchema2LayerGzipEnc, images.MediaTypeDockerSchema2LayerEnc: + // this one can be decrypted but also its recipients list changed + if lf(child) { + nl, err := cryptLayer(ctx, cs, ls, l, child, cc, cryptoOp) + if err != nil || cryptoOp == cryptoOpUnwrapOnly { + return ocispec.Descriptor{}, false, err + } + modified = true + newLayers = append(newLayers, nl) + } else { + newLayers = append(newLayers, child) + } + case images.MediaTypeDockerSchema2LayerForeign, images.MediaTypeDockerSchema2LayerForeignGzip: + // never encrypt/decrypt + newLayers = append(newLayers, child) + default: + return ocispec.Descriptor{}, false, errors.Errorf("bad/unhandled MediaType %s in encryptChildren\n", child.MediaType) } } - if !privKeyGiven { - return nil, errors.New("missing private key needed for decryption") - } - return nil, errors.Errorf("no suitable key unwrapper found or none of the private keys could be used for decryption") -} -// preUnwrapKey decodes the comma separated base64 strings and calls the Unwrap function -// of the given keywrapper with it and returns the result in case the Unwrap functions -// does not return an error. If all attempts fail, an error is returned. -func preUnwrapKey(keywrapper keywrap.KeyWrapper, dc *config.DecryptConfig, b64Annotations string) ([]byte, error) { - if b64Annotations == "" { - return nil, nil - } - for _, b64Annotation := range strings.Split(b64Annotations, ",") { - annotation, err := base64.StdEncoding.DecodeString(b64Annotation) + if modified && len(newLayers) > 0 { + newManifest := ocispec.Manifest{ + Versioned: specs.Versioned{ + SchemaVersion: 2, + }, + Config: config, + Layers: newLayers, + } + + mb, err := json.MarshalIndent(newManifest, "", " ") if err != nil { - return nil, errors.New("could not base64 decode the annotation") + return ocispec.Descriptor{}, false, errors.Wrap(err, "failed to marshal image") } - optsData, err := keywrapper.UnwrapKey(dc, annotation) + + newDesc := ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageManifest, + Size: int64(len(mb)), + Digest: digest.Canonical.FromBytes(mb), + Platform: desc.Platform, + } + + labels := map[string]string{} + labels["containerd.io/gc.ref.content.0"] = newManifest.Config.Digest.String() + for i, ch := range newManifest.Layers { + labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = ch.Digest.String() + } + + ref := fmt.Sprintf("manifest-%s", newDesc.Digest.String()) + + if ls == nil { + return ocispec.Descriptor{}, false, errors.New("Unexpected write to object without lease") + } + + rsrc := leases.Resource{ + ID: desc.Digest.String(), + Type: "content", + } + + if err := ls.AddResource(ctx, l, rsrc); err != nil { + return ocispec.Descriptor{}, false, errors.Wrap(err, "Unable to add resource to lease") + } + + if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(mb), newDesc, content.WithLabels(labels)); err != nil { + return ocispec.Descriptor{}, false, errors.Wrap(err, "failed to write config") + } + return newDesc, true, nil + } + + return desc, modified, nil +} + +// cryptManifest encrypts or decrypts the children of a top level manifest +func cryptManifest(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp) (ocispec.Descriptor, bool, error) { + p, err := content.ReadBlob(ctx, cs, desc) + if err != nil { + return ocispec.Descriptor{}, false, err + } + var manifest ocispec.Manifest + if err := json.Unmarshal(p, &manifest); err != nil { + return ocispec.Descriptor{}, false, err + } + platform := platforms.DefaultSpec() + newDesc, modified, err := cryptChildren(ctx, cs, ls, l, desc, cc, lf, cryptoOp, &platform) + if err != nil || cryptoOp == cryptoOpUnwrapOnly { + return ocispec.Descriptor{}, false, err + } + return newDesc, modified, nil +} + +// cryptManifestList encrypts or decrypts the children of a top level manifest list +func cryptManifestList(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp) (ocispec.Descriptor, bool, error) { + // read the index; if any layer is encrypted and any manifests change we will need to rewrite it + b, err := content.ReadBlob(ctx, cs, desc) + if err != nil { + return ocispec.Descriptor{}, false, err + } + + var index ocispec.Index + if err := json.Unmarshal(b, &index); err != nil { + return ocispec.Descriptor{}, false, err + } + + var newManifests []ocispec.Descriptor + modified := false + for _, manifest := range index.Manifests { + newManifest, m, err := cryptChildren(ctx, cs, ls, l, manifest, cc, lf, cryptoOp, manifest.Platform) + if err != nil || cryptoOp == cryptoOpUnwrapOnly { + return ocispec.Descriptor{}, false, err + } + if m { + modified = true + } + newManifests = append(newManifests, newManifest) + } + + if modified { + // we need to update the index + newIndex := ocispec.Index{ + Versioned: index.Versioned, + Manifests: newManifests, + } + + mb, err := json.MarshalIndent(newIndex, "", " ") if err != nil { - continue + return ocispec.Descriptor{}, false, errors.Wrap(err, "failed to marshal index") } - return optsData, nil + + newDesc := ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageIndex, + Size: int64(len(mb)), + Digest: digest.Canonical.FromBytes(mb), + } + + labels := map[string]string{} + for i, m := range newIndex.Manifests { + labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = m.Digest.String() + } + + ref := fmt.Sprintf("index-%s", newDesc.Digest.String()) + + if ls == nil { + return ocispec.Descriptor{}, false, errors.New("Unexpected write to object without lease") + } + + rsrc := leases.Resource{ + ID: desc.Digest.String(), + Type: "content", + } + + if err := ls.AddResource(ctx, l, rsrc); err != nil { + return ocispec.Descriptor{}, false, errors.Wrap(err, "Unable to add resource to lease") + } + + if err = content.WriteBlob(ctx, cs, ref, bytes.NewReader(mb), newDesc, content.WithLabels(labels)); err != nil { + return ocispec.Descriptor{}, false, errors.Wrap(err, "failed to write index") + } + return newDesc, true, nil } - return nil, errors.New("no suitable key found for decrypting layer key") + + return desc, false, nil } -// commonEncryptLayer is a function to encrypt the plain layer using a new random -// symmetric key and return the LayerBlockCipherHandler's JSON in string form for -// later use during decryption -func commonEncryptLayer(plainLayerReader io.Reader, d digest.Digest, typ blockcipher.LayerCipherType) (io.Reader, []byte, error) { - lbch, err := blockcipher.NewLayerBlockCipherHandler() - if err != nil { - return nil, nil, err +// cryptImage is the dispatcher to encrypt/decrypt an image; it accepts either an OCI descriptor +// representing a manifest list or a single manifest +func cryptImage(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp) (ocispec.Descriptor, bool, error) { + if cc == nil { + return ocispec.Descriptor{}, false, errors.Wrapf(errdefs.ErrInvalidArgument, "CryptoConfig must not be nil") } - - encLayerReader, opts, err := lbch.Encrypt(plainLayerReader, typ) - if err != nil { - return nil, nil, err + switch desc.MediaType { + case ocispec.MediaTypeImageIndex, images.MediaTypeDockerSchema2ManifestList: + return cryptManifestList(ctx, cs, ls, l, desc, cc, lf, cryptoOp) + case ocispec.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest: + return cryptManifest(ctx, cs, ls, l, desc, cc, lf, cryptoOp) + default: + return ocispec.Descriptor{}, false, errors.Errorf("CryptImage: Unhandled media type: %s", desc.MediaType) } - opts.Digest = d - - optsData, err := json.Marshal(opts) - if err != nil { - return nil, nil, errors.Wrapf(err, "could not JSON marshal opts") - } - - return encLayerReader, optsData, err } -// commonDecryptLayer decrypts an encrypted layer previously encrypted with commonEncryptLayer -// by passing along the optsData -func commonDecryptLayer(encLayerReader io.Reader, optsData []byte) (io.Reader, digest.Digest, error) { - opts := blockcipher.LayerBlockCipherOptions{} - err := json.Unmarshal(optsData, &opts) - if err != nil { - return nil, "", errors.Wrapf(err, "could not JSON unmarshal optsData") - } - - lbch, err := blockcipher.NewLayerBlockCipherHandler() - if err != nil { - return nil, "", err - } - - plainLayerReader, opts, err := lbch.Decrypt(encLayerReader, opts) - if err != nil { - return nil, "", err - } - - return plainLayerReader, opts.Digest, nil +// EncryptImage encrypts an image; it accepts either an OCI descriptor representing a manifest list or a single manifest +func EncryptImage(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter) (ocispec.Descriptor, bool, error) { + return cryptImage(ctx, cs, ls, l, desc, cc, lf, cryptoOpEncrypt) +} + +// DecryptImage decrypts an image; it accepts either an OCI descriptor representing a manifest list or a single manifest +func DecryptImage(ctx context.Context, cs content.Store, ls leases.Manager, l leases.Lease, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter) (ocispec.Descriptor, bool, error) { + return cryptImage(ctx, cs, ls, l, desc, cc, lf, cryptoOpDecrypt) +} + +// CheckAuthorization checks whether a user has the right keys to be allowed to access an image (every layer) +// It takes decrypting of the layers only as far as decrypting the asymmetrically encrypted data +// The decryption is only done for the current platform +func CheckAuthorization(ctx context.Context, cs content.Store, desc ocispec.Descriptor, dc *encconfig.DecryptConfig) error { + cc := encconfig.CryptoConfig{ + DecryptConfig: dc, + } + lf := func(desc ocispec.Descriptor) bool { + return true + } + // We shouldn't need to create any objects in CheckAuthorization, so no lease required. + _, _, err := cryptImage(ctx, cs, nil, leases.Lease{}, desc, &cc, lf, cryptoOpUnwrapOnly) + if err != nil { + return errors.Wrapf(err, "you are not authorized to use this image") + } + return nil } diff --git a/images/image.go b/images/image.go index 7d48d45fa..81c92ee4f 100644 --- a/images/image.go +++ b/images/image.go @@ -64,9 +64,6 @@ type DeleteOptions struct { // DeleteOpt allows configuring a delete operation type DeleteOpt func(context.Context, *DeleteOptions) error -// LayerFilter allows to select Layers by certain criteria -type LayerFilter func(desc ocispec.Descriptor) bool - // SynchronousDelete is used to indicate that an image deletion and removal of // the image resources should occur synchronously before returning a result. func SynchronousDelete() DeleteOpt { @@ -89,14 +86,6 @@ type Store interface { Delete(ctx context.Context, name string, opts ...DeleteOpt) error } -type cryptoOp int - -const ( - cryptoOpEncrypt cryptoOp = iota - cryptoOpDecrypt = iota - cryptoOpUnwrapOnly = iota -) - // TODO(stevvooe): Many of these functions make strong platform assumptions, // which are untrue in a lot of cases. More refactoring must be done here to // make this work in all cases. @@ -408,7 +397,7 @@ func RootFS(ctx context.Context, provider content.Provider, configDesc ocispec.D func IsCompressedDiff(ctx context.Context, mediaType string) (bool, error) { switch mediaType { case ocispec.MediaTypeImageLayer, MediaTypeDockerSchema2Layer: - case ocispec.MediaTypeImageLayerGzip, MediaTypeDockerSchema2LayerGzip, MediaTypeDockerSchema2LayerGzipEnc: + case ocispec.MediaTypeImageLayerGzip, MediaTypeDockerSchema2LayerGzip: return true, nil default: // Still apply all generic media types *.tar[.+]gzip and *.tar @@ -447,13 +436,17 @@ func GetImageLayerDescriptors(ctx context.Context, cs content.Store, desc ocispe for _, child := range children { var tmp []ocispec.Descriptor - if isDescriptorALayer(child) { + switch child.MediaType { + case MediaTypeDockerSchema2LayerGzip, MediaTypeDockerSchema2Layer, + ocispec.MediaTypeImageLayerGzip, ocispec.MediaTypeImageLayer, + MediaTypeDockerSchema2LayerGzipEnc, MediaTypeDockerSchema2LayerEnc: tdesc := child tdesc.Platform = platform tmp = append(tmp, tdesc) - } else { + default: tmp, err = GetImageLayerDescriptors(ctx, cs, child) } + if err != nil { return []ocispec.Descriptor{}, err } diff --git a/images/encryption/blockcipher/blockcipher.go b/pkg/encryption/blockcipher/blockcipher.go similarity index 100% rename from images/encryption/blockcipher/blockcipher.go rename to pkg/encryption/blockcipher/blockcipher.go diff --git a/images/encryption/blockcipher/blockcipher_siv.go b/pkg/encryption/blockcipher/blockcipher_siv.go similarity index 100% rename from images/encryption/blockcipher/blockcipher_siv.go rename to pkg/encryption/blockcipher/blockcipher_siv.go diff --git a/images/encryption/blockcipher/blockcipher_siv_test.go b/pkg/encryption/blockcipher/blockcipher_siv_test.go similarity index 100% rename from images/encryption/blockcipher/blockcipher_siv_test.go rename to pkg/encryption/blockcipher/blockcipher_siv_test.go diff --git a/images/encryption/blockcipher/blockcipher_test.go b/pkg/encryption/blockcipher/blockcipher_test.go similarity index 100% rename from images/encryption/blockcipher/blockcipher_test.go rename to pkg/encryption/blockcipher/blockcipher_test.go diff --git a/images/encryption/config/config.go b/pkg/encryption/config/config.go similarity index 100% rename from images/encryption/config/config.go rename to pkg/encryption/config/config.go diff --git a/pkg/encryption/encryption.go b/pkg/encryption/encryption.go new file mode 100644 index 000000000..b785d35e0 --- /dev/null +++ b/pkg/encryption/encryption.go @@ -0,0 +1,244 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package encryption + +import ( + "encoding/base64" + "encoding/json" + "io" + "strings" + + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/pkg/encryption/blockcipher" + "github.com/containerd/containerd/pkg/encryption/config" + "github.com/containerd/containerd/pkg/encryption/keywrap" + "github.com/containerd/containerd/pkg/encryption/keywrap/jwe" + "github.com/containerd/containerd/pkg/encryption/keywrap/pgp" + "github.com/containerd/containerd/pkg/encryption/keywrap/pkcs7" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +func init() { + keyWrappers = make(map[string]keywrap.KeyWrapper) + keyWrapperAnnotations = make(map[string]string) + RegisterKeyWrapper("pgp", pgp.NewKeyWrapper()) + RegisterKeyWrapper("jwe", jwe.NewKeyWrapper()) + RegisterKeyWrapper("pkcs7", pkcs7.NewKeyWrapper()) +} + +var keyWrappers map[string]keywrap.KeyWrapper +var keyWrapperAnnotations map[string]string + +// RegisterKeyWrapper allows to register key wrappers by their encryption scheme +func RegisterKeyWrapper(scheme string, iface keywrap.KeyWrapper) { + keyWrappers[scheme] = iface + keyWrapperAnnotations[iface.GetAnnotationID()] = scheme +} + +// GetKeyWrapper looks up the encryptor interface given an encryption scheme (gpg, jwe) +func GetKeyWrapper(scheme string) keywrap.KeyWrapper { + return keyWrappers[scheme] +} + +// GetWrappedKeysMap returns a map of wrappedKeys as values in a +// map with the encryption scheme(s) as the key(s) +func GetWrappedKeysMap(desc ocispec.Descriptor) map[string]string { + wrappedKeysMap := make(map[string]string) + + for annotationsID, scheme := range keyWrapperAnnotations { + if annotation, ok := desc.Annotations[annotationsID]; ok { + wrappedKeysMap[scheme] = annotation + } + } + return wrappedKeysMap +} + +// EncryptLayer encrypts the layer by running one encryptor after the other +func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, desc ocispec.Descriptor) (io.Reader, map[string]string, error) { + var ( + encLayerReader io.Reader + err error + optsData []byte + ) + + if ec == nil { + return nil, nil, errors.Wrapf(errdefs.ErrInvalidArgument, "EncryptConfig must not be nil") + } + + for annotationsID := range keyWrapperAnnotations { + annotation := desc.Annotations[annotationsID] + if annotation != "" { + optsData, err = decryptLayerKeyOptsData(&ec.DecryptConfig, desc) + if err != nil { + return nil, nil, err + } + // already encrypted! + } + } + + newAnnotations := make(map[string]string) + + for annotationsID, scheme := range keyWrapperAnnotations { + b64Annotations := desc.Annotations[annotationsID] + if b64Annotations == "" && optsData == nil { + encLayerReader, optsData, err = commonEncryptLayer(encOrPlainLayerReader, desc.Digest, blockcipher.AESSIVCMAC512) + if err != nil { + return nil, nil, err + } + } + keywrapper := GetKeyWrapper(scheme) + b64Annotations, err = preWrapKeys(keywrapper, ec, b64Annotations, optsData) + if err != nil { + return nil, nil, err + } + if b64Annotations != "" { + newAnnotations[annotationsID] = b64Annotations + } + } + if len(newAnnotations) == 0 { + err = errors.Errorf("no encryptor found to handle encryption") + } + // if nothing was encrypted, we just return encLayer = nil + return encLayerReader, newAnnotations, err +} + +// preWrapKeys calls WrapKeys and handles the base64 encoding and concatenation of the +// annotation data +func preWrapKeys(keywrapper keywrap.KeyWrapper, ec *config.EncryptConfig, b64Annotations string, optsData []byte) (string, error) { + newAnnotation, err := keywrapper.WrapKeys(ec, optsData) + if err != nil || len(newAnnotation) == 0 { + return b64Annotations, err + } + b64newAnnotation := base64.StdEncoding.EncodeToString(newAnnotation) + if b64Annotations == "" { + return b64newAnnotation, nil + } + return b64Annotations + "," + b64newAnnotation, nil +} + +// DecryptLayer decrypts a layer trying one keywrap.KeyWrapper after the other to see whether it +// can apply the provided private key +// If unwrapOnly is set we will only try to decrypt the layer encryption key and return +func DecryptLayer(dc *config.DecryptConfig, encLayerReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool) (io.Reader, digest.Digest, error) { + if dc == nil { + return nil, "", errors.Wrapf(errdefs.ErrInvalidArgument, "DecryptConfig must not be nil") + } + optsData, err := decryptLayerKeyOptsData(dc, desc) + if err != nil || unwrapOnly { + return nil, "", err + } + + return commonDecryptLayer(encLayerReader, optsData) +} + +func decryptLayerKeyOptsData(dc *config.DecryptConfig, desc ocispec.Descriptor) ([]byte, error) { + privKeyGiven := false + for annotationsID, scheme := range keyWrapperAnnotations { + b64Annotation := desc.Annotations[annotationsID] + if b64Annotation != "" { + keywrapper := GetKeyWrapper(scheme) + + if len(keywrapper.GetPrivateKeys(dc.Parameters)) == 0 { + continue + } + privKeyGiven = true + + optsData, err := preUnwrapKey(keywrapper, dc, b64Annotation) + if err != nil { + // try next keywrap.KeyWrapper + continue + } + if optsData == nil { + // try next keywrap.KeyWrapper + continue + } + return optsData, nil + } + } + if !privKeyGiven { + return nil, errors.New("missing private key needed for decryption") + } + return nil, errors.Errorf("no suitable key unwrapper found or none of the private keys could be used for decryption") +} + +// preUnwrapKey decodes the comma separated base64 strings and calls the Unwrap function +// of the given keywrapper with it and returns the result in case the Unwrap functions +// does not return an error. If all attempts fail, an error is returned. +func preUnwrapKey(keywrapper keywrap.KeyWrapper, dc *config.DecryptConfig, b64Annotations string) ([]byte, error) { + if b64Annotations == "" { + return nil, nil + } + for _, b64Annotation := range strings.Split(b64Annotations, ",") { + annotation, err := base64.StdEncoding.DecodeString(b64Annotation) + if err != nil { + return nil, errors.New("could not base64 decode the annotation") + } + optsData, err := keywrapper.UnwrapKey(dc, annotation) + if err != nil { + continue + } + return optsData, nil + } + return nil, errors.New("no suitable key found for decrypting layer key") +} + +// commonEncryptLayer is a function to encrypt the plain layer using a new random +// symmetric key and return the LayerBlockCipherHandler's JSON in string form for +// later use during decryption +func commonEncryptLayer(plainLayerReader io.Reader, d digest.Digest, typ blockcipher.LayerCipherType) (io.Reader, []byte, error) { + lbch, err := blockcipher.NewLayerBlockCipherHandler() + if err != nil { + return nil, nil, err + } + + encLayerReader, opts, err := lbch.Encrypt(plainLayerReader, typ) + if err != nil { + return nil, nil, err + } + opts.Digest = d + + optsData, err := json.Marshal(opts) + if err != nil { + return nil, nil, errors.Wrapf(err, "could not JSON marshal opts") + } + + return encLayerReader, optsData, err +} + +// commonDecryptLayer decrypts an encrypted layer previously encrypted with commonEncryptLayer +// by passing along the optsData +func commonDecryptLayer(encLayerReader io.Reader, optsData []byte) (io.Reader, digest.Digest, error) { + opts := blockcipher.LayerBlockCipherOptions{} + err := json.Unmarshal(optsData, &opts) + if err != nil { + return nil, "", errors.Wrapf(err, "could not JSON unmarshal optsData") + } + + lbch, err := blockcipher.NewLayerBlockCipherHandler() + if err != nil { + return nil, "", err + } + + plainLayerReader, opts, err := lbch.Decrypt(encLayerReader, opts) + if err != nil { + return nil, "", err + } + + return plainLayerReader, opts.Digest, nil +} diff --git a/images/encryption/encryption_test.go b/pkg/encryption/encryption_test.go similarity index 98% rename from images/encryption/encryption_test.go rename to pkg/encryption/encryption_test.go index 1756a208d..9fa05ae6d 100644 --- a/images/encryption/encryption_test.go +++ b/pkg/encryption/encryption_test.go @@ -21,7 +21,7 @@ import ( "reflect" "testing" - "github.com/containerd/containerd/images/encryption/config" + "github.com/containerd/containerd/pkg/encryption/config" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) diff --git a/images/encryption/gpg.go b/pkg/encryption/gpg.go similarity index 100% rename from images/encryption/gpg.go rename to pkg/encryption/gpg.go diff --git a/images/encryption/gpgvault.go b/pkg/encryption/gpgvault.go similarity index 100% rename from images/encryption/gpgvault.go rename to pkg/encryption/gpgvault.go diff --git a/images/encryption/keywrap/jwe/keywrapper_jwe.go b/pkg/encryption/keywrap/jwe/keywrapper_jwe.go similarity index 95% rename from images/encryption/keywrap/jwe/keywrapper_jwe.go rename to pkg/encryption/keywrap/jwe/keywrapper_jwe.go index 004b1ace6..90594b724 100644 --- a/images/encryption/keywrap/jwe/keywrapper_jwe.go +++ b/pkg/encryption/keywrap/jwe/keywrapper_jwe.go @@ -19,9 +19,9 @@ package jwe import ( "crypto/ecdsa" - "github.com/containerd/containerd/images/encryption/config" - "github.com/containerd/containerd/images/encryption/keywrap" - "github.com/containerd/containerd/images/encryption/utils" + "github.com/containerd/containerd/pkg/encryption/config" + "github.com/containerd/containerd/pkg/encryption/keywrap" + "github.com/containerd/containerd/pkg/encryption/utils" "github.com/pkg/errors" jose "gopkg.in/square/go-jose.v2" ) diff --git a/images/encryption/keywrap/jwe/keywrapper_jwe_test.go b/pkg/encryption/keywrap/jwe/keywrapper_jwe_test.go similarity index 98% rename from images/encryption/keywrap/jwe/keywrapper_jwe_test.go rename to pkg/encryption/keywrap/jwe/keywrapper_jwe_test.go index 67eafbdfb..9a97f5be1 100644 --- a/images/encryption/keywrap/jwe/keywrapper_jwe_test.go +++ b/pkg/encryption/keywrap/jwe/keywrapper_jwe_test.go @@ -20,8 +20,8 @@ import ( "crypto/elliptic" "testing" - "github.com/containerd/containerd/images/encryption/config" - "github.com/containerd/containerd/images/encryption/utils" + "github.com/containerd/containerd/pkg/encryption/config" + "github.com/containerd/containerd/pkg/encryption/utils" jose "gopkg.in/square/go-jose.v2" ) diff --git a/images/encryption/keywrap/keywrap.go b/pkg/encryption/keywrap/keywrap.go similarity index 96% rename from images/encryption/keywrap/keywrap.go rename to pkg/encryption/keywrap/keywrap.go index 01b6cb7d0..f2669a493 100644 --- a/images/encryption/keywrap/keywrap.go +++ b/pkg/encryption/keywrap/keywrap.go @@ -17,7 +17,7 @@ package keywrap import ( - "github.com/containerd/containerd/images/encryption/config" + "github.com/containerd/containerd/pkg/encryption/config" ) // KeyWrapper is the interface used for wrapping keys using diff --git a/images/encryption/keywrap/pgp/keywrapper_gpg.go b/pkg/encryption/keywrap/pgp/keywrapper_gpg.go similarity index 98% rename from images/encryption/keywrap/pgp/keywrapper_gpg.go rename to pkg/encryption/keywrap/pgp/keywrapper_gpg.go index 9fd18e983..8eba6bcd6 100644 --- a/images/encryption/keywrap/pgp/keywrapper_gpg.go +++ b/pkg/encryption/keywrap/pgp/keywrapper_gpg.go @@ -29,8 +29,8 @@ import ( "strings" "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/images/encryption/config" - "github.com/containerd/containerd/images/encryption/keywrap" + "github.com/containerd/containerd/pkg/encryption/config" + "github.com/containerd/containerd/pkg/encryption/keywrap" "github.com/pkg/errors" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/packet" diff --git a/images/encryption/keywrap/pgp/keywrapper_gpg_test.go b/pkg/encryption/keywrap/pgp/keywrapper_gpg_test.go similarity index 98% rename from images/encryption/keywrap/pgp/keywrapper_gpg_test.go rename to pkg/encryption/keywrap/pgp/keywrapper_gpg_test.go index 33108c3c2..18b9165f7 100644 --- a/images/encryption/keywrap/pgp/keywrapper_gpg_test.go +++ b/pkg/encryption/keywrap/pgp/keywrapper_gpg_test.go @@ -19,7 +19,7 @@ package pgp import ( "testing" - "github.com/containerd/containerd/images/encryption/config" + "github.com/containerd/containerd/pkg/encryption/config" ) var validGpgCcs = []*config.CryptoConfig{ diff --git a/images/encryption/keywrap/pgp/testingkeys_test.go b/pkg/encryption/keywrap/pgp/testingkeys_test.go similarity index 100% rename from images/encryption/keywrap/pgp/testingkeys_test.go rename to pkg/encryption/keywrap/pgp/testingkeys_test.go diff --git a/images/encryption/keywrap/pkcs7/keywrapper_pkcs7.go b/pkg/encryption/keywrap/pkcs7/keywrapper_pkcs7.go similarity index 95% rename from images/encryption/keywrap/pkcs7/keywrapper_pkcs7.go rename to pkg/encryption/keywrap/pkcs7/keywrapper_pkcs7.go index 87554d88e..2a8011126 100644 --- a/images/encryption/keywrap/pkcs7/keywrapper_pkcs7.go +++ b/pkg/encryption/keywrap/pkcs7/keywrapper_pkcs7.go @@ -20,9 +20,9 @@ import ( "crypto" "crypto/x509" - "github.com/containerd/containerd/images/encryption/config" - "github.com/containerd/containerd/images/encryption/keywrap" - "github.com/containerd/containerd/images/encryption/utils" + "github.com/containerd/containerd/pkg/encryption/config" + "github.com/containerd/containerd/pkg/encryption/keywrap" + "github.com/containerd/containerd/pkg/encryption/utils" "github.com/fullsailor/pkcs7" "github.com/pkg/errors" ) diff --git a/images/encryption/keywrap/pkcs7/keywrapper_pkcs7_test.go b/pkg/encryption/keywrap/pkcs7/keywrapper_pkcs7_test.go similarity index 98% rename from images/encryption/keywrap/pkcs7/keywrapper_pkcs7_test.go rename to pkg/encryption/keywrap/pkcs7/keywrapper_pkcs7_test.go index 37c175a39..82787ffcf 100644 --- a/images/encryption/keywrap/pkcs7/keywrapper_pkcs7_test.go +++ b/pkg/encryption/keywrap/pkcs7/keywrapper_pkcs7_test.go @@ -20,8 +20,8 @@ import ( "crypto/x509" "testing" - "github.com/containerd/containerd/images/encryption/config" - "github.com/containerd/containerd/images/encryption/utils" + "github.com/containerd/containerd/pkg/encryption/config" + "github.com/containerd/containerd/pkg/encryption/utils" ) var oneEmpty []byte diff --git a/images/encryption/reader.go b/pkg/encryption/reader.go similarity index 100% rename from images/encryption/reader.go rename to pkg/encryption/reader.go diff --git a/images/encryption/utils/testing.go b/pkg/encryption/utils/testing.go similarity index 100% rename from images/encryption/utils/testing.go rename to pkg/encryption/utils/testing.go diff --git a/images/encryption/utils/utils.go b/pkg/encryption/utils/utils.go similarity index 100% rename from images/encryption/utils/utils.go rename to pkg/encryption/utils/utils.go