Skip to content

Commit 6044c9c

Browse files
committed
pkg/verify: Refactor signature fetching only use a Store interface
This consolidates around abstract signature retrieval, removing the split between stores and locations which we grew in 9bbf366 (verify: Refactor the verify package to have common code, 2019-12-09, #279). This will make it easier to make future changes like parallel HTTP(S) signature retrieval retrieval [1]. [1]: https://bugzilla.redhat.com/show_bug.cgi?id=1840343
1 parent 2b84929 commit 6044c9c

File tree

9 files changed

+437
-287
lines changed

9 files changed

+437
-287
lines changed

pkg/cvo/cvo.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ import (
4646
"github.com/openshift/cluster-version-operator/pkg/payload/precondition"
4747
preconditioncv "github.com/openshift/cluster-version-operator/pkg/payload/precondition/clusterversion"
4848
"github.com/openshift/cluster-version-operator/pkg/verify"
49-
"github.com/openshift/cluster-version-operator/pkg/verify/verifyconfigmap"
49+
"github.com/openshift/cluster-version-operator/pkg/verify/store"
50+
"github.com/openshift/cluster-version-operator/pkg/verify/store/configmap"
51+
"github.com/openshift/cluster-version-operator/pkg/verify/store/serial"
5052
)
5153

5254
const (
@@ -312,8 +314,8 @@ func loadConfigMapVerifierDataFromUpdate(update *payload.Update, clientBuilder v
312314

313315
// allow the verifier to consult the cluster for signature data, and also configure
314316
// a process that writes signatures back to that store
315-
signatureStore := verifyconfigmap.NewStore(configMapClient, nil)
316-
verifier = verifier.WithStores(signatureStore)
317+
signatureStore := configmap.NewStore(configMapClient, nil)
318+
verifier.Store = &serial.Store{Stores: []store.Store{signatureStore, verifier.Store}}
317319
persister := verify.NewSignatureStorePersister(signatureStore, verifier)
318320
return verifier, persister, nil
319321
}

pkg/verify/configmap.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
"github.com/pkg/errors"
1010
"golang.org/x/crypto/openpgp"
1111
"k8s.io/klog"
12+
13+
"github.com/openshift/cluster-version-operator/pkg/verify/store"
14+
"github.com/openshift/cluster-version-operator/pkg/verify/store/serial"
1215
)
1316

1417
// ReleaseAnnotationConfigMapVerifier is an annotation set on a config map in the
@@ -48,7 +51,7 @@ const ReleaseAnnotationConfigMapVerifier = "release.openshift.io/verification-co
4851
// store and the lookup order is internally defined.
4952
func NewFromConfigMapData(src string, data map[string]string, clientBuilder ClientBuilder) (*ReleaseVerifier, error) {
5053
verifiers := make(map[string]openpgp.EntityList)
51-
var stores []*url.URL
54+
var stores []store.Store
5255
for k, v := range data {
5356
switch {
5457
case strings.HasPrefix(k, "verifier-public-key-"):
@@ -63,7 +66,16 @@ func NewFromConfigMapData(src string, data map[string]string, clientBuilder Clie
6366
if err != nil || (u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "file") {
6467
return nil, fmt.Errorf("%s has an invalid key %q: must be a valid URL with scheme file://, http://, or https://", src, k)
6568
}
66-
stores = append(stores, u)
69+
if u.Scheme == "file" {
70+
stores = append(stores, &fileStore{
71+
directory: u.Path,
72+
})
73+
} else {
74+
stores = append(stores, &httpStore{
75+
uri: u,
76+
httpClient: clientBuilder.HTTPClient,
77+
})
78+
}
6779
default:
6880
klog.Warningf("An unexpected key was found in %s and will be ignored (expected store-* or verifier-public-key-*): %s", src, k)
6981
}
@@ -75,7 +87,7 @@ func NewFromConfigMapData(src string, data map[string]string, clientBuilder Clie
7587
return nil, fmt.Errorf("%s did not provide any GPG public keys to verify signatures from and cannot be used", src)
7688
}
7789

78-
return NewReleaseVerifier(verifiers, stores, clientBuilder), nil
90+
return NewReleaseVerifier(verifiers, &serial.Store{Stores: stores}), nil
7991
}
8092

8193
func loadArmoredOrUnarmoredGPGKeyRing(data []byte) (openpgp.EntityList, error) {

pkg/verify/verifyconfigmap/store.go renamed to pkg/verify/store/configmap/configmap.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package verifyconfigmap
1+
package configmap
22

33
import (
44
"context"
@@ -16,6 +16,8 @@ import (
1616
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
1717
"k8s.io/client-go/util/retry"
1818
"k8s.io/klog"
19+
20+
"github.com/openshift/cluster-version-operator/pkg/verify/store"
1921
)
2022

2123
// ReleaseLabelConfigMap is a label applied to a configmap inside the
@@ -87,10 +89,10 @@ func digestToKeyPrefix(digest string) (string, error) {
8789
return fmt.Sprintf("%s-%s", algo, hash), nil
8890
}
8991

90-
// DigestSignatures returns a list of signatures that match the request
92+
// Signatures returns a list of signatures that match the request
9193
// digest out of config maps labelled with ReleaseLabelConfigMap in the
9294
// openshift-config-managed namespace.
93-
func (s *Store) DigestSignatures(ctx context.Context, digest string) ([][]byte, error) {
95+
func (s *Store) Signatures(ctx context.Context, name string, digest string, fn store.Callback) error {
9496
// avoid repeatedly reloading config maps
9597
items := s.mostRecentConfigMaps()
9698
r := s.limiter.Reserve()
@@ -100,31 +102,36 @@ func (s *Store) DigestSignatures(ctx context.Context, digest string) ([][]byte,
100102
})
101103
if err != nil {
102104
s.rememberMostRecentConfigMaps([]corev1.ConfigMap{})
103-
return nil, err
105+
return err
104106
}
105107
items = configMaps.Items
106108
s.rememberMostRecentConfigMaps(configMaps.Items)
107109
}
108110

109111
prefix, err := digestToKeyPrefix(digest)
110112
if err != nil {
111-
return nil, err
113+
return err
112114
}
113115

114-
var signatures [][]byte
115116
for _, cm := range items {
116117
klog.V(4).Infof("searching for %s in signature config map %s", prefix, cm.ObjectMeta.Name)
117118
for k, v := range cm.BinaryData {
118119
if strings.HasPrefix(k, prefix) {
119120
klog.V(4).Infof("key %s from signature config map %s matches %s", k, cm.ObjectMeta.Name, digest)
120-
signatures = append(signatures, v)
121+
done, err := fn(ctx, v, nil)
122+
if err != nil || done {
123+
return err
124+
}
125+
if err := ctx.Err(); err != nil {
126+
return err
127+
}
121128
}
122129
}
123130
}
124-
return signatures, nil
131+
return nil
125132
}
126133

127-
// Store attempts to persist the provided signatures into a form DigestSignatures will
134+
// Store attempts to persist the provided signatures into a form Signatures will
128135
// retrieve.
129136
func (s *Store) Store(ctx context.Context, signaturesByDigest map[string][][]byte) error {
130137
cm := &corev1.ConfigMap{

pkg/verify/store/memory/memory.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Package memory implements an in-memory signature store. This is
2+
// mostly useful for testing.
3+
package memory
4+
5+
import (
6+
"context"
7+
8+
"github.com/openshift/cluster-version-operator/pkg/verify/store"
9+
)
10+
11+
// Store provides access to signatures stored in memory.
12+
type Store struct {
13+
// Data maps digests to slices of signatures.
14+
Data map[string][][]byte
15+
}
16+
17+
// Signatures fetches signatures for the provided digest.
18+
func (s *Store) Signatures(ctx context.Context, name string, digest string, fn store.Callback) error {
19+
for _, signature := range s.Data[digest] {
20+
done, err := fn(ctx, signature, nil)
21+
if err != nil || done {
22+
return err
23+
}
24+
if err := ctx.Err(); err != nil {
25+
return err
26+
}
27+
}
28+
29+
return nil
30+
}
31+
32+
// String returns a description of where this store finds
33+
// signatures.
34+
func (s *Store) String() string {
35+
return "in-memory signature store"
36+
}

pkg/verify/store/serial/serial.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Package serial combines several signature stores in a single store.
2+
// Signatures are searched in each substore in turn until a match is
3+
// found.
4+
package serial
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"strings"
10+
11+
"github.com/openshift/cluster-version-operator/pkg/verify/store"
12+
)
13+
14+
// Store provides access to signatures stored in sub-stores.
15+
type Store struct {
16+
Stores []store.Store
17+
}
18+
19+
// Signatures fetches signatures for the provided digest.
20+
func (s *Store) Signatures(ctx context.Context, name string, digest string, fn store.Callback) error {
21+
allDone := false
22+
23+
wrapper := func(ctx context.Context, signature []byte, errIn error) (done bool, err error) {
24+
done, err = fn(ctx, signature, errIn)
25+
if done {
26+
allDone = true
27+
}
28+
return done, err
29+
}
30+
31+
for _, store := range s.Stores {
32+
err := store.Signatures(ctx, name, digest, wrapper)
33+
if err != nil || allDone {
34+
return err
35+
}
36+
if err := ctx.Err(); err != nil {
37+
return err
38+
}
39+
}
40+
41+
return nil
42+
}
43+
44+
// String returns a description of where this store finds
45+
// signatures.
46+
func (s *Store) String() string {
47+
wrapped := "no stores"
48+
if len(s.Stores) > 0 {
49+
names := make([]string, 0, len(s.Stores))
50+
for _, store := range s.Stores {
51+
names = append(names, store.String())
52+
}
53+
wrapped = strings.Join(names, ", ")
54+
}
55+
return fmt.Sprintf("serial signature store wrapping %s", wrapped)
56+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package serial
2+
3+
import (
4+
"context"
5+
"errors"
6+
"reflect"
7+
"regexp"
8+
"testing"
9+
10+
"github.com/openshift/cluster-version-operator/pkg/verify/store"
11+
"github.com/openshift/cluster-version-operator/pkg/verify/store/memory"
12+
)
13+
14+
func TestStore(t *testing.T) {
15+
ctx := context.Background()
16+
serial := &Store{
17+
Stores: []store.Store{
18+
&memory.Store{
19+
Data: map[string][][]byte{
20+
"sha256:123": {
21+
[]byte("store-1-sig-1"),
22+
[]byte("store-1-sig-2"),
23+
},
24+
},
25+
},
26+
&memory.Store{
27+
Data: map[string][][]byte{
28+
"sha256:123": {
29+
[]byte("store-2-sig-1"),
30+
[]byte("store-2-sig-2"),
31+
},
32+
},
33+
},
34+
},
35+
}
36+
37+
for _, testCase := range []struct {
38+
name string
39+
doneSignature string
40+
doneError error
41+
expectedSignatures []string
42+
expectedError *regexp.Regexp
43+
}{
44+
{
45+
name: "all",
46+
expectedSignatures: []string{
47+
"store-1-sig-1",
48+
"store-1-sig-2",
49+
"store-2-sig-1",
50+
"store-2-sig-2",
51+
},
52+
},
53+
{
54+
name: "done early",
55+
doneSignature: "store-2-sig-1",
56+
expectedSignatures: []string{
57+
"store-1-sig-1",
58+
"store-1-sig-2",
59+
"store-2-sig-1",
60+
},
61+
},
62+
{
63+
name: "error early",
64+
doneSignature: "store-2-sig-1",
65+
doneError: errors.New("test error"),
66+
expectedSignatures: []string{
67+
"store-1-sig-1",
68+
"store-1-sig-2",
69+
"store-2-sig-1",
70+
},
71+
expectedError: regexp.MustCompile("^test error$"),
72+
},
73+
} {
74+
t.Run(testCase.name, func(t *testing.T) {
75+
signatures := []string{}
76+
err := serial.Signatures(ctx, "name", "sha256:123", func(ctx context.Context, signature []byte, errIn error) (done bool, err error) {
77+
if errIn != nil {
78+
return false, errIn
79+
}
80+
signatures = append(signatures, string(signature))
81+
if string(signature) == testCase.doneSignature {
82+
return true, testCase.doneError
83+
}
84+
return false, nil
85+
})
86+
if err == nil {
87+
if testCase.expectedError != nil {
88+
t.Fatalf("signatures succeeded when we expected %s", testCase.expectedError)
89+
}
90+
} else if testCase.expectedError == nil {
91+
t.Fatalf("signatures failed when we expected success: %v", err)
92+
} else if !testCase.expectedError.MatchString(err.Error()) {
93+
t.Fatalf("signatures failed with %v (expected %s)", err, testCase.expectedError)
94+
}
95+
96+
if !reflect.DeepEqual(signatures, testCase.expectedSignatures) {
97+
t.Fatalf("signatures gathered %v when we expected %v", signatures, testCase.expectedSignatures)
98+
}
99+
})
100+
}
101+
}

pkg/verify/store/store.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Package store defines generic interfaces for signature stores.
2+
package store
3+
4+
import (
5+
"context"
6+
)
7+
8+
// Callback returns true if an acceptable signature has been found, or
9+
// an error if the loop should be aborted. If there was a problem
10+
// retrieving the signature, the incoming error will describe the
11+
// problem and the function can decide how to handle that error.
12+
type Callback func(ctx context.Context, signature []byte, errIn error) (done bool, err error)
13+
14+
// Store provides access to signatures by digest.
15+
type Store interface {
16+
17+
// Signatures fetches signatures for the provided digest, feeding
18+
// them into the provided callback until an acceptable signature is
19+
// found or an error occurs. Not finding any acceptable signatures
20+
// is not an error; it is up to the caller to handle that case.
21+
Signatures(ctx context.Context, name string, digest string, fn Callback) error
22+
23+
// String returns a description of where this store finds
24+
// signatures. The string is a short clause intended for display in
25+
// a description of the verifier.
26+
String() string
27+
}

0 commit comments

Comments
 (0)