mirror of
https://github.com/containers/skopeo.git
synced 2025-09-03 23:55:21 +00:00
Partial image encryption support
Signed-off-by: Harshal Patil <harshal.patil@in.ibm.com>
This commit is contained in:
@@ -28,6 +28,7 @@ type copyOptions struct {
|
|||||||
format optionalString // Force conversion of the image to a specified format
|
format optionalString // Force conversion of the image to a specified format
|
||||||
quiet bool // Suppress output information when copying images
|
quiet bool // Suppress output information when copying images
|
||||||
all bool // Copy all of the images if the source is a list
|
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
|
encryptionKeys cli.StringSlice // Keys needed to encrypt the image
|
||||||
decryptionKeys cli.StringSlice // Keys needed to decrypt 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)",
|
Usage: "*Experimental* key with the encryption protocol to use needed to encrypt the image (e.g. jwe:/path/to/key.pem)",
|
||||||
Value: &opts.encryptionKeys,
|
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{
|
cli.StringSliceFlag{
|
||||||
Name: "decryption-key",
|
Name: "decryption-key",
|
||||||
Usage: "*Experimental* key needed to decrypt the image",
|
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 encConfig *encconfig.EncryptConfig
|
||||||
var decConfig *encconfig.DecryptConfig
|
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 {
|
if len(opts.encryptionKeys.Value()) > 0 {
|
||||||
// encryption
|
// encryption
|
||||||
encLayers = &[]int{}
|
p := opts.encryptLayer.Value()
|
||||||
|
encLayers = &p
|
||||||
encryptionKeys := opts.encryptionKeys.Value()
|
encryptionKeys := opts.encryptionKeys.Value()
|
||||||
ecc, err := enchelpers.CreateCryptoConfig(encryptionKeys, []string{})
|
ecc, err := enchelpers.CreateCryptoConfig(encryptionKeys, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -98,12 +98,12 @@ To copy and sign an image:
|
|||||||
|
|
||||||
To encrypt an image:
|
To encrypt an image:
|
||||||
```sh
|
```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 genrsa -out private.key 1024
|
||||||
openssl rsa -in private.key -pubout > public.key
|
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:
|
To decrypt an image:
|
||||||
@@ -120,6 +120,14 @@ To decrypt an image that requires more than one key:
|
|||||||
```sh
|
```sh
|
||||||
skopeo copy --decryption-key ./private1.key --decryption-key ./private2.key --decryption-key ./private3.key oci:try-encrypt:encrypted oci:try-decrypt:decrypted
|
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
|
## SEE ALSO
|
||||||
skopeo(1), podman-login(1), docker-login(1)
|
skopeo(1), podman-login(1), docker-login(1)
|
||||||
|
|
||||||
|
@@ -553,6 +553,15 @@ func (s *CopySuite) TestCopyEncryption(c *check.C) {
|
|||||||
defer os.RemoveAll(keysDir)
|
defer os.RemoveAll(keysDir)
|
||||||
undecryptedImgDir, err := ioutil.TempDir("", "copy-5")
|
undecryptedImgDir, err := ioutil.TempDir("", "copy-5")
|
||||||
defer os.RemoveAll(undecryptedImgDir)
|
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
|
// Create RSA key pair
|
||||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||||
@@ -577,7 +586,7 @@ func (s *CopySuite) TestCopyEncryption(c *check.C) {
|
|||||||
"oci:"+encryptedImgDir+":encrypted", "oci:"+decryptedImgDir+":decrypted")
|
"oci:"+encryptedImgDir+":encrypted", "oci:"+decryptedImgDir+":decrypted")
|
||||||
|
|
||||||
// Copy a standard busybox image locally
|
// 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
|
// Encrypt the image
|
||||||
assertSkopeoSucceeds(c, "", "copy", "--encryption-key",
|
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")
|
assertSkopeoSucceeds(c, "", "copy", "oci:"+encryptedImgDir+":encrypted", "oci:"+undecryptedImgDir+":encrypted")
|
||||||
// Original busybox image has gzipped layers. But encrypted busybox layers should
|
// Original busybox image has gzipped layers. But encrypted busybox layers should
|
||||||
// not be of gzip type
|
// 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
|
// Decrypt the image
|
||||||
assertSkopeoSucceeds(c, "", "copy", "--decryption-key", keysDir+"/private.key",
|
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
|
// After successful decryption we should find the gzipped layer from the
|
||||||
// busybox image
|
// 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)
|
files, err := ioutil.ReadDir(ociImageDirPath)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
blobFound := false
|
|
||||||
|
foundCount := 0
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
fileContent, err := os.Open(ociImageDirPath + "/" + f.Name())
|
fileContent, err := os.Open(ociImageDirPath + "/" + f.Name())
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -619,13 +647,11 @@ func matchLayerBlobBinaryType(c *check.C, ociImageDirPath string, contentType st
|
|||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
if layerContentType == contentType {
|
if layerContentType == contentType {
|
||||||
blobFound = true
|
foundCount = foundCount + 1
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(blobFound, check.Equals, shouldMatch)
|
c.Assert(foundCount, check.Equals, matchCount)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileContentType(out *os.File) (string, error) {
|
func getFileContentType(out *os.File) (string, error) {
|
||||||
|
Reference in New Issue
Block a user