mirror of
https://github.com/containers/skopeo.git
synced 2025-04-28 03:10:18 +00:00
... to include https://github.com/containers/image/pull/2173 and https://github.com/containers/common/pull/1731 . Signed-off-by: Miloslav Trmač <mitr@redhat.com>
135 lines
4.4 KiB
Go
135 lines
4.4 KiB
Go
package copy
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"github.com/containers/image/v5/types"
|
||
"github.com/containers/ocicrypt"
|
||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||
"golang.org/x/exp/maps"
|
||
"golang.org/x/exp/slices"
|
||
)
|
||
|
||
// isOciEncrypted returns a bool indicating if a mediatype is encrypted
|
||
// This function will be moved to be part of OCI spec when adopted.
|
||
func isOciEncrypted(mediatype string) bool {
|
||
return strings.HasSuffix(mediatype, "+encrypted")
|
||
}
|
||
|
||
// isEncrypted checks if an image is encrypted
|
||
func isEncrypted(i types.Image) bool {
|
||
layers := i.LayerInfos()
|
||
return slices.ContainsFunc(layers, func(l types.BlobInfo) bool {
|
||
return isOciEncrypted(l.MediaType)
|
||
})
|
||
}
|
||
|
||
// bpDecryptionStepData contains data that the copy pipeline needs about the decryption step.
|
||
type bpDecryptionStepData struct {
|
||
decrypting bool // We are actually decrypting the stream
|
||
}
|
||
|
||
// blobPipelineDecryptionStep updates *stream to decrypt if, it necessary.
|
||
// srcInfo is only used for error messages.
|
||
// Returns data for other steps; the caller should eventually use updateCryptoOperation.
|
||
func (ic *imageCopier) blobPipelineDecryptionStep(stream *sourceStream, srcInfo types.BlobInfo) (*bpDecryptionStepData, error) {
|
||
if !isOciEncrypted(stream.info.MediaType) || ic.c.options.OciDecryptConfig == nil {
|
||
return &bpDecryptionStepData{
|
||
decrypting: false,
|
||
}, nil
|
||
}
|
||
|
||
if ic.cannotModifyManifestReason != "" {
|
||
return nil, fmt.Errorf("layer %s should be decrypted, but we can’t modify the manifest: %s", srcInfo.Digest, ic.cannotModifyManifestReason)
|
||
}
|
||
|
||
desc := imgspecv1.Descriptor{
|
||
Annotations: stream.info.Annotations,
|
||
}
|
||
reader, decryptedDigest, err := ocicrypt.DecryptLayer(ic.c.options.OciDecryptConfig, stream.reader, desc, false)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("decrypting layer %s: %w", srcInfo.Digest, err)
|
||
}
|
||
|
||
stream.reader = reader
|
||
stream.info.Digest = decryptedDigest
|
||
stream.info.Size = -1
|
||
maps.DeleteFunc(stream.info.Annotations, func(k string, _ string) bool {
|
||
return strings.HasPrefix(k, "org.opencontainers.image.enc")
|
||
})
|
||
return &bpDecryptionStepData{
|
||
decrypting: true,
|
||
}, nil
|
||
}
|
||
|
||
// updateCryptoOperation sets *operation, if necessary.
|
||
func (d *bpDecryptionStepData) updateCryptoOperation(operation *types.LayerCrypto) {
|
||
if d.decrypting {
|
||
*operation = types.Decrypt
|
||
}
|
||
}
|
||
|
||
// bpEncryptionStepData contains data that the copy pipeline needs about the encryption step.
|
||
type bpEncryptionStepData struct {
|
||
encrypting bool // We are actually encrypting the stream
|
||
finalizer ocicrypt.EncryptLayerFinalizer
|
||
}
|
||
|
||
// blobPipelineEncryptionStep updates *stream to encrypt if, it required by toEncrypt.
|
||
// srcInfo is primarily used for error messages.
|
||
// Returns data for other steps; the caller should eventually call updateCryptoOperationAndAnnotations.
|
||
func (ic *imageCopier) blobPipelineEncryptionStep(stream *sourceStream, toEncrypt bool, srcInfo types.BlobInfo,
|
||
decryptionStep *bpDecryptionStepData) (*bpEncryptionStepData, error) {
|
||
if !toEncrypt || isOciEncrypted(srcInfo.MediaType) || ic.c.options.OciEncryptConfig == nil {
|
||
return &bpEncryptionStepData{
|
||
encrypting: false,
|
||
}, nil
|
||
}
|
||
|
||
if ic.cannotModifyManifestReason != "" {
|
||
return nil, fmt.Errorf("layer %s should be encrypted, but we can’t modify the manifest: %s", srcInfo.Digest, ic.cannotModifyManifestReason)
|
||
}
|
||
|
||
var annotations map[string]string
|
||
if !decryptionStep.decrypting {
|
||
annotations = srcInfo.Annotations
|
||
}
|
||
desc := imgspecv1.Descriptor{
|
||
MediaType: srcInfo.MediaType,
|
||
Digest: srcInfo.Digest,
|
||
Size: srcInfo.Size,
|
||
Annotations: annotations,
|
||
}
|
||
reader, finalizer, err := ocicrypt.EncryptLayer(ic.c.options.OciEncryptConfig, stream.reader, desc)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("encrypting blob %s: %w", srcInfo.Digest, err)
|
||
}
|
||
|
||
stream.reader = reader
|
||
stream.info.Digest = ""
|
||
stream.info.Size = -1
|
||
return &bpEncryptionStepData{
|
||
encrypting: true,
|
||
finalizer: finalizer,
|
||
}, nil
|
||
}
|
||
|
||
// updateCryptoOperationAndAnnotations sets *operation and updates *annotations, if necessary.
|
||
func (d *bpEncryptionStepData) updateCryptoOperationAndAnnotations(operation *types.LayerCrypto, annotations *map[string]string) error {
|
||
if !d.encrypting {
|
||
return nil
|
||
}
|
||
|
||
encryptAnnotations, err := d.finalizer()
|
||
if err != nil {
|
||
return fmt.Errorf("Unable to finalize encryption: %w", err)
|
||
}
|
||
*operation = types.Encrypt
|
||
if *annotations == nil {
|
||
*annotations = map[string]string{}
|
||
}
|
||
maps.Copy(*annotations, encryptAnnotations)
|
||
return nil
|
||
}
|