@@ -3,6 +3,7 @@ package app
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
+ "errors"
6
7
"fmt"
7
8
"net/http"
8
9
"os"
@@ -13,22 +14,34 @@ import (
13
14
14
15
"github.com/spf13/cobra"
15
16
17
+ "github.com/stacklok/toolhive/pkg/container/verifier"
16
18
"github.com/stacklok/toolhive/pkg/logger"
17
19
"github.com/stacklok/toolhive/pkg/registry"
18
20
)
19
21
20
22
var (
21
- count int
22
- dryRun bool
23
- githubToken string
24
- serverName string
23
+ count int
24
+ dryRun bool
25
+ githubToken string
26
+ serverName string
27
+ verifyProvenance bool
25
28
)
26
29
27
30
type serverWithName struct {
28
31
name string
29
32
server * registry.ImageMetadata
30
33
}
31
34
35
+ // ProvenanceVerificationError represents an error during provenance verification
36
+ type ProvenanceVerificationError struct {
37
+ ServerName string
38
+ Reason string
39
+ }
40
+
41
+ func (e * ProvenanceVerificationError ) Error () string {
42
+ return fmt .Sprintf ("provenance verification failed for server %s: %s" , e .ServerName , e .Reason )
43
+ }
44
+
32
45
var updateCmd = & cobra.Command {
33
46
Use : "update" ,
34
47
Short : "Update registry entries with latest information" ,
@@ -45,6 +58,8 @@ func init() {
45
58
"GitHub token for API authentication (can also be set via GITHUB_TOKEN env var)" )
46
59
updateCmd .Flags ().StringVarP (& serverName , "server" , "s" , "" ,
47
60
"Specific server name to update" )
61
+ updateCmd .Flags ().BoolVar (& verifyProvenance , "verify-provenance" , false ,
62
+ "Verify provenance information and remove servers that fail verification" )
48
63
49
64
// Mark count and server flags as mutually exclusive
50
65
updateCmd .MarkFlagsMutuallyExclusive ("count" , "server" )
@@ -69,10 +84,16 @@ func updateCmdFunc(_ *cobra.Command, _ []string) error {
69
84
}
70
85
71
86
// Update servers
72
- updatedServers := updateServers (servers )
87
+ updatedServers , failedServers := updateServers (servers , reg )
88
+
89
+ // Log summary
90
+ if len (failedServers ) > 0 {
91
+ logger .Warnf ("Removed %d servers due to provenance verification failures: %v" ,
92
+ len (failedServers ), failedServers )
93
+ }
73
94
74
95
// Save results
75
- return saveResults (reg , updatedServers )
96
+ return saveResults (reg , updatedServers , failedServers )
76
97
}
77
98
78
99
func loadRegistry () (* registry.Registry , error ) {
@@ -159,37 +180,49 @@ func isOlder(serverI, serverJ *registry.ImageMetadata) bool {
159
180
return timeI .Before (timeJ )
160
181
}
161
182
162
- func updateServers (servers []serverWithName ) []string {
183
+ func updateServers (servers []serverWithName , reg * registry. Registry ) ( []string , [] string ) {
163
184
updatedServers := make ([]string , 0 , len (servers ))
185
+ failedServers := make ([]string , 0 )
164
186
165
187
for _ , s := range servers {
166
188
logger .Infof ("Updating server: %s" , s .name )
167
189
168
190
if err := updateServerInfo (s .name , s .server ); err != nil {
191
+ var provenanceErr * ProvenanceVerificationError
192
+ if errors .As (err , & provenanceErr ) {
193
+ logger .Errorf ("Provenance verification failed for server %s: %v" , s .name , err )
194
+ failedServers = append (failedServers , s .name )
195
+ continue
196
+ }
169
197
logger .Errorf ("Failed to update server %s: %v" , s .name , err )
170
198
continue
171
199
}
172
200
173
201
updatedServers = append (updatedServers , s .name )
174
202
}
175
203
176
- return updatedServers
204
+ // Remove failed servers from registry if provenance verification is enabled
205
+ if verifyProvenance && len (failedServers ) > 0 {
206
+ removeFailedServers (reg , failedServers )
207
+ }
208
+
209
+ return updatedServers , failedServers
177
210
}
178
211
179
- func saveResults (reg * registry.Registry , updatedServers []string ) error {
212
+ func saveResults (reg * registry.Registry , updatedServers []string , failedServers [] string ) error {
180
213
// If we're in dry run mode, don't save changes
181
214
if dryRun {
182
215
logger .Info ("Dry run completed, no changes made" )
183
216
return nil
184
217
}
185
218
186
- // If we updated any servers, save the registry
187
- if len (updatedServers ) > 0 {
219
+ // If we updated any servers or removed any servers , save the registry
220
+ if len (updatedServers ) > 0 || len ( failedServers ) > 0 {
188
221
// Update the last_updated timestamp
189
222
reg .LastUpdated = time .Now ().UTC ().Format (time .RFC3339 )
190
223
191
224
// Save the updated registry
192
- if err := saveRegistry (reg , updatedServers ); err != nil {
225
+ if err := saveRegistry (reg , updatedServers , failedServers ); err != nil {
193
226
return fmt .Errorf ("failed to save registry: %w" , err )
194
227
}
195
228
@@ -203,6 +236,16 @@ func saveResults(reg *registry.Registry, updatedServers []string) error {
203
236
204
237
// updateServerInfo updates the GitHub stars and pulls for a server
205
238
func updateServerInfo (name string , server * registry.ImageMetadata ) error {
239
+ // Verify provenance if requested
240
+ if verifyProvenance {
241
+ if err := verifyServerProvenance (name , server ); err != nil {
242
+ return & ProvenanceVerificationError {
243
+ ServerName : name ,
244
+ Reason : err .Error (),
245
+ }
246
+ }
247
+ }
248
+
206
249
// Skip if no repository URL
207
250
if server .RepositoryURL == "" {
208
251
logger .Warnf ("ImageMetadata %s has no repository URL, skipping" , name )
@@ -245,6 +288,50 @@ func updateServerInfo(name string, server *registry.ImageMetadata) error {
245
288
return nil
246
289
}
247
290
291
+ // verifyServerProvenance verifies the provenance information for a server
292
+ func verifyServerProvenance (name string , server * registry.ImageMetadata ) error {
293
+ // Skip if no provenance information
294
+ if server .Provenance == nil {
295
+ logger .Warnf ("Server %s has no provenance information, skipping verification" , name )
296
+ return nil
297
+ }
298
+
299
+ // Skip if no image reference
300
+ if server .Image == "" {
301
+ return fmt .Errorf ("no image reference provided" )
302
+ }
303
+
304
+ logger .Infof ("Verifying provenance for server %s with image %s" , name , server .Image )
305
+
306
+ // Create verifier
307
+ v , err := verifier .New (server )
308
+ if err != nil {
309
+ return fmt .Errorf ("failed to create verifier: %w" , err )
310
+ }
311
+
312
+ // Get verification results
313
+ isVerified , err := v .VerifyServer (server .Image , server )
314
+ if err != nil {
315
+ return fmt .Errorf ("verification failed: %w" , err )
316
+ }
317
+
318
+ // Check if we have valid verification results
319
+ if isVerified {
320
+ logger .Infof ("Server %s verified successfully" , name )
321
+ return nil
322
+ }
323
+
324
+ return fmt .Errorf ("no verified signatures found" )
325
+ }
326
+
327
+ // removeFailedServers removes servers that failed provenance verification from the registry
328
+ func removeFailedServers (reg * registry.Registry , failedServers []string ) {
329
+ for _ , serverName := range failedServers {
330
+ logger .Warnf ("Removing server %s from registry due to provenance verification failure" , serverName )
331
+ delete (reg .Servers , serverName )
332
+ }
333
+ }
334
+
248
335
// extractOwnerRepo extracts the owner and repo from a GitHub repository URL
249
336
func extractOwnerRepo (url string ) (string , string , error ) {
250
337
// Remove trailing .git if present
@@ -333,7 +420,7 @@ func getGitHubRepoInfo(owner, repo, serverName string, currentPulls int) (stars
333
420
}
334
421
335
422
// saveRegistry saves the registry to the filesystem while preserving the order of entries
336
- func saveRegistry (reg * registry.Registry , updatedServers []string ) error {
423
+ func saveRegistry (reg * registry.Registry , updatedServers []string , failedServers [] string ) error {
337
424
// Find the registry file path
338
425
registryPath := filepath .Join ("pkg" , "registry" , "data" , "registry.json" )
339
426
@@ -359,6 +446,12 @@ func saveRegistry(reg *registry.Registry, updatedServers []string) error {
359
446
return fmt .Errorf ("invalid servers map in registry" )
360
447
}
361
448
449
+ // Remove failed servers from the JSON
450
+ for _ , name := range failedServers {
451
+ logger .Infof ("Removing server %s from registry JSON" , name )
452
+ delete (serversMap , name )
453
+ }
454
+
362
455
// Update only the servers that were modified
363
456
for _ , name := range updatedServers {
364
457
server , ok := reg .Servers [name ]
@@ -369,7 +462,7 @@ func saveRegistry(reg *registry.Registry, updatedServers []string) error {
369
462
// Get the server from the original JSON
370
463
serverJSON , ok := serversMap [name ].(map [string ]interface {})
371
464
if ! ok {
372
- logger .Warnf ("ImageMetadata %s not found in original registry, skipping" , name )
465
+ logger .Warnf ("Server %s not found in original registry, skipping" , name )
373
466
continue
374
467
}
375
468
0 commit comments