Skip to content

Commit 34ab90d

Browse files
committed
Add proxy support to CVO image verification.
This commit adds support for using the cluster proxy and trust bundle to verify image signatures in the CVO. Before this commit, the CVO would create the release verifier and store inside the verifier an http client for all traffic. The verifier is created before the proxy lister and values have been established by the operator, so those values cannot be read in order to properly configure the client; furthermore, the client needs to take into account that the cluster proxy is subject to change. To address these problems, the verifier has been updated to accept an interface which can provide an up-to-date client whenever a client is needed. The initial creation of the verifier accepts a reference to the operator to implement the interface.
1 parent 00abef6 commit 34ab90d

File tree

3 files changed

+69
-28
lines changed

3 files changed

+69
-28
lines changed

pkg/cvo/cvo.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/tls"
66
"fmt"
7+
"net/http"
78
"net/url"
89
"strconv"
910
"sync"
@@ -24,6 +25,7 @@ import (
2425
"k8s.io/client-go/rest"
2526
"k8s.io/client-go/tools/cache"
2627
"k8s.io/client-go/tools/record"
28+
"k8s.io/client-go/transport"
2729
"k8s.io/client-go/util/workqueue"
2830
"k8s.io/klog"
2931

@@ -207,6 +209,16 @@ func New(
207209
return optr
208210
}
209211

212+
// verifyClientBuilder is a wrapper around the operator's HTTPClient method.
213+
// It is used by the releaseVerifier to get an up-to-date http client.
214+
type verifyClientBuilder struct {
215+
builder func() (*http.Client, error)
216+
}
217+
218+
func (vcb *verifyClientBuilder) HTTPClient() (*http.Client, error) {
219+
return vcb.builder()
220+
}
221+
210222
// InitializeFromPayload retrieves the payload contents and verifies the initial state, then configures the
211223
// controller that loads and applies content to the cluster. It returns an error if the payload appears to
212224
// be in error rather than continuing.
@@ -223,8 +235,11 @@ func (optr *Operator) InitializeFromPayload(restConfig *rest.Config, burstRestCo
223235
optr.releaseCreated = update.ImageRef.CreationTimestamp.Time
224236
optr.releaseVersion = update.ImageRef.Name
225237

238+
// Wraps operator's HTTPClient method to allow releaseVerifier to create http client with up-to-date config.
239+
clientBuilder := &verifyClientBuilder{builder: optr.HTTPClient}
240+
226241
// attempt to load a verifier as defined in the payload
227-
verifier, err := verify.LoadFromPayload(update)
242+
verifier, err := verify.LoadFromPayload(update, clientBuilder)
228243
if err != nil {
229244
return err
230245
}
@@ -655,6 +670,27 @@ func (optr *Operator) defaultPreconditionChecks() precondition.List {
655670
}
656671
}
657672

673+
// HTTPClient provides a method for generating an HTTP client
674+
// with the proxy and trust settings, if set in the cluster.
675+
func (optr *Operator) HTTPClient() (*http.Client, error) {
676+
proxyURL, tlsConfig, err := optr.getTransportOpts()
677+
if err != nil {
678+
return nil, err
679+
}
680+
transportOption := &http.Transport{
681+
Proxy: http.ProxyURL(proxyURL),
682+
TLSClientConfig: tlsConfig,
683+
}
684+
transportConfig := &transport.Config{Transport: transportOption}
685+
transport, err := transport.New(transportConfig)
686+
if err != nil {
687+
return nil, err
688+
}
689+
return &http.Client{
690+
Transport: transport,
691+
}, nil
692+
}
693+
658694
// getTransportOpts retrieves the URL of the cluster proxy and the CA
659695
// trust, if they exist.
660696
func (optr *Operator) getTransportOpts() (*url.URL, *tls.Config, error) {

pkg/verify/verify.go

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@ import (
1818
"strings"
1919
"time"
2020

21-
"k8s.io/klog"
2221
"github.com/pkg/errors"
2322
"golang.org/x/crypto/openpgp"
24-
2523
corev1 "k8s.io/api/core/v1"
2624
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27-
"k8s.io/client-go/transport"
25+
"k8s.io/klog"
2826

2927
"github.com/openshift/cluster-version-operator/pkg/payload"
3028
)
@@ -74,7 +72,7 @@ var Reject Interface = rejectVerifier{}
7472
// if each provided public key has signed the release image digest. The signature may be in any
7573
// store and the lookup order is internally defined.
7674
//
77-
func LoadFromPayload(update *payload.Update) (Interface, error) {
75+
func LoadFromPayload(update *payload.Update, clientBuilder ClientBuilder) (Interface, error) {
7876
configMapGVK := corev1.SchemeGroupVersion.WithKind("ConfigMap")
7977
for _, manifest := range update.Manifests {
8078
if manifest.GVK != configMapGVK {
@@ -117,16 +115,10 @@ func LoadFromPayload(update *payload.Update) (Interface, error) {
117115
return nil, fmt.Errorf("%s did not provide any GPG public keys to verify signatures from and cannot be used", src)
118116
}
119117

120-
transport, err := transport.New(&transport.Config{})
121-
if err != nil {
122-
return nil, errors.Wrapf(err, "unable to create HTTP client for verifying signatures: %v", err)
123-
}
124118
return &releaseVerifier{
125-
verifiers: verifiers,
126-
stores: stores,
127-
client: &http.Client{
128-
Transport: transport,
129-
},
119+
verifiers: verifiers,
120+
stores: stores,
121+
clientBuilder: clientBuilder,
130122
}, nil
131123
}
132124
return nil, nil
@@ -148,9 +140,23 @@ var validReleaseDigest = regexp.MustCompile(`^[a-zA-Z0-9:]+$`)
148140
// digest - all verifiers must have at least one valid signature attesting the release
149141
// digest. If any failure occurs the caller should assume the content is unverified.
150142
type releaseVerifier struct {
151-
verifiers map[string]openpgp.EntityList
152-
stores []*url.URL
153-
client *http.Client
143+
verifiers map[string]openpgp.EntityList
144+
stores []*url.URL
145+
clientBuilder ClientBuilder
146+
}
147+
148+
// ClientBuilder provides a method for generating an HTTP Client configured
149+
// with cluster proxy settings, if they exist.
150+
type ClientBuilder interface {
151+
HTTPClient() (*http.Client, error)
152+
}
153+
154+
// simpleClientBuilder implements the ClientBuilder interface and should be used for testing.
155+
type simpleClientBuilder struct{}
156+
157+
// HTTPClient from simpleClientBuilder creates an httpClient with no configuration.
158+
func (s *simpleClientBuilder) HTTPClient() (*http.Client, error) {
159+
return &http.Client{}, nil
154160
}
155161

156162
// String summarizes the verifier for human consumption
@@ -243,6 +249,11 @@ func (v *releaseVerifier) Verify(ctx context.Context, releaseDigest string) erro
243249
return len(remaining) > 0, nil
244250
}
245251

252+
client, err := v.clientBuilder.HTTPClient()
253+
if err != nil {
254+
return err
255+
}
256+
246257
for _, store := range v.stores {
247258
if len(remaining) == 0 {
248259
break
@@ -256,7 +267,7 @@ func (v *releaseVerifier) Verify(ctx context.Context, releaseDigest string) erro
256267
case "http", "https":
257268
copied := *store
258269
copied.Path = path.Join(store.Path, name)
259-
if err := checkHTTPSignatures(ctx, v.client, copied, maxSignatureSearch, verifier); err != nil {
270+
if err := checkHTTPSignatures(ctx, client, copied, maxSignatureSearch, verifier); err != nil {
260271
return err
261272
}
262273
default:
@@ -325,7 +336,6 @@ func checkHTTPSignatures(ctx context.Context, client *http.Client, u url.URL, ma
325336
return fmt.Errorf("could not build request to check signature: %v", err)
326337
}
327338
req = req.WithContext(ctx)
328-
329339
// load the body, being careful not to allow unbounded reads
330340
resp, err := client.Do(req)
331341
if err != nil {

pkg/verify/verify_test.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import (
1010
"path/filepath"
1111
"testing"
1212

13-
"k8s.io/client-go/transport"
14-
1513
"github.com/openshift/cluster-version-operator/lib"
1614
"github.com/openshift/cluster-version-operator/pkg/payload"
1715
"golang.org/x/crypto/openpgp"
@@ -161,16 +159,13 @@ func Test_releaseVerifier_Verify(t *testing.T) {
161159
}
162160
for _, tt := range tests {
163161
t.Run(tt.name, func(t *testing.T) {
164-
transport, err := transport.New(&transport.Config{})
165162
if err != nil {
166163
t.Fatal(err)
167164
}
168165
v := &releaseVerifier{
169-
verifiers: tt.verifiers,
170-
stores: tt.stores,
171-
client: &http.Client{
172-
Transport: transport,
173-
},
166+
verifiers: tt.verifiers,
167+
stores: tt.stores,
168+
clientBuilder: &simpleClientBuilder{},
174169
}
175170
if err := v.Verify(context.Background(), tt.releaseDigest); (err != nil) != tt.wantErr {
176171
t.Errorf("releaseVerifier.Verify() error = %v, wantErr %v", err, tt.wantErr)
@@ -344,7 +339,7 @@ func Test_loadReleaseVerifierFromPayload(t *testing.T) {
344339
}
345340
for _, tt := range tests {
346341
t.Run(tt.name, func(t *testing.T) {
347-
got, err := LoadFromPayload(tt.update)
342+
got, err := LoadFromPayload(tt.update, &simpleClientBuilder{})
348343
if (err != nil) != tt.wantErr {
349344
t.Fatalf("loadReleaseVerifierFromPayload() error = %v, wantErr %v", err, tt.wantErr)
350345
return

0 commit comments

Comments
 (0)