Skip to content

Commit abd6802

Browse files
verify: Allow the release verifier to load signatures abstractly
Enable the verifier to lookup signatures by digest. A caller may create a copy of a ReleaseVerifier via WithStores(...) to include one or more sources for those signatures. Errors on retrieval of signature content are logged. Rename the current `store` field to `location` to better distinguish the two types.
1 parent a82639c commit abd6802

File tree

2 files changed

+177
-45
lines changed

2 files changed

+177
-45
lines changed

pkg/verify/verify.go

Lines changed: 91 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ type Interface interface {
3030
Verify(ctx context.Context, releaseDigest string) error
3131
}
3232

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+
3344
type rejectVerifier struct{}
3445

3546
func (rejectVerifier) Verify(ctx context.Context, releaseDigest string) error {
@@ -47,25 +58,37 @@ var validReleaseDigest = regexp.MustCompile(`^[a-zA-Z0-9:]+$`)
4758
// digest - all verifiers must have at least one valid signature attesting the release
4859
// digest. If any failure occurs the caller should assume the content is unverified.
4960
type ReleaseVerifier struct {
50-
verifiers map[string]openpgp.EntityList
51-
stores []*url.URL
61+
verifiers map[string]openpgp.EntityList
62+
63+
locations []*url.URL
5264
clientBuilder ClientBuilder
65+
stores []SignatureStore
5366

5467
lock sync.Mutex
5568
signatureCache map[string][][]byte
5669
}
5770

5871
// 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 {
6073
return &ReleaseVerifier{
6174
verifiers: verifiers,
62-
stores: stores,
75+
locations: locations,
6376
clientBuilder: clientBuilder,
6477

6578
signatureCache: make(map[string][][]byte),
6679
}
6780
}
6881

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+
6992
// ClientBuilder provides a method for generating an HTTP Client configured
7093
// with cluster proxy settings, if they exist.
7194
type ClientBuilder interface {
@@ -128,24 +151,44 @@ func (v *ReleaseVerifier) String() string {
128151
}
129152
fmt.Fprint(&builder, ")")
130153
}
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+
}
138180
}
139-
fmt.Fprintf(&builder, " %s", store.String())
181+
} else {
182+
fmt.Fprintf(&builder, " - <ERROR: no locations or stores>")
140183
}
141184
return builder.String()
142185
}
143186

144187
// 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
146189
// an error.
147190
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) {
149192
return fmt.Errorf("the release verifier is incorrectly configured, unable to verify digests")
150193
}
151194
if len(releaseDigest) == 0 {
@@ -189,29 +232,54 @@ func (v *ReleaseVerifier) Verify(ctx context.Context, releaseDigest string) erro
189232
return len(remaining) > 0, nil
190233
}
191234

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+
}
195254
}
196255

197-
for _, store := range v.stores {
256+
var client *http.Client
257+
for _, location := range v.locations {
198258
if len(remaining) == 0 {
199259
break
200260
}
201-
switch store.Scheme {
261+
switch location.Scheme {
202262
case "file":
203-
dir := filepath.Join(store.Path, name)
263+
dir := filepath.Join(location.Path, name)
204264
if err := checkFileSignatures(ctx, dir, maxSignatureSearch, verifier); err != nil {
205265
return err
206266
}
207267
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)
210278
if err := checkHTTPSignatures(ctx, client, copied, maxSignatureSearch, verifier); err != nil {
211279
return err
212280
}
213281
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)
215283
}
216284
}
217285

0 commit comments

Comments
 (0)