Skip to content

Commit 2bae716

Browse files
Copilotkhauser
andcommitted
Update operator metrics to use port 8443 with authentication and authorization
- Updated operator/main.go to configure metrics server with SecureServing on port 8443 - Added WithAuthenticationAndAuthorization filter for metrics endpoint - Updated deployment to use port 8443 for metrics - Created metrics service for operator - Added RBAC permissions for TokenReviews and SubjectAccessReviews - Created ClusterRole for metrics reader access - Added e2e test for operator metrics endpoint - Updated go.mod and go.sum with required dependencies Co-authored-by: khauser <1460475+khauser@users.noreply.github.com> Signed-off-by: Karsten Ludwig Hauser <KHauser@intershop.com>
1 parent 57b6393 commit 2bae716

File tree

10 files changed

+227
-3
lines changed

10 files changed

+227
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,4 @@ admin/Cargo.lock
362362
*.csr
363363
*.srl
364364
*.ext
365+
*.test

config/operator/deployment.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ spec:
3838
value: ""
3939
ports:
4040
- name: metrics
41-
containerPort: 8080
41+
containerPort: 8443
4242
- name: probes
4343
containerPort: 8081
4444
livenessProbe:

config/operator/kustomization.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1
22
kind: Kustomization
33
resources:
44
- deployment.yaml
5+
- metrics_service.yaml
6+
- metrics_reader_role.yaml
57
- role.yaml
68
- role_binding.yaml
79
- service_account.yaml
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
apiVersion: rbac.authorization.k8s.io/v1
3+
kind: ClusterRole
4+
metadata:
5+
name: operator-metrics-reader
6+
rules:
7+
- nonResourceURLs:
8+
- "/metrics"
9+
verbs:
10+
- get
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: operator-metrics
5+
spec:
6+
type: ClusterIP
7+
ports:
8+
- name: metrics
9+
protocol: TCP
10+
port: 8443
11+
targetPort: metrics

config/operator/role.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ rules:
4242
- patch
4343
- update
4444
- watch
45+
- apiGroups:
46+
- authentication.k8s.io
47+
resources:
48+
- tokenreviews
49+
verbs:
50+
- create
51+
- apiGroups:
52+
- authorization.k8s.io
53+
resources:
54+
- subjectaccessreviews
55+
verbs:
56+
- create
4557
---
4658
apiVersion: rbac.authorization.k8s.io/v1
4759
kind: Role

go.mod

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ require (
6060
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1
6161
)
6262

63+
require (
64+
cel.dev/expr v0.24.0 // indirect
65+
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
66+
github.com/google/cel-go v0.26.0 // indirect
67+
github.com/stoewer/go-strcase v1.3.1 // indirect
68+
k8s.io/apiserver v0.33.5 // indirect
69+
k8s.io/component-base v0.33.5 // indirect
70+
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
71+
)
72+
6373
require (
6474
github.com/Masterminds/semver/v3 v3.4.0 // indirect
6575
github.com/beorn7/perks v1.0.1 // indirect

go.sum

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
2+
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
13
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
24
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
5+
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
6+
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
37
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
48
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
59
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -64,6 +68,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
6468
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
6569
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
6670
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
71+
github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g=
72+
github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8=
6773
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
6874
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
6975
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -172,6 +178,8 @@ github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wx
172178
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
173179
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
174180
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
181+
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
182+
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
175183
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
176184
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
177185
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -328,10 +336,14 @@ k8s.io/apiextensions-apiserver v0.33.5 h1:93NZh6rmrcamX/tfv/dZrTsMiQX69ufANmDcKP
328336
k8s.io/apiextensions-apiserver v0.33.5/go.mod h1:JIbyQnNlu6nQa7b1vgFi51pmlXOk8mdn0WJwUJnz/7U=
329337
k8s.io/apimachinery v0.33.5 h1:NiT64hln4TQXeYR18/ES39OrNsjGz8NguxsBgp+6QIo=
330338
k8s.io/apimachinery v0.33.5/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
339+
k8s.io/apiserver v0.33.5 h1:X1Gy33r4YkRLRqTjGjofk7X1/EjSLEVSJ/A+1qjoj60=
340+
k8s.io/apiserver v0.33.5/go.mod h1:Q+b5Btbc8x0PqOCeh/xBTesKk+cXQRN+PF2wdrTKDeg=
331341
k8s.io/client-go v0.33.5 h1:I8BdmQGxInpkMEnJvV6iG7dqzP3JRlpZZlib3OMFc3o=
332342
k8s.io/client-go v0.33.5/go.mod h1:W8PQP4MxbM4ypgagVE65mUUqK1/ByQkSALF9tzuQ6u0=
333343
k8s.io/code-generator v0.33.5 h1:KwkOvhwAaorjSwF2MQhhdhL3i8bBmAal/TWhX67kdHw=
334344
k8s.io/code-generator v0.33.5/go.mod h1:Ra+sdZquRakeTGcEnQAPw6BmlZ92IvxwQQTX/XOvOIE=
345+
k8s.io/component-base v0.33.5 h1:4D3kxjEx1pJRy3WHAZsmX3+LCpmd4ftE+2J4v6naTnQ=
346+
k8s.io/component-base v0.33.5/go.mod h1:Zma1YjBVuuGxIbspj1vGR3/5blzo2ARf1v0QTtog1to=
335347
k8s.io/gengo/v2 v2.0.0-20250820003526-c297c0c1eb9d h1:qUrYOinhdAUL0xxhA4gPqogPBaS9nIq2l2kTb6pmeB0=
336348
k8s.io/gengo/v2 v2.0.0-20250820003526-c297c0c1eb9d/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
337349
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
@@ -342,6 +354,8 @@ k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzk
342354
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
343355
knative.dev/pkg v0.0.0-20250602175424-3c3a920206ea h1:ukJPq9MzFTEH/Sei5MSVnSE8+7NSCKixCDZPd6p4ohw=
344356
knative.dev/pkg v0.0.0-20250602175424-3c3a920206ea/go.mod h1:tFayQbi6t4+5HXuEGLOGvILW228Q7uaJp/FYEgbjJ3A=
357+
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
358+
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
345359
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
346360
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
347361
sigs.k8s.io/controller-tools v0.17.3 h1:lwFPLicpBKLgIepah+c8ikRBubFW5kOQyT88r3EwfNw=

operator/main.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"sigs.k8s.io/controller-runtime/pkg/cache"
3030
"sigs.k8s.io/controller-runtime/pkg/healthz"
3131
"sigs.k8s.io/controller-runtime/pkg/log/zap"
32+
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
3233
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
3334

3435
httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
@@ -58,7 +59,7 @@ func main() {
5859
var enableLeaderElection bool
5960
var probeAddr string
6061
var profilingAddr string
61-
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
62+
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8443", "The address the metric endpoint binds to.")
6263
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
6364
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
6465
"Enable leader election for controller manager. "+
@@ -96,7 +97,9 @@ func main() {
9697
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
9798
Scheme: scheme,
9899
Metrics: server.Options{
99-
BindAddress: metricsAddr,
100+
BindAddress: metricsAddr,
101+
SecureServing: true,
102+
FilterProvider: filters.WithAuthenticationAndAuthorization,
100103
},
101104
PprofBindAddress: profilingAddr,
102105
HealthProbeBindAddress: probeAddr,
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//go:build e2e
2+
// +build e2e
3+
4+
package operator_metrics_test
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"strings"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
15+
. "github.com/kedacore/http-add-on/tests/helper"
16+
)
17+
18+
const (
19+
testName = "operator-metrics-test"
20+
)
21+
22+
var (
23+
testNamespace = fmt.Sprintf("%s-ns", testName)
24+
clientName = fmt.Sprintf("%s-client", testName)
25+
kedaOperatorMetricsURL = "https://keda-add-ons-http-operator-metrics.keda:8443/metrics"
26+
kedaOperatorMetricsHTTPURL = "http://keda-add-ons-http-operator-metrics.keda:8443/metrics"
27+
operatorPodSelector = "app.kubernetes.io/instance=operator"
28+
)
29+
30+
type templateData struct {
31+
TestNamespace string
32+
ClientName string
33+
}
34+
35+
const (
36+
clientTemplate = `
37+
apiVersion: v1
38+
kind: Pod
39+
metadata:
40+
name: {{.ClientName}}
41+
namespace: {{.TestNamespace}}
42+
spec:
43+
containers:
44+
- name: {{.ClientName}}
45+
image: curlimages/curl
46+
command:
47+
- sh
48+
- -c
49+
- "exec tail -f /dev/null"`
50+
51+
serviceAccountTemplate = `
52+
apiVersion: v1
53+
kind: ServiceAccount
54+
metadata:
55+
name: {{.ClientName}}
56+
namespace: {{.TestNamespace}}`
57+
58+
clusterRoleBindingTemplate = `
59+
apiVersion: rbac.authorization.k8s.io/v1
60+
kind: ClusterRoleBinding
61+
metadata:
62+
name: {{.ClientName}}-metrics-reader
63+
roleRef:
64+
apiGroup: rbac.authorization.k8s.io
65+
kind: ClusterRole
66+
name: operator-metrics-reader
67+
subjects:
68+
- kind: ServiceAccount
69+
name: {{.ClientName}}
70+
namespace: {{.TestNamespace}}`
71+
)
72+
73+
func TestOperatorMetrics(t *testing.T) {
74+
// setup
75+
t.Log("--- setting up ---")
76+
// Create kubernetes resources
77+
kc := GetKubernetesClient(t)
78+
data, templates := getTemplateData()
79+
CreateKubernetesResources(t, kc, testNamespace, data, templates)
80+
81+
// Wait for client pod to be ready
82+
assert.True(t, WaitForAllPodRunningInNamespace(t, kc, testNamespace, 6, 10),
83+
"client pod should be running")
84+
85+
t.Log("--- testing operator metrics endpoint ---")
86+
87+
// Test 1: HTTPS endpoint should be accessible (will fail cert validation but should return metrics)
88+
t.Log("Test 1: Verify HTTPS endpoint is available")
89+
testHTTPSEndpoint(t)
90+
91+
// Test 2: HTTP should not work (redirected or refused)
92+
t.Log("Test 2: Verify HTTP endpoint is not accessible")
93+
testHTTPEndpointNotAccessible(t)
94+
95+
// Test 3: Verify metrics are returned
96+
t.Log("Test 3: Verify metrics content")
97+
testMetricsContent(t)
98+
99+
// cleanup
100+
DeleteKubernetesResources(t, testNamespace, data, templates)
101+
}
102+
103+
func testHTTPSEndpoint(t *testing.T) {
104+
// Use curl with -k to skip certificate validation (self-signed cert)
105+
cmd := fmt.Sprintf("curl -k --max-time 10 %s", kedaOperatorMetricsURL)
106+
out, errOut, err := ExecCommandOnSpecificPod(t, clientName, testNamespace, cmd)
107+
108+
// We expect this to succeed with a self-signed certificate
109+
if err != nil {
110+
t.Logf("HTTPS endpoint test - Output: %s, Error output: %s, Error: %v", out, errOut, err)
111+
}
112+
113+
// The endpoint should return something (even if authentication fails, it should respond)
114+
assert.True(t, err == nil || strings.Contains(errOut, "Forbidden") || strings.Contains(out, "Forbidden"),
115+
"HTTPS endpoint should respond (either with metrics or authentication error)")
116+
}
117+
118+
func testHTTPEndpointNotAccessible(t *testing.T) {
119+
// Try HTTP - should fail or redirect
120+
cmd := fmt.Sprintf("curl --max-time 10 %s", kedaOperatorMetricsHTTPURL)
121+
out, errOut, err := ExecCommandOnSpecificPod(t, clientName, testNamespace, cmd)
122+
123+
// HTTP should not work since we're using SecureServing
124+
assert.True(t, err != nil || strings.Contains(errOut, "Empty reply") || strings.Contains(out, "Empty reply"),
125+
"HTTP endpoint should not be accessible. Output: %s, Error: %s", out, errOut)
126+
}
127+
128+
func testMetricsContent(t *testing.T) {
129+
// Get the operator pod name
130+
pods, err := KubeClient.CoreV1().Pods("keda").List(context.Background(), metav1.ListOptions{
131+
LabelSelector: operatorPodSelector,
132+
})
133+
assert.NoError(t, err, "should be able to list operator pods")
134+
assert.NotEmpty(t, pods.Items, "should find at least one operator pod")
135+
136+
operatorPodName := pods.Items[0].Name
137+
138+
// Access metrics from within the operator pod itself (bypasses auth)
139+
cmd := "curl -k https://localhost:8443/metrics"
140+
out, errOut, err := ExecCommandOnSpecificPod(t, operatorPodName, "keda", cmd)
141+
142+
if err != nil {
143+
t.Logf("Metrics content test - Output: %s, Error output: %s, Error: %v", out, errOut, err)
144+
}
145+
146+
// Verify that metrics are returned
147+
assert.NoError(t, err, "should be able to access metrics from operator pod")
148+
assert.True(t, strings.Contains(out, "# HELP") || strings.Contains(out, "# TYPE"),
149+
"metrics should contain Prometheus format. Output: %s", out)
150+
}
151+
152+
func getTemplateData() (templateData, []Template) {
153+
return templateData{
154+
TestNamespace: testNamespace,
155+
ClientName: clientName,
156+
}, []Template{
157+
{Name: "clientTemplate", Config: clientTemplate},
158+
{Name: "serviceAccountTemplate", Config: serviceAccountTemplate},
159+
{Name: "clusterRoleBindingTemplate", Config: clusterRoleBindingTemplate},
160+
}
161+
}

0 commit comments

Comments
 (0)