Skip to content

Commit 38f50bf

Browse files
authored
feat: Adding support to the interceptor proxy for TLS on the wire (kedacore#928)
* feat: Adding support to the interceptor proxy for TLS on the wire Signed-off-by: Joe Wogan <joe.wogan@10xbanking.com> * chore: adding e2e tests and tidying up changes Signed-off-by: Joe Wogan <joe.wogan@10xbanking.com> * chore: changing interceptor tls port for e2e tests Signed-off-by: Joe Wogan <joe.wogan@10xbanking.com> --------- Signed-off-by: Joe Wogan <joe.wogan@10xbanking.com>
1 parent e3d2e81 commit 38f50bf

20 files changed

+905
-15
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,10 @@ admin/Cargo.lock
355355

356356
/target
357357
.envrc
358+
359+
# locally generated certs for testing TLS
360+
*.crt
361+
*.pem
362+
*.csr
363+
*.srl
364+
*.ext

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ This changelog keeps track of work items that have been completed and are ready
2929

3030
### Improvements
3131

32-
- **General**: TODO ([#TODO](https://github.com/kedacore/http-add-on/issues/TODO))
32+
- **General**: Add configurable TLS on the wire support to the interceptor proxy ([#907](https://github.com/kedacore/http-add-on/issues/907))
3333

3434
### Fixes
3535

Makefile

+37-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ GO_LDFLAGS="-X github.com/kedacore/http-add-on/pkg/build.version=${VERSION} -X g
3232
GIT_COMMIT ?= $(shell git rev-list -1 HEAD)
3333
GIT_COMMIT_SHORT ?= $(shell git rev-parse --short HEAD)
3434

35+
define DOMAINS
36+
basicConstraints=CA:FALSE
37+
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
38+
subjectAltName = @alt_names
39+
[alt_names]
40+
DNS.1 = localhost
41+
DNS.2 = *.keda
42+
DNS.3 = *.interceptor-tls-test-ns
43+
endef
44+
export DOMAINS
45+
3546
# Build targets
3647

3748
build-operator:
@@ -45,8 +56,22 @@ build-scaler:
4556

4657
build: build-operator build-interceptor build-scaler
4758

59+
# generate certs for local unit and e2e tests
60+
rootca-test-certs:
61+
mkdir -p certs
62+
openssl req -x509 -nodes -new -sha256 -days 1024 -newkey rsa:2048 -keyout certs/RootCA.key -out certs/RootCA.pem -subj "/C=US/CN=Keda-Root-CA"
63+
openssl x509 -outform pem -in certs/RootCA.pem -out certs/RootCA.crt
64+
65+
test-certs: rootca-test-certs
66+
echo "$$DOMAINS" > certs/domains.ext
67+
openssl req -new -nodes -newkey rsa:2048 -keyout certs/tls.key -out certs/tls.csr -subj "/C=US/ST=KedaState/L=KedaCity/O=Keda-Certificates/CN=keda.local"
68+
openssl x509 -req -sha256 -days 1024 -in certs/tls.csr -CA certs/RootCA.pem -CAkey certs/RootCA.key -CAcreateserial -extfile certs/domains.ext -out certs/tls.crt
69+
70+
clean-test-certs:
71+
rm -r certs || true
72+
4873
# Test targets
49-
test: fmt vet
74+
test: fmt vet test-certs
5075
go test ./...
5176

5277
e2e-test:
@@ -156,6 +181,12 @@ deploy: manifests kustomize ## Deploy to the K8s cluster specified in ~/.kube/co
156181
cd config/interceptor && \
157182
$(KUSTOMIZE) edit add patch --path e2e-test/scaledobject.yaml --group keda.sh --kind ScaledObject --name interceptor --version v1alpha1
158183

184+
cd config/interceptor && \
185+
$(KUSTOMIZE) edit add patch --path tls/deployment.yaml --group apps --kind Deployment --name interceptor --version v1
186+
187+
cd config/interceptor && \
188+
$(KUSTOMIZE) edit add patch --path tls/proxy.service.yaml --kind Service --name interceptor-proxy --version v1
189+
159190
cd config/scaler && \
160191
$(KUSTOMIZE) edit set image ghcr.io/kedacore/http-add-on-scaler=${IMAGE_SCALER_VERSIONED_TAG}
161192

@@ -174,3 +205,8 @@ kind-load:
174205
kind load docker-image ghcr.io/kedacore/http-add-on-operator:${VERSION}
175206
kind load docker-image ghcr.io/kedacore/http-add-on-interceptor:${VERSION}
176207
kind load docker-image ghcr.io/kedacore/http-add-on-scaler:${VERSION}
208+
209+
k3d-import:
210+
k3d image import ghcr.io/kedacore/http-add-on-operator:main
211+
k3d image import ghcr.io/kedacore/http-add-on-interceptor:main
212+
k3d image import ghcr.io/kedacore/http-add-on-scaler:main
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: interceptor
5+
spec:
6+
replicas: 1
7+
template:
8+
spec:
9+
containers:
10+
- name: interceptor
11+
ports:
12+
- name: proxy-tls
13+
containerPort: 8443
14+
env:
15+
- name: KEDA_HTTP_PROXY_TLS_ENABLED
16+
value: "true"
17+
- name: KEDA_HTTP_PROXY_TLS_CERT_PATH
18+
value: "/certs/tls.crt"
19+
- name: KEDA_HTTP_PROXY_TLS_KEY_PATH
20+
value: "/certs/tls.key"
21+
- name: KEDA_HTTP_PROXY_TLS_PORT
22+
value: "8443"
23+
volumeMounts:
24+
- readOnly: true
25+
mountPath: "/certs"
26+
name: certs
27+
volumes:
28+
- name: certs
29+
secret:
30+
secretName: keda-tls
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
resources:
4+
- deployment.yaml
5+
- proxy.service.yaml
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: interceptor-proxy
5+
spec:
6+
type: ClusterIP
7+
ports:
8+
- name: proxy-tls
9+
protocol: TCP
10+
port: 8443
11+
targetPort: proxy-tls

docs/operate.md

+6
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ The OTEL exporter can be enabled by setting the `KEDA_HTTP_OTEL_HTTP_EXPORTER_EN
1919
If the collector is exposed on a unsecured endpoint then you can set the `KEDA_HTTP_OTEL_HTTP_COLLECTOR_INSECURE` environment variable to `true` (`false` by default) which will disable client security on the exporter.
2020

2121
If you need to provide any headers such as authentication details in order to utilise your OTEL collector you can add them into the `KEDA_HTTP_OTEL_HTTP_HEADERS` environment variable. The frequency at which the metrics are exported can be configured by setting `KEDA_HTTP_OTEL_METRIC_EXPORT_INTERVAL` to the number of seconds you require between each export interval (`30` by default).
22+
23+
# Configuring TLS for the KEDA HTTP Add-on interceptor proxy
24+
25+
The interceptor proxy has the ability to run both a HTTP and HTTPS server simultaneously to allow you to scale workloads that use either protocol. By default, the interceptor proxy will only serve over HTTP, but this behavior can be changed by configuring the appropriate environment variables on the deployment.
26+
27+
The TLS server can be enabled by setting the environment variable `KEDA_HTTP_PROXY_TLS_ENABLED` to `true` on the interceptor deployment (`false` by default). The TLS server will start on port `8443` by default, but this can be configured by setting `KEDA_HTTP_PROXY_TLS_PORT` to your desired port number. The TLS server will require valid TLS certificates to start, the path to the certificates can be configured via the `KEDA_HTTP_PROXY_TLS_CERT_PATH` and `KEDA_HTTP_PROXY_TLS_KEY_PATH` environment variables (`/certs/tls.crt` and `/certs/tls.key` by default).

interceptor/config/serving.go

+9
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ type Serving struct {
3434
//
3535
// This is the interval (in milliseconds) representing how often to do a fetch
3636
EndpointsCachePollIntervalMS int `envconfig:"KEDA_HTTP_ENDPOINTS_CACHE_POLLING_INTERVAL_MS" default:"250"`
37+
// ProxyTLSEnabled is a flag to specify whether the interceptor proxy should
38+
// be running using a TLS enabled server
39+
ProxyTLSEnabled bool `envconfig:"KEDA_HTTP_PROXY_TLS_ENABLED" default:"false"`
40+
// TLSCertPath is the path to read the certificate file from for the TLS server
41+
TLSCertPath string `envconfig:"KEDA_HTTP_PROXY_TLS_CERT_PATH" default:"/certs/tls.crt"`
42+
// TLSKeyPath is the path to read the private key file from for the TLS server
43+
TLSKeyPath string `envconfig:"KEDA_HTTP_PROXY_TLS_KEY_PATH" default:"/certs/tls.key"`
44+
// TLSPort is the port that the server should serve on if TLS is enabled
45+
TLSPort int `envconfig:"KEDA_HTTP_PROXY_TLS_PORT" default:"8443"`
3746
}
3847

3948
// Parse parses standard configs using envconfig and returns a pointer to the

interceptor/main.go

+50-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package main
22

33
import (
44
"context"
5+
"crypto/tls"
6+
"crypto/x509"
57
"errors"
68
"flag"
79
"fmt"
@@ -68,6 +70,7 @@ func main() {
6870

6971
proxyPort := servingCfg.ProxyPort
7072
adminPort := servingCfg.AdminPort
73+
proxyTLSEnabled := servingCfg.ProxyTLSEnabled
7174

7275
// setup the configured metrics collectors
7376
metrics.NewMetricsCollectors(metricsCfg)
@@ -160,12 +163,29 @@ func main() {
160163
})
161164
}
162165

163-
// start the proxy server. this is the server that
166+
// start the proxy servers. This is the server that
164167
// accepts, holds and forwards user requests
168+
// start a proxy server with TLS
169+
if proxyTLSEnabled {
170+
eg.Go(func() error {
171+
proxyTLSConfig := map[string]string{"certificatePath": servingCfg.TLSCertPath, "keyPath": servingCfg.TLSKeyPath}
172+
proxyTLSPort := servingCfg.TLSPort
173+
174+
setupLog.Info("starting the proxy server with TLS enabled", "port", proxyTLSPort)
175+
176+
if err := runProxyServer(ctx, ctrl.Log, queues, waitFunc, routingTable, timeoutCfg, proxyTLSPort, proxyTLSEnabled, proxyTLSConfig); !util.IsIgnoredErr(err) {
177+
setupLog.Error(err, "tls proxy server failed")
178+
return err
179+
}
180+
return nil
181+
})
182+
}
183+
184+
// start a proxy server without TLS.
165185
eg.Go(func() error {
166-
setupLog.Info("starting the proxy server", "port", proxyPort)
186+
setupLog.Info("starting the proxy server with TLS disabled", "port", proxyPort)
167187

168-
if err := runProxyServer(ctx, ctrl.Log, queues, waitFunc, routingTable, timeoutCfg, proxyPort); !util.IsIgnoredErr(err) {
188+
if err := runProxyServer(ctx, ctrl.Log, queues, waitFunc, routingTable, timeoutCfg, proxyPort, false, nil); !util.IsIgnoredErr(err) {
169189
setupLog.Error(err, "proxy server failed")
170190
return err
171191
}
@@ -199,7 +219,7 @@ func runAdminServer(
199219

200220
addr := fmt.Sprintf("0.0.0.0:%d", port)
201221
lggr.Info("admin server starting", "address", addr)
202-
return kedahttp.ServeContext(ctx, addr, adminServer)
222+
return kedahttp.ServeContext(ctx, addr, adminServer, false, nil)
203223
}
204224

205225
func runMetricsServer(
@@ -209,7 +229,7 @@ func runMetricsServer(
209229
) error {
210230
lggr.Info("starting the prometheus metrics server", "port", metricsCfg.OtelPrometheusExporterPort, "path", "/metrics")
211231
addr := fmt.Sprintf("0.0.0.0:%d", metricsCfg.OtelPrometheusExporterPort)
212-
return kedahttp.ServeContext(ctx, addr, promhttp.Handler())
232+
return kedahttp.ServeContext(ctx, addr, promhttp.Handler(), false, nil)
213233
}
214234

215235
func runProxyServer(
@@ -220,6 +240,8 @@ func runProxyServer(
220240
routingTable routing.Table,
221241
timeouts *config.Timeouts,
222242
port int,
243+
tlsEnabled bool,
244+
tlsConfig map[string]string,
223245
) error {
224246
dialer := kedanet.NewNetDialer(timeouts.Connect, timeouts.KeepAlive)
225247
dialContextFunc := kedanet.DialContextWithRetry(dialer, timeouts.DefaultBackoff())
@@ -229,12 +251,33 @@ func runProxyServer(
229251
})
230252
go probeHandler.Start(ctx)
231253

254+
tlsCfg := tls.Config{}
255+
if tlsEnabled {
256+
caCert, err := os.ReadFile(tlsConfig["certificatePath"])
257+
if err != nil {
258+
logger.Error(fmt.Errorf("error reading file from TLSCertPath"), "error", err)
259+
os.Exit(1)
260+
}
261+
caCertPool := x509.NewCertPool()
262+
caCertPool.AppendCertsFromPEM(caCert)
263+
cert, err := tls.LoadX509KeyPair(tlsConfig["certificatePath"], tlsConfig["keyPath"])
264+
265+
if err != nil {
266+
logger.Error(fmt.Errorf("error creating TLS configuration for proxy server"), "error", err)
267+
os.Exit(1)
268+
}
269+
270+
tlsCfg.RootCAs = caCertPool
271+
tlsCfg.Certificates = []tls.Certificate{cert}
272+
}
273+
232274
var upstreamHandler http.Handler
233275
upstreamHandler = newForwardingHandler(
234276
logger,
235277
dialContextFunc,
236278
waitFunc,
237279
newForwardingConfigFromTimeouts(timeouts),
280+
&tlsCfg,
238281
)
239282
upstreamHandler = middleware.NewCountingMiddleware(
240283
q,
@@ -246,6 +289,7 @@ func runProxyServer(
246289
routingTable,
247290
probeHandler,
248291
upstreamHandler,
292+
tlsEnabled,
249293
)
250294
rootHandler = middleware.NewLogging(
251295
logger,
@@ -258,5 +302,5 @@ func runProxyServer(
258302

259303
addr := fmt.Sprintf("0.0.0.0:%d", port)
260304
logger.Info("proxy server starting", "address", addr)
261-
return kedahttp.ServeContext(ctx, addr, rootHandler)
305+
return kedahttp.ServeContext(ctx, addr, rootHandler, tlsEnabled, tlsConfig)
262306
}

0 commit comments

Comments
 (0)