mirror of
https://github.com/containers/skopeo.git
synced 2025-08-30 03:52:47 +00:00
Merge pull request #814 from harche/partial_enc
Partial image encryption support
This commit is contained in:
commit
88f6057eaa
@ -28,6 +28,7 @@ type copyOptions struct {
|
||||
format optionalString // Force conversion of the image to a specified format
|
||||
quiet bool // Suppress output information when copying images
|
||||
all bool // Copy all of the images if the source is a list
|
||||
encryptLayer cli.IntSlice // The list of layers to encrypt
|
||||
encryptionKeys cli.StringSlice // Keys needed to encrypt the image
|
||||
decryptionKeys cli.StringSlice // Keys needed to decrypt the image
|
||||
}
|
||||
@ -92,6 +93,11 @@ func copyCmd(global *globalOptions) cli.Command {
|
||||
Usage: "*Experimental* key with the encryption protocol to use needed to encrypt the image (e.g. jwe:/path/to/key.pem)",
|
||||
Value: &opts.encryptionKeys,
|
||||
},
|
||||
cli.IntSliceFlag{
|
||||
Name: "encrypt-layer",
|
||||
Usage: "*Experimental* the 0-indexed layer indices, with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer)",
|
||||
Value: &opts.encryptLayer,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "decryption-key",
|
||||
Usage: "*Experimental* key needed to decrypt the image",
|
||||
@ -180,9 +186,14 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
||||
var encConfig *encconfig.EncryptConfig
|
||||
var decConfig *encconfig.DecryptConfig
|
||||
|
||||
if len(opts.encryptLayer.Value()) > 0 && len(opts.encryptionKeys.Value()) == 0 {
|
||||
return fmt.Errorf("--encrypt-layer can only be used with --encryption-key")
|
||||
}
|
||||
|
||||
if len(opts.encryptionKeys.Value()) > 0 {
|
||||
// encryption
|
||||
encLayers = &[]int{}
|
||||
p := opts.encryptLayer.Value()
|
||||
encLayers = &p
|
||||
encryptionKeys := opts.encryptionKeys.Value()
|
||||
ecc, err := enchelpers.CreateCryptoConfig(encryptionKeys, []string{})
|
||||
if err != nil {
|
||||
|
@ -98,12 +98,12 @@ To copy and sign an image:
|
||||
|
||||
To encrypt an image:
|
||||
```sh
|
||||
skopeo copy docker://docker.io/library/nginx:latest oci:local_nginx:latest
|
||||
skopeo copy docker://docker.io/library/nginx:1.17.8 oci:local_nginx:1.17.8
|
||||
|
||||
openssl genrsa -out private.key 1024
|
||||
openssl rsa -in private.key -pubout > public.key
|
||||
|
||||
skopeo copy --encryption-key jwe:./public.key oci:local_nginx:latest oci:try-encrypt:encrypted
|
||||
skopeo copy --encryption-key jwe:./public.key oci:local_nginx:1.17.8 oci:try-encrypt:encrypted
|
||||
```
|
||||
|
||||
To decrypt an image:
|
||||
@ -120,6 +120,14 @@ To decrypt an image that requires more than one key:
|
||||
```sh
|
||||
skopeo copy --decryption-key ./private1.key --decryption-key ./private2.key --decryption-key ./private3.key oci:try-encrypt:encrypted oci:try-decrypt:decrypted
|
||||
```
|
||||
|
||||
Container images can also be partially encrypted by specifying the index of the layer. Layers are 0-indexed indices, with support for negative indexing. i.e. 0 is the first layer, -1 is the last layer.
|
||||
|
||||
Let's say out of 3 layers that the image `docker.io/library/nginx:1.17.8` is made up of, we only want to encrypt the 2nd layer,
|
||||
```sh
|
||||
skopeo copy --encryption-key jwe:./public.key --encrypt-layer 1 oci:local_nginx:1.17.8 oci:try-encrypt:encrypted
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
skopeo(1), podman-login(1), docker-login(1)
|
||||
|
||||
|
@ -553,6 +553,15 @@ func (s *CopySuite) TestCopyEncryption(c *check.C) {
|
||||
defer os.RemoveAll(keysDir)
|
||||
undecryptedImgDir, err := ioutil.TempDir("", "copy-5")
|
||||
defer os.RemoveAll(undecryptedImgDir)
|
||||
multiLayerImageDir, err := ioutil.TempDir("", "copy-6")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(multiLayerImageDir)
|
||||
partiallyEncryptedImgDir, err := ioutil.TempDir("", "copy-7")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(partiallyEncryptedImgDir)
|
||||
partiallyDecryptedImgDir, err := ioutil.TempDir("", "copy-8")
|
||||
c.Assert(err, check.IsNil)
|
||||
defer os.RemoveAll(partiallyDecryptedImgDir)
|
||||
|
||||
// Create RSA key pair
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
@ -577,7 +586,7 @@ func (s *CopySuite) TestCopyEncryption(c *check.C) {
|
||||
"oci:"+encryptedImgDir+":encrypted", "oci:"+decryptedImgDir+":decrypted")
|
||||
|
||||
// Copy a standard busybox image locally
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://busybox", "oci:"+originalImageDir+":latest")
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://busybox:1.31.1", "oci:"+originalImageDir+":latest")
|
||||
|
||||
// Encrypt the image
|
||||
assertSkopeoSucceeds(c, "", "copy", "--encryption-key",
|
||||
@ -597,7 +606,7 @@ func (s *CopySuite) TestCopyEncryption(c *check.C) {
|
||||
assertSkopeoSucceeds(c, "", "copy", "oci:"+encryptedImgDir+":encrypted", "oci:"+undecryptedImgDir+":encrypted")
|
||||
// Original busybox image has gzipped layers. But encrypted busybox layers should
|
||||
// not be of gzip type
|
||||
matchLayerBlobBinaryType(c, undecryptedImgDir+"/blobs/sha256", "application/x-gzip", false)
|
||||
matchLayerBlobBinaryType(c, undecryptedImgDir+"/blobs/sha256", "application/x-gzip", 0)
|
||||
|
||||
// Decrypt the image
|
||||
assertSkopeoSucceeds(c, "", "copy", "--decryption-key", keysDir+"/private.key",
|
||||
@ -605,13 +614,32 @@ func (s *CopySuite) TestCopyEncryption(c *check.C) {
|
||||
|
||||
// After successful decryption we should find the gzipped layer from the
|
||||
// busybox image
|
||||
matchLayerBlobBinaryType(c, decryptedImgDir+"/blobs/sha256", "application/x-gzip", true)
|
||||
matchLayerBlobBinaryType(c, decryptedImgDir+"/blobs/sha256", "application/x-gzip", 1)
|
||||
|
||||
// Copy a standard multi layer nginx image locally
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://nginx:1.17.8", "oci:"+multiLayerImageDir+":latest")
|
||||
|
||||
// Partially encrypt the image
|
||||
assertSkopeoSucceeds(c, "", "copy", "--encryption-key", "jwe:"+keysDir+"/public.key",
|
||||
"--encrypt-layer", "1", "oci:"+multiLayerImageDir+":latest", "oci:"+partiallyEncryptedImgDir+":encrypted")
|
||||
|
||||
// Since the image is partially encrypted we should find layers that aren't encrypted
|
||||
matchLayerBlobBinaryType(c, partiallyEncryptedImgDir+"/blobs/sha256", "application/x-gzip", 2)
|
||||
|
||||
// Decrypt the partically encrypted image
|
||||
assertSkopeoSucceeds(c, "", "copy", "--decryption-key", keysDir+"/private.key",
|
||||
"oci:"+partiallyEncryptedImgDir+":encrypted", "oci:"+partiallyDecryptedImgDir+":decrypted")
|
||||
|
||||
// After successful decryption we should find the gzipped layers from the nginx image
|
||||
matchLayerBlobBinaryType(c, partiallyDecryptedImgDir+"/blobs/sha256", "application/x-gzip", 3)
|
||||
|
||||
}
|
||||
|
||||
func matchLayerBlobBinaryType(c *check.C, ociImageDirPath string, contentType string, shouldMatch bool) {
|
||||
func matchLayerBlobBinaryType(c *check.C, ociImageDirPath string, contentType string, matchCount int) {
|
||||
files, err := ioutil.ReadDir(ociImageDirPath)
|
||||
c.Assert(err, check.IsNil)
|
||||
blobFound := false
|
||||
|
||||
foundCount := 0
|
||||
for _, f := range files {
|
||||
fileContent, err := os.Open(ociImageDirPath + "/" + f.Name())
|
||||
c.Assert(err, check.IsNil)
|
||||
@ -619,13 +647,11 @@ func matchLayerBlobBinaryType(c *check.C, ociImageDirPath string, contentType st
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
if layerContentType == contentType {
|
||||
blobFound = true
|
||||
break
|
||||
foundCount = foundCount + 1
|
||||
}
|
||||
}
|
||||
|
||||
c.Assert(blobFound, check.Equals, shouldMatch)
|
||||
|
||||
c.Assert(foundCount, check.Equals, matchCount)
|
||||
}
|
||||
|
||||
func getFileContentType(out *os.File) (string, error) {
|
||||
|
Loading…
Reference in New Issue
Block a user