@@ -7,6 +7,7 @@ package ocisif
77
88import (
99 "context"
10+ "errors"
1011 "fmt"
1112 "os"
1213 "path/filepath"
@@ -18,7 +19,9 @@ import (
1819 v1 "github.com/google/go-containerregistry/pkg/v1"
1920 "github.com/google/go-containerregistry/pkg/v1/remote"
2021 "github.com/google/go-containerregistry/pkg/v1/tarball"
22+ cosignremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
2123 ocimutate "github.com/sylabs/oci-tools/pkg/mutate"
24+ "github.com/sylabs/oci-tools/pkg/sourcesink"
2225 "github.com/sylabs/sif/v2/pkg/sif"
2326 "github.com/sylabs/singularity/v4/internal/pkg/cache"
2427 "github.com/sylabs/singularity/v4/internal/pkg/client/progress"
@@ -45,6 +48,7 @@ type PullOptions struct {
4548 Platform ggcrv1.Platform
4649 ReqAuthFile string
4750 KeepLayers bool
51+ WithCosign bool
4852}
4953
5054// PullOCISIF will create an OCI-SIF image in the cache if directTo="", or a specific file if directTo is set.
@@ -172,6 +176,9 @@ type PushOptions struct {
172176 // TmpDir is a temporary directory to be used for an temporary files created
173177 // during the push.
174178 TmpDir string
179+ // WithCosign controls whether cosign signatures present in the SIF are also
180+ // pushed to the destination repository in the registry.
181+ WithCosign bool
175182}
176183
177184// PushOCISIF pushes a single image from sourceFile to the OCI registry destRef.
@@ -187,18 +194,20 @@ func PushOCISIF(ctx context.Context, sourceFile, destRef string, opts PushOption
187194 return err
188195 }
189196
190- fi , err := sif . LoadContainerFromPath (sourceFile , sif . OptLoadWithFlag ( os . O_RDONLY ) )
197+ ss , err := sourcesink . SIFFromPath (sourceFile )
191198 if err != nil {
192- return err
199+ return fmt . Errorf ( "failed to open OCI-SIF: %w" , err )
193200 }
194- defer fi .UnloadContainer ()
195-
196- image , err := ocisif .GetSingleImage (fi )
201+ d , err := ss .Get (ctx )
202+ if err != nil {
203+ return fmt .Errorf ("while fetching image from OCI-SIF: %v" , err )
204+ }
205+ image , err := d .Image ()
197206 if err != nil {
198- return fmt .Errorf ("while obtaining image: %w" , err )
207+ return fmt .Errorf ("failed to retrieve image: %w" , err )
199208 }
200209
201- image , err = transformLayers (image , opts . LayerFormat , opts . TmpDir )
210+ image , err = transformLayers (image , opts )
202211 if err != nil {
203212 return err
204213 }
@@ -237,10 +246,18 @@ func PushOCISIF(ctx context.Context, sourceFile, destRef string, opts PushOption
237246 remoteOpts = append (remoteOpts , remote .WithProgress (progChan ))
238247 }
239248
240- return remote .Write (ir , image , remoteOpts ... )
249+ if err := remote .Write (ir , image , remoteOpts ... ); err != nil {
250+ return err
251+ }
252+
253+ if opts .WithCosign {
254+ return writeSignatures (ctx , ir , d , opts )
255+ }
256+
257+ return nil
241258}
242259
243- func transformLayers (base v1.Image , layerFormat , tmpDir string ) (v1.Image , error ) {
260+ func transformLayers (base v1.Image , opts PushOptions ) (v1.Image , error ) {
244261 ls , err := base .Layers ()
245262 if err != nil {
246263 return nil , err
@@ -254,15 +271,15 @@ func transformLayers(base v1.Image, layerFormat, tmpDir string) (v1.Image, error
254271 return nil , err
255272 }
256273
257- switch layerFormat {
274+ switch opts . LayerFormat {
258275 case DefaultLayerFormat :
259276 continue
260277 case SquashfsLayerFormat :
261278 if mt != ocisif .SquashfsLayerMediaType {
262279 return nil , fmt .Errorf ("unexpected layer mediaType: %v" , mt )
263280 }
264281 case TarLayerFormat :
265- opener , err := ocimutate .TarFromSquashfsLayer (l , ocimutate .OptTarTempDir (tmpDir ))
282+ opener , err := ocimutate .TarFromSquashfsLayer (l , ocimutate .OptTarTempDir (opts . TmpDir ))
266283 if err != nil {
267284 return nil , err
268285 }
@@ -272,10 +289,14 @@ func transformLayers(base v1.Image, layerFormat, tmpDir string) (v1.Image, error
272289 }
273290 ms = append (ms , ocimutate .SetLayer (i , tarLayer ))
274291 default :
275- return nil , fmt .Errorf ("unsupported layer format: %v" , layerFormat )
292+ return nil , fmt .Errorf ("unsupported layer format: %v" , opts . TmpDir )
276293 }
277294 }
278295
296+ if len (ms ) > 0 && opts .WithCosign {
297+ return nil , fmt .Errorf ("cannot push signature - invalidated by transforming layer format to %s" , opts .LayerFormat )
298+ }
299+
279300 return ocimutate .Apply (base , ms ... )
280301}
281302
@@ -295,7 +316,42 @@ func handleOverlay(sourceFile string, opts PushOptions) error {
295316 return fmt .Errorf ("cannot push overlay with layer format %q, use 'overlay seal' before pushing this image " , opts .LayerFormat )
296317 }
297318
319+ if opts .WithCosign {
320+ return errors .New ("cannot push signature - would be invalidated by synchronizing overlay" )
321+ }
322+
298323 // Make sure true overlay digest have been synced to the OCI constructs.
299324 sylog .Infof ("Synchronizing overlay digest to OCI image." )
300325 return ocisif .SyncOverlay (sourceFile )
301326}
327+
328+ func writeSignatures (ctx context.Context , ir name.Reference , d sourcesink.Descriptor , opts PushOptions ) error {
329+ sd , ok := d .(sourcesink.SignedDescriptor )
330+ if ! ok {
331+ return fmt .Errorf ("failed to upgrade Descriptor to SignedDescriptor" )
332+ }
333+ si , err := sd .SignedImage (ctx )
334+ if err != nil {
335+ return fmt .Errorf ("failed to retrieve SignedImage: %w" , err )
336+ }
337+ id , err := si .Digest ()
338+ if err != nil {
339+ return fmt .Errorf ("failed to retrieve image digest: %w" , err )
340+ }
341+ sigImg , err := si .Signatures ()
342+ if err != nil {
343+ return fmt .Errorf ("failed to retrieve signatures: %w" , err )
344+ }
345+ csRef , err := sourcesink .CosignRef (id , ir , cosignremote .SignatureTagSuffix )
346+ if err != nil {
347+ return err
348+ }
349+
350+ sylog .Infof ("Writing cosign signatures: %s" , csRef .Name ())
351+ remoteOpts := []remote.Option {
352+ ociauth .AuthOptn (opts .Auth , opts .AuthFile ),
353+ remote .WithUserAgent (useragent .Value ()),
354+ remote .WithContext (ctx ),
355+ }
356+ return remote .Write (csRef , sigImg , remoteOpts ... )
357+ }
0 commit comments