Skip to content

Commit a5d0cbc

Browse files
Replace kube-rbac-proxy to ensure the same level of protection with controller-runtime feature
Utilise Controller-Runtime's WithAuthenticationAndAuthorization feature to protect the metrics endpoint. This approach provides access control, similar to the functionality of kube-rbac-proxy. kube-rbac-proxy image from gcr.io/kubebuilder/kube-rbac-proxy is deprecated and should no longer be used More info: kubernetes-sigs/kubebuilder#3907
1 parent b67bd38 commit a5d0cbc

File tree

9 files changed

+92
-31
lines changed

9 files changed

+92
-31
lines changed

cmd/manager/main.go

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ package main
1818

1919
import (
2020
"context"
21+
"crypto/tls"
2122
"flag"
2223
"fmt"
24+
"log"
2325
"net/http"
2426
"os"
2527
"path/filepath"
@@ -41,9 +43,11 @@ import (
4143
"k8s.io/klog/v2/textlogger"
4244
ctrl "sigs.k8s.io/controller-runtime"
4345
crcache "sigs.k8s.io/controller-runtime/pkg/cache"
46+
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
4447
"sigs.k8s.io/controller-runtime/pkg/client"
4548
crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer"
4649
"sigs.k8s.io/controller-runtime/pkg/healthz"
50+
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
4751
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
4852

4953
catalogd "github.com/operator-framework/catalogd/api/v1"
@@ -70,6 +74,7 @@ import (
7074
var (
7175
setupLog = ctrl.Log.WithName("setup")
7276
defaultSystemNamespace = "olmv1-system"
77+
certWatcher *certwatcher.CertWatcher
7378
)
7479

7580
const authFilePrefix = "operator-controller-global-pull-secrets"
@@ -89,6 +94,8 @@ func podNamespace() string {
8994
func main() {
9095
var (
9196
metricsAddr string
97+
certFile string
98+
keyFile string
9299
enableLeaderElection bool
93100
probeAddr string
94101
cachePath string
@@ -97,9 +104,15 @@ func main() {
97104
caCertDir string
98105
globalPullSecret string
99106
)
100-
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
107+
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address for the metrics endpoint. "+
108+
"The metrics server only runs with TLS protection (HTTPS) and will not be enabled without specifying tls-cert and tls-key. "+
109+
"We recommend using port :8443, which is the target port mapped for the Service. "+
110+
"If a different port is used, the Service configuration may need to be updated. "+
111+
"If the value provided is '0', the metrics server will be disabled.")
101112
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
102113
flag.StringVar(&caCertDir, "ca-certs-dir", "", "The directory of TLS certificate to use for verifying HTTPS connections to the Catalogd and docker-registry web servers.")
114+
flag.StringVar(&certFile, "tls-cert", "", "The certificate file used for serving metrics contents over HTTPS. Requires tls-key.")
115+
flag.StringVar(&keyFile, "tls-key", "", "The key file used for serving metrics contents over HTTPS. Requires tls-cert.")
103116
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
104117
"Enable leader election for controller manager. "+
105118
"Enabling this will ensure there is only one active controller manager.")
@@ -119,6 +132,11 @@ func main() {
119132
os.Exit(0)
120133
}
121134

135+
if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") {
136+
setupLog.Error(nil, "unable to configure TLS certificates: tls-cert and tls-key flags must be used together")
137+
os.Exit(1)
138+
}
139+
122140
ctrl.SetLogger(textlogger.NewLogger(textlogger.NewConfig()))
123141

124142
setupLog.Info("starting up the controller", "version info", version.String())
@@ -161,9 +179,53 @@ func main() {
161179
},
162180
}
163181
}
182+
183+
// Note that the metrics server is not serving if the BindAddress is set to "0".
184+
// Therefore, the metrics server is disabled by default. It is only enabled
185+
// if certFile and keyFile are provided. The intention is not allowing the metrics
186+
// be served with the default self-signed certificate generated by controller-runtime.
187+
metricsServerOptions := server.Options{
188+
BindAddress: "0",
189+
}
190+
191+
if len(certFile) > 0 && len(keyFile) > 0 {
192+
setupLog.Info("Starting metrics server with TLS enabled.")
193+
194+
metricsServerOptions.BindAddress = metricsAddr
195+
metricsServerOptions.SecureServing = true
196+
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
197+
198+
setupLog.Info("Using provided TLS certificate and key files for the metrics server.",
199+
"certFile", certFile, "keyFile", keyFile)
200+
201+
// If the certificate files change, the watcher will reload them.
202+
var err error
203+
certWatcher, err = certwatcher.New(certFile, keyFile)
204+
if err != nil {
205+
log.Fatalf("Failed to initialize certificate watcher: %v", err)
206+
}
207+
metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) {
208+
config.GetCertificate = certWatcher.GetCertificate
209+
210+
// If the enable-http2 flag is false (the default), http/2 should be disabled
211+
// due to its vulnerabilities. More specifically, disabling http/2 will
212+
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
213+
// Rapid Reset CVEs. For more information see:
214+
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
215+
// - https://github.com/advisories/GHSA-4374-p667-p6c8
216+
// Besides, those CVEs are solved already; the solution is still insufficient, and we need to mitigate
217+
// the risks. More info https://github.com/golang/go/issues/63417
218+
setupLog.Info("disabling http/2")
219+
config.NextProtos = []string{"http/1.1"}
220+
})
221+
} else {
222+
setupLog.Info("WARNING: Metrics Server is disabled. " +
223+
"Metrics will not be served since certificate ('--tls-cert') and key files ('--tls-key') are not provided.")
224+
}
225+
164226
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
165227
Scheme: scheme.Scheme,
166-
Metrics: server.Options{BindAddress: metricsAddr},
228+
Metrics: metricsServerOptions,
167229
HealthProbeBindAddress: probeAddr,
168230
LeaderElection: enableLeaderElection,
169231
LeaderElectionID: "9c4404e7.operatorframework.io",
@@ -220,6 +282,14 @@ func main() {
220282
os.Exit(1)
221283
}
222284

285+
if certWatcher != nil {
286+
setupLog.Info("Adding certificate watcher to manager")
287+
if err := mgr.Add(certWatcher); err != nil {
288+
setupLog.Error(err, "unable to add certificate watcher to manager")
289+
os.Exit(1)
290+
}
291+
}
292+
223293
unpacker := &source.ContainersImageRegistry{
224294
BaseCachePath: filepath.Join(cachePath, "unpack"),
225295
SourceContextFunc: func(logger logr.Logger) (*types.SystemContext, error) {

config/base/manager/manager.yaml

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ spec:
5252
- /manager
5353
args:
5454
- "--health-probe-bind-address=:8081"
55-
- "--metrics-bind-address=127.0.0.1:8080"
55+
- "--metrics-bind-address=:8443"
5656
- "--leader-elect"
5757
image: controller:latest
5858
imagePullPolicy: IfNotPresent
@@ -84,27 +84,6 @@ spec:
8484
cpu: 10m
8585
memory: 64Mi
8686
terminationMessagePolicy: FallbackToLogsOnError
87-
- name: kube-rbac-proxy
88-
securityContext:
89-
allowPrivilegeEscalation: false
90-
capabilities:
91-
drop:
92-
- "ALL"
93-
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.15.0
94-
args:
95-
- --secure-listen-address=0.0.0.0:8443
96-
- --http2-disable
97-
- --upstream=http://127.0.0.1:8080/
98-
- --logtostderr=true
99-
ports:
100-
- containerPort: 8443
101-
protocol: TCP
102-
name: https
103-
resources:
104-
requests:
105-
cpu: 5m
106-
memory: 64Mi
107-
terminationMessagePolicy: FallbackToLogsOnError
10887
serviceAccountName: operator-controller-controller-manager
10988
terminationGracePeriodSeconds: 10
11089
volumes:

config/base/rbac/auth_proxy_service.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ spec:
1010
- name: https
1111
port: 8443
1212
protocol: TCP
13-
targetPort: https
13+
targetPort: 8443
1414
selector:
1515
control-plane: operator-controller-controller-manager

config/base/rbac/kustomization.yaml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ resources:
1717
- extension_editor_role.yaml
1818
- extension_viewer_role.yaml
1919

20-
# Comment the following 4 lines if you want to disable
21-
# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
22-
# which protects your /metrics endpoint.
20+
# The following RBAC configurations are used to protect
21+
# the metrics endpoint with authn/authz. These configurations
22+
# ensure that only authorized users and service accounts
23+
# can access the metrics endpoint. Comment the following
24+
# permissions if you want to disable this protection.
25+
# More info: https://book.kubebuilder.io/reference/metrics.html
2326
- auth_proxy_service.yaml
2427
- auth_proxy_role.yaml
2528
- auth_proxy_role_binding.yaml
2629
- auth_proxy_client_clusterrole.yaml
30+

config/components/coverage/manager_e2e_coverage_patch.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ spec:
77
template:
88
spec:
99
containers:
10-
- name: kube-rbac-proxy
1110
- name: manager
1211
env:
1312
- name: GOCOVERDIR

config/components/registries-conf/manager_e2e_registries_conf_patch.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ spec:
77
template:
88
spec:
99
containers:
10-
- name: kube-rbac-proxy
1110
- name: manager
1211
volumeMounts:
1312
- name: e2e-registries-conf
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
- op: add
22
path: /spec/template/spec/volumes/-
3-
value: {"name":"olmv1-certificate", "secret":{"secretName":"olmv1-cert", "optional": false, "items": [{"key": "ca.crt", "path": "olm-ca.crt"}]}}
3+
value: {"name":"olmv1-certificate", "secret":{"secretName":"olmv1-cert", "optional": false, "items": [{"key": "ca.crt", "path": "olm-ca.crt"}, {"key": "tls.crt", "path": "tls.cert"}, {"key": "tls.key", "path": "tls.key"}]}}
44
- op: add
55
path: /spec/template/spec/containers/0/volumeMounts/-
66
value: {"name":"olmv1-certificate", "readOnly": true, "mountPath":"/var/certs/"}
77
- op: add
88
path: /spec/template/spec/containers/0/args/-
99
value: "--ca-certs-dir=/var/certs"
10+
- op: add
11+
path: /spec/template/spec/containers/0/args/-
12+
value: "--tls-cert=/var/certs/tls.cert"
13+
- op: add
14+
path: /spec/template/spec/containers/0/args/-
15+
value: "--tls-key=/var/certs/tls.key"

config/components/tls/resources/manager_cert.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ spec:
77
dnsNames:
88
- operator-controller.olmv1-system.svc
99
- operator-controller.olmv1-system.svc.cluster.local
10+
- operator-controller-controller-manager-metrics-service.olmv1-system.svc
11+
- operator-controller-controller-manager-metrics-service.olmv1-system.svc.cluster.local
1012
privateKey:
1113
algorithm: ECDSA
1214
size: 256

testdata/build-test-registry.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ spec:
4242
dnsNames:
4343
- ${name}.${namespace}.svc
4444
- ${name}.${namespace}.svc.cluster.local
45+
- ${name}-controller-manager-metrics-service.${namespace}.svc
46+
- ${name}-controller-manager-metrics-service.${namespace}.svc.cluster.local
4547
privateKey:
4648
algorithm: ECDSA
4749
size: 256

0 commit comments

Comments
 (0)