@@ -11,11 +11,9 @@ import (
1111
1212 v1 "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1"
1313 "github.com/acorn-io/acorn/pkg/imagesystem"
14- "github.com/google/go-containerregistry/pkg/authn"
1514 "github.com/google/go-containerregistry/pkg/crane"
1615 "github.com/google/go-containerregistry/pkg/name"
1716 ggcrv1 "github.com/google/go-containerregistry/pkg/v1"
18- "github.com/google/go-containerregistry/pkg/v1/remote"
1917 "github.com/google/go-containerregistry/pkg/v1/remote/transport"
2018 "github.com/sigstore/cosign/pkg/cosign"
2119 "github.com/sigstore/cosign/pkg/oci"
@@ -31,89 +29,83 @@ import (
3129)
3230
3331type VerifyOpts struct {
34- ImageRef string
32+ ImageRef name.Digest
33+ SignatureRef name.Reference
3534 Namespace string
3635 AnnotationRules v1.SignatureAnnotations
3736 Key string
3837 SignatureAlgorithm string
39- RegistryClientOpts []ociremote.Option
38+ OciRemoteOpts []ociremote.Option
39+ CraneOpts []crane.Option
4040 NoCache bool
4141}
4242
43- // verifySignature checks if the image is signed with the given key and if the annotations match the given rules
44- func VerifySignature (ctx context.Context , c client.Reader , opts VerifyOpts ) error {
43+ // EnsureReferences will enrich the VerifyOpts with the image digest and signature reference.
44+ // It's outsourced here, so we can ensure that it's used as few times as possible to reduce the number of potential
45+ // GET requests to the registry which would count against potential rate limits.
46+ func EnsureReferences (ctx context.Context , c client.Reader , img string , opts * VerifyOpts ) error {
4547 // --- image name to digest hash
46- imgRef , err := name .ParseReference (opts . ImageRef )
48+ imgRef , err := name .ParseReference (img )
4749 if err != nil {
48- return fmt .Errorf ("failed to parse image %s: %w" , opts . ImageRef , err )
50+ return fmt .Errorf ("failed to parse image %s: %w" , img , err )
4951 }
5052
51- imgDigest , err := ociremote . ResolveDigest (imgRef , opts .RegistryClientOpts ... )
53+ imgDigest , err := crane . Digest (imgRef . Name () , opts .CraneOpts ... ) // this uses HEAD to determine the digest, but falls back to GET if HEAD fails
5254 if err != nil {
5355 return fmt .Errorf ("failed to resolve image digest: %w" , err )
5456 }
5557
56- imgDigestHash , err := ggcrv1 .NewHash (imgDigest .Identifier ())
57- if err != nil {
58- return err
59- }
58+ opts .ImageRef = imgRef .Context ().Digest (imgDigest )
6059
61- // --- cosign verifier options
62-
63- cosignOpts := & cosign.CheckOpts {
64- Annotations : map [string ]interface {}{},
65- ClaimVerifier : cosign .SimpleClaimVerifier ,
66- RegistryClientOpts : opts .RegistryClientOpts ,
60+ signatureRef , err := ensureSignatureArtifact (ctx , c , opts .Namespace , opts .ImageRef , opts .NoCache , opts .OciRemoteOpts , opts .CraneOpts )
61+ if err != nil {
62+ return fmt .Errorf ("failed to ensure signature artifact: %w" , err )
6763 }
64+ opts .SignatureRef = signatureRef
6865
69- // --- parse key
70- if opts .Key != "" {
71- verifier , err := LoadKey (ctx , opts .Key , opts .SignatureAlgorithm )
72- if err != nil {
73- return fmt .Errorf ("failed to load key: %w" , err )
74- }
75- cosignOpts .SigVerifier = verifier
76- }
66+ return nil
67+ }
7768
69+ func ensureSignatureArtifact (ctx context.Context , c client.Reader , namespace string , img name.Digest , noCache bool , ociRemoteOpts []ociremote.Option , craneOpts []crane.Option ) (name.Reference , error ) {
7870 // -- signature hash
79- sigTag , err := ociremote .SignatureTag (imgRef , opts . RegistryClientOpts ... )
71+ sigTag , err := ociremote .SignatureTag (img , ociRemoteOpts ... ) // we force imgRef to be a digest above, so this should *not* make a GET request to the registry
8072 if err != nil {
81- return fmt .Errorf ("failed to get signature tag: %w" , err )
73+ return nil , fmt .Errorf ("failed to get signature tag: %w" , err )
8274 }
8375
84- sigDigest , err := crane .Digest (sigTag .Name (), crane . WithAuthFromKeychain ( authn . DefaultKeychain ))
76+ sigDigest , err := crane .Digest (sigTag .Name (), craneOpts ... ) // this uses HEAD to determine the digest, but falls back to GET if HEAD fails
8577 if err != nil {
8678 if terr , ok := err .(* transport.Error ); ok && terr .StatusCode == http .StatusNotFound {
8779 // signature artifact not found -> that's an actual verification error
88- return fmt .Errorf ("%w: expected signature artifact %s not found" , cosign .ErrNoMatchingSignatures , sigTag .Name ())
80+ return nil , fmt .Errorf ("%w: expected signature artifact %s not found" , cosign .ErrNoMatchingSignatures , sigTag .Name ())
8981 }
90- return fmt .Errorf ("failed to get signature digest: %w" , err )
82+ return nil , fmt .Errorf ("failed to get signature digest: %w" , err )
9183 }
9284
9385 sigRefToUse , err := name .ParseReference (sigTag .Name (), name .WeakValidation )
9486 if err != nil {
95- return fmt .Errorf ("failed to parse signature reference: %w" , err )
87+ return nil , fmt .Errorf ("failed to parse signature reference: %w" , err )
9688 }
9789 logrus .Debugf ("Signature %s has digest: %s" , sigRefToUse .Name (), sigDigest )
9890
99- if ! opts . NoCache {
100- internalRepo , _ , err := imagesystem .GetInternalRepoForNamespace (ctx , c , opts . Namespace )
91+ if ! noCache {
92+ internalRepo , _ , err := imagesystem .GetInternalRepoForNamespace (ctx , c , namespace )
10193 if err != nil {
102- return fmt .Errorf ("failed to get internal repo for namespace %s: %w" , opts . Namespace , err )
94+ return nil , fmt .Errorf ("failed to get internal repo for namespace %s: %w" , namespace , err )
10395 }
10496
10597 localSignatureArtifact := fmt .Sprintf ("%s:%s" , internalRepo , sigTag .Identifier ())
10698
10799 // --- check if we have the signature artifact locally, if not, copy it over from external registry
108100 mustPull := false
109- localSigSHA , err := crane .Digest (localSignatureArtifact , crane . WithAuthFromKeychain ( authn . DefaultKeychain ))
101+ localSigSHA , err := crane .Digest (localSignatureArtifact , craneOpts ... ) // this uses HEAD to determine the digest, but falls back to GET if HEAD fails
110102 if err != nil {
111103 var terr * transport.Error
112104 if ok := errors .As (err , & terr ); ok && terr .StatusCode == http .StatusNotFound {
113105 logrus .Debugf ("signature artifact %s not found locally, will try to pull it" , localSignatureArtifact )
114106 mustPull = true
115107 } else {
116- return fmt .Errorf ("failed to get local signature digest, cannot check if we have it cached locally: %w" , err )
108+ return nil , fmt .Errorf ("failed to get local signature digest, cannot check if we have it cached locally: %w" , err )
117109 }
118110 } else if localSigSHA != sigDigest {
119111 logrus .Debugf ("Local signature digest %s does not match remote signature digest %s, will try to pull it" , localSigSHA , sigDigest )
@@ -122,27 +114,57 @@ func VerifySignature(ctx context.Context, c client.Reader, opts VerifyOpts) erro
122114
123115 if mustPull {
124116 // --- pull signature artifact
125- err := crane .Copy (sigTag .Name (), localSignatureArtifact , crane . WithAuthFromKeychain ( authn . DefaultKeychain ))
117+ err := crane .Copy (sigTag .Name (), localSignatureArtifact , craneOpts ... ) // Pull (GET) counts against the rate limits, so this shouldn't be done too often
126118 if err != nil {
127- return fmt .Errorf ("failed to copy signature artifact: %w" , err )
119+ return nil , fmt .Errorf ("failed to copy signature artifact: %w" , err )
128120 }
129121 }
130122
131123 lname , err := name .ParseReference (localSignatureArtifact , name .WeakValidation )
132124 if err != nil {
133- return fmt .Errorf ("failed to parse local signature artifact %s: %w" , localSignatureArtifact , err )
125+ return nil , fmt .Errorf ("failed to parse local signature artifact %s: %w" , localSignatureArtifact , err )
134126 }
135127
136128 sigRefToUse = lname
137129
138- logrus .Debugf ("Checking if image %s is signed with %s (cache: %s)" , imgRef , sigTag , localSignatureArtifact )
130+ logrus .Debugf ("Checking if image %s is signed with %s (cache: %s)" , img , sigTag , localSignatureArtifact )
139131 }
140132
141- sigs , err := ociremote .Signatures (sigRefToUse , ociremote .WithRemoteOptions (remote .WithAuthFromKeychain (authn .DefaultKeychain )))
133+ return sigRefToUse , nil
134+ }
135+
136+ // VerifySignature checks if the image is signed with the given key and if the annotations match the given rules
137+ // This does a lot of image and image manifest juggling to fetch artifacts, digests, etc. from the registry, so we have to be
138+ // careful to not do too many GET requests that count against registry rate limits (e.g. for DockerHub).
139+ // Crane uses HEAD (with GET as a fallback) wherever it can, so it's a good choice here e.g. for fetching digests.
140+ func VerifySignature (ctx context.Context , opts VerifyOpts ) error {
141+ sigs , err := ociremote .Signatures (opts .SignatureRef , opts .OciRemoteOpts ... ) // this runs against our internal registry, so it should not count against the rate limits
142142 if err != nil {
143143 return fmt .Errorf ("failed to get signatures: %w" , err )
144144 }
145145
146+ imgDigestHash , err := ggcrv1 .NewHash (opts .ImageRef .DigestStr ())
147+ if err != nil {
148+ return err
149+ }
150+
151+ // --- cosign verifier options
152+
153+ cosignOpts := & cosign.CheckOpts {
154+ Annotations : map [string ]interface {}{},
155+ ClaimVerifier : cosign .SimpleClaimVerifier ,
156+ RegistryClientOpts : opts .OciRemoteOpts ,
157+ }
158+
159+ // --- parse key
160+ if opts .Key != "" {
161+ verifier , err := LoadKey (ctx , opts .Key , opts .SignatureAlgorithm )
162+ if err != nil {
163+ return fmt .Errorf ("failed to load key: %w" , err )
164+ }
165+ cosignOpts .SigVerifier = verifier
166+ }
167+
146168 // --- get and verify signatures
147169 signatures , bundlesVerified , err := verifySignatures (ctx , sigs , imgDigestHash , cosignOpts )
148170 if err != nil {
@@ -152,7 +174,7 @@ func VerifySignature(ctx context.Context, c client.Reader, opts VerifyOpts) erro
152174 return fmt .Errorf ("failed to verify image signatures: %w" , err )
153175 }
154176
155- logrus .Debugf ("image %s: %d signatures verified (bundle verified: %v)" , imgRef , len (signatures ), bundlesVerified )
177+ logrus .Debugf ("image %s: %d signatures verified (bundle verified: %v)" , opts . ImageRef . Name () , len (signatures ), bundlesVerified )
156178
157179 // --- extract payloads for subsequent checks
158180 payloads , err := extractPayload (signatures )
@@ -167,7 +189,7 @@ func VerifySignature(ctx context.Context, c client.Reader, opts VerifyOpts) erro
167189 }
168190 return fmt .Errorf ("failed to check annotations: %w" , err )
169191 }
170- logrus .Debugf ("image %s: Annotations (%+v) verified" , imgRef , opts .AnnotationRules )
192+ logrus .Debugf ("image %s: Annotations (%+v) verified" , opts . ImageRef . Name () , opts .AnnotationRules )
171193
172194 return nil
173195}
0 commit comments