Skip to content

Commit 335f65d

Browse files
Require Bearer token for /metrics endpoint (#3317)
Signed-off-by: João Vilaça <[email protected]>
1 parent 511d4ea commit 335f65d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3134
-12
lines changed

cmd/hyperconverged-cluster-operator/main.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import (
5757
"github.com/kubevirt/hyperconverged-cluster-operator/controllers/nodes"
5858
"github.com/kubevirt/hyperconverged-cluster-operator/controllers/observability"
5959
"github.com/kubevirt/hyperconverged-cluster-operator/controllers/operands"
60+
"github.com/kubevirt/hyperconverged-cluster-operator/pkg/authorization"
6061
"github.com/kubevirt/hyperconverged-cluster-operator/pkg/monitoring/metrics"
6162
hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
6263
)
@@ -341,7 +342,8 @@ func getCacheOption(operatorNamespace string, ci hcoutil.ClusterInfo) cache.Opti
341342
func getManagerOptions(operatorNamespace string, needLeaderElection bool, ci hcoutil.ClusterInfo, scheme *apiruntime.Scheme) manager.Options {
342343
return manager.Options{
343344
Metrics: server.Options{
344-
BindAddress: fmt.Sprintf("%s:%d", hcoutil.MetricsHost, hcoutil.MetricsPort),
345+
BindAddress: fmt.Sprintf("%s:%d", hcoutil.MetricsHost, hcoutil.MetricsPort),
346+
FilterProvider: authorization.HttpWithBearerToken,
345347
},
346348
HealthProbeBindAddress: fmt.Sprintf("%s:%d", hcoutil.HealthProbeHost, hcoutil.HealthProbePort),
347349
ReadinessEndpointName: hcoutil.ReadinessEndpointName,

cmd/hyperconverged-cluster-webhook/main.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"sigs.k8s.io/controller-runtime/pkg/webhook"
1414

1515
webhookscontrollers "github.com/kubevirt/hyperconverged-cluster-operator/controllers/webhooks"
16+
"github.com/kubevirt/hyperconverged-cluster-operator/pkg/authorization"
1617
"github.com/kubevirt/hyperconverged-cluster-operator/pkg/webhooks/validator"
1718

1819
csvv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
@@ -93,7 +94,8 @@ func main() {
9394
// Create a new Cmd to provide shared dependencies and start components
9495
mgr, err := manager.New(cfg, manager.Options{
9596
Metrics: server.Options{
96-
BindAddress: fmt.Sprintf("%s:%d", hcoutil.MetricsHost, hcoutil.MetricsPort),
97+
BindAddress: fmt.Sprintf("%s:%d", hcoutil.MetricsHost, hcoutil.MetricsPort),
98+
FilterProvider: authorization.HttpWithBearerToken,
9799
},
98100
HealthProbeBindAddress: fmt.Sprintf("%s:%d", hcoutil.HealthProbeHost, hcoutil.HealthProbePort),
99101
ReadinessEndpointName: hcoutil.ReadinessEndpointName,

controllers/alerts/metrics_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ var _ = Describe("alert tests", func() {
103103
req = commontestutils.NewReq(hco)
104104
Expect(r.UpdateRelatedObjects(req)).To(Succeed())
105105
Expect(req.StatusDirty).To(BeTrue())
106-
Expect(hco.Status.RelatedObjects).To(HaveLen(5))
106+
Expect(hco.Status.RelatedObjects).To(HaveLen(6))
107107

108108
Expect(ee.CheckEvents(expectedEvents)).To(BeTrue())
109109
})

controllers/alerts/reconciler.go

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func NewMonitoringReconciler(ci hcoutil.ClusterInfo, cl client.Client, ee hcouti
6969
newRoleReconciler(namespace, owner),
7070
newRoleBindingReconciler(namespace, owner, ci),
7171
newMetricServiceReconciler(namespace, owner),
72+
newSecretReconciler(namespace, owner),
7273
newServiceMonitorReconciler(namespace, owner),
7374
},
7475
scheme: scheme,

controllers/alerts/secret.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package alerts
2+
3+
import (
4+
"context"
5+
6+
"github.com/go-logr/logr"
7+
corev1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"sigs.k8s.io/controller-runtime/pkg/client"
10+
11+
"github.com/kubevirt/hyperconverged-cluster-operator/pkg/authorization"
12+
hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
13+
)
14+
15+
const (
16+
secretName = "hco-bearer-auth"
17+
)
18+
19+
type secretReconciler struct {
20+
theSecret *corev1.Secret
21+
}
22+
23+
func newSecretReconciler(namespace string, owner metav1.OwnerReference) *secretReconciler {
24+
return &secretReconciler{
25+
theSecret: NewSecret(namespace, owner),
26+
}
27+
}
28+
29+
func (r *secretReconciler) Kind() string {
30+
return "Secret"
31+
}
32+
33+
func (r *secretReconciler) ResourceName() string {
34+
return secretName
35+
}
36+
37+
func (r *secretReconciler) GetFullResource() client.Object {
38+
return r.theSecret.DeepCopy()
39+
}
40+
41+
func (r *secretReconciler) EmptyObject() client.Object {
42+
return &corev1.Secret{}
43+
}
44+
45+
func (r *secretReconciler) UpdateExistingResource(ctx context.Context, cl client.Client, resource client.Object, logger logr.Logger) (client.Object, bool, error) {
46+
found := resource.(*corev1.Secret)
47+
modified := false
48+
49+
token, err := authorization.CreateToken()
50+
if err != nil {
51+
return nil, false, err
52+
}
53+
54+
if found.Data["token"] == nil || string(found.Data["token"]) != token {
55+
found.StringData = map[string]string{
56+
"token": token,
57+
}
58+
modified = true
59+
}
60+
61+
modified = updateCommonDetails(&r.theSecret.ObjectMeta, &found.ObjectMeta) || modified
62+
63+
if modified {
64+
err := cl.Update(ctx, found)
65+
if err != nil {
66+
logger.Error(err, "failed to update the Secret")
67+
return nil, false, err
68+
}
69+
}
70+
71+
return found, modified, nil
72+
}
73+
74+
func NewSecret(namespace string, owner metav1.OwnerReference) *corev1.Secret {
75+
token, err := authorization.CreateToken()
76+
if err != nil {
77+
logger.Error(err, "failed to create bearer token")
78+
return nil
79+
}
80+
81+
return &corev1.Secret{
82+
TypeMeta: metav1.TypeMeta{
83+
APIVersion: "v1",
84+
Kind: "Secret",
85+
},
86+
ObjectMeta: metav1.ObjectMeta{
87+
Name: secretName,
88+
Namespace: namespace,
89+
Labels: hcoutil.GetLabels(hcoutil.HyperConvergedName, hcoutil.AppComponentMonitoring),
90+
OwnerReferences: []metav1.OwnerReference{owner},
91+
},
92+
StringData: map[string]string{
93+
"token": token,
94+
},
95+
}
96+
}

controllers/alerts/serviceMonitor.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/go-logr/logr"
88
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
9+
corev1 "k8s.io/api/core/v1"
910
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1011
"sigs.k8s.io/controller-runtime/pkg/client"
1112

@@ -63,7 +64,17 @@ func NewServiceMonitor(namespace string, owner metav1.OwnerReference) *monitorin
6364
Selector: metav1.LabelSelector{
6465
MatchLabels: labels,
6566
},
66-
Endpoints: []monitoringv1.Endpoint{{Port: operatorPortName}},
67+
Endpoints: []monitoringv1.Endpoint{{
68+
Port: operatorPortName,
69+
Authorization: &monitoringv1.SafeAuthorization{
70+
Credentials: &corev1.SecretKeySelector{
71+
LocalObjectReference: corev1.LocalObjectReference{
72+
Name: secretName,
73+
},
74+
Key: "token",
75+
},
76+
},
77+
}},
6778
}
6879

6980
return &monitoringv1.ServiceMonitor{

controllers/hyperconverged/hyperconverged_controller_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ var _ = Describe("HyperconvergedController", func() {
244244
foundResource),
245245
).ToNot(HaveOccurred())
246246
// Check conditions
247-
Expect(foundResource.Status.RelatedObjects).To(HaveLen(26))
247+
Expect(foundResource.Status.RelatedObjects).To(HaveLen(27))
248248
expectedRef := corev1.ObjectReference{
249249
Kind: "PrometheusRule",
250250
Namespace: namespace,
@@ -309,7 +309,7 @@ var _ = Describe("HyperconvergedController", func() {
309309

310310
verifySystemHealthStatusError(foundResource)
311311

312-
Expect(foundResource.Status.RelatedObjects).To(HaveLen(21))
312+
Expect(foundResource.Status.RelatedObjects).To(HaveLen(22))
313313
expectedRef := corev1.ObjectReference{
314314
Kind: "PrometheusRule",
315315
Namespace: namespace,
@@ -820,7 +820,7 @@ var _ = Describe("HyperconvergedController", func() {
820820
).To(Succeed())
821821

822822
Expect(foundResource.Status.RelatedObjects).ToNot(BeNil())
823-
Expect(foundResource.Status.RelatedObjects).To(HaveLen(21))
823+
Expect(foundResource.Status.RelatedObjects).To(HaveLen(22))
824824
Expect(foundResource.ObjectMeta.Finalizers).To(Equal([]string{FinalizerName}))
825825

826826
// Now, delete HCO

deploy/cluster_role.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,16 @@ rules:
858858
- get
859859
- list
860860
- watch
861+
- apiGroups:
862+
- ""
863+
resources:
864+
- secrets
865+
verbs:
866+
- get
867+
- list
868+
- watch
869+
- create
870+
- update
861871
- apiGroups:
862872
- ""
863873
resources:

deploy/index-image/community-kubevirt-hyperconverged/1.14.0/manifests/kubevirt-hyperconverged-operator.v1.14.0.clusterserviceversion.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,16 @@ spec:
329329
- get
330330
- list
331331
- watch
332+
- apiGroups:
333+
- ""
334+
resources:
335+
- secrets
336+
verbs:
337+
- get
338+
- list
339+
- watch
340+
- create
341+
- update
332342
- apiGroups:
333343
- ""
334344
resources:

deploy/olm-catalog/community-kubevirt-hyperconverged/1.14.0/manifests/kubevirt-hyperconverged-operator.v1.14.0.clusterserviceversion.yaml

+11-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ metadata:
99
certified: "false"
1010
console.openshift.io/disable-operand-delete: "true"
1111
containerImage: quay.io/kubevirt/hyperconverged-cluster-operator:1.14.0-unstable
12-
createdAt: "2025-02-28 11:04:27"
12+
createdAt: "2025-03-02 09:34:08"
1313
description: A unified operator deploying and controlling KubeVirt and its supporting
1414
operators with opinionated defaults
1515
features.operators.openshift.io/cnf: "false"
@@ -329,6 +329,16 @@ spec:
329329
- get
330330
- list
331331
- watch
332+
- apiGroups:
333+
- ""
334+
resources:
335+
- secrets
336+
verbs:
337+
- get
338+
- list
339+
- watch
340+
- create
341+
- update
332342
- apiGroups:
333343
- ""
334344
resources:

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/gertd/go-pluralize v0.2.1
1212
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
1313
github.com/go-logr/logr v1.4.2
14+
github.com/golang-jwt/jwt/v5 v5.2.1
1415
github.com/google/uuid v1.6.0
1516
github.com/kubevirt/cluster-network-addons-operator v0.96.0
1617
github.com/kubevirt/monitoring/pkg/metrics/parser v0.0.0-20240505100225-e29dee0bb12b

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
8080
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
8181
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
8282
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
83+
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
84+
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
8385
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
8486
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
8587
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package authorization_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestAuthorization(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Authorization Suite")
13+
}

pkg/authorization/bearer_token.go

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package authorization
2+
3+
import (
4+
"crypto/rand"
5+
"fmt"
6+
"os"
7+
8+
"github.com/golang-jwt/jwt/v5"
9+
)
10+
11+
const (
12+
TokenPathEnvVar = "KUBERNETES_SERVICE_TOKEN_PATH"
13+
14+
defaultTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
15+
)
16+
17+
var secretKey []byte
18+
19+
func CreateToken() (string, error) {
20+
token := jwt.New(jwt.SigningMethodHS256)
21+
22+
key, err := getSecretKey()
23+
if err != nil {
24+
return "", fmt.Errorf("error getting secret key: %v", err)
25+
}
26+
27+
tokenString, err := token.SignedString(key)
28+
if err != nil {
29+
return "", fmt.Errorf("error signing token: %v", err)
30+
}
31+
32+
return tokenString, nil
33+
}
34+
35+
func ValidateToken(tokenString string) (bool, error) {
36+
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
37+
return getSecretKey()
38+
})
39+
if err != nil {
40+
return false, fmt.Errorf("error parsing token: %v", err)
41+
}
42+
43+
return token.Valid, nil
44+
}
45+
46+
func RefreshSecretKey() {
47+
secretKey = nil
48+
}
49+
50+
func getSecretKey() ([]byte, error) {
51+
// if secretKey is already available, return it
52+
if secretKey != nil {
53+
return secretKey, nil
54+
}
55+
56+
var err error
57+
58+
// get ServiceAccount token from file
59+
secretKey, err = getServiceAccountToken()
60+
if err != nil {
61+
// if ServiceAccount token is not available, generate an in-memory token
62+
secretKey, err = generateInMemorySecretKey()
63+
}
64+
65+
return secretKey, err
66+
}
67+
68+
func getServiceAccountToken() ([]byte, error) {
69+
tokenPath := os.Getenv(TokenPathEnvVar)
70+
if tokenPath == "" {
71+
tokenPath = defaultTokenPath
72+
}
73+
74+
saToken, err := os.ReadFile(tokenPath)
75+
if err != nil {
76+
return nil, fmt.Errorf("error reading ServiceAccount token: %v", err)
77+
}
78+
79+
return saToken, nil
80+
}
81+
82+
func generateInMemorySecretKey() ([]byte, error) {
83+
tokenBytes := make([]byte, 32)
84+
85+
_, err := rand.Read(tokenBytes)
86+
if err != nil {
87+
return nil, fmt.Errorf("error generating in-memory token: %v", err)
88+
}
89+
90+
return tokenBytes, nil
91+
}

0 commit comments

Comments
 (0)