dnm: Vendor changes from containers/container-libs#312
This commit is contained in:
parent
ae46726d30
commit
e9b5d376b7
6
go.mod
6
go.mod
|
@ -137,3 +137,9 @@ require (
|
|||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect
|
||||
)
|
||||
|
||||
replace go.podman.io/common => github.com/bitoku/container-libs/common v0.0.0-20251001085230-f74ad300c684
|
||||
|
||||
replace go.podman.io/storage => github.com/bitoku/container-libs/storage v0.0.0-20251001085230-f74ad300c684
|
||||
|
||||
replace go.podman.io/image/v5 => github.com/bitoku/container-libs/image/v5 v5.0.0-20251001085230-f74ad300c684
|
||||
|
|
12
go.sum
12
go.sum
|
@ -18,6 +18,12 @@ github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpY
|
|||
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bitoku/container-libs/common v0.0.0-20251001085230-f74ad300c684 h1:9nKP5MUjA0tyrqUqvEzeHpYLBGkK+qh+EdVXfHC0kfc=
|
||||
github.com/bitoku/container-libs/common v0.0.0-20251001085230-f74ad300c684/go.mod h1:DyOdwtkwzYA8lE0TueJnxRju4Lmsrx6ZAC/ATAkYYck=
|
||||
github.com/bitoku/container-libs/image/v5 v5.0.0-20251001085230-f74ad300c684 h1:no+ZS52NRYbenjo2Hte4UCdFH9PMV8gIDt7yXfAAF2E=
|
||||
github.com/bitoku/container-libs/image/v5 v5.0.0-20251001085230-f74ad300c684/go.mod h1:cGWb3IyBziJGxhFikTOlt9Ap+zo6s3rz9Qd1rbzqs4s=
|
||||
github.com/bitoku/container-libs/storage v0.0.0-20251001085230-f74ad300c684 h1:ZPtKzmDeEUf4RU/I77Xb1jASYLTYj5h9/325ymQH624=
|
||||
github.com/bitoku/container-libs/storage v0.0.0-20251001085230-f74ad300c684/go.mod h1:AeZXAN8Qu1gTlAEHIc6mVhxk+61oMSM3K3iLx5UAQWE=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
|
@ -317,12 +323,6 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr
|
|||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.podman.io/common v0.65.1-0.20250916163606-92222dcd3da4 h1:YjBqTOxz4cqfpifcd71VoBl1FTQL2U2La5NgMqmRRqU=
|
||||
go.podman.io/common v0.65.1-0.20250916163606-92222dcd3da4/go.mod h1:DyOdwtkwzYA8lE0TueJnxRju4Lmsrx6ZAC/ATAkYYck=
|
||||
go.podman.io/image/v5 v5.37.1-0.20250916163606-92222dcd3da4 h1:hfc3lZaxi6KGnWN3IusIaCkcMPR4rTR+vWZzakeD1EA=
|
||||
go.podman.io/image/v5 v5.37.1-0.20250916163606-92222dcd3da4/go.mod h1:cGWb3IyBziJGxhFikTOlt9Ap+zo6s3rz9Qd1rbzqs4s=
|
||||
go.podman.io/storage v1.60.1-0.20250916163606-92222dcd3da4 h1:jo0PSKh6muU7rmhXXqOV9aK+HrA8koqs47KhBsZf6LY=
|
||||
go.podman.io/storage v1.60.1-0.20250916163606-92222dcd3da4/go.mod h1:AeZXAN8Qu1gTlAEHIc6mVhxk+61oMSM3K3iLx5UAQWE=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
|
|
|
@ -707,6 +707,13 @@ type Destination struct {
|
|||
// Identity file with ssh key, optional
|
||||
Identity string `json:",omitempty" toml:"identity,omitempty"`
|
||||
|
||||
// Path to TLS client certificate PEM file, optional
|
||||
TLSCert string `json:",omitempty" toml:"tls_cert,omitempty"`
|
||||
// Path to TLS client certificate private key PEM file, optional
|
||||
TLSKey string `json:",omitempty" toml:"tls_key,omitempty"`
|
||||
// Path to TLS certificate authority PEM file, optional
|
||||
TLSCA string `json:",omitempty" toml:"tls_ca,omitempty"`
|
||||
|
||||
// isMachine describes if the remote destination is a machine.
|
||||
IsMachine bool `json:",omitempty" toml:"is_machine,omitempty"`
|
||||
}
|
||||
|
|
|
@ -779,10 +779,17 @@ default_sysctls = [
|
|||
# rootful "unix:///run/podman/podman.sock (Default)
|
||||
# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
|
||||
# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
|
||||
# tcp/tls remote tcp://10.10.1.136:9443
|
||||
#
|
||||
# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
|
||||
# Path to file containing ssh identity key
|
||||
# identity = "~/.ssh/id_rsa"
|
||||
# Path to PEM file containing TLS client certificate
|
||||
# tls_cert = "/path/to/certs/podman/tls.crt"
|
||||
# Path to PEM file containing TLS client certificate private key
|
||||
# tls_key = "/path/to/certs/podman/tls.key"
|
||||
# Path to PEM file containing TLS certificate authority (CA) bundle
|
||||
# tls_ca = "/path/to/certs/podman/ca.crt"
|
||||
|
||||
# Directory for temporary files. Must be tmpfs (wiped after reboot)
|
||||
#
|
||||
|
|
|
@ -598,10 +598,17 @@ default_sysctls = [
|
|||
# rootful "unix:///run/podman/podman.sock (Default)
|
||||
# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
|
||||
# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
|
||||
# tcp/tls remote tcp://10.10.1.136:9443
|
||||
#
|
||||
# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
|
||||
# Path to file containing ssh identity key
|
||||
# identity = "~/.ssh/id_rsa"
|
||||
# Path to PEM file containing TLS client certificate
|
||||
# tls_cert = "/path/to/certs/podman/tls.crt"
|
||||
# Path to PEM file containing TLS client certificate private key
|
||||
# tls_key = "/path/to/certs/podman/tls.key"
|
||||
# Path to PEM file containing TLS certificate authority (CA) bundle
|
||||
# tls_ca = "/path/to/certs/podman/ca.crt"
|
||||
|
||||
# Directory for temporary files. Must be tmpfs (wiped after reboot)
|
||||
#
|
||||
|
|
|
@ -39,7 +39,7 @@ func (stub NoSignaturesInitialize) PutSignaturesWithFormat(ctx context.Context,
|
|||
return nil
|
||||
}
|
||||
|
||||
// SupportsSignatures implements SupportsSignatures() that returns nil.
|
||||
// AlwaysSupportsSignatures implements SupportsSignatures() that returns nil.
|
||||
// Note that it might be even more useful to return a value dynamically detected based on
|
||||
type AlwaysSupportsSignatures struct{}
|
||||
|
||||
|
|
|
@ -37,12 +37,22 @@ func (ref ociReference) DeleteImage(ctx context.Context, sys *types.SystemContex
|
|||
return err
|
||||
}
|
||||
|
||||
signaturesToDelete, err := ref.getObsoleteSignatures(blobsToDelete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ref.deleteBlobs(blobsToDelete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ref.deleteReferenceFromIndex(descriptorIndex)
|
||||
err = ref.deleteReferenceFromIndex(descriptorIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ref.deleteSignatures(sys, signaturesToDelete)
|
||||
}
|
||||
|
||||
// countBlobsForDescriptor updates dest with usage counts of blobs required for descriptor, INCLUDING descriptor itself.
|
||||
|
@ -148,6 +158,7 @@ func deleteBlob(blobPath string) error {
|
|||
}
|
||||
}
|
||||
|
||||
// deleteReferencesFromIndex deletes manifest from the root index.
|
||||
func (ref ociReference) deleteReferenceFromIndex(referenceIndex int) error {
|
||||
index, err := ref.getIndex()
|
||||
if err != nil {
|
||||
|
@ -159,6 +170,25 @@ func (ref ociReference) deleteReferenceFromIndex(referenceIndex int) error {
|
|||
return saveJSON(ref.indexPath(), index)
|
||||
}
|
||||
|
||||
// deleteReferencesFromIndex deletes referenceIndex first, and then remove signatures.
|
||||
func (ref ociReference) deleteSignaturesFromIndex(signatures []imgspecv1.Descriptor) error {
|
||||
index, err := ref.getIndex()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signaturesSet := set.New[digest.Digest]()
|
||||
for _, sign := range signatures {
|
||||
signaturesSet.Add(sign.Digest)
|
||||
}
|
||||
|
||||
index.Manifests = slices.DeleteFunc(index.Manifests, func(d imgspecv1.Descriptor) bool {
|
||||
return signaturesSet.Contains(d.Digest)
|
||||
})
|
||||
|
||||
return saveJSON(ref.indexPath(), index)
|
||||
}
|
||||
|
||||
func saveJSON(path string, content any) (retErr error) {
|
||||
// If the file already exists, get its mode to preserve it
|
||||
var mode fs.FileMode
|
||||
|
@ -187,3 +217,64 @@ func saveJSON(path string, content any) (retErr error) {
|
|||
|
||||
return json.NewEncoder(file).Encode(content)
|
||||
}
|
||||
|
||||
func (ref ociReference) getObsoleteSignatures(blobsToDelete *set.Set[digest.Digest]) (signaturesToDelete []imgspecv1.Descriptor, err error) {
|
||||
// create a mapping from sigstore tag to its descriptor
|
||||
signDigestMap := make(map[string]imgspecv1.Descriptor)
|
||||
index, err := ref.getIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range index.Manifests {
|
||||
if isSigstoreTag(m.Annotations[imgspecv1.AnnotationRefName]) {
|
||||
signDigestMap[m.Annotations[imgspecv1.AnnotationRefName]] = m
|
||||
}
|
||||
}
|
||||
|
||||
for dgst := range blobsToDelete.All() {
|
||||
sigstoreTag, err := sigstoreAttachmentTag(dgst)
|
||||
if err != nil {
|
||||
// This shouldn't happen because all digests in the root index should be valid.
|
||||
continue
|
||||
}
|
||||
signDesc, ok := signDigestMap[sigstoreTag]
|
||||
if !ok {
|
||||
// No signature found for this digest
|
||||
continue
|
||||
}
|
||||
signaturesToDelete = append(signaturesToDelete, signDesc)
|
||||
}
|
||||
return signaturesToDelete, nil
|
||||
}
|
||||
|
||||
// deleteSignatures delete sigstore signatures of the given manifest digest.
|
||||
func (ref ociReference) deleteSignatures(sys *types.SystemContext, signaturesToDelete []imgspecv1.Descriptor) error {
|
||||
sharedBlobsDir := ""
|
||||
if sys != nil && sys.OCISharedBlobDirPath != "" {
|
||||
sharedBlobsDir = sys.OCISharedBlobDirPath
|
||||
}
|
||||
|
||||
blobsUsedByImage := make(map[digest.Digest]int)
|
||||
for _, descriptor := range signaturesToDelete {
|
||||
if err := ref.countBlobsForDescriptor(blobsUsedByImage, &descriptor, sharedBlobsDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
blobsToDelete, err := ref.getBlobsToDelete(blobsUsedByImage, sharedBlobsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ref.deleteBlobs(blobsToDelete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ref.deleteSignaturesFromIndex(signaturesToDelete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,25 +1,32 @@
|
|||
package layout
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspec "github.com/opencontainers/image-spec/specs-go"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.podman.io/image/v5/internal/imagedestination/impl"
|
||||
"go.podman.io/image/v5/internal/imagedestination/stubs"
|
||||
"go.podman.io/image/v5/internal/manifest"
|
||||
"go.podman.io/image/v5/internal/iolimits"
|
||||
"go.podman.io/image/v5/internal/private"
|
||||
"go.podman.io/image/v5/internal/putblobdigest"
|
||||
"go.podman.io/image/v5/internal/set"
|
||||
"go.podman.io/image/v5/internal/signature"
|
||||
"go.podman.io/image/v5/manifest"
|
||||
"go.podman.io/image/v5/pkg/blobinfocache/none"
|
||||
"go.podman.io/image/v5/types"
|
||||
"go.podman.io/storage/pkg/fileutils"
|
||||
)
|
||||
|
@ -27,13 +34,17 @@ import (
|
|||
type ociImageDestination struct {
|
||||
impl.Compat
|
||||
impl.PropertyMethodsInitialize
|
||||
stubs.AlwaysSupportsSignatures
|
||||
stubs.IgnoresOriginalOCIConfig
|
||||
stubs.NoPutBlobPartialInitialize
|
||||
stubs.NoSignaturesInitialize
|
||||
|
||||
ref ociReference
|
||||
index imgspecv1.Index
|
||||
sharedBlobDir string
|
||||
ref ociReference
|
||||
index imgspecv1.Index
|
||||
sharedBlobDir string
|
||||
manifestDigest digest.Digest // or "" if not yet known.
|
||||
// blobDeleteCandidates is a set of digests which may be deleted _if_ we find no other references to them;
|
||||
// it’s safe to optimistically include entries which may have other references
|
||||
blobDeleteCandidates *set.Set[digest.Digest]
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||||
|
@ -75,10 +86,10 @@ func newImageDestination(sys *types.SystemContext, ref ociReference) (private.Im
|
|||
HasThreadSafePutBlob: true,
|
||||
}),
|
||||
NoPutBlobPartialInitialize: stubs.NoPutBlobPartial(ref),
|
||||
NoSignaturesInitialize: stubs.NoSignatures("Pushing signatures for OCI images is not supported"),
|
||||
|
||||
ref: ref,
|
||||
index: *index,
|
||||
ref: ref,
|
||||
index: *index,
|
||||
blobDeleteCandidates: set.New[digest.Digest](),
|
||||
}
|
||||
d.Compat = impl.AddCompat(d)
|
||||
if sys != nil {
|
||||
|
@ -255,6 +266,9 @@ func (d *ociImageDestination) PutManifest(ctx context.Context, m []byte, instanc
|
|||
if instanceDigest != nil {
|
||||
return nil
|
||||
}
|
||||
// d.manifestDigest is used for a single image (not a manifest list).
|
||||
// This should be placed after checking instanceDigest is nil.
|
||||
d.manifestDigest = digest
|
||||
|
||||
// If we had platform information, we'd build an imgspecv1.Platform structure here.
|
||||
|
||||
|
@ -312,6 +326,26 @@ func (d *ociImageDestination) CommitWithOptions(ctx context.Context, options pri
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete unreferenced blobs (e.g. old signature manifest and its config)
|
||||
if !d.blobDeleteCandidates.Empty() {
|
||||
blobsUsedInRootIndex := make(map[digest.Digest]int)
|
||||
err = d.ref.countBlobsReferencedByIndex(blobsUsedInRootIndex, &d.index, d.sharedBlobDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error counting blobs to delete: %w", err)
|
||||
}
|
||||
// Don't delete blobs which are referenced
|
||||
actualBlobsToDelete := set.New[digest.Digest]()
|
||||
for dgst := range d.blobDeleteCandidates.All() {
|
||||
if blobsUsedInRootIndex[dgst] == 0 {
|
||||
actualBlobsToDelete.Add(dgst)
|
||||
}
|
||||
}
|
||||
err := d.ref.deleteBlobs(actualBlobsToDelete)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting blobs: %w", err)
|
||||
}
|
||||
d.blobDeleteCandidates = set.New[digest.Digest]()
|
||||
}
|
||||
if err := os.WriteFile(d.ref.ociLayoutPath(), layoutBytes, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -322,6 +356,164 @@ func (d *ociImageDestination) CommitWithOptions(ctx context.Context, options pri
|
|||
return os.WriteFile(d.ref.indexPath(), indexJSON, 0644)
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) PutSignaturesWithFormat(ctx context.Context, signatures []signature.Signature, instanceDigest *digest.Digest) error {
|
||||
if instanceDigest == nil {
|
||||
if d.manifestDigest == "" {
|
||||
// This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
|
||||
return errors.New("unknown manifest digest, can't add signatures")
|
||||
}
|
||||
instanceDigest = &d.manifestDigest
|
||||
}
|
||||
|
||||
var sigstoreSignatures []signature.Sigstore
|
||||
for _, sig := range signatures {
|
||||
if sigstoreSig, ok := sig.(signature.Sigstore); ok {
|
||||
sigstoreSignatures = append(sigstoreSignatures, sigstoreSig)
|
||||
} else {
|
||||
return errors.New("oci: layout only supports sigstore signatures")
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.putSignaturesToSigstoreAttachment(ctx, sigstoreSignatures, *instanceDigest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) putSignaturesToSigstoreAttachment(ctx context.Context, signatures []signature.Sigstore, manifestDigest digest.Digest) error {
|
||||
var signConfig imgspecv1.Image // Most fields empty by default
|
||||
|
||||
signManifest, signDesc, err := d.ref.getSigstoreAttachmentManifest(manifestDigest, &d.index, d.sharedBlobDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if signManifest == nil {
|
||||
signManifest = manifest.OCI1FromComponents(imgspecv1.Descriptor{
|
||||
MediaType: imgspecv1.MediaTypeImageConfig,
|
||||
Digest: "", // We will fill this in later.
|
||||
Size: 0,
|
||||
}, nil)
|
||||
signConfig.RootFS.Type = "layers"
|
||||
} else {
|
||||
logrus.Debugf("Fetching sigstore attachment config %s", signManifest.Config.Digest.String())
|
||||
configBlob, err := d.ref.getOCIDescriptorContents(signManifest.Config.Digest, iolimits.MaxConfigBodySize, d.sharedBlobDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(configBlob, &signConfig); err != nil {
|
||||
return fmt.Errorf("parsing sigstore attachment config %s in %s: %w", signManifest.Config.Digest.String(),
|
||||
d.ref.StringWithinTransport(), err)
|
||||
}
|
||||
// The signature manifest and its config may be updated and unreferenced when a new config is created.
|
||||
d.blobDeleteCandidates.Add(signDesc.Digest)
|
||||
d.blobDeleteCandidates.Add(signManifest.Config.Digest)
|
||||
}
|
||||
|
||||
desc, err := d.getDescriptor(manifestDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signManifest.Subject = desc
|
||||
|
||||
// To make sure we can safely append to the slices of signManifest, without adding a remote dependency on the code that creates it.
|
||||
signManifest.Layers = slices.Clone(signManifest.Layers)
|
||||
for _, sig := range signatures {
|
||||
mimeType := sig.UntrustedMIMEType()
|
||||
payloadBlob := sig.UntrustedPayload()
|
||||
annotations := sig.UntrustedAnnotations()
|
||||
|
||||
// Skip if the signature is already on the registry.
|
||||
if slices.ContainsFunc(signManifest.Layers, func(layer imgspecv1.Descriptor) bool {
|
||||
return layerMatchesSigstoreSignature(layer, mimeType, payloadBlob, annotations)
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
|
||||
signDesc, err := d.putBlobBytesAsOCI(ctx, payloadBlob, mimeType, private.PutBlobOptions{
|
||||
Cache: none.NoCache,
|
||||
IsConfig: false,
|
||||
EmptyLayer: false,
|
||||
LayerIndex: nil,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signDesc.Annotations = annotations
|
||||
signManifest.Layers = append(signManifest.Layers, signDesc)
|
||||
signConfig.RootFS.DiffIDs = append(signConfig.RootFS.DiffIDs, signDesc.Digest)
|
||||
logrus.Debugf("Adding new signature, digest %s", signDesc.Digest.String())
|
||||
}
|
||||
|
||||
configBlob, err := json.Marshal(signConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("Creating updated sigstore attachment config")
|
||||
configDesc, err := d.putBlobBytesAsOCI(ctx, configBlob, imgspecv1.MediaTypeImageConfig, private.PutBlobOptions{
|
||||
Cache: none.NoCache,
|
||||
IsConfig: true,
|
||||
EmptyLayer: false,
|
||||
LayerIndex: nil,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signManifest.Config = configDesc
|
||||
signManifestBlob, err := signManifest.Serialize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("Creating sigstore attachment manifest")
|
||||
signDigest := digest.FromBytes(signManifestBlob)
|
||||
if err = d.PutManifest(ctx, signManifestBlob, &signDigest); err != nil {
|
||||
return err
|
||||
}
|
||||
signTag, err := sigstoreAttachmentTag(manifestDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.addManifest(&imgspecv1.Descriptor{
|
||||
MediaType: signManifest.MediaType,
|
||||
Digest: signDigest,
|
||||
Size: int64(len(signManifestBlob)),
|
||||
Annotations: map[string]string{
|
||||
imgspecv1.AnnotationRefName: signTag,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) getDescriptor(digest digest.Digest) (*imgspecv1.Descriptor, error) {
|
||||
for _, desc := range d.index.Manifests {
|
||||
if desc.Digest == digest {
|
||||
return &desc, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("manifest %s not found in index", digest.String())
|
||||
}
|
||||
|
||||
// putBlobBytesAsOCI uploads a blob with the specified contents, and returns an appropriate
|
||||
// OCI descriptor.
|
||||
func (d *ociImageDestination) putBlobBytesAsOCI(ctx context.Context, contents []byte, mimeType string, options private.PutBlobOptions) (imgspecv1.Descriptor, error) {
|
||||
blobDigest := digest.FromBytes(contents)
|
||||
info, err := d.PutBlobWithOptions(ctx, bytes.NewReader(contents),
|
||||
types.BlobInfo{
|
||||
Digest: blobDigest,
|
||||
Size: int64(len(contents)),
|
||||
MediaType: mimeType,
|
||||
}, options)
|
||||
if err != nil {
|
||||
return imgspecv1.Descriptor{}, fmt.Errorf("writing blob %s: %w", blobDigest.String(), err)
|
||||
}
|
||||
return imgspecv1.Descriptor{
|
||||
MediaType: mimeType,
|
||||
Digest: info.Digest,
|
||||
Size: info.Size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PutBlobFromLocalFileOption is unused but may receive functionality in the future.
|
||||
type PutBlobFromLocalFileOption struct{}
|
||||
|
||||
|
@ -412,3 +604,18 @@ func indexExists(ref ociReference) bool {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func layerMatchesSigstoreSignature(layer imgspecv1.Descriptor, mimeType string,
|
||||
payloadBlob []byte, annotations map[string]string) bool {
|
||||
if layer.MediaType != mimeType ||
|
||||
layer.Size != int64(len(payloadBlob)) ||
|
||||
// This is not quite correct, we should use the layer’s digest algorithm.
|
||||
// But right now we don’t want to deal with corner cases like bad digest formats
|
||||
// or unavailable algorithms; in the worst case we end up with duplicate signature
|
||||
// entries.
|
||||
layer.Digest.String() != digest.FromBytes(payloadBlob).String() ||
|
||||
!maps.Equal(layer.Annotations, annotations) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -16,8 +16,10 @@ import (
|
|||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"go.podman.io/image/v5/internal/imagesource/impl"
|
||||
"go.podman.io/image/v5/internal/imagesource/stubs"
|
||||
"go.podman.io/image/v5/internal/manifest"
|
||||
"go.podman.io/image/v5/internal/iolimits"
|
||||
"go.podman.io/image/v5/internal/private"
|
||||
"go.podman.io/image/v5/internal/signature"
|
||||
"go.podman.io/image/v5/manifest"
|
||||
"go.podman.io/image/v5/pkg/tlsclientconfig"
|
||||
"go.podman.io/image/v5/types"
|
||||
"go.podman.io/storage/pkg/fileutils"
|
||||
|
@ -37,7 +39,6 @@ func (e ImageNotFoundError) Error() string {
|
|||
type ociImageSource struct {
|
||||
impl.Compat
|
||||
impl.PropertyMethodsInitialize
|
||||
impl.NoSignatures
|
||||
impl.DoesNotAffectLayerInfosForCopy
|
||||
stubs.NoGetBlobAtInitialize
|
||||
|
||||
|
@ -158,20 +159,7 @@ func (s *ociImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache
|
|||
}
|
||||
}
|
||||
|
||||
path, err := s.ref.blobPath(info.Digest, s.sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
r, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
fi, err := r.Stat()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return r, fi.Size(), nil
|
||||
return s.ref.getBlob(info.Digest, s.sharedBlobDir)
|
||||
}
|
||||
|
||||
// getExternalBlob returns the reader of the first available blob URL from urls, which must not be empty.
|
||||
|
@ -246,3 +234,30 @@ func GetLocalBlobPath(ctx context.Context, src types.ImageSource, digest digest.
|
|||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (s *ociImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) {
|
||||
if instanceDigest == nil {
|
||||
instanceDigest = &s.descriptor.Digest
|
||||
}
|
||||
|
||||
ociManifest, _, err := s.ref.getSigstoreAttachmentManifest(*instanceDigest, s.index, s.sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ociManifest == nil {
|
||||
// No signature found
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
signatures := make([]signature.Signature, 0, len(ociManifest.Layers))
|
||||
for _, layer := range ociManifest.Layers {
|
||||
// Note that this copies all kinds of attachments: attestations, and whatever else is there,
|
||||
// not just signatures. We leave the signature consumers to decide based on the MIME type.
|
||||
payload, err := s.ref.getOCIDescriptorContents(layer.Digest, iolimits.MaxSignatureBodySize, s.sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signatures = append(signatures, signature.SigstoreFromComponents(layer.MediaType, payload, layer.Annotations))
|
||||
}
|
||||
return signatures, nil
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -14,7 +15,8 @@ import (
|
|||
"go.podman.io/image/v5/directory/explicitfilepath"
|
||||
"go.podman.io/image/v5/docker/reference"
|
||||
"go.podman.io/image/v5/internal/image"
|
||||
"go.podman.io/image/v5/internal/manifest"
|
||||
"go.podman.io/image/v5/internal/iolimits"
|
||||
"go.podman.io/image/v5/manifest"
|
||||
"go.podman.io/image/v5/oci/internal"
|
||||
"go.podman.io/image/v5/transports"
|
||||
"go.podman.io/image/v5/types"
|
||||
|
@ -28,6 +30,9 @@ var (
|
|||
// Transport is an ImageTransport for OCI directories.
|
||||
Transport = ociTransport{}
|
||||
|
||||
// ErrEmptyIndex is an error returned when the index includes no image.
|
||||
ErrEmptyIndex = errors.New("no image in oci")
|
||||
|
||||
// ErrMoreThanOneImage is an error returned when the manifest includes
|
||||
// more than one image and the user should choose which one to use.
|
||||
ErrMoreThanOneImage = errors.New("more than one image in oci, choose an image")
|
||||
|
@ -248,11 +253,33 @@ func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, int, erro
|
|||
|
||||
default:
|
||||
// return manifest if only one image is in the oci directory
|
||||
if len(index.Manifests) != 1 {
|
||||
// ask user to choose image when more than one image in the oci directory
|
||||
if len(index.Manifests) == 0 {
|
||||
return imgspecv1.Descriptor{}, -1, ErrEmptyIndex
|
||||
}
|
||||
// if there's one image return it, even if it is a signature
|
||||
if len(index.Manifests) == 1 {
|
||||
return index.Manifests[0], 0, nil
|
||||
}
|
||||
// when there's more than one image, try to get a non-signature image
|
||||
var desc imgspecv1.Descriptor
|
||||
idx := -1
|
||||
for i, md := range index.Manifests {
|
||||
if isSigstoreTag(md.Annotations[imgspecv1.AnnotationRefName]) {
|
||||
continue
|
||||
}
|
||||
// More than one non-signature image was found
|
||||
if idx != -1 {
|
||||
// ask user to choose image when more than one image in the oci directory
|
||||
return imgspecv1.Descriptor{}, -1, ErrMoreThanOneImage
|
||||
}
|
||||
desc = md
|
||||
idx = i
|
||||
}
|
||||
// there's only multiple signature images
|
||||
if idx == -1 {
|
||||
return imgspecv1.Descriptor{}, -1, ErrMoreThanOneImage
|
||||
}
|
||||
return index.Manifests[0], 0, nil
|
||||
return desc, idx, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,3 +329,101 @@ func (ref ociReference) blobPath(digest digest.Digest, sharedBlobDir string) (st
|
|||
}
|
||||
return filepath.Join(blobDir, digest.Algorithm().String(), digest.Encoded()), nil
|
||||
}
|
||||
|
||||
// sigstoreAttachmentTag returns a sigstore attachment tag for the specified digest.
|
||||
func sigstoreAttachmentTag(d digest.Digest) (string, error) {
|
||||
if err := d.Validate(); err != nil { // Make sure d.String() doesn’t contain any unexpected characters
|
||||
return "", err
|
||||
}
|
||||
return strings.Replace(d.String(), ":", "-", 1) + ".sig", nil
|
||||
}
|
||||
|
||||
func (ref ociReference) getSigstoreAttachmentManifest(d digest.Digest, idx *imgspecv1.Index, sharedBlobDir string) (*manifest.OCI1, *imgspecv1.Descriptor, error) {
|
||||
signTag, err := sigstoreAttachmentTag(d)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var signDesc *imgspecv1.Descriptor
|
||||
for _, m := range idx.Manifests {
|
||||
if m.Annotations[imgspecv1.AnnotationRefName] == signTag {
|
||||
signDesc = &m
|
||||
break
|
||||
}
|
||||
}
|
||||
if signDesc == nil {
|
||||
// No signature found
|
||||
return nil, nil, nil
|
||||
}
|
||||
if signDesc.MediaType != imgspecv1.MediaTypeImageManifest {
|
||||
return nil, nil, fmt.Errorf("unexpected MIME type for sigstore attachment manifest %s: %q",
|
||||
signTag, signDesc.MediaType)
|
||||
}
|
||||
blobReader, _, err := ref.getBlob(signDesc.Digest, sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get Blob %s: %w", signTag, err)
|
||||
}
|
||||
defer blobReader.Close()
|
||||
signBlob, err := iolimits.ReadAtMost(blobReader, iolimits.MaxManifestBodySize)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read blob: %w", err)
|
||||
}
|
||||
res, err := manifest.OCI1FromManifest(signBlob)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parsing manifest %s: %w", signDesc.Digest, err)
|
||||
}
|
||||
return res, signDesc, nil
|
||||
}
|
||||
|
||||
func (ref ociReference) getBlob(d digest.Digest, sharedBlobDir string) (io.ReadCloser, int64, error) {
|
||||
path, err := ref.blobPath(d, sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
r, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
fi, err := r.Stat()
|
||||
if err != nil {
|
||||
_ = r.Close() // Avoid leak r.
|
||||
return nil, 0, err
|
||||
}
|
||||
return r, fi.Size(), nil
|
||||
}
|
||||
|
||||
func (ref ociReference) getOCIDescriptorContents(dgst digest.Digest, maxSize int, sharedBlobDir string) ([]byte, error) {
|
||||
if err := dgst.Validate(); err != nil { // .Algorithm() might panic without this check
|
||||
return nil, fmt.Errorf("invalid digest %q: %w", dgst.String(), err)
|
||||
}
|
||||
digestAlgorithm := dgst.Algorithm()
|
||||
if !digestAlgorithm.Available() {
|
||||
return nil, fmt.Errorf("invalid digest %q: unsupported digest algorithm %q", dgst.String(), digestAlgorithm.String())
|
||||
}
|
||||
|
||||
reader, _, err := ref.getBlob(dgst, sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
payload, err := iolimits.ReadAtMost(reader, maxSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading blob %s in %s: %w", dgst.String(), ref.image, err)
|
||||
}
|
||||
actualDigest := digestAlgorithm.FromBytes(payload)
|
||||
if actualDigest != dgst {
|
||||
return nil, fmt.Errorf("digest mismatch, expected %q, got %q", dgst.String(), actualDigest.String())
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
// isSigstoreTag returns true if the tag is sigstore signature tag.
|
||||
func isSigstoreTag(tag string) bool {
|
||||
digestPart, found := strings.CutSuffix(tag, ".sig")
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
digestPart = strings.Replace(digestPart, "-", ":", 1)
|
||||
_, err := digest.Parse(digestPart)
|
||||
return err == nil
|
||||
}
|
||||
|
|
|
@ -474,7 +474,7 @@ go.opentelemetry.io/otel/trace
|
|||
go.opentelemetry.io/otel/trace/embedded
|
||||
go.opentelemetry.io/otel/trace/internal/telemetry
|
||||
go.opentelemetry.io/otel/trace/noop
|
||||
# go.podman.io/common v0.65.1-0.20250916163606-92222dcd3da4
|
||||
# go.podman.io/common v0.65.1-0.20250916163606-92222dcd3da4 => github.com/bitoku/container-libs/common v0.0.0-20251001085230-f74ad300c684
|
||||
## explicit; go 1.24.2
|
||||
go.podman.io/common/internal
|
||||
go.podman.io/common/internal/attributedstring
|
||||
|
@ -528,7 +528,7 @@ go.podman.io/common/pkg/umask
|
|||
go.podman.io/common/pkg/util
|
||||
go.podman.io/common/pkg/version
|
||||
go.podman.io/common/version
|
||||
# go.podman.io/image/v5 v5.37.1-0.20250916163606-92222dcd3da4
|
||||
# go.podman.io/image/v5 v5.37.1-0.20250916163606-92222dcd3da4 => github.com/bitoku/container-libs/image/v5 v5.0.0-20251001085230-f74ad300c684
|
||||
## explicit; go 1.24.0
|
||||
go.podman.io/image/v5/copy
|
||||
go.podman.io/image/v5/directory
|
||||
|
@ -596,7 +596,7 @@ go.podman.io/image/v5/transports
|
|||
go.podman.io/image/v5/transports/alltransports
|
||||
go.podman.io/image/v5/types
|
||||
go.podman.io/image/v5/version
|
||||
# go.podman.io/storage v1.60.1-0.20250916163606-92222dcd3da4
|
||||
# go.podman.io/storage v1.60.1-0.20250916163606-92222dcd3da4 => github.com/bitoku/container-libs/storage v0.0.0-20251001085230-f74ad300c684
|
||||
## explicit; go 1.24.0
|
||||
go.podman.io/storage
|
||||
go.podman.io/storage/drivers
|
||||
|
@ -836,3 +836,6 @@ tags.cncf.io/container-device-interface/pkg/parser
|
|||
# tags.cncf.io/container-device-interface/specs-go v1.0.0
|
||||
## explicit; go 1.19
|
||||
tags.cncf.io/container-device-interface/specs-go
|
||||
# go.podman.io/common => github.com/bitoku/container-libs/common v0.0.0-20251001085230-f74ad300c684
|
||||
# go.podman.io/storage => github.com/bitoku/container-libs/storage v0.0.0-20251001085230-f74ad300c684
|
||||
# go.podman.io/image/v5 => github.com/bitoku/container-libs/image/v5 v5.0.0-20251001085230-f74ad300c684
|
||||
|
|
Loading…
Reference in New Issue