@@ -30,6 +30,17 @@ type Interface interface {
30
30
Verify (ctx context.Context , releaseDigest string ) error
31
31
}
32
32
33
+ // SignatureStore retrieves signatures for a digest or returns an error. It requires String()
34
+ // for describing the store.
35
+ type SignatureStore interface {
36
+ // DigestSignatures returns zero or more signatures for the provided digest, or returns an
37
+ // error if no signatures could be retrieved.
38
+ DigestSignatures (ctx context.Context , digest string ) ([][]byte , error )
39
+ // String should return a description of where this store finds signatures in a short
40
+ // clause intended for display in a description of the verifier.
41
+ String () string
42
+ }
43
+
33
44
type rejectVerifier struct {}
34
45
35
46
func (rejectVerifier ) Verify (ctx context.Context , releaseDigest string ) error {
@@ -47,25 +58,37 @@ var validReleaseDigest = regexp.MustCompile(`^[a-zA-Z0-9:]+$`)
47
58
// digest - all verifiers must have at least one valid signature attesting the release
48
59
// digest. If any failure occurs the caller should assume the content is unverified.
49
60
type ReleaseVerifier struct {
50
- verifiers map [string ]openpgp.EntityList
51
- stores []* url.URL
61
+ verifiers map [string ]openpgp.EntityList
62
+
63
+ locations []* url.URL
52
64
clientBuilder ClientBuilder
65
+ stores []SignatureStore
53
66
54
67
lock sync.Mutex
55
68
signatureCache map [string ][][]byte
56
69
}
57
70
58
71
// NewReleaseVerifier creates a release verifier for the provided inputs.
59
- func NewReleaseVerifier (verifiers map [string ]openpgp.EntityList , stores []* url.URL , clientBuilder ClientBuilder ) * ReleaseVerifier {
72
+ func NewReleaseVerifier (verifiers map [string ]openpgp.EntityList , locations []* url.URL , clientBuilder ClientBuilder ) * ReleaseVerifier {
60
73
return & ReleaseVerifier {
61
74
verifiers : verifiers ,
62
- stores : stores ,
75
+ locations : locations ,
63
76
clientBuilder : clientBuilder ,
64
77
65
78
signatureCache : make (map [string ][][]byte ),
66
79
}
67
80
}
68
81
82
+ // WithStores copies the provided verifier and adds any provided stores to the list.
83
+ func (v * ReleaseVerifier ) WithStores (stores []SignatureStore ) * ReleaseVerifier {
84
+ return & ReleaseVerifier {
85
+ verifiers : v .verifiers ,
86
+ locations : v .locations ,
87
+ stores : append (append (make ([]SignatureStore , 0 , len (v .stores )+ len (stores )), v .stores ... ), stores ... ),
88
+ signatureCache : v .Signatures (),
89
+ }
90
+ }
91
+
69
92
// ClientBuilder provides a method for generating an HTTP Client configured
70
93
// with cluster proxy settings, if they exist.
71
94
type ClientBuilder interface {
@@ -128,24 +151,44 @@ func (v *ReleaseVerifier) String() string {
128
151
}
129
152
fmt .Fprint (& builder , ")" )
130
153
}
131
- fmt .Fprintf (& builder , " - will check for signatures in containers/image format at" )
132
- if len (v .stores ) == 0 {
133
- fmt .Fprint (& builder , " <ERROR: no stores>" )
134
- }
135
- for i , store := range v .stores {
136
- if i != 0 {
137
- fmt .Fprint (& builder , "," )
154
+
155
+ hasLocations := len (v .locations ) > 0
156
+ hasStores := len (v .stores ) > 0
157
+ if hasLocations || hasStores {
158
+ fmt .Fprintf (& builder , " - will check for signatures in containers/image format" )
159
+ if hasLocations {
160
+ fmt .Fprintf (& builder , " at" )
161
+ for i , location := range v .locations {
162
+ if i != 0 {
163
+ fmt .Fprint (& builder , "," )
164
+ }
165
+ fmt .Fprintf (& builder , " %s" , location .String ())
166
+ }
167
+ }
168
+ if hasStores {
169
+ if hasLocations {
170
+ fmt .Fprintf (& builder , " and from" )
171
+ } else {
172
+ fmt .Fprintf (& builder , " from" )
173
+ }
174
+ for i , store := range v .stores {
175
+ if i != 0 {
176
+ fmt .Fprint (& builder , "," )
177
+ }
178
+ fmt .Fprintf (& builder , " %s" , store .String ())
179
+ }
138
180
}
139
- fmt .Fprintf (& builder , " %s" , store .String ())
181
+ } else {
182
+ fmt .Fprintf (& builder , " - <ERROR: no locations or stores>" )
140
183
}
141
184
return builder .String ()
142
185
}
143
186
144
187
// Verify ensures that at least one valid signature exists for an image with digest
145
- // matching release digest in any of the provided stores for all verifiers, or returns
188
+ // matching release digest in any of the provided locations for all verifiers, or returns
146
189
// an error.
147
190
func (v * ReleaseVerifier ) Verify (ctx context.Context , releaseDigest string ) error {
148
- if len (v .verifiers ) == 0 || len (v .stores ) == 0 {
191
+ if len (v .verifiers ) == 0 || ( len (v .locations ) == 0 && len ( v . stores ) == 0 ) {
149
192
return fmt .Errorf ("the release verifier is incorrectly configured, unable to verify digests" )
150
193
}
151
194
if len (releaseDigest ) == 0 {
@@ -189,29 +232,54 @@ func (v *ReleaseVerifier) Verify(ctx context.Context, releaseDigest string) erro
189
232
return len (remaining ) > 0 , nil
190
233
}
191
234
192
- client , err := v .clientBuilder .HTTPClient ()
193
- if err != nil {
194
- return err
235
+ // check the stores to see if they match any signatures
236
+ for i , store := range v .stores {
237
+ if len (remaining ) == 0 {
238
+ break
239
+ }
240
+ signatures , err := store .DigestSignatures (ctx , releaseDigest )
241
+ if err != nil {
242
+ klog .V (4 ).Infof ("store %s could not load signatures: %v" , store , err )
243
+ continue
244
+ }
245
+ for _ , signature := range signatures {
246
+ hasUnsigned , err := verifier (fmt .Sprintf ("store %d" , i ), signature )
247
+ if err != nil {
248
+ return err
249
+ }
250
+ if ! hasUnsigned {
251
+ break
252
+ }
253
+ }
195
254
}
196
255
197
- for _ , store := range v .stores {
256
+ var client * http.Client
257
+ for _ , location := range v .locations {
198
258
if len (remaining ) == 0 {
199
259
break
200
260
}
201
- switch store .Scheme {
261
+ switch location .Scheme {
202
262
case "file" :
203
- dir := filepath .Join (store .Path , name )
263
+ dir := filepath .Join (location .Path , name )
204
264
if err := checkFileSignatures (ctx , dir , maxSignatureSearch , verifier ); err != nil {
205
265
return err
206
266
}
207
267
case "http" , "https" :
208
- copied := * store
209
- copied .Path = path .Join (store .Path , name )
268
+ if client == nil {
269
+ var err error
270
+ client , err = v .clientBuilder .HTTPClient ()
271
+ if err != nil {
272
+ return err
273
+ }
274
+ }
275
+
276
+ copied := * location
277
+ copied .Path = path .Join (location .Path , name )
210
278
if err := checkHTTPSignatures (ctx , client , copied , maxSignatureSearch , verifier ); err != nil {
211
279
return err
212
280
}
213
281
default :
214
- return fmt .Errorf ("internal error: the store %s type is unrecognized, cannot verify signatures" , store )
282
+ return fmt .Errorf ("internal error: the store %s type is unrecognized, cannot verify signatures" , location )
215
283
}
216
284
}
217
285
0 commit comments