Remove encryption code from containerd core

We are separating out the encryption code and have designed a few new
interfaces and APIs for processing content streams.  This keep the core
clean of encryption code but enables not only encryption but support of
multiple content types ( custom media types ).

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby
2019-08-09 15:01:16 +00:00
parent ec4ad5332d
commit d085d9b464
101 changed files with 0 additions and 27005 deletions

View File

@@ -131,22 +131,6 @@ var (
Usage: "add a device to a container",
},
}
// ImageDecryptionFlags are cli flags needed when decrypting an image
ImageDecryptionFlags = []cli.Flag{
cli.StringFlag{
Name: "gpg-homedir",
Usage: "The GPG homedir to use; by default gpg uses ~/.gnupg",
}, cli.StringFlag{
Name: "gpg-version",
Usage: "The GPG version (\"v1\" or \"v2\"), default will make an educated guess",
}, cli.StringSliceFlag{
Name: "key",
Usage: "A secret key's filename and an optional password separated by colon; this option may be provided multiple times",
}, cli.StringSliceFlag{
Name: "dec-recipient",
Usage: "Recipient of the image; used only for PKCS7 and must be an x509 certificate",
},
}
)
// ObjectWithLabelArgs returns the first arg and a LabelArgs object

View File

@@ -1,455 +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 (
gocontext "context"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/containerd/containerd"
"github.com/containerd/containerd/images"
imgenc "github.com/containerd/containerd/images/encryption"
"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"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
// LayerInfo holds information about an image layer
type LayerInfo struct {
// The Number of this layer in the sequence; starting at 0
Index uint32
Descriptor ocispec.Descriptor
}
// isUserSelectedLayer checks whether a layer is user-selected given its number
// A layer can be described with its (positive) index number or its negative number.
// The latter is counted relative to the topmost one (-1), the former relative to
// the bottommost one (0).
func isUserSelectedLayer(layerIndex, layersTotal int32, layers []int32) bool {
if len(layers) == 0 {
// convenience for the user; none given means 'all'
return true
}
negNumber := layerIndex - layersTotal
for _, l := range layers {
if l == negNumber || l == layerIndex {
return true
}
}
return false
}
// isUserSelectedPlatform determines whether the platform matches one in
// the array of user-provided platforms
func isUserSelectedPlatform(platform *ocispec.Platform, platformList []ocispec.Platform) bool {
if len(platformList) == 0 {
// convenience for the user; none given means 'all'
return true
}
matcher := platforms.NewMatcher(*platform)
for _, platform := range platformList {
if matcher.Match(platform) {
return true
}
}
return false
}
// processRecipientKeys sorts the array of recipients by type. Recipients may be either
// x509 certificates, public keys, or PGP public keys identified by email address or name
func processRecipientKeys(recipients []string) ([][]byte, [][]byte, [][]byte, error) {
var (
gpgRecipients [][]byte
pubkeys [][]byte
x509s [][]byte
)
for _, recipient := range recipients {
idx := strings.Index(recipient, ":")
if idx < 0 {
return nil, nil, nil, errors.New("Invalid recipient format")
}
protocol := recipient[:idx]
value := recipient[idx+1:]
switch protocol {
case "pgp":
gpgRecipients = append(gpgRecipients, []byte(value))
case "jwe":
tmp, err := ioutil.ReadFile(value)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "Unable to read file")
}
if !encutils.IsPublicKey(tmp) {
return nil, nil, nil, errors.New("File provided is not a public key")
}
pubkeys = append(pubkeys, tmp)
case "pkcs7":
tmp, err := ioutil.ReadFile(value)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "Unable to read file")
}
if !encutils.IsCertificate(tmp) {
return nil, nil, nil, errors.New("File provided is not an x509 cert")
}
x509s = append(x509s, tmp)
default:
return nil, nil, nil, errors.New("Provided protocol not recognized")
}
}
return gpgRecipients, pubkeys, x509s, nil
}
// Process a password that may be in any of the following formats:
// - file=<passwordfile>
// - pass=<password>
// - fd=<filedescriptor>
// - <password>
func processPwdString(pwdString string) ([]byte, error) {
if strings.HasPrefix(pwdString, "file=") {
return ioutil.ReadFile(pwdString[5:])
} else if strings.HasPrefix(pwdString, "pass=") {
return []byte(pwdString[5:]), nil
} else if strings.HasPrefix(pwdString, "fd=") {
fdStr := pwdString[3:]
fd, err := strconv.Atoi(fdStr)
if err != nil {
return nil, errors.Wrapf(err, "could not parse file descriptor %s", fdStr)
}
f := os.NewFile(uintptr(fd), "pwdfile")
if f == nil {
return nil, fmt.Errorf("%s is not a valid file descriptor", fdStr)
}
defer f.Close()
pwd := make([]byte, 64)
n, err := f.Read(pwd)
if err != nil {
return nil, errors.Wrapf(err, "could not read from file descriptor")
}
return pwd[:n], nil
}
return []byte(pwdString), nil
}
// processPrivateKeyFiles sorts the different types of private key files; private key files may either be
// private keys or GPG private key ring files. The private key files may include the password for the
// private key and take any of the following forms:
// - <filename>
// - <filename>:file=<passwordfile>
// - <filename>:pass=<password>
// - <filename>:fd=<filedescriptor>
// - <filename>:<password>
func processPrivateKeyFiles(keyFilesAndPwds []string) ([][]byte, [][]byte, [][]byte, [][]byte, error) {
var (
gpgSecretKeyRingFiles [][]byte
gpgSecretKeyPasswords [][]byte
privkeys [][]byte
privkeysPasswords [][]byte
err error
)
// keys needed for decryption in case of adding a recipient
for _, keyfileAndPwd := range keyFilesAndPwds {
var password []byte
parts := strings.Split(keyfileAndPwd, ":")
if len(parts) == 2 {
password, err = processPwdString(parts[1])
if err != nil {
return nil, nil, nil, nil, err
}
}
keyfile := parts[0]
tmp, err := ioutil.ReadFile(keyfile)
if err != nil {
return nil, nil, nil, nil, err
}
isPrivKey, err := encutils.IsPrivateKey(tmp, password)
if encutils.IsPasswordError(err) {
return nil, nil, nil, nil, err
}
if isPrivKey {
privkeys = append(privkeys, tmp)
privkeysPasswords = append(privkeysPasswords, password)
} else if encutils.IsGPGPrivateKeyRing(tmp) {
gpgSecretKeyRingFiles = append(gpgSecretKeyRingFiles, tmp)
gpgSecretKeyPasswords = append(gpgSecretKeyPasswords, password)
} else {
return nil, nil, nil, nil, fmt.Errorf("unidentified private key in file %s (password=%s)", keyfile, string(password))
}
}
return gpgSecretKeyRingFiles, gpgSecretKeyPasswords, privkeys, privkeysPasswords, nil
}
func createGPGClient(context *cli.Context) (encryption.GPGClient, error) {
return encryption.NewGPGClient(context.String("gpg-version"), context.String("gpg-homedir"))
}
func getGPGPrivateKeys(context *cli.Context, gpgSecretKeyRingFiles [][]byte, descs []ocispec.Descriptor, mustFindKey bool) (gpgPrivKeys [][]byte, gpgPrivKeysPwds [][]byte, err error) {
gpgClient, err := createGPGClient(context)
if err != nil {
return nil, nil, err
}
var gpgVault encryption.GPGVault
if len(gpgSecretKeyRingFiles) > 0 {
gpgVault = encryption.NewGPGVault()
err = gpgVault.AddSecretKeyRingDataArray(gpgSecretKeyRingFiles)
if err != nil {
return nil, nil, err
}
}
return encryption.GPGGetPrivateKey(descs, gpgClient, gpgVault, mustFindKey)
}
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
}
_, descs := filterLayerDescriptors(alldescs, layers, platformList)
lf := func(d ocispec.Descriptor) bool {
for _, desc := range descs {
if desc.Digest.String() == d.Digest.String() {
return true
}
}
return false
}
return lf, nil
}
// cryptImage encrypts or decrypts an image with the given name and stores it either under the newName
// or updates the existing one
func cryptImage(client *containerd.Client, ctx gocontext.Context, name, newName string, cc *encconfig.CryptoConfig, layers []int32, platformList []string, encrypt bool) (images.Image, error) {
s := client.ImageService()
image, err := s.Get(ctx, name)
if err != nil {
return images.Image{}, err
}
pl, err := parsePlatformArray(platformList)
if err != nil {
return images.Image{}, err
}
lf, err := createLayerFilter(client, ctx, image.Target, layers, pl)
if err != nil {
return images.Image{}, err
}
var (
modified bool
newSpec ocispec.Descriptor
)
ctx, done, err := client.WithLease(ctx)
if err != nil {
return images.Image{}, err
}
defer done(ctx)
if encrypt {
newSpec, modified, err = imgenc.EncryptImage(ctx, client.ContentStore(), image.Target, cc, lf)
} else {
newSpec, modified, err = imgenc.DecryptImage(ctx, client.ContentStore(), image.Target, cc, lf)
}
if err != nil {
return image, err
}
if !modified {
return image, nil
}
image.Target = newSpec
// if newName is either empty or equal to the existing name, it's an update
if newName == "" || strings.Compare(image.Name, newName) == 0 {
// first Delete the existing and then Create a new one
// We have to do it this way since we have a newSpec!
err = s.Delete(ctx, image.Name)
if err != nil {
return images.Image{}, err
}
newName = image.Name
}
image.Name = newName
return s.Create(ctx, image)
}
func encryptImage(client *containerd.Client, ctx gocontext.Context, name, newName string, cc *encconfig.CryptoConfig, layers []int32, platformList []string) (images.Image, error) {
return cryptImage(client, ctx, name, newName, cc, layers, platformList, true)
}
func decryptImage(client *containerd.Client, ctx gocontext.Context, name, newName string, cc *encconfig.CryptoConfig, layers []int32, platformList []string) (images.Image, error) {
return cryptImage(client, ctx, name, newName, cc, layers, platformList, false)
}
func getImageLayerInfos(client *containerd.Client, ctx gocontext.Context, name string, layers []int32, platformList []string) ([]LayerInfo, []ocispec.Descriptor, error) {
s := client.ImageService()
image, err := s.Get(ctx, name)
if err != nil {
return nil, nil, err
}
pl, err := parsePlatformArray(platformList)
if err != nil {
return nil, nil, err
}
alldescs, err := images.GetImageLayerDescriptors(ctx, client.ContentStore(), image.Target)
if err != nil {
return nil, nil, err
}
lis, descs := filterLayerDescriptors(alldescs, layers, pl)
return lis, descs, nil
}
func countLayers(descs []ocispec.Descriptor, platform *ocispec.Platform) int32 {
c := int32(0)
for _, desc := range descs {
if desc.Platform == platform {
c = c + 1
}
}
return c
}
func filterLayerDescriptors(alldescs []ocispec.Descriptor, layers []int32, pl []ocispec.Platform) ([]LayerInfo, []ocispec.Descriptor) {
var (
layerInfos []LayerInfo
descs []ocispec.Descriptor
curplat *ocispec.Platform
layerIndex int32
layersTotal int32
)
for _, desc := range alldescs {
if curplat != desc.Platform {
curplat = desc.Platform
layerIndex = 0
layersTotal = countLayers(alldescs, desc.Platform)
} else {
layerIndex = layerIndex + 1
}
if isUserSelectedLayer(layerIndex, layersTotal, layers) && isUserSelectedPlatform(curplat, pl) {
li := LayerInfo{
Index: uint32(layerIndex),
Descriptor: desc,
}
descs = append(descs, desc)
layerInfos = append(layerInfos, li)
}
}
return layerInfos, descs
}
// CreateDecryptCryptoConfig creates the CryptoConfig object that contains the necessary
// information to perform decryption from command line options and possibly
// LayerInfos describing the image and helping us to query for the PGP decryption keys
func CreateDecryptCryptoConfig(context *cli.Context, descs []ocispec.Descriptor) (encconfig.CryptoConfig, error) {
ccs := []encconfig.CryptoConfig{}
// x509 cert is needed for PKCS7 decryption
_, _, x509s, err := processRecipientKeys(context.StringSlice("dec-recipient"))
if err != nil {
return encconfig.CryptoConfig{}, err
}
gpgSecretKeyRingFiles, gpgSecretKeyPasswords, privKeys, privKeysPasswords, err := processPrivateKeyFiles(context.StringSlice("key"))
if err != nil {
return encconfig.CryptoConfig{}, err
}
_, err = createGPGClient(context)
gpgInstalled := err == nil
if gpgInstalled {
if len(gpgSecretKeyRingFiles) == 0 && len(privKeys) == 0 && descs != nil {
// Get pgp private keys from keyring only if no private key was passed
gpgPrivKeys, gpgPrivKeyPasswords, err := getGPGPrivateKeys(context, gpgSecretKeyRingFiles, descs, true)
if err != nil {
return encconfig.CryptoConfig{}, err
}
gpgCc, err := encconfig.DecryptWithGpgPrivKeys(gpgPrivKeys, gpgPrivKeyPasswords)
if err != nil {
return encconfig.CryptoConfig{}, err
}
ccs = append(ccs, gpgCc)
} else if len(gpgSecretKeyRingFiles) > 0 {
gpgCc, err := encconfig.DecryptWithGpgPrivKeys(gpgSecretKeyRingFiles, gpgSecretKeyPasswords)
if err != nil {
return encconfig.CryptoConfig{}, err
}
ccs = append(ccs, gpgCc)
}
}
x509sCc, err := encconfig.DecryptWithX509s(x509s)
if err != nil {
return encconfig.CryptoConfig{}, err
}
ccs = append(ccs, x509sCc)
privKeysCc, err := encconfig.DecryptWithPrivKeys(privKeys, privKeysPasswords)
if err != nil {
return encconfig.CryptoConfig{}, err
}
ccs = append(ccs, privKeysCc)
return encconfig.CombineCryptoConfigs(ccs), nil
}
// parsePlatformArray parses an array of specifiers and converts them into an array of specs.Platform
func parsePlatformArray(specifiers []string) ([]ocispec.Platform, error) {
var speclist []ocispec.Platform
for _, specifier := range specifiers {
spec, err := platforms.Parse(specifier)
if err != nil {
return []ocispec.Platform{}, err
}
speclist = append(speclist, spec)
}
return speclist, nil
}

View File

@@ -1,94 +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 (
"fmt"
"github.com/containerd/containerd/cmd/ctr/commands"
imgenc "github.com/containerd/containerd/images/encryption"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var decryptCommand = cli.Command{
Name: "decrypt",
Usage: "decrypt an image locally",
ArgsUsage: "[flags] <local> <new name>",
Description: `Decrypt an image locally.
Decrypt an image using private keys.
The user has contol over which layers to decrypt and for which platform.
If no payers or platforms are specified, all layers for all platforms are
decrypted.
Private keys in PEM format may be encrypted and the password may be passed
along in any of the following formats:
- <filename>:<password>
- <filename>:pass=<password>
- <filename>:fd=<file descriptor>
- <filename>:filename=<password file>
`,
Flags: append(append(commands.RegistryFlags, cli.IntSliceFlag{
Name: "layer",
Usage: "The layer to decrypt; this must be either the layer number or a negative number starting with -1 for topmost layer",
}, cli.StringSliceFlag{
Name: "platform",
Usage: "For which platform to decrypt; by default decryption is done for all platforms",
},
), commands.ImageDecryptionFlags...),
Action: func(context *cli.Context) error {
local := context.Args().First()
if local == "" {
return errors.New("please provide the name of an image to decrypt")
}
newName := context.Args().Get(1)
if newName != "" {
fmt.Printf("Decrypting %s to %s\n", local, newName)
} else {
fmt.Printf("Decrypting %s and replacing it with the decrypted image\n", local)
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
layers32 := commands.IntToInt32Array(context.IntSlice("layer"))
_, descs, err := getImageLayerInfos(client, ctx, local, layers32, context.StringSlice("platform"))
if err != nil {
return err
}
isEncrypted := imgenc.HasEncryptedLayer(ctx, descs)
if !isEncrypted {
fmt.Printf("Nothing to decrypted.\n")
return nil
}
cc, err := CreateDecryptCryptoConfig(context, descs)
if err != nil {
return err
}
_, err = decryptImage(client, ctx, local, newName, &cc, layers32, context.StringSlice("platform"))
return err
},
}

View File

@@ -1,145 +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 (
"fmt"
"github.com/containerd/containerd/cmd/ctr/commands"
encconfig "github.com/containerd/containerd/pkg/encryption/config"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var encryptCommand = cli.Command{
Name: "encrypt",
Usage: "encrypt an image locally",
ArgsUsage: "[flags] <local> <new name>",
Description: `Encrypt an image locally.
Encrypt an image using public keys managed by GPG.
The user must provide recpients who will be able to decrypt the image using
their GPG-managed private key. For this the user's GPG keyring must hold the public
keys of the recipients.
The user has control over the individual layers and the platforms they are
associated with and can encrypt them separately. If no layers or platforms are
specified, all layers for all platforms will be encrypted.
This tool also allows management of the recipients of the image through changes
to the list of recipients.
Once the image has been encrypted it may be pushed to a registry.
Recipients are declared with the protocol prefix as follows:
- pgp:<email-address>
- jwe:<public-key-file-path>
- pkcs7:<x509-file-path>
`,
Flags: append(append(commands.RegistryFlags, cli.StringSliceFlag{
Name: "recipient",
Usage: "Recipient of the image is the person who can decrypt it in the form specified above (i.e. jwe:/path/to/key)",
}, cli.IntSliceFlag{
Name: "layer",
Usage: "The layer to encrypt; this must be either the layer number or a negative number starting with -1 for topmost layer",
}, cli.StringSliceFlag{
Name: "platform",
Usage: "For which platform to encrypt; by default encrytion is done for all platforms",
}), commands.ImageDecryptionFlags...),
Action: func(context *cli.Context) error {
local := context.Args().First()
if local == "" {
return errors.New("please provide the name of an image to encrypt")
}
newName := context.Args().Get(1)
if newName != "" {
fmt.Printf("Encrypting %s to %s\n", local, newName)
} else {
fmt.Printf("Encrypting %s and replacing it with the encrypted image\n", local)
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
recipients := context.StringSlice("recipient")
if len(recipients) == 0 {
return errors.New("no recipients given -- nothing to do")
}
layers32 := commands.IntToInt32Array(context.IntSlice("layer"))
gpgRecipients, pubKeys, x509s, err := processRecipientKeys(recipients)
if err != nil {
return err
}
_, descs, err := getImageLayerInfos(client, ctx, local, layers32, context.StringSlice("platform"))
if err != nil {
return err
}
encryptCcs := []encconfig.CryptoConfig{}
_, err = createGPGClient(context)
gpgInstalled := err == nil
if len(gpgRecipients) > 0 && gpgInstalled {
gpgClient, err := createGPGClient(context)
if err != nil {
return err
}
gpgPubRingFile, err := gpgClient.ReadGPGPubRingFile()
if err != nil {
return err
}
gpgCc, err := encconfig.EncryptWithGpg(gpgRecipients, gpgPubRingFile)
if err != nil {
return err
}
encryptCcs = append(encryptCcs, gpgCc)
}
// Create Encryption Crypto Config
pkcs7Cc, err := encconfig.EncryptWithPkcs7(x509s)
if err != nil {
return err
}
encryptCcs = append(encryptCcs, pkcs7Cc)
jweCc, err := encconfig.EncryptWithJwe(pubKeys)
if err != nil {
return err
}
encryptCcs = append(encryptCcs, jweCc)
cc := encconfig.CombineCryptoConfigs(encryptCcs)
// Create Decryption CryptoConfig for use in adding recipients to
// existing image if decryptable.
decryptCc, err := CreateDecryptCryptoConfig(context, descs)
if err != nil {
return err
}
cc.EncryptConfig.AttachDecryptConfig(decryptCc.DecryptConfig)
_, err = encryptImage(client, ctx, local, newName, &cc, layers32, context.StringSlice("platform"))
return err
},
}

View File

@@ -48,9 +48,6 @@ var Command = cli.Command{
removeCommand,
tagCommand,
setLabelsCommand,
encryptCommand,
decryptCommand,
layerinfoCommand,
},
}

View File

@@ -1,118 +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 (
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/pkg/encryption"
"github.com/containerd/containerd/platforms"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var layerinfoCommand = cli.Command{
Name: "layerinfo",
Usage: "get information about an image's layers",
ArgsUsage: "[flags] <local>",
Description: `Get encryption information about the layers of an image.
Get information about the layers of an image and display with which
encryption technology the individual layers are encrypted with.
The user has control over the individual layers and the platforms they are
associated with and can retrieve information for them separately. If no
layers or platforms are specified, infomration for all layers and all
platforms will be retrieved.
`,
Flags: append(commands.RegistryFlags, cli.IntSliceFlag{
Name: "layer",
Usage: "The layer to get info for; this must be either the layer number or a negative number starting with -1 for topmost layer",
}, cli.StringSliceFlag{
Name: "platform",
Usage: "For which platform to get the layer info; by default info for all platforms is retrieved",
}, cli.StringFlag{
Name: "gpg-homedir",
Usage: "The GPG homedir to use; by default gpg uses ~/.gnupg",
}, cli.StringFlag{
Name: "gpg-version",
Usage: "The GPG version (\"v1\" or \"v2\"), default will make an educated guess",
}, cli.BoolFlag{
Name: "n",
Usage: "Do not resolve PGP key IDs to email addresses",
}),
Action: func(context *cli.Context) error {
local := context.Args().First()
if local == "" {
return errors.New("please provide the name of an image to decrypt")
}
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
layers32 := commands.IntToInt32Array(context.IntSlice("layer"))
LayerInfos, _, err := getImageLayerInfos(client, ctx, local, layers32, context.StringSlice("platform"))
if err != nil {
return err
}
if len(LayerInfos) == 0 {
return nil
}
var gpgClient encryption.GPGClient
if !context.Bool("n") {
// create a GPG client to resolve keyIds to names
gpgClient, _ = createGPGClient(context)
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.AlignRight)
fmt.Fprintf(w, "#\tDIGEST\tPLATFORM\tSIZE\tENCRYPTION\tRECIPIENTS\t\n")
for _, layer := range LayerInfos {
var recipients []string
var schemes []string
for scheme, wrappedKeys := range encryption.GetWrappedKeysMap(layer.Descriptor) {
schemes = append(schemes, scheme)
keywrapper := encryption.GetKeyWrapper(scheme)
if keywrapper != nil {
addRecipients, err := keywrapper.GetRecipients(wrappedKeys)
if err != nil {
return err
}
if scheme == "pgp" && gpgClient != nil {
addRecipients = gpgClient.ResolveRecipients(addRecipients)
}
recipients = append(recipients, addRecipients...)
} else {
recipients = append(recipients, fmt.Sprintf("No %s KeyWrapper", scheme))
}
}
sort.Strings(schemes)
sort.Strings(recipients)
fmt.Fprintf(w, "%d\t%s\t%s\t%d\t%s\t%s\t\n", layer.Index, layer.Descriptor.Digest.String(), platforms.Format(*layer.Descriptor.Platform), layer.Descriptor.Size, strings.Join(schemes, ","), strings.Join(recipients, ", "))
}
w.Flush()
return nil
},
}