Add option to preserve digests on copy

When enabled, if digests can't be preserved an error will be raised.

Signed-off-by: James Hewitt <james.hewitt@uk.ibm.com>
This commit is contained in:
James Hewitt 2021-11-21 16:20:33 +00:00
parent 25868f17c0
commit 2046bfdaaa
15 changed files with 207 additions and 43 deletions

View File

@ -33,6 +33,7 @@ type copyOptions struct {
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
multiArch commonFlag.OptionalString // How to handle multi architecture images multiArch commonFlag.OptionalString // How to handle multi architecture images
preserveDigests bool // Preserve digests during copy
encryptLayer []int // The list of layers to encrypt encryptLayer []int // The list of layers to encrypt
encryptionKeys []string // Keys needed to encrypt the image encryptionKeys []string // Keys needed to encrypt the image
decryptionKeys []string // Keys needed to decrypt the image decryptionKeys []string // Keys needed to decrypt the image
@ -74,6 +75,7 @@ See skopeo(1) section "IMAGE NAMES" for the expected format
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress output information when copying images") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress output information when copying images")
flags.BoolVarP(&opts.all, "all", "a", false, "Copy all images if SOURCE-IMAGE is a list") flags.BoolVarP(&opts.all, "all", "a", false, "Copy all images if SOURCE-IMAGE is a list")
flags.Var(commonFlag.NewOptionalStringValue(&opts.multiArch), "multi-arch", `How to handle multi-architecture images (system, all, or index-only)`) flags.Var(commonFlag.NewOptionalStringValue(&opts.multiArch), "multi-arch", `How to handle multi-architecture images (system, all, or index-only)`)
flags.BoolVar(&opts.preserveDigests, "preserve-digests", false, "Preserve digests of images and lists")
flags.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from SOURCE-IMAGE") flags.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from SOURCE-IMAGE")
flags.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`") flags.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`")
flags.StringVar(&opts.digestFile, "digestfile", "", "Write the digest of the pushed image to the specified file") flags.StringVar(&opts.digestFile, "digestfile", "", "Write the digest of the pushed image to the specified file")
@ -226,6 +228,7 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
DestinationCtx: destinationCtx, DestinationCtx: destinationCtx,
ForceManifestMIMEType: manifestType, ForceManifestMIMEType: manifestType,
ImageListSelection: imageListSelection, ImageListSelection: imageListSelection,
PreserveDigests: opts.preserveDigests,
OciDecryptConfig: decConfig, OciDecryptConfig: decConfig,
OciEncryptLayers: encLayers, OciEncryptLayers: encLayers,
OciEncryptConfig: encConfig, OciEncryptConfig: encConfig,

View File

@ -67,6 +67,7 @@ _skopeo_copy() {
--dest-no-creds --dest-no-creds
--dest-oci-accept-uncompressed-layers --dest-oci-accept-uncompressed-layers
--dest-precompute-digests --dest-precompute-digests
--preserve-digests
" "
local transports local transports

View File

@ -54,6 +54,10 @@ Directory to use to share blobs across OCI repositories.
After copying the image, write the digest of the resulting image to the file. After copying the image, write the digest of the resulting image to the file.
**--preserve-digests**
Preserve the digests during copying. Fail if the digest cannot be preserved.
**--encrypt-layer** _ints_ **--encrypt-layer** _ints_
*Experimental* the 0-indexed layer indices, with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer) *Experimental* the 0-indexed layer indices, with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer)

2
go.mod
View File

@ -5,7 +5,7 @@ go 1.12
require ( require (
github.com/containerd/containerd v1.5.8 // indirect github.com/containerd/containerd v1.5.8 // indirect
github.com/containers/common v0.46.1-0.20211026130826-7abfd453c86f github.com/containers/common v0.46.1-0.20211026130826-7abfd453c86f
github.com/containers/image/v5 v5.17.0 github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c
github.com/containers/ocicrypt v1.1.2 github.com/containers/ocicrypt v1.1.2
github.com/containers/storage v1.37.0 github.com/containers/storage v1.37.0
github.com/docker/docker v20.10.11+incompatible github.com/docker/docker v20.10.11+incompatible

7
go.sum
View File

@ -232,8 +232,8 @@ github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRD
github.com/containers/common v0.46.1-0.20211026130826-7abfd453c86f h1:jFFIV8QvsPgwkJHh3tjfREFRwSeMq5M8lt8vklkZaOk= github.com/containers/common v0.46.1-0.20211026130826-7abfd453c86f h1:jFFIV8QvsPgwkJHh3tjfREFRwSeMq5M8lt8vklkZaOk=
github.com/containers/common v0.46.1-0.20211026130826-7abfd453c86f/go.mod h1:pVvmLTLCOZE300e4rex/QDmpnRmEM/5aZ/YfCkkjgZo= github.com/containers/common v0.46.1-0.20211026130826-7abfd453c86f/go.mod h1:pVvmLTLCOZE300e4rex/QDmpnRmEM/5aZ/YfCkkjgZo=
github.com/containers/image/v5 v5.16.1/go.mod h1:mCvIFdzyyP1B0NBcZ80OIuaYqFn/OpFpaOMOMn1kU2M= github.com/containers/image/v5 v5.16.1/go.mod h1:mCvIFdzyyP1B0NBcZ80OIuaYqFn/OpFpaOMOMn1kU2M=
github.com/containers/image/v5 v5.17.0 h1:KS5pro80CCsSp5qDBTMmSAWQo+xcBX19zUPExmYX2OQ= github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c h1:WfMOQlq3CDvVe5ONUGfj9/MajskqUHnbo24j24Xg2ZM=
github.com/containers/image/v5 v5.17.0/go.mod h1:GnYVusVRFPMMTAAUkrcS8NNSpBp8oyrjOUe04AAmRr4= github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c/go.mod h1:boW5ckkT0wu9obDEiOIxrtWQmz1znMuHiVMQPcpHnk0=
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE=
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=
@ -651,8 +651,9 @@ github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqi
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
github.com/opencontainers/selinux v1.8.5/go.mod h1:HTvjPFoGMbpQsG886e3lQwnsRWtE4TC1OF3OUvG9FAo= github.com/opencontainers/selinux v1.8.5/go.mod h1:HTvjPFoGMbpQsG886e3lQwnsRWtE4TC1OF3OUvG9FAo=
github.com/opencontainers/selinux v1.9.1 h1:b4VPEF3O5JLZgdTDBmGepaaIbAo0GqoF6EBRq5f/g3Y=
github.com/opencontainers/selinux v1.9.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opencontainers/selinux v1.9.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw=
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=

View File

@ -1248,6 +1248,15 @@ func (s *CopySuite) TestCopyManifestConversion(c *check.C) {
verifyManifestMIMEType(c, destDir2, manifest.DockerV2Schema2MediaType) verifyManifestMIMEType(c, destDir2, manifest.DockerV2Schema2MediaType)
} }
func (s *CopySuite) TestCopyPreserveDigests(c *check.C) {
topDir, err := ioutil.TempDir("", "preserve-digests")
c.Assert(err, check.IsNil)
defer os.RemoveAll(topDir)
assertSkopeoSucceeds(c, "", "copy", knownListImage, "--multi-arch=all", "--preserve-digests", "dir:"+topDir)
assertSkopeoFails(c, ".*Instructed to preserve digests.*", "copy", knownListImage, "--multi-arch=all", "--preserve-digests", "--format=oci", "dir:"+topDir)
}
func (s *CopySuite) testCopySchemaConversionRegistries(c *check.C, schema1Registry, schema2Registry string) { func (s *CopySuite) testCopySchemaConversionRegistries(c *check.C, schema1Registry, schema2Registry string) {
topDir, err := ioutil.TempDir("", "schema-conversion") topDir, err := ioutil.TempDir("", "schema-conversion")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)

View File

@ -80,13 +80,13 @@ type copier struct {
// imageCopier tracks state specific to a single image (possibly an item of a manifest list) // imageCopier tracks state specific to a single image (possibly an item of a manifest list)
type imageCopier struct { type imageCopier struct {
c *copier c *copier
manifestUpdates *types.ManifestUpdateOptions manifestUpdates *types.ManifestUpdateOptions
src types.Image src types.Image
diffIDsAreNeeded bool diffIDsAreNeeded bool
canModifyManifest bool cannotModifyManifestReason string // The reason the manifest cannot be modified, or an empty string if it can
canSubstituteBlobs bool canSubstituteBlobs bool
ociEncryptLayers *[]int ociEncryptLayers *[]int
} }
const ( const (
@ -129,10 +129,14 @@ type Options struct {
DestinationCtx *types.SystemContext DestinationCtx *types.SystemContext
ProgressInterval time.Duration // time to wait between reports to signal the progress channel ProgressInterval time.Duration // time to wait between reports to signal the progress channel
Progress chan types.ProgressProperties // Reported to when ProgressInterval has arrived for a single artifact+offset. Progress chan types.ProgressProperties // Reported to when ProgressInterval has arrived for a single artifact+offset.
// Preserve digests, and fail if we cannot.
PreserveDigests bool
// manifest MIME type of image set by user. "" is default and means use the autodetection to the the manifest MIME type // manifest MIME type of image set by user. "" is default and means use the autodetection to the the manifest MIME type
ForceManifestMIMEType string ForceManifestMIMEType string
ImageListSelection ImageListSelection // set to either CopySystemImage (the default), CopyAllImages, or CopySpecificImages to control which instances we copy when the source reference is a list; ignored if the source reference is not a list ImageListSelection ImageListSelection // set to either CopySystemImage (the default), CopyAllImages, or CopySpecificImages to control which instances we copy when the source reference is a list; ignored if the source reference is not a list
Instances []digest.Digest // if ImageListSelection is CopySpecificImages, copy only these instances and the list itself Instances []digest.Digest // if ImageListSelection is CopySpecificImages, copy only these instances and the list itself
// If OciEncryptConfig is non-nil, it indicates that an image should be encrypted. // If OciEncryptConfig is non-nil, it indicates that an image should be encrypted.
// The encryption options is derived from the construction of EncryptConfig object. // The encryption options is derived from the construction of EncryptConfig object.
// Note: During initial encryption process of a layer, the resultant digest is not known // Note: During initial encryption process of a layer, the resultant digest is not known
@ -410,7 +414,36 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
return nil, errors.Wrapf(err, "Can not copy signatures to %s", transports.ImageName(c.dest.Reference())) return nil, errors.Wrapf(err, "Can not copy signatures to %s", transports.ImageName(c.dest.Reference()))
} }
} }
canModifyManifestList := (len(sigs) == 0)
// If the destination is a digested reference, make a note of that, determine what digest value we're
// expecting, and check that the source manifest matches it.
destIsDigestedReference := false
if named := c.dest.Reference().DockerReference(); named != nil {
if digested, ok := named.(reference.Digested); ok {
destIsDigestedReference = true
matches, err := manifest.MatchesDigest(manifestList, digested.Digest())
if err != nil {
return nil, errors.Wrapf(err, "computing digest of source image's manifest")
}
if !matches {
return nil, errors.New("Digest of source image's manifest would not match destination reference")
}
}
}
// Determine if we're allowed to modify the manifest list.
// If we can, set to the empty string. If we can't, set to the reason why.
// Compare, and perhaps keep in sync with, the version in copyOneImage.
cannotModifyManifestListReason := ""
if len(sigs) > 0 {
cannotModifyManifestListReason = "Would invalidate signatures"
}
if destIsDigestedReference {
cannotModifyManifestListReason = "Destination specifies a digest"
}
if options.PreserveDigests {
cannotModifyManifestListReason = "Instructed to preserve digests"
}
// Determine if we'll need to convert the manifest list to a different format. // Determine if we'll need to convert the manifest list to a different format.
forceListMIMEType := options.ForceManifestMIMEType forceListMIMEType := options.ForceManifestMIMEType
@ -425,8 +458,8 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
return nil, errors.Wrapf(err, "determining manifest list type to write to destination") return nil, errors.Wrapf(err, "determining manifest list type to write to destination")
} }
if selectedListType != originalList.MIMEType() { if selectedListType != originalList.MIMEType() {
if !canModifyManifestList { if cannotModifyManifestListReason != "" {
return nil, errors.Errorf("manifest list must be converted to type %q to be written to destination, but that would invalidate signatures", selectedListType) return nil, errors.Errorf("Manifest list must be converted to type %q to be written to destination, but we cannot modify it: %q", selectedListType, cannotModifyManifestListReason)
} }
} }
@ -510,8 +543,8 @@ func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signatur
// If we can't just use the original value, but we have to change it, flag an error. // If we can't just use the original value, but we have to change it, flag an error.
if !bytes.Equal(attemptedManifestList, originalManifestList) { if !bytes.Equal(attemptedManifestList, originalManifestList) {
if !canModifyManifestList { if cannotModifyManifestListReason != "" {
return nil, errors.Errorf(" manifest list must be converted to type %q to be written to destination, but that would invalidate signatures", thisListType) return nil, errors.Errorf("Manifest list must be converted to type %q to be written to destination, but we cannot modify it: %q", thisListType, cannotModifyManifestListReason)
} }
logrus.Debugf("Manifest list has been updated") logrus.Debugf("Manifest list has been updated")
} else { } else {
@ -629,13 +662,27 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
} }
} }
// Determine if we're allowed to modify the manifest.
// If we can, set to the empty string. If we can't, set to the reason why.
// Compare, and perhaps keep in sync with, the version in copyMultipleImages.
cannotModifyManifestReason := ""
if len(sigs) > 0 {
cannotModifyManifestReason = "Would invalidate signatures"
}
if destIsDigestedReference {
cannotModifyManifestReason = "Destination specifies a digest"
}
if options.PreserveDigests {
cannotModifyManifestReason = "Instructed to preserve digests"
}
ic := imageCopier{ ic := imageCopier{
c: c, c: c,
manifestUpdates: &types.ManifestUpdateOptions{InformationOnly: types.ManifestUpdateInformation{Destination: c.dest}}, manifestUpdates: &types.ManifestUpdateOptions{InformationOnly: types.ManifestUpdateInformation{Destination: c.dest}},
src: src, src: src,
// diffIDsAreNeeded is computed later // diffIDsAreNeeded is computed later
canModifyManifest: len(sigs) == 0 && !destIsDigestedReference, cannotModifyManifestReason: cannotModifyManifestReason,
ociEncryptLayers: options.OciEncryptLayers, ociEncryptLayers: options.OciEncryptLayers,
} }
// Ensure _this_ copy sees exactly the intended data when either processing a signed image or signing it. // Ensure _this_ copy sees exactly the intended data when either processing a signed image or signing it.
// This may be too conservative, but for now, better safe than sorry, _especially_ on the SignBy path: // This may be too conservative, but for now, better safe than sorry, _especially_ on the SignBy path:
@ -643,7 +690,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
// We do intend the RecordDigestUncompressedPair calls to only work with reliable data, but at least theres a risk // We do intend the RecordDigestUncompressedPair calls to only work with reliable data, but at least theres a risk
// that the compressed version coming from a third party may be designed to attack some other decompressor implementation, // that the compressed version coming from a third party may be designed to attack some other decompressor implementation,
// and we would reuse and sign it. // and we would reuse and sign it.
ic.canSubstituteBlobs = ic.canModifyManifest && options.SignBy == "" ic.canSubstituteBlobs = ic.cannotModifyManifestReason == "" && options.SignBy == ""
if err := ic.updateEmbeddedDockerReference(); err != nil { if err := ic.updateEmbeddedDockerReference(); err != nil {
return nil, "", "", err return nil, "", "", err
@ -710,10 +757,10 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
} }
// If the original MIME type is acceptable, determineManifestConversion always uses it as preferredManifestMIMEType. // If the original MIME type is acceptable, determineManifestConversion always uses it as preferredManifestMIMEType.
// So if we are here, we will definitely be trying to convert the manifest. // So if we are here, we will definitely be trying to convert the manifest.
// With !ic.canModifyManifest, that would just be a string of repeated failures for the same reason, // With ic.cannotModifyManifestReason != "", that would just be a string of repeated failures for the same reason,
// so lets bail out early and with a better error message. // so lets bail out early and with a better error message.
if !ic.canModifyManifest { if ic.cannotModifyManifestReason != "" {
return nil, "", "", errors.Wrap(err, "Writing manifest failed (and converting it is not possible, image is signed or the destination specifies a digest)") return nil, "", "", errors.Wrapf(err, "Writing manifest failed and we cannot try conversions: %q", cannotModifyManifestReason)
} }
// errs is a list of errors when trying various manifest types. Also serves as an "upload succeeded" flag when set to nil. // errs is a list of errors when trying various manifest types. Also serves as an "upload succeeded" flag when set to nil.
@ -813,9 +860,9 @@ func (ic *imageCopier) updateEmbeddedDockerReference() error {
return nil // No reference embedded in the manifest, or it matches destRef already. return nil // No reference embedded in the manifest, or it matches destRef already.
} }
if !ic.canModifyManifest { if ic.cannotModifyManifestReason != "" {
return errors.Errorf("Copying a schema1 image with an embedded Docker reference to %s (Docker reference %s) would change the manifest, which is not possible (image is signed or the destination specifies a digest)", return errors.Errorf("Copying a schema1 image with an embedded Docker reference to %s (Docker reference %s) would change the manifest, which we cannot do: %q",
transports.ImageName(ic.c.dest.Reference()), destRef.String()) transports.ImageName(ic.c.dest.Reference()), destRef.String(), ic.cannotModifyManifestReason)
} }
ic.manifestUpdates.EmbeddedDockerReference = destRef ic.manifestUpdates.EmbeddedDockerReference = destRef
return nil return nil
@ -833,7 +880,7 @@ func isTTY(w io.Writer) bool {
return false return false
} }
// copyLayers copies layers from ic.src/ic.c.rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.canModifyManifest. // copyLayers copies layers from ic.src/ic.c.rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.cannotModifyManifestReason == "".
func (ic *imageCopier) copyLayers(ctx context.Context) error { func (ic *imageCopier) copyLayers(ctx context.Context) error {
srcInfos := ic.src.LayerInfos() srcInfos := ic.src.LayerInfos()
numLayers := len(srcInfos) numLayers := len(srcInfos)
@ -844,8 +891,8 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error {
srcInfosUpdated := false srcInfosUpdated := false
// If we only need to check authorization, no updates required. // If we only need to check authorization, no updates required.
if updatedSrcInfos != nil && !reflect.DeepEqual(srcInfos, updatedSrcInfos) { if updatedSrcInfos != nil && !reflect.DeepEqual(srcInfos, updatedSrcInfos) {
if !ic.canModifyManifest { if ic.cannotModifyManifestReason != "" {
return errors.Errorf("Copying this image requires changing layer representation, which is not possible (image is signed or the destination specifies a digest)") return errors.Errorf("Copying this image would require changing layer representation, which we cannot do: %q", ic.cannotModifyManifestReason)
} }
srcInfos = updatedSrcInfos srcInfos = updatedSrcInfos
srcInfosUpdated = true srcInfosUpdated = true
@ -975,8 +1022,8 @@ func layerDigestsDiffer(a, b []types.BlobInfo) bool {
func (ic *imageCopier) copyUpdatedConfigAndManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, digest.Digest, error) { func (ic *imageCopier) copyUpdatedConfigAndManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, digest.Digest, error) {
pendingImage := ic.src pendingImage := ic.src
if !ic.noPendingManifestUpdates() { if !ic.noPendingManifestUpdates() {
if !ic.canModifyManifest { if ic.cannotModifyManifestReason != "" {
return nil, "", errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden") return nil, "", errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden: %q", ic.cannotModifyManifestReason)
} }
if !ic.diffIDsAreNeeded && ic.src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates) { if !ic.diffIDsAreNeeded && ic.src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates) {
// We have set ic.diffIDsAreNeeded based on the preferred MIME type returned by determineManifestConversion. // We have set ic.diffIDsAreNeeded based on the preferred MIME type returned by determineManifestConversion.
@ -1359,7 +1406,7 @@ func (ic *imageCopier) copyLayerFromStream(ctx context.Context, srcStream io.Rea
} }
} }
blobInfo, err := ic.c.copyBlobFromStream(ctx, srcStream, srcInfo, getDiffIDRecorder, ic.canModifyManifest, false, toEncrypt, bar, layerIndex, emptyLayer) // Sets err to nil on success blobInfo, err := ic.c.copyBlobFromStream(ctx, srcStream, srcInfo, getDiffIDRecorder, ic.cannotModifyManifestReason == "", false, toEncrypt, bar, layerIndex, emptyLayer) // Sets err to nil on success
return blobInfo, diffIDChan, err return blobInfo, diffIDChan, err
// We need the defer … pipeWriter.CloseWithError() to happen HERE so that the caller can block on reading from diffIDChan // We need the defer … pipeWriter.CloseWithError() to happen HERE so that the caller can block on reading from diffIDChan
} }

View File

@ -79,10 +79,10 @@ func (ic *imageCopier) determineManifestConversion(ctx context.Context, destSupp
if _, ok := supportedByDest[srcType]; ok { if _, ok := supportedByDest[srcType]; ok {
prioritizedTypes.append(srcType) prioritizedTypes.append(srcType)
} }
if !ic.canModifyManifest { if ic.cannotModifyManifestReason != "" {
// We could also drop the !ic.canModifyManifest check and have the caller // We could also drop this check and have the caller
// make the choice; it is already doing that to an extent, to improve error // make the choice; it is already doing that to an extent, to improve error
// messages. But it is nice to hide the “if !ic.canModifyManifest, do no conversion” // messages. But it is nice to hide the “if we can't modify, do no conversion”
// special case in here; the caller can then worry (or not) only about a good UI. // special case in here; the caller can then worry (or not) only about a good UI.
logrus.Debugf("We can't modify the manifest, hoping for the best...") logrus.Debugf("We can't modify the manifest, hoping for the best...")
return srcType, []string{}, nil // Take our chances - FIXME? Or should we fail without trying? return srcType, []string{}, nil // Take our chances - FIXME? Or should we fail without trying?

View File

@ -561,6 +561,11 @@ type SystemContext struct {
UserShortNameAliasConfPath string UserShortNameAliasConfPath string
// If set, short-name resolution in pkg/shortnames must follow the specified mode // If set, short-name resolution in pkg/shortnames must follow the specified mode
ShortNameMode *ShortNameMode ShortNameMode *ShortNameMode
// If set, short names will resolve in pkg/shortnames to docker.io only, and unqualified-search registries and
// short-name aliases in registries.conf are ignored. Note that this field is only intended to help enforce
// resolving to Docker Hub in the Docker-compatible REST API of Podman; it should never be used outside this
// specific context.
PodmanOnlyShortNamesIgnoreRegistriesConfAndForceDockerHub bool
// If not "", overrides the default path for the authentication file, but only new format files // If not "", overrides the default path for the authentication file, but only new format files
AuthFilePath string AuthFilePath string
// if not "", overrides the default path for the authentication file, but with the legacy format; // if not "", overrides the default path for the authentication file, but with the legacy format;

View File

@ -8,10 +8,10 @@ const (
// VersionMinor is for functionality in a backwards-compatible manner // VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 17 VersionMinor = 17
// VersionPatch is for backwards-compatible bug fixes // VersionPatch is for backwards-compatible bug fixes
VersionPatch = 0 VersionPatch = 1
// VersionDev indicates development branch. Releases will be empty string. // VersionDev indicates development branch. Releases will be empty string.
VersionDev = "" VersionDev = "-dev"
) )
// Version is the specification version that the package types support. // Version is the specification version that the package types support.

View File

@ -61,16 +61,30 @@ func ClassIndex(class string) (int, error) {
return classIndex(class) return classIndex(class)
} }
// SetFileLabel sets the SELinux label for this path or returns an error. // SetFileLabel sets the SELinux label for this path, following symlinks,
// or returns an error.
func SetFileLabel(fpath string, label string) error { func SetFileLabel(fpath string, label string) error {
return setFileLabel(fpath, label) return setFileLabel(fpath, label)
} }
// FileLabel returns the SELinux label for this path or returns an error. // LsetFileLabel sets the SELinux label for this path, not following symlinks,
// or returns an error.
func LsetFileLabel(fpath string, label string) error {
return lSetFileLabel(fpath, label)
}
// FileLabel returns the SELinux label for this path, following symlinks,
// or returns an error.
func FileLabel(fpath string) (string, error) { func FileLabel(fpath string) (string, error) {
return fileLabel(fpath) return fileLabel(fpath)
} }
// LfileLabel returns the SELinux label for this path, not following symlinks,
// or returns an error.
func LfileLabel(fpath string) (string, error) {
return lFileLabel(fpath)
}
// SetFSCreateLabel tells the kernel what label to use for all file system objects // SetFSCreateLabel tells the kernel what label to use for all file system objects
// created by this task. // created by this task.
// Set the label to an empty string to return to the default label. Calls to SetFSCreateLabel // Set the label to an empty string to return to the default label. Calls to SetFSCreateLabel

View File

@ -316,8 +316,9 @@ func classIndex(class string) (int, error) {
return index, nil return index, nil
} }
// setFileLabel sets the SELinux label for this path or returns an error. // lSetFileLabel sets the SELinux label for this path, not following symlinks,
func setFileLabel(fpath string, label string) error { // or returns an error.
func lSetFileLabel(fpath string, label string) error {
if fpath == "" { if fpath == "" {
return ErrEmptyPath return ErrEmptyPath
} }
@ -334,12 +335,50 @@ func setFileLabel(fpath string, label string) error {
return nil return nil
} }
// fileLabel returns the SELinux label for this path or returns an error. // setFileLabel sets the SELinux label for this path, following symlinks,
// or returns an error.
func setFileLabel(fpath string, label string) error {
if fpath == "" {
return ErrEmptyPath
}
for {
err := unix.Setxattr(fpath, xattrNameSelinux, []byte(label), 0)
if err == nil {
break
}
if err != unix.EINTR { //nolint:errorlint // unix errors are bare
return &os.PathError{Op: "setxattr", Path: fpath, Err: err}
}
}
return nil
}
// fileLabel returns the SELinux label for this path, following symlinks,
// or returns an error.
func fileLabel(fpath string) (string, error) { func fileLabel(fpath string) (string, error) {
if fpath == "" { if fpath == "" {
return "", ErrEmptyPath return "", ErrEmptyPath
} }
label, err := getxattr(fpath, xattrNameSelinux)
if err != nil {
return "", &os.PathError{Op: "getxattr", Path: fpath, Err: err}
}
// Trim the NUL byte at the end of the byte buffer, if present.
if len(label) > 0 && label[len(label)-1] == '\x00' {
label = label[:len(label)-1]
}
return string(label), nil
}
// lFileLabel returns the SELinux label for this path, not following symlinks,
// or returns an error.
func lFileLabel(fpath string) (string, error) {
if fpath == "" {
return "", ErrEmptyPath
}
label, err := lgetxattr(fpath, xattrNameSelinux) label, err := lgetxattr(fpath, xattrNameSelinux)
if err != nil { if err != nil {
return "", &os.PathError{Op: "lgetxattr", Path: fpath, Err: err} return "", &os.PathError{Op: "lgetxattr", Path: fpath, Err: err}

View File

@ -17,10 +17,18 @@ func setFileLabel(fpath string, label string) error {
return nil return nil
} }
func lSetFileLabel(fpath string, label string) error {
return nil
}
func fileLabel(fpath string) (string, error) { func fileLabel(fpath string) (string, error) {
return "", nil return "", nil
} }
func lFileLabel(fpath string) (string, error) {
return "", nil
}
func setFSCreateLabel(label string) error { func setFSCreateLabel(label string) error {
return nil return nil
} }

View File

@ -36,3 +36,36 @@ func doLgetxattr(path, attr string, dest []byte) (int, error) {
} }
} }
} }
// getxattr returns a []byte slice containing the value of
// an extended attribute attr set for path.
func getxattr(path, attr string) ([]byte, error) {
// Start with a 128 length byte array
dest := make([]byte, 128)
sz, errno := dogetxattr(path, attr, dest)
for errno == unix.ERANGE { //nolint:errorlint // unix errors are bare
// Buffer too small, use zero-sized buffer to get the actual size
sz, errno = dogetxattr(path, attr, []byte{})
if errno != nil {
return nil, errno
}
dest = make([]byte, sz)
sz, errno = dogetxattr(path, attr, dest)
}
if errno != nil {
return nil, errno
}
return dest[:sz], nil
}
// dogetxattr is a wrapper that retries on EINTR
func dogetxattr(path, attr string, dest []byte) (int, error) {
for {
sz, err := unix.Getxattr(path, attr, dest)
if err != unix.EINTR { //nolint:errorlint // unix errors are bare
return sz, err
}
}
}

4
vendor/modules.txt vendored
View File

@ -53,7 +53,7 @@ github.com/containers/common/pkg/flag
github.com/containers/common/pkg/report github.com/containers/common/pkg/report
github.com/containers/common/pkg/report/camelcase github.com/containers/common/pkg/report/camelcase
github.com/containers/common/pkg/retry github.com/containers/common/pkg/retry
# github.com/containers/image/v5 v5.17.0 # github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c
github.com/containers/image/v5/copy github.com/containers/image/v5/copy
github.com/containers/image/v5/directory github.com/containers/image/v5/directory
github.com/containers/image/v5/directory/explicitfilepath github.com/containers/image/v5/directory/explicitfilepath
@ -283,7 +283,7 @@ github.com/opencontainers/runc/libcontainer/user
github.com/opencontainers/runc/libcontainer/userns github.com/opencontainers/runc/libcontainer/userns
# github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 # github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
github.com/opencontainers/runtime-spec/specs-go github.com/opencontainers/runtime-spec/specs-go
# github.com/opencontainers/selinux v1.9.1 # github.com/opencontainers/selinux v1.10.0
github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux
github.com/opencontainers/selinux/go-selinux/label github.com/opencontainers/selinux/go-selinux/label
github.com/opencontainers/selinux/pkg/pwalk github.com/opencontainers/selinux/pkg/pwalk