Skip to content

Commit 7dd216c

Browse files
authored
Merge pull request #6645 from chrischdi/pr-runtimesdk-enforce-https
🌱 RuntimeSDK: enforce https for extensions
2 parents f87f1f3 + ac2c073 commit 7dd216c

File tree

7 files changed

+58
-37
lines changed

7 files changed

+58
-37
lines changed

config/crd/bases/runtime.cluster.x-k8s.io_extensionconfigs.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ spec:
8585
of `url` or `service` must be specified. \n The `host` should
8686
not refer to a service running in the cluster; use the `service`
8787
field instead. \n The scheme should be \"https\"; the URL should
88-
begin with \"https://\". \"http\" is supported for insecure
89-
development purposes only. \n A path is optional, and if present
88+
begin with \"https://\". \n A path is optional, and if present
9089
may be any string permissible in a URL. If a path is set it
9190
will be used as prefix and the hook-specific path will be appended.
9291
\n Attempting to use a user or basic auth e.g. \"user:password@\"

exp/runtime/api/v1alpha1/extensionconfig_types.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ type ClientConfig struct {
4747
// the `service` field instead.
4848
//
4949
// The scheme should be "https"; the URL should begin with "https://".
50-
// "http" is supported for insecure development purposes only.
5150
//
5251
// A path is optional, and if present may be any string permissible in
5352
// a URL. If a path is set it will be used as prefix and the hook-specific

exp/runtime/internal/controllers/extensionconfig_controller_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ func TestExtensionReconciler_discoverExtensionConfig(t *testing.T) {
220220
})
221221

222222
extensionConfig := fakeExtensionConfigForURL(ns.Name, extensionName, srv1.URL)
223+
extensionConfig.Spec.ClientConfig.CABundle = testcerts.CACert
223224

224225
discoveredExtensionConfig, err := discoverExtensionConfig(ctx, runtimeClient, extensionConfig)
225226
g.Expect(err).To(BeNil())
@@ -252,7 +253,8 @@ func TestExtensionReconciler_discoverExtensionConfig(t *testing.T) {
252253
Registry: registry,
253254
})
254255

255-
extensionConfig := fakeExtensionConfigForURL(ns.Name, extensionName, "http://localhost:31239")
256+
extensionConfig := fakeExtensionConfigForURL(ns.Name, extensionName, "https://localhost:31239")
257+
extensionConfig.Spec.ClientConfig.CABundle = testcerts.CACert
256258

257259
discoveredExtensionConfig, err := discoverExtensionConfig(ctx, runtimeClient, extensionConfig)
258260
g.Expect(err).ToNot(BeNil())

exp/runtime/internal/controllers/warmup_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func Test_warmupRunnable_Start(t *testing.T) {
123123
brokenExtension := "ext2"
124124
for _, name := range []string{"ext1", "ext2", "ext3"} {
125125
if name == brokenExtension {
126-
g.Expect(env.CreateAndWait(ctx, fakeExtensionConfigForURL(ns.Name, name, "http://localhost:1234"))).To(Succeed())
126+
g.Expect(env.CreateAndWait(ctx, fakeExtensionConfigForURL(ns.Name, name, "https://localhost:1234"))).To(Succeed())
127127
continue
128128
}
129129
server, err := fakeSecureExtensionServer(discoveryHandler("first", "second", "third"))

internal/runtime/client/client.go

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -432,21 +432,20 @@ func httpCall(ctx context.Context, request, response runtime.Object, opts *httpC
432432

433433
// use client-go's transport.TLSConfigureFor to ensure good defaults for tls
434434
client := http.DefaultClient
435-
if opts.config.CABundle != nil {
436-
tlsConfig, err := transport.TLSConfigFor(&transport.Config{
437-
TLS: transport.TLSConfig{
438-
CAData: opts.config.CABundle,
439-
ServerName: extensionURL.Hostname(),
440-
},
441-
})
442-
if err != nil {
443-
return errors.Wrap(err, "failed to create tls config")
444-
}
445-
// this also adds http2
446-
client.Transport = utilnet.SetTransportDefaults(&http.Transport{
447-
TLSClientConfig: tlsConfig,
448-
})
435+
tlsConfig, err := transport.TLSConfigFor(&transport.Config{
436+
TLS: transport.TLSConfig{
437+
CAData: opts.config.CABundle,
438+
ServerName: extensionURL.Hostname(),
439+
},
440+
})
441+
if err != nil {
442+
return errors.Wrap(err, "failed to create tls config")
449443
}
444+
// this also adds http2
445+
client.Transport = utilnet.SetTransportDefaults(&http.Transport{
446+
TLSClientConfig: tlsConfig,
447+
})
448+
450449
resp, err := client.Do(httpRequest)
451450
if err != nil {
452451
return errCallingExtensionHandler(
@@ -486,12 +485,8 @@ func urlForExtension(config runtimev1.ClientConfig, gvh runtimecatalog.GroupVers
486485
if svc.Port != nil {
487486
host = net.JoinHostPort(host, strconv.Itoa(int(*svc.Port)))
488487
}
489-
scheme := "http"
490-
if len(config.CABundle) > 0 {
491-
scheme = "https"
492-
}
493488
u = &url.URL{
494-
Scheme: scheme,
489+
Scheme: "https",
495490
Host: host,
496491
}
497492
if svc.Path != nil {
@@ -506,6 +501,9 @@ func urlForExtension(config runtimev1.ClientConfig, gvh runtimecatalog.GroupVers
506501
if err != nil {
507502
return nil, errors.Wrap(err, "URL in ClientConfig is invalid")
508503
}
504+
if u.Scheme != "https" {
505+
return nil, errors.Errorf("expected https scheme, got %s", u.Scheme)
506+
}
509507
}
510508
// Add the subpatch to the ExtensionHandler for the given hook.
511509
u.Path = path.Join(u.Path, runtimecatalog.GVHToPath(gvh, name))

internal/runtime/client/client_test.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package client
1818

1919
import (
2020
"context"
21+
"crypto/tls"
2122
"encoding/json"
2223
"fmt"
2324
"net"
@@ -32,6 +33,7 @@ import (
3233
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3334
"k8s.io/apimachinery/pkg/labels"
3435
"k8s.io/apimachinery/pkg/runtime"
36+
"k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts"
3537
"k8s.io/utils/pointer"
3638
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
3739
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -187,11 +189,14 @@ func TestClient_httpCall(t *testing.T) {
187189
// create http server with fakeHookHandler
188190
mux := http.NewServeMux()
189191
mux.HandleFunc("/", fakeHookHandler)
190-
srv := httptest.NewServer(mux)
192+
193+
srv := newUnstartedTLSServer(mux)
194+
srv.StartTLS()
191195
defer srv.Close()
192196

193197
// set url to srv for in tt.opts
194198
tt.opts.config.URL = pointer.String(srv.URL)
199+
tt.opts.config.CABundle = testcerts.CACert
195200
}
196201

197202
err := httpCall(context.TODO(), tt.request, tt.response, tt.opts)
@@ -260,7 +265,7 @@ func TestURLForExtension(t *testing.T) {
260265
extensionHandlerName: "test-handler",
261266
},
262267
want: want{
263-
scheme: "http",
268+
scheme: "https",
264269
host: "extension-service.test1.svc:8443",
265270
path: runtimecatalog.GVHToPath(gvh, "test-handler"),
266271
},
@@ -525,7 +530,8 @@ func TestClient_CallExtension(t *testing.T) {
525530
validExtensionHandlerWithFailPolicy := runtimev1.ExtensionConfig{
526531
Spec: runtimev1.ExtensionConfigSpec{
527532
ClientConfig: runtimev1.ClientConfig{
528-
URL: pointer.String(fmt.Sprintf("http://%s/", testHostPort)),
533+
URL: pointer.String(fmt.Sprintf("https://%s/", testHostPort)),
534+
CABundle: testcerts.CACert,
529535
},
530536
NamespaceSelector: &metav1.LabelSelector{},
531537
},
@@ -546,7 +552,8 @@ func TestClient_CallExtension(t *testing.T) {
546552
validExtensionHandlerWithIgnorePolicy := runtimev1.ExtensionConfig{
547553
Spec: runtimev1.ExtensionConfigSpec{
548554
ClientConfig: runtimev1.ClientConfig{
549-
URL: pointer.String(fmt.Sprintf("http://%s/", testHostPort)),
555+
URL: pointer.String(fmt.Sprintf("https://%s/", testHostPort)),
556+
CABundle: testcerts.CACert,
550557
},
551558
NamespaceSelector: &metav1.LabelSelector{}},
552559
Status: runtimev1.ExtensionConfigStatus{
@@ -722,8 +729,8 @@ func TestClient_CallExtension(t *testing.T) {
722729
if tt.testServer.hostPort == "" {
723730
tt.testServer.hostPort = testHostPort
724731
}
725-
srv := createTestServer(g, tt.testServer)
726-
srv.Start()
732+
srv := createSecureTestServer(g, tt.testServer)
733+
srv.StartTLS()
727734
defer srv.Close()
728735
}
729736

@@ -773,7 +780,8 @@ func TestClient_CallAllExtensions(t *testing.T) {
773780
extensionConfig := runtimev1.ExtensionConfig{
774781
Spec: runtimev1.ExtensionConfigSpec{
775782
ClientConfig: runtimev1.ClientConfig{
776-
URL: pointer.String(fmt.Sprintf("http://%s/", testHostPort)),
783+
URL: pointer.String(fmt.Sprintf("https://%s/", testHostPort)),
784+
CABundle: testcerts.CACert,
777785
},
778786
NamespaceSelector: &metav1.LabelSelector{},
779787
},
@@ -925,8 +933,8 @@ func TestClient_CallAllExtensions(t *testing.T) {
925933
if tt.testServer.hostPort == "" {
926934
tt.testServer.hostPort = testHostPort
927935
}
928-
srv := createTestServer(g, tt.testServer)
929-
srv.Start()
936+
srv := createSecureTestServer(g, tt.testServer)
937+
srv.StartTLS()
930938
defer srv.Close()
931939
}
932940

@@ -1142,7 +1150,7 @@ func response(status runtimehooksv1.ResponseStatus) testServerResponse {
11421150
}
11431151
}
11441152

1145-
func createTestServer(g *WithT, server testServerConfig) *httptest.Server {
1153+
func createSecureTestServer(g *WithT, server testServerConfig) *httptest.Server {
11461154
l, err := net.Listen("tcp", server.hostPort)
11471155
g.Expect(err).NotTo(HaveOccurred())
11481156

@@ -1166,7 +1174,9 @@ func createTestServer(g *WithT, server testServerConfig) *httptest.Server {
11661174
// Otherwise write a 404.
11671175
w.WriteHeader(http.StatusNotFound)
11681176
})
1169-
srv := httptest.NewUnstartedServer(mux)
1177+
1178+
srv := newUnstartedTLSServer(mux)
1179+
11701180
// NewUnstartedServer creates a listener. Close that listener and replace
11711181
// with the one we created.
11721182
g.Expect(srv.Listener.Close()).To(Succeed())
@@ -1214,3 +1224,16 @@ func fakeRetryableSuccessResponse(retryAfterSeconds int32) *fakev1alpha1.Retryab
12141224
},
12151225
}
12161226
}
1227+
1228+
func newUnstartedTLSServer(handler http.Handler) *httptest.Server {
1229+
cert, err := tls.X509KeyPair(testcerts.ServerCert, testcerts.ServerKey)
1230+
if err != nil {
1231+
panic(err)
1232+
}
1233+
srv := httptest.NewUnstartedServer(handler)
1234+
srv.TLS = &tls.Config{
1235+
MinVersion: tls.VersionTLS13,
1236+
Certificates: []tls.Certificate{cert},
1237+
}
1238+
return srv
1239+
}

internal/webhooks/runtime/extensionconfig_webhook.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,11 @@ func validateExtensionConfigSpec(e *runtimev1.ExtensionConfig) field.ErrorList {
143143
*e.Spec.ClientConfig.URL,
144144
fmt.Sprintf("must be a valid URL, e.g. https://example.com: %v", err),
145145
))
146-
} else if uri.Scheme != "http" && uri.Scheme != "https" {
146+
} else if uri.Scheme != "https" {
147147
allErrs = append(allErrs, field.Invalid(
148148
specPath.Child("clientConfig", "url"),
149149
*e.Spec.ClientConfig.URL,
150-
"'https' and 'http' are the only allowed URL schemes, e.g. https://example.com",
150+
"'https' is the only allowed URL scheme, e.g. https://example.com",
151151
))
152152
}
153153
}

0 commit comments

Comments
 (0)