sigs.k8s.io/release-sdk@v0.11.1-0.20240417074027-8061fb5e4952/sign/sign.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes 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 sign
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"errors"
    23  	"fmt"
    24  	"net/http"
    25  	"os"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  
    30  	"github.com/google/go-containerregistry/pkg/authn"
    31  	"github.com/google/go-containerregistry/pkg/crane"
    32  	"github.com/google/go-containerregistry/pkg/logs"
    33  	"github.com/google/go-containerregistry/pkg/name"
    34  	"github.com/google/go-containerregistry/pkg/v1/remote"
    35  	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
    36  	"github.com/jellydator/ttlcache/v3"
    37  	"github.com/nozzle/throttler"
    38  	cliOpts "github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
    39  	sigs "github.com/sigstore/cosign/v2/pkg/signature"
    40  	signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
    41  	"github.com/sirupsen/logrus"
    42  	"k8s.io/apimachinery/pkg/util/wait"
    43  	"sigs.k8s.io/release-utils/hash"
    44  )
    45  
    46  // Signer is the main structure to be used by API consumers.
    47  type Signer struct {
    48  	impl       impl
    49  	options    *Options
    50  	signedRefs *ttlcache.Cache[string, bool]                       // key: imageRef, value: isSigned
    51  	parsedRefs *ttlcache.Cache[string, name.Reference]             // key: imageRef, value: parsedRef
    52  	transports *ttlcache.Cache[name.Repository, http.RoundTripper] // key: repo of parsedRef, value: transport
    53  	signedObjs *ttlcache.Cache[string, *SignedObject]              // key: imageRef, value: signed object
    54  }
    55  
    56  // New returns a new Signer instance.
    57  func New(options *Options) *Signer {
    58  	if options == nil {
    59  		options = Default()
    60  	}
    61  
    62  	if options.Logger == nil {
    63  		options.Logger = logrus.New()
    64  	}
    65  
    66  	if options.Verbose {
    67  		logs.Debug.SetOutput(os.Stderr)
    68  		options.Logger.SetLevel(logrus.DebugLevel)
    69  	}
    70  
    71  	signer := &Signer{
    72  		impl:    &defaultImpl{},
    73  		options: options,
    74  		signedRefs: ttlcache.New(
    75  			ttlcache.WithTTL[string, bool](options.CacheTimeout),
    76  			ttlcache.WithCapacity[string, bool](options.MaxCacheItems),
    77  		),
    78  		parsedRefs: ttlcache.New(
    79  			ttlcache.WithTTL[string, name.Reference](options.CacheTimeout),
    80  			ttlcache.WithCapacity[string, name.Reference](options.MaxCacheItems),
    81  		),
    82  		transports: ttlcache.New(
    83  			ttlcache.WithTTL[name.Repository, http.RoundTripper](options.CacheTimeout),
    84  			ttlcache.WithCapacity[name.Repository, http.RoundTripper](options.MaxCacheItems),
    85  		),
    86  		signedObjs: ttlcache.New(
    87  			ttlcache.WithTTL[string, *SignedObject](options.CacheTimeout),
    88  			ttlcache.WithCapacity[string, *SignedObject](options.MaxCacheItems),
    89  		),
    90  	}
    91  
    92  	go signer.signedRefs.Start()
    93  	go signer.parsedRefs.Start()
    94  	go signer.transports.Start()
    95  	go signer.signedObjs.Start()
    96  
    97  	return signer
    98  }
    99  
   100  // SetImpl can be used to set the internal implementation, which is mainly used
   101  // for testing.
   102  func (s *Signer) SetImpl(impl impl) {
   103  	s.impl = impl
   104  }
   105  
   106  // log returns the internally set logger.
   107  func (s *Signer) log() *logrus.Logger {
   108  	return s.options.Logger
   109  }
   110  
   111  func (s *Signer) UploadBlob(path string) error {
   112  	s.log().Infof("Uploading blob: %s", path)
   113  
   114  	// TODO: unimplemented
   115  
   116  	return nil
   117  }
   118  
   119  // SignImage can be used to sign any provided container image reference by
   120  // using keyless signing.
   121  func (s *Signer) SignImage(reference string) (object *SignedObject, err error) {
   122  	return s.SignImageWithOptions(s.options, reference)
   123  }
   124  
   125  // SignImageWithOptions can be used to sign any provided container image
   126  // reference by using the provided custom options.
   127  func (s *Signer) SignImageWithOptions(options *Options, reference string) (object *SignedObject, err error) {
   128  	s.log().Infof("Signing reference: %s", reference)
   129  
   130  	// Ensure options to sign are correct
   131  	if err := options.verifySignOptions(); err != nil {
   132  		return nil, fmt.Errorf("checking signing options: %w", err)
   133  	}
   134  	s.log().Debugf("Using options: %+v", options)
   135  
   136  	ctx, cancel := options.context()
   137  	defer cancel()
   138  
   139  	// If we don't have a key path, we must ensure we can get an OIDC
   140  	// token or there is no way to sign. Depending on the options set,
   141  	// we may get the ID token from the cosign providers
   142  	identityToken := ""
   143  	if options.PrivateKeyPath == "" {
   144  		tok, err := s.identityToken(ctx)
   145  		if err != nil {
   146  			return nil, fmt.Errorf("getting identity token for keyless signing: %w", err)
   147  		}
   148  		identityToken = tok
   149  		if identityToken == "" {
   150  			return nil, errors.New(
   151  				"no private key or identity token are available, unable to sign",
   152  			)
   153  		}
   154  	}
   155  
   156  	ko := cliOpts.KeyOpts{
   157  		KeyRef:           options.PrivateKeyPath,
   158  		IDToken:          identityToken,
   159  		PassFunc:         options.PassFunc,
   160  		FulcioURL:        cliOpts.DefaultFulcioURL,
   161  		RekorURL:         cliOpts.DefaultRekorURL,
   162  		OIDCIssuer:       cliOpts.DefaultOIDCIssuerURL,
   163  		SkipConfirmation: true,
   164  
   165  		InsecureSkipFulcioVerify: false,
   166  	}
   167  
   168  	signOpts := cliOpts.SignOptions{
   169  		OutputSignature:   options.OutputSignaturePath,
   170  		OutputCertificate: options.OutputCertificatePath,
   171  		Upload:            true,
   172  		Recursive:         options.Recursive,
   173  		TlogUpload:        true,
   174  		SkipConfirmation:  true,
   175  		AnnotationOptions: cliOpts.AnnotationOptions{
   176  			Annotations: options.Annotations,
   177  		},
   178  		Registry: cliOpts.RegistryOptions{
   179  			AllowInsecure: options.AllowInsecure,
   180  		},
   181  		SignContainerIdentity: options.SignContainerIdentity,
   182  	}
   183  
   184  	images := []string{reference}
   185  
   186  	if err := s.impl.SignImageInternal(options.ToCosignRootOptions(), ko, signOpts, images); err != nil {
   187  		return nil, fmt.Errorf("sign reference: %s: %w", reference, err)
   188  	}
   189  
   190  	// remove the reference from the cache in case we called IsImageSigned before signing.
   191  	s.signedRefs.Delete(reference)
   192  
   193  	// After signing, registry consistency may not be there right
   194  	// away. Retry the image verification if it fails
   195  	// ref: https://github.com/kubernetes-sigs/promo-tools/issues/536
   196  	waitErr := wait.ExponentialBackoff(wait.Backoff{
   197  		Duration: 500 * time.Millisecond,
   198  		Factor:   1.5,
   199  		Steps:    int(options.MaxRetries),
   200  	}, func() (bool, error) {
   201  		object, err = s.VerifyImage(images[0])
   202  		if err != nil {
   203  			err = fmt.Errorf("verifying reference %s: %w", images[0], err)
   204  			return false, nil
   205  		}
   206  		return true, nil
   207  	})
   208  
   209  	if waitErr != nil {
   210  		return nil, fmt.Errorf("retrying image verification: %w: %w", waitErr, err)
   211  	}
   212  
   213  	return object, err
   214  }
   215  
   216  // SignFile can be used to sign any provided file path by using keyless
   217  // signing.
   218  func (s *Signer) SignFile(path string) (*SignedObject, error) {
   219  	s.log().Infof("Signing file path: %s", path)
   220  
   221  	ctx, cancel := s.options.context()
   222  	defer cancel()
   223  
   224  	// If we don't have a key path, we must ensure we can get an OIDC
   225  	// token or there is no way to sign. Depending on the options set,
   226  	// we may get the ID token from the cosign providers
   227  	identityToken := ""
   228  	if s.options.PrivateKeyPath == "" {
   229  		tok, err := s.identityToken(ctx)
   230  		if err != nil {
   231  			return nil, fmt.Errorf("getting identity token for keyless signing: %w", err)
   232  		}
   233  		identityToken = tok
   234  		if identityToken == "" {
   235  			return nil, errors.New(
   236  				"no private key or identity token are available, unable to sign",
   237  			)
   238  		}
   239  	}
   240  
   241  	ko := cliOpts.KeyOpts{
   242  		KeyRef:           s.options.PrivateKeyPath,
   243  		IDToken:          identityToken,
   244  		PassFunc:         s.options.PassFunc,
   245  		FulcioURL:        cliOpts.DefaultFulcioURL,
   246  		RekorURL:         cliOpts.DefaultRekorURL,
   247  		OIDCIssuer:       cliOpts.DefaultOIDCIssuerURL,
   248  		SkipConfirmation: true,
   249  
   250  		InsecureSkipFulcioVerify: false,
   251  	}
   252  
   253  	if s.options.OutputCertificatePath == "" {
   254  		s.options.OutputCertificatePath = fmt.Sprintf("%s.cert", path)
   255  	}
   256  	if s.options.OutputSignaturePath == "" {
   257  		s.options.OutputSignaturePath = fmt.Sprintf("%s.sig", path)
   258  	}
   259  
   260  	fileSHA, err := hash.SHA256ForFile(path)
   261  	if err != nil {
   262  		return nil, fmt.Errorf("file retrieve sha256: %s: %w", path, err)
   263  	}
   264  
   265  	if err := s.impl.SignFileInternal(
   266  		s.options.ToCosignRootOptions(), ko, path, true,
   267  		s.options.OutputSignaturePath, s.options.OutputCertificatePath, true,
   268  	); err != nil {
   269  		return nil, fmt.Errorf("sign file: %s: %w", path, err)
   270  	}
   271  
   272  	verifyKo := ko
   273  	verifyKo.KeyRef = s.options.PublicKeyPath
   274  
   275  	certOpts := cliOpts.CertVerifyOptions{
   276  		CertIdentity:         s.options.CertIdentity,
   277  		CertIdentityRegexp:   s.options.CertIdentityRegexp,
   278  		CertOidcIssuer:       s.options.CertOidcIssuer,
   279  		CertOidcIssuerRegexp: s.options.CertOidcIssuerRegexp,
   280  		IgnoreSCT:            s.options.IgnoreSCT,
   281  	}
   282  
   283  	if s.options.PublicKeyPath == "" {
   284  		certOpts.Cert = s.options.OutputCertificatePath
   285  	}
   286  
   287  	err = s.impl.VerifyFileInternal(ctx, verifyKo, certOpts, s.options.OutputSignaturePath, path)
   288  	if err != nil {
   289  		return nil, fmt.Errorf("verifying signed file: %s: %w", path, err)
   290  	}
   291  
   292  	return &SignedObject{
   293  		file: &SignedFile{
   294  			path:            path,
   295  			sha256:          fileSHA,
   296  			signaturePath:   s.options.OutputSignaturePath,
   297  			certificatePath: s.options.OutputCertificatePath,
   298  		},
   299  	}, nil
   300  }
   301  
   302  // VerifyImage can be used to validate any provided container image reference by
   303  // using keyless signing. It ignores unsigned images.
   304  func (s *Signer) VerifyImage(reference string) (*SignedObject, error) {
   305  	s.log().Infof("Verifying reference: %s", reference)
   306  
   307  	item := s.signedObjs.Get(reference)
   308  	if item != nil {
   309  		return item.Value(), nil
   310  	}
   311  
   312  	res, err := s.VerifyImages(reference)
   313  	if err != nil {
   314  		return nil, fmt.Errorf("verify image: %w", err)
   315  	}
   316  
   317  	o, ok := res.Load(reference)
   318  	if !ok {
   319  		// Probably not signed
   320  		return nil, nil
   321  	}
   322  
   323  	obj, ok := o.(*SignedObject)
   324  	if !ok {
   325  		return nil, fmt.Errorf("interface conversion error, result is not a *SignedObject: %v", o)
   326  	}
   327  
   328  	s.signedObjs.Set(reference, obj, ttlcache.DefaultTTL)
   329  
   330  	return obj, nil
   331  }
   332  
   333  // VerifyImages can be used to validate any provided container image reference
   334  // list by using keyless signing. It ignores unsigned images. Returns a sync map
   335  // where the key is the ref (string) and the value is the *SignedObject
   336  func (s *Signer) VerifyImages(refs ...string) (*sync.Map, error) {
   337  	s.log().Debug("Checking cache")
   338  	res := &sync.Map{}
   339  	unknownRefs := []string{}
   340  	for _, ref := range refs {
   341  		item := s.signedObjs.Get(ref)
   342  		if item != nil {
   343  			res.Store(ref, item.Value())
   344  			continue
   345  		}
   346  
   347  		unknownRefs = append(unknownRefs, ref)
   348  	}
   349  
   350  	if len(unknownRefs) == 0 {
   351  		s.log().Debug("All references already available in cache")
   352  		return res, nil
   353  	}
   354  
   355  	s.log().Infof("Verifying %d references", len(unknownRefs))
   356  
   357  	// checking whether the image being verified has a signature
   358  	// if there is no signature, we should skip
   359  	// ref: https://kubernetes.slack.com/archives/CJH2GBF7Y/p1647459428848859?thread_ts=1647428695.280269&cid=CJH2GBF7Y
   360  	ctx, cancel := s.options.context()
   361  	defer cancel()
   362  	imagesSigned, err := s.impl.ImagesSigned(ctx, s, unknownRefs...)
   363  	if err != nil {
   364  		return nil, fmt.Errorf("verify if images are signed: %w", err)
   365  	}
   366  	unknownRefs = []string{}
   367  	imagesSigned.Range(func(key, value any) bool {
   368  		ref, ok := key.(string)
   369  		if !ok {
   370  			logrus.Errorf("Interface conversion failed: key is not a string: %v", key)
   371  			return false
   372  		}
   373  		isSigned, ok := value.(bool)
   374  		if !ok {
   375  			logrus.Errorf("Interface conversion failed: value is not a bool: %v", value)
   376  			return false
   377  		}
   378  
   379  		if isSigned {
   380  			unknownRefs = append(unknownRefs, ref)
   381  		}
   382  
   383  		return true
   384  	})
   385  
   386  	t := throttler.New(int(s.options.MaxWorkers), len(unknownRefs))
   387  	for _, ref := range unknownRefs {
   388  		go func(ref string) {
   389  			ctx, cancel := s.options.context()
   390  			defer cancel()
   391  
   392  			certOpts := cliOpts.CertVerifyOptions{
   393  				CertIdentity:         s.options.CertIdentity,
   394  				CertIdentityRegexp:   s.options.CertIdentityRegexp,
   395  				CertOidcIssuer:       s.options.CertOidcIssuer,
   396  				CertOidcIssuerRegexp: s.options.CertOidcIssuerRegexp,
   397  				IgnoreSCT:            s.options.IgnoreSCT,
   398  			}
   399  
   400  			_, err = s.impl.VerifyImageInternal(ctx, certOpts, s.options.PublicKeyPath, []string{ref}, s.options.IgnoreTlog)
   401  			if err != nil {
   402  				t.Done(fmt.Errorf("verify image reference: %s: %w", ref, err))
   403  				return
   404  			}
   405  
   406  			var parsedRef name.Reference
   407  			item := s.parsedRefs.Get(ref)
   408  			if item != nil {
   409  				parsedRef = item.Value()
   410  			} else {
   411  				parsedRef, err = s.impl.ParseReference(ref)
   412  				if err != nil {
   413  					t.Done(fmt.Errorf("parsing reference: %s: %w", ref, err))
   414  					return
   415  				}
   416  			}
   417  
   418  			digest, err := s.impl.Digest(parsedRef.String())
   419  			if err != nil {
   420  				t.Done(fmt.Errorf("getting the reference digest for %s: %w", ref, err))
   421  				return
   422  			}
   423  
   424  			obj := &SignedObject{
   425  				image: &SignedImage{
   426  					digest:    digest,
   427  					reference: parsedRef.String(),
   428  					signature: repoDigestToSig(parsedRef.Context(), digest),
   429  				},
   430  				file: nil,
   431  			}
   432  
   433  			res.Store(ref, obj)
   434  			s.signedObjs.Set(ref, obj, ttlcache.DefaultTTL)
   435  			t.Done(nil)
   436  		}(ref)
   437  
   438  		if t.Throttle() > 0 {
   439  			break
   440  		}
   441  	}
   442  
   443  	s.log().Debug("Done verifying references")
   444  
   445  	if err := t.Err(); err != nil {
   446  		return res, fmt.Errorf("verifying references: %w", err)
   447  	}
   448  
   449  	return res, nil
   450  }
   451  
   452  // VerifyFile can be used to validate any provided file path.
   453  // If no signed entry is found we skip the file without errors.
   454  func (s *Signer) VerifyFile(path string, ignoreTLog bool) (*SignedObject, error) {
   455  	s.log().Infof("Verifying file path: %s", path)
   456  
   457  	ko := cliOpts.KeyOpts{
   458  		KeyRef:   s.options.PublicKeyPath,
   459  		RekorURL: cliOpts.DefaultRekorURL,
   460  	}
   461  
   462  	if s.options.OutputCertificatePath == "" {
   463  		s.options.OutputCertificatePath = fmt.Sprintf("%s.cert", path)
   464  	}
   465  	if s.options.OutputSignaturePath == "" {
   466  		s.options.OutputSignaturePath = fmt.Sprintf("%s.sig", path)
   467  	}
   468  
   469  	certOpts := cliOpts.CertVerifyOptions{
   470  		CertIdentity:         s.options.CertIdentity,
   471  		CertIdentityRegexp:   s.options.CertIdentityRegexp,
   472  		CertOidcIssuer:       s.options.CertOidcIssuer,
   473  		CertOidcIssuerRegexp: s.options.CertOidcIssuerRegexp,
   474  		IgnoreSCT:            s.options.IgnoreSCT,
   475  	}
   476  
   477  	if s.options.PublicKeyPath == "" {
   478  		certOpts.Cert = s.options.OutputCertificatePath
   479  	}
   480  
   481  	ctx, cancel := s.options.context()
   482  	defer cancel()
   483  
   484  	isSigned := false
   485  	if !ignoreTLog {
   486  		var err error
   487  		isSigned, err = s.IsFileSigned(ctx, path)
   488  		if err != nil {
   489  			return nil, fmt.Errorf("checking if file is signed. file: %s, error: %w", path, err)
   490  		}
   491  	}
   492  
   493  	fileSHA, err := hash.SHA256ForFile(path)
   494  	if err != nil {
   495  		return nil, fmt.Errorf("file retrieve sha256 error: %s: %w", path, err)
   496  	}
   497  
   498  	if !isSigned && !ignoreTLog {
   499  		s.log().Infof("Skipping unsigned file: %s", path)
   500  		return nil, nil
   501  	}
   502  
   503  	err = s.impl.VerifyFileInternal(ctx, ko, certOpts, s.options.OutputSignaturePath, path)
   504  	if err != nil {
   505  		return nil, fmt.Errorf("verify file reference: %s: %w", path, err)
   506  	}
   507  
   508  	return &SignedObject{
   509  		file: &SignedFile{
   510  			path:            path,
   511  			sha256:          fileSHA,
   512  			signaturePath:   s.options.OutputSignaturePath,
   513  			certificatePath: s.options.OutputCertificatePath,
   514  		},
   515  	}, nil
   516  }
   517  
   518  // IsImageSigned takes an image reference and returns true if there are
   519  // signatures available for it. It makes no signature verification, only
   520  // checks to see if more than one signature is available.
   521  func (s *Signer) IsImageSigned(imageRef string) (bool, error) {
   522  	item := s.signedRefs.Get(imageRef)
   523  	if item != nil {
   524  		return item.Value(), nil
   525  	}
   526  
   527  	res, err := s.impl.ImagesSigned(context.Background(), s, imageRef)
   528  	if err != nil {
   529  		return false, fmt.Errorf("check if image is signed: %w", err)
   530  	}
   531  	signed, ok := res.Load(imageRef)
   532  	if !ok {
   533  		return false, errors.New("ref is not part of result")
   534  	}
   535  	signedBool, ok := signed.(bool)
   536  	if !ok {
   537  		return false, fmt.Errorf("interface conversion error, result is not a bool: %v", signed)
   538  	}
   539  
   540  	s.signedRefs.Set(imageRef, signedBool, ttlcache.DefaultTTL)
   541  
   542  	return signedBool, nil
   543  }
   544  
   545  // ImagesSigned verifies if the provided image references are signed. It
   546  // returns a sync map where the key is the ref and the value is a boolean which
   547  // indicates if the image is signed or not. The method runs highly parallel.
   548  func (s *Signer) ImagesSigned(ctx context.Context, refs ...string) (*sync.Map, error) {
   549  	s.log().Debug("Checking cache")
   550  	res := &sync.Map{}
   551  	unknownRefs := []string{}
   552  	for _, ref := range refs {
   553  		item := s.signedRefs.Get(ref)
   554  		if item != nil {
   555  			res.Store(ref, item.Value())
   556  			continue
   557  		}
   558  
   559  		unknownRefs = append(unknownRefs, ref)
   560  	}
   561  
   562  	if len(unknownRefs) == 0 {
   563  		s.log().Debug("All references already available in cache")
   564  		return res, nil
   565  	}
   566  
   567  	s.log().Debug("Parsing references")
   568  	repos := []name.Repository{}
   569  	for _, ref := range unknownRefs {
   570  		item := s.parsedRefs.Get(ref)
   571  		if item != nil {
   572  			repos = append(repos, item.Value().Context())
   573  			continue
   574  		}
   575  
   576  		parsedRef, err := s.impl.ParseReference(ref)
   577  		if err != nil {
   578  			return nil, fmt.Errorf("parsing image reference: %w", err)
   579  		}
   580  
   581  		repos = append(repos, parsedRef.Context())
   582  		s.parsedRefs.Set(ref, parsedRef, ttlcache.DefaultTTL)
   583  	}
   584  
   585  	s.log().Debug("Building transports")
   586  	transports, count, err := s.transportsForRefs(ctx, repos...)
   587  	if err != nil {
   588  		return nil, fmt.Errorf("build transports: %w", err)
   589  	}
   590  	s.log().Debugf("Built %d transports for %d refs", count, len(unknownRefs))
   591  
   592  	s.log().Debug("Checking if refs are signed")
   593  	t := throttler.New(int(s.options.MaxWorkers), len(unknownRefs))
   594  	for i, repo := range repos {
   595  		go func(repo name.Repository, i int) {
   596  			ref := unknownRefs[i]
   597  
   598  			trans, ok := transports.Load(repo)
   599  			if !ok {
   600  				t.Done(fmt.Errorf("no transport found for repo: %s", repo.String()))
   601  				return
   602  			}
   603  			tr, ok := trans.(http.RoundTripper)
   604  			if !ok {
   605  				t.Done(fmt.Errorf("transport has wrong type: %v", tr))
   606  				return
   607  			}
   608  
   609  			digest, err := s.impl.Digest(ref, crane.WithTransport(tr))
   610  			if err != nil {
   611  				t.Done(fmt.Errorf("get digest for image reference: %w", err))
   612  				return
   613  			}
   614  			if _, err := s.impl.Digest(repoDigestToSig(repo, digest), crane.WithTransport(tr)); err != nil {
   615  				if transportErr, ok := err.(*transport.Error); ok && len(transportErr.Errors) > 0 {
   616  					if transportErr.Errors[0].Code == transport.ManifestUnknownErrorCode {
   617  						res.Store(ref, false)
   618  						s.signedRefs.Set(ref, false, ttlcache.DefaultTTL)
   619  						t.Done(nil)
   620  						return
   621  					}
   622  				}
   623  				t.Done(fmt.Errorf("get digest for signature: %w", err))
   624  				return
   625  			}
   626  
   627  			res.Store(ref, true)
   628  			s.signedRefs.Set(ref, true, ttlcache.DefaultTTL)
   629  			t.Done(nil)
   630  		}(repo, i)
   631  
   632  		if t.Throttle() > 0 {
   633  			break
   634  		}
   635  	}
   636  
   637  	s.log().Debug("Done checking if refs are signed")
   638  
   639  	if err := t.Err(); err != nil {
   640  		return res, fmt.Errorf("check if images are signed: %w", err)
   641  	}
   642  
   643  	return res, nil
   644  }
   645  
   646  func (s *Signer) transportsForRefs(ctx context.Context, repos ...name.Repository) (*sync.Map, int, error) {
   647  	count := 0
   648  	t := throttler.New(int(s.options.MaxWorkers), len(repos))
   649  	transports := &sync.Map{}
   650  
   651  	for _, repo := range repos {
   652  		go func(repo name.Repository) {
   653  			if _, loaded := transports.LoadOrStore(repo, nil); loaded {
   654  				t.Done(nil)
   655  				return
   656  			}
   657  
   658  			item := s.transports.Get(repo)
   659  			if item != nil {
   660  				transports.Store(repo, item.Value())
   661  				count++
   662  				t.Done(nil)
   663  				return
   664  			}
   665  
   666  			tr, err := s.transportForRepo(ctx, repo)
   667  			if err != nil {
   668  				t.Done(fmt.Errorf("create transport for repo %s: %w", repo.String(), err))
   669  				return
   670  			}
   671  
   672  			s.transports.Set(repo, tr, ttlcache.DefaultTTL)
   673  			transports.Store(repo, tr)
   674  			count++
   675  			t.Done(nil)
   676  		}(repo)
   677  
   678  		if t.Throttle() > 0 {
   679  			break
   680  		}
   681  	}
   682  
   683  	if err := t.Err(); err != nil {
   684  		return nil, 0, fmt.Errorf("building transports: %w", err)
   685  	}
   686  
   687  	return transports, count, nil
   688  }
   689  
   690  func (s *Signer) transportForRepo(ctx context.Context, repo name.Repository) (http.RoundTripper, error) {
   691  	scopes := []string{repo.Scope(transport.PullScope)}
   692  
   693  	t := remote.DefaultTransport
   694  	t = transport.NewLogger(t)
   695  	t = transport.NewRetry(t)
   696  	t = transport.NewUserAgent(t, "k8s-release-sdk")
   697  
   698  	t, err := s.impl.NewWithContext(ctx, repo.Registry, authn.Anonymous, t, scopes)
   699  	if err != nil {
   700  		return nil, fmt.Errorf("create new transport: %w", err)
   701  	}
   702  
   703  	return t, nil
   704  }
   705  
   706  func repoDigestToSig(repo name.Repository, digest string) string {
   707  	return repo.Name() + ":" + strings.Replace(digest, ":", "-", 1) + ".sig"
   708  }
   709  
   710  // IsFileSigned takes an path reference and return true if there is a signature
   711  // available for it. It makes no signature verification, only checks to see if
   712  // there is a TLog to be found on Rekor.
   713  func (s *Signer) IsFileSigned(ctx context.Context, path string) (bool, error) {
   714  	ko := cliOpts.KeyOpts{
   715  		KeyRef:   s.options.PublicKeyPath,
   716  		RekorURL: cliOpts.DefaultRekorURL,
   717  	}
   718  
   719  	rClient, err := s.impl.NewRekorClient(ko.RekorURL)
   720  	if err != nil {
   721  		return false, fmt.Errorf("creating rekor client: %w", err)
   722  	}
   723  
   724  	blobBytes, err := s.impl.PayloadBytes(path)
   725  	if err != nil {
   726  		return false, err
   727  	}
   728  
   729  	var b64sig string
   730  	if isb64(blobBytes) {
   731  		b64sig = string(blobBytes)
   732  	} else {
   733  		b64sig = base64.StdEncoding.EncodeToString(blobBytes)
   734  	}
   735  
   736  	pubKey, err := sigs.PublicKeyFromKeyRef(ctx, ko.KeyRef)
   737  	if err != nil {
   738  		return false, fmt.Errorf("getting the public key: %w", err)
   739  	}
   740  
   741  	pubBytes, err := sigs.PublicKeyPem(pubKey, signatureoptions.WithContext(ctx))
   742  	if err != nil {
   743  		return false, fmt.Errorf("generating the public key pem: %w", err)
   744  	}
   745  
   746  	uuids, err := s.impl.FindTlogEntry(ctx, rClient, b64sig, blobBytes, pubBytes)
   747  	if err != nil {
   748  		return false, fmt.Errorf("find rekor tlog entries: %w", err)
   749  	}
   750  
   751  	return len(uuids) > 0, nil
   752  }
   753  
   754  // identityToken returns an identity token to perform keyless signing.
   755  // If there is one set in the options we will use that one. If not,
   756  // signer will try to get one from the cosign OIDC identity providers
   757  // if options.EnableTokenProviders is set
   758  func (s *Signer) identityToken(ctx context.Context) (string, error) {
   759  	tok := s.options.IdentityToken
   760  	if s.options.PrivateKeyPath == "" && s.options.IdentityToken == "" {
   761  		// We only attempt to pull from the providers if the option is set
   762  		if !s.options.EnableTokenProviders {
   763  			s.log().Warn("No token set in options and OIDC providers are disabled")
   764  			return "", nil
   765  		}
   766  
   767  		s.log().Info("No identity token was provided. Attempting to get one from supported providers.")
   768  		token, err := s.impl.TokenFromProviders(ctx, s.log())
   769  		if err != nil {
   770  			return "", fmt.Errorf("getting identity token from providers: %w", err)
   771  		}
   772  		tok = token
   773  	}
   774  	return tok, nil
   775  }
   776  
   777  func isb64(data []byte) bool {
   778  	_, err := base64.StdEncoding.DecodeString(string(data))
   779  	return err == nil
   780  }