github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/image/crypt.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package image
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  
    24  	"github.com/containerd/containerd"
    25  	"github.com/containerd/containerd/content"
    26  	"github.com/containerd/containerd/images/converter"
    27  	"github.com/containerd/imgcrypt/images/encryption"
    28  	"github.com/containerd/imgcrypt/images/encryption/parsehelpers"
    29  	"github.com/containerd/nerdctl/v2/pkg/api/types"
    30  	"github.com/containerd/nerdctl/v2/pkg/platformutil"
    31  	"github.com/containerd/nerdctl/v2/pkg/referenceutil"
    32  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    33  )
    34  
    35  func Crypt(ctx context.Context, client *containerd.Client, srcRawRef, targetRawRef string, encrypt bool, options types.ImageCryptOptions) error {
    36  	var convertOpts = []converter.Opt{}
    37  	if srcRawRef == "" || targetRawRef == "" {
    38  		return errors.New("src and target image need to be specified")
    39  	}
    40  
    41  	srcNamed, err := referenceutil.ParseAny(srcRawRef)
    42  	if err != nil {
    43  		return err
    44  	}
    45  	srcRef := srcNamed.String()
    46  
    47  	targetNamed, err := referenceutil.ParseDockerRef(targetRawRef)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	targetRef := targetNamed.String()
    52  
    53  	platMC, err := platformutil.NewMatchComparer(options.AllPlatforms, options.Platforms)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	convertOpts = append(convertOpts, converter.WithPlatform(platMC))
    58  
    59  	imgcryptFlags, err := parseImgcryptFlags(options, encrypt)
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	srcImg, err := client.ImageService().Get(ctx, srcRef)
    65  	if err != nil {
    66  		return err
    67  	}
    68  	layerDescs, err := platformutil.LayerDescs(ctx, client.ContentStore(), srcImg.Target, platMC)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	layerFilter := func(desc ocispec.Descriptor) bool {
    73  		return true
    74  	}
    75  	var convertFunc converter.ConvertFunc
    76  	if encrypt {
    77  		cc, err := parsehelpers.CreateCryptoConfig(imgcryptFlags, layerDescs)
    78  		if err != nil {
    79  			return err
    80  		}
    81  		convertFunc = encryption.GetImageEncryptConverter(&cc, layerFilter)
    82  	} else {
    83  		cc, err := parsehelpers.CreateDecryptCryptoConfig(imgcryptFlags, layerDescs)
    84  		if err != nil {
    85  			return err
    86  		}
    87  		convertFunc = encryption.GetImageDecryptConverter(&cc, layerFilter)
    88  	}
    89  	// we have to compose the DefaultIndexConvertFunc here to match platforms.
    90  	convertFunc = composeConvertFunc(converter.DefaultIndexConvertFunc(nil, false, platMC), convertFunc)
    91  	convertOpts = append(convertOpts, converter.WithIndexConvertFunc(convertFunc))
    92  
    93  	// converter.Convert() gains the lease by itself
    94  	newImg, err := converter.Convert(ctx, client, targetRef, srcRef, convertOpts...)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	fmt.Fprintln(options.Stdout, newImg.Target.Digest.String())
    99  	return nil
   100  }
   101  
   102  // parseImgcryptFlags corresponds to https://github.com/containerd/imgcrypt/blob/v1.1.2/cmd/ctr/commands/images/crypt_utils.go#L244-L252
   103  func parseImgcryptFlags(options types.ImageCryptOptions, encrypt bool) (parsehelpers.EncArgs, error) {
   104  	var a parsehelpers.EncArgs
   105  
   106  	a.GPGHomedir = options.GpgHomeDir
   107  	a.GPGVersion = options.GpgVersion
   108  	a.Key = options.Keys
   109  	if encrypt {
   110  		a.Recipient = options.Recipients
   111  		if len(a.Recipient) == 0 {
   112  			return a, errors.New("at least one recipient must be specified (e.g., --recipient=jwe:mypubkey.pem)")
   113  		}
   114  	}
   115  	// While --recipient can be specified only for `nerdctl image encrypt`,
   116  	// --dec-recipient can be specified for both `nerdctl image encrypt` and `nerdctl image decrypt`.
   117  	a.DecRecipient = options.DecRecipients
   118  	return a, nil
   119  }
   120  
   121  func composeConvertFunc(a, b converter.ConvertFunc) converter.ConvertFunc {
   122  	return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) {
   123  		newDesc, err := a(ctx, cs, desc)
   124  		if err != nil {
   125  			return newDesc, err
   126  		}
   127  		if newDesc == nil {
   128  			return b(ctx, cs, desc)
   129  		}
   130  		return b(ctx, cs, *newDesc)
   131  	}
   132  }