Implemented image encryption/decryption libraries and ctr commands
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com> Signed-off-by: Brandon Lum <lumjjb@gmail.com>
This commit is contained in:

committed by
Brandon Lum

parent
30c3443947
commit
bf8804c743
215
image_enc_test.go
Normal file
215
image_enc_test.go
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/containerd/containerd/leases"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
func setupBusyboxImage(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
const imageName = "docker.io/library/busybox:latest"
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Cleanup
|
||||
err = client.ImageService().Delete(ctx, imageName)
|
||||
if err != nil && !errdefs.IsNotFound(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// By default pull does not unpack an image
|
||||
image, err := client.Pull(ctx, imageName, WithPlatform("linux/amd64"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = image.Unpack(ctx, DefaultSnapshotter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageEncryption(t *testing.T) {
|
||||
setupBusyboxImage(t)
|
||||
|
||||
publicKey, privateKey, err := utils.CreateRSATestKey(2048, nil, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const imageName = "docker.io/library/busybox:latest"
|
||||
const encImageName = "docker.io/library/busybox:enc"
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
s := client.ImageService()
|
||||
ls := client.LeasesService()
|
||||
|
||||
image, err := s.Get(ctx, imageName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pl, err := platforms.Parse("linux/amd64")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
matcher := platforms.NewMatcher(pl)
|
||||
|
||||
alldescs, err := images.GetImageLayerDescriptors(ctx, client.ContentStore(), image.Target)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var descs []ocispec.Descriptor
|
||||
for _, desc := range alldescs {
|
||||
if matcher.Match(*desc.Platform) {
|
||||
descs = append(descs, desc)
|
||||
}
|
||||
}
|
||||
|
||||
lf := func(d ocispec.Descriptor) bool {
|
||||
for _, desc := range descs {
|
||||
if desc.Digest.String() == d.Digest.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
dcparameters := make(map[string][][]byte)
|
||||
parameters := make(map[string][][]byte)
|
||||
|
||||
parameters["pubkeys"] = [][]byte{publicKey}
|
||||
dcparameters["privkeys"] = [][]byte{privateKey}
|
||||
dcparameters["privkeys-passwords"] = [][]byte{{}}
|
||||
|
||||
cc := &encconfig.CryptoConfig{
|
||||
EncryptConfig: &encconfig.EncryptConfig{
|
||||
Parameters: parameters,
|
||||
DecryptConfig: encconfig.DecryptConfig{
|
||||
Parameters: dcparameters,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(5*time.Minute))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create lease for encryption")
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !modified || image.Target.Digest == encSpec.Digest {
|
||||
t.Fatal("Encryption did not modify the spec")
|
||||
}
|
||||
|
||||
if !hasEncryption(ctx, client.ContentStore(), encSpec) {
|
||||
t.Fatal("Encrypted image does not have encrypted layers")
|
||||
}
|
||||
image.Name = encImageName
|
||||
image.Target = encSpec
|
||||
if _, err := s.Create(ctx, image); err != nil {
|
||||
t.Fatalf("Unable to create image: %v", err)
|
||||
}
|
||||
// Force deletion of lease early to check for proper referencing
|
||||
ls.Delete(ctx, l, leases.SynchronousDelete)
|
||||
|
||||
cc = &encconfig.CryptoConfig{
|
||||
DecryptConfig: &encconfig.DecryptConfig{
|
||||
Parameters: dcparameters,
|
||||
},
|
||||
}
|
||||
|
||||
// Perform decryption of image
|
||||
defer client.ImageService().Delete(ctx, imageName, images.SynchronousDelete())
|
||||
defer client.ImageService().Delete(ctx, encImageName, images.SynchronousDelete())
|
||||
lf = func(desc ocispec.Descriptor) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
l, err = ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(5*time.Minute))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create lease for decryption")
|
||||
}
|
||||
defer ls.Delete(ctx, l, leases.SynchronousDelete)
|
||||
|
||||
decSpec, modified, err := images.DecryptImage(ctx, client.ContentStore(), ls, l, encSpec, cc, lf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !modified || encSpec.Digest == decSpec.Digest {
|
||||
t.Fatal("Decryption did not modify the spec")
|
||||
}
|
||||
|
||||
if hasEncryption(ctx, client.ContentStore(), decSpec) {
|
||||
t.Fatal("Decrypted image has encrypted layers")
|
||||
}
|
||||
}
|
||||
|
||||
func hasEncryption(ctx context.Context, provider content.Provider, spec ocispec.Descriptor) bool {
|
||||
switch spec.MediaType {
|
||||
case images.MediaTypeDockerSchema2LayerEnc, images.MediaTypeDockerSchema2LayerGzipEnc:
|
||||
return true
|
||||
default:
|
||||
// pass
|
||||
}
|
||||
cspecs, err := images.Children(ctx, provider, spec)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range cspecs {
|
||||
if hasEncryption(ctx, provider, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
Reference in New Issue
Block a user