@@ -17,6 +17,8 @@ limitations under the License.
17
17
package imagepromoter
18
18
19
19
import (
20
+ "bytes"
21
+ "encoding/json"
20
22
"fmt"
21
23
"net/url"
22
24
"strings"
@@ -33,6 +35,9 @@ import (
33
35
"github.com/google/go-containerregistry/pkg/name"
34
36
"github.com/google/go-containerregistry/pkg/v1/google"
35
37
"github.com/sirupsen/logrus"
38
+
39
+ "github.com/sigstore/sigstore/pkg/cryptoutils"
40
+
36
41
checkresults "sigs.k8s.io/promo-tools/v3/promoter/image/checkresults"
37
42
options "sigs.k8s.io/promo-tools/v3/promoter/image/options"
38
43
"sigs.k8s.io/promo-tools/v3/types/image"
@@ -64,7 +69,7 @@ func (di *DefaultPromoterImplementation) GetLatestImages(opts *options.Options)
64
69
if err != nil {
65
70
return nil , fmt .Errorf ("fetching latest images: %w" , err )
66
71
}
67
- logrus .Infof ("+%v" , images )
72
+ logrus .Infof ("Images to check: +%v" , images )
68
73
return images , nil
69
74
}
70
75
@@ -148,7 +153,7 @@ func (di *DefaultPromoterImplementation) GetSignatureStatus(
148
153
}
149
154
150
155
logrus .Infof ("Checking %s for signatures in %d mirrors" , refString , len (targetImages ))
151
- existing , missing , err := checkObjects ( targetImages )
156
+ existing , missing , err := di . CheckSignatureLayers ( opts , targetImages )
152
157
if err != nil {
153
158
return results , fmt .Errorf ("checking objects: %w" , err )
154
159
}
@@ -160,37 +165,90 @@ func (di *DefaultPromoterImplementation) GetSignatureStatus(
160
165
return results , nil
161
166
}
162
167
163
- func checkObjects (oList []string ) (existing , missing []string , err error ) {
168
+ // miniManifest is a minimal representation of the sigstore signature manifest
169
+ type miniManifest struct {
170
+ Layers []struct {
171
+ MediaType string
172
+ Annotations map [string ]string `json:"annotations"`
173
+ } `json:"layers"`
174
+ }
175
+
176
+ // CheckSignatureLayers checks a list of signature layers to ensure
177
+ func (di * DefaultPromoterImplementation ) CheckSignatureLayers (opts * options.Options , oList []string ) (existing , missing []string , err error ) {
164
178
// TODO: Parallelize this check
165
179
existing = []string {}
166
180
missing = []string {}
167
181
for _ , s := range oList {
168
- e , err := objectExists (s )
182
+ e , err := objectExists (opts , s )
169
183
if err != nil {
170
184
return existing , missing , fmt .Errorf ("checking reference: %w" , err )
171
185
}
172
186
173
- if e {
174
- existing = append (existing , s )
175
- } else {
187
+ if ! e {
176
188
missing = append (missing , s )
189
+ continue
177
190
}
191
+
192
+ existing = append (existing , s )
178
193
}
179
194
return existing , missing , nil
180
195
}
181
196
182
- func objectExists (refString string ) (bool , error ) {
197
+ func objectExists (opts * options. Options , refString string ) (bool , error ) {
183
198
// Check
184
- _ , err := crane .Digest (refString )
185
- if err == nil {
186
- return true , nil
199
+ manifestData , err := crane .Manifest (refString )
200
+ if err != nil {
201
+ if strings .Contains (err .Error (), "MANIFEST_UNKNOWN" ) {
202
+ logrus .WithField ("image" , refString ).Info ("No signature found" )
203
+ return false , nil
204
+ }
205
+ return false , fmt .Errorf ("pulling signature manifest: %w" , err )
187
206
}
188
207
189
- if strings .Contains (err .Error (), "MANIFEST_UNKNOWN" ) {
208
+ manifest := & miniManifest {}
209
+ if err := json .Unmarshal (manifestData , manifest ); err != nil {
210
+ return false , fmt .Errorf ("parsing .sig image manifest: %w" , err )
211
+ }
212
+
213
+ // Get the certificate
214
+ if manifest .Layers == nil || len (manifest .Layers ) == 0 {
190
215
return false , nil
191
216
}
217
+ signedLayers := 0
218
+ for _ , layer := range manifest .Layers {
219
+ if layer .MediaType != "application/vnd.dev.cosign.simplesigning.v1+json" {
220
+ continue
221
+ }
222
+
223
+ certData , ok := layer .Annotations ["dev.sigstore.cosign/certificate" ]
224
+ if ! ok {
225
+ continue
226
+ }
192
227
193
- return false , fmt .Errorf ("checking if reference exists in the registry: %w" , err )
228
+ var b bytes.Buffer
229
+ b .Write ([]byte (certData ))
230
+
231
+ certs , err := cryptoutils .LoadCertificatesFromPEM (& b )
232
+ if err != nil {
233
+ return false , err
234
+ }
235
+
236
+ names := cryptoutils .GetSubjectAlternateNames (certs [0 ])
237
+ for _ , n := range names {
238
+ if n == opts .SignCheckIdentity {
239
+ return true , nil
240
+ }
241
+ }
242
+ signedLayers ++
243
+ }
244
+
245
+ if signedLayers == 0 {
246
+ logrus .WithField ("image" , refString ).Debugf ("No certificates found" )
247
+ } else {
248
+ logrus .WithField ("image" , refString ).Debugf ("Image signed, but not with expected identity" )
249
+ }
250
+
251
+ return false , nil
194
252
}
195
253
196
254
// FixMissingSignatures signs an image that has no signatures at all
@@ -200,13 +258,15 @@ func (di *DefaultPromoterImplementation) FixMissingSignatures(opts *options.Opti
200
258
continue
201
259
}
202
260
203
- logrus .Infof ("Signing and replicating %s" , mainImg )
261
+ logrus .Infof ("Signing and replicating first mirror (%s)" , mainImg )
262
+
204
263
// Build the digest of the first missing one
205
- digestRef := strings .ReplaceAll (res .Missing [0 ], ":sha256-" , "@sha256:" )
264
+ digestRef := strings .TrimSuffix ( strings . ReplaceAll (res .Missing [0 ], ":sha256-" , "@sha256:" ), ".sig " )
206
265
if err := di .signReference (opts , digestRef ); err != nil {
207
- return fmt .Errorf ("signing %s: %w" , digestRef , err )
266
+ return fmt .Errorf ("signing first mirror reference %s: %w" , digestRef , err )
208
267
}
209
268
269
+ logrus .Infof ("Replicating image to %d mirrors" , len (res .Missing [1 :]))
210
270
for _ , targetRef := range res .Missing [1 :] {
211
271
if err := di .replicateReference (opts , res .Missing [0 ], targetRef ); err != nil {
212
272
return fmt .Errorf ("replicating signature: %w" , err )
@@ -304,7 +364,15 @@ func (di *DefaultPromoterImplementation) readLatestImages(opts *options.Options)
304
364
google .Keychain ,
305
365
))
306
366
307
- dateCutOff := time .Now ().AddDate (0 , 0 , opts .SignCheckDays * - 1 )
367
+ dateCutOff := time .Now ().AddDate (0 , 0 , opts .SignCheckFromDays * - 1 )
368
+ dateCutOffTo := time .Now ()
369
+ if opts .SignCheckToDays > 0 {
370
+ dateCutOffTo = time .Now ().AddDate (0 , 0 , opts .SignCheckToDays * - 1 )
371
+ }
372
+ logrus .Infof ("Checking images from %s to %s" ,
373
+ dateCutOff .Local ().Format (time .RFC822 ),
374
+ dateCutOffTo .Local ().Format (time .RFC822 ),
375
+ )
308
376
images := []string {}
309
377
310
378
repo , err := name .NewRepository (scanRegistry + "/" + repositoryPath , name .WeakValidation )
@@ -342,6 +410,10 @@ func (di *DefaultPromoterImplementation) readLatestImages(opts *options.Options)
342
410
continue
343
411
}
344
412
413
+ if opts .SignCheckToDays > 0 && manifest .Uploaded .After (dateCutOffTo ) {
414
+ continue
415
+ }
416
+
345
417
mt .Lock ()
346
418
images = append (images , strings .ReplaceAll (
347
419
fmt .Sprintf ("%s:%s" , repo , manifest .Tags [0 ]),
@@ -356,5 +428,9 @@ func (di *DefaultPromoterImplementation) readLatestImages(opts *options.Options)
356
428
return nil , fmt .Errorf ("walking repo: %w" , err )
357
429
}
358
430
431
+ if opts .SignCheckMaxImages != 0 && len (images ) > opts .SignCheckMaxImages {
432
+ images = images [0 :opts .SignCheckMaxImages ]
433
+ }
434
+
359
435
return images , nil
360
436
}
0 commit comments