Summary
The koku-metrics-operator CostManagementMetricsConfig CRD exposes spec.prometheusconfig.serviceaddress as a free-form string with no host validation. The operator reads its own Kubernetes service-account token from /var/run/secrets/kubernetes.io/serviceaccount/token and configures it as the BearerToken for PromQL queries sent to this user-supplied address. The service-account token carries the manager-role ClusterRole, which grants get on all Secrets cluster-wide.
Impact
A principal with create/update permission on costmanagementmetricsconfigs in the operator namespace can harvest the operator's Kubernetes service-account token by redirecting Prometheus queries to an attacker-controlled endpoint. This token grants read access to every named Secret in any namespace across the cluster.
Affected Code
- api/v1beta1/metricsconfigtypes.go:198 — serviceaddress field definition (no validation) - internal/collector/prometheus.go:95-117 — token read and client configuration - config/rbac/role.yaml:18-22 — ClusterRole grants secrets:get cluster-wide
Remediation
Restrict serviceaddress to in-cluster Prometheus/Thanos endpoints via CRD CEL validation:
yaml x-kubernetes-validations: - rule: "url(self.serviceaddress).getHostname().endsWith('.openshift-monitoring.svc') || url(self.serviceaddress).getHostname().endsWith('.openshift-monitoring.svc.cluster.local')" message: "serviceaddress must target the in-cluster monitoring service"
Or use a TokenRequest API call with audience bound to prometheus-k8s so the token is not replayable against kube-apiserver.
Summary
The koku-metrics-operator CostManagementMetricsConfig CRD exposes spec.apiurl as a free-form string with no host allow-list or schema validation. When spec.authentication.type == token (the default), the operator reads the cluster-global openshift-config/pull-secret, extracts the cloud.openshift.com bearer token, and attaches it as Authorization: Bearer <token> on an outbound POST to the user-supplied URL. No comparison against the default https://console.redhat.com gates credential attachment.
Impact
A principal with create/update permission on costmanagementmetricsconfigs in the operator namespace can cause the operator to send the cluster's Red Hat Cloud / registry pull-secret bearer token to an arbitrary external or in-cluster endpoint. This credential authenticates the entire cluster to console.redhat.com and registry.redhat.io.
Affected Code
- api/v1beta1/metricsconfigtypes.go:249 — APIURL field definition (no validation) - internal/controller/costmanagementmetricsconfigcontroller.go:128,235,258,590 — credential extraction and URL construction - internal/crhchttp/httpclouddotredhat.go:94 — bearer token attachment to HTTP request
Remediation
Reject token authentication when apiurl != DefaultAPIURL. Add a CEL validation rule on the CRD:
yaml x-kubernetes-validations: - rule: "self.authentication.type != 'token' || self.apiurl == 'https://console.redhat.com'" message: "token authentication is only permitted against https://console.redhat.com"
Or implement the check in the reconciler before calling Upload().
Summary
The koku-metrics-operator CostManagementMetricsConfig CRD exposes spec.prometheusconfig.serviceaddress as a free-form string with no host validation. The operator reads its own Kubernetes service-account token from /var/run/secrets/kubernetes.io/serviceaccount/token and configures it as the BearerToken for PromQL queries sent to this user-supplied address. The service-account token carries the manager-role ClusterRole, which grants get on all Secrets cluster-wide.
Impact
A principal with create/update permission on costmanagementmetricsconfigs in the operator namespace can harvest the operator's Kubernetes service-account token by redirecting Prometheus queries to an attacker-controlled endpoint. This token grants read access to every named Secret in any namespace across the cluster.
Affected Code
- api/v1beta1/metricsconfigtypes.go:198 — serviceaddress field definition (no validation) - internal/collector/prometheus.go:95-117 — token read and client configuration - config/rbac/role.yaml:18-22 — ClusterRole grants secrets:get cluster-wide
Remediation
Restrict serviceaddress to in-cluster Prometheus/Thanos endpoints via CRD CEL validation:
yaml x-kubernetes-validations: - rule: "url(self.serviceaddress).getHostname().endsWith('.openshift-monitoring.svc') || url(self.serviceaddress).getHostname().endsWith('.openshift-monitoring.svc.cluster.local')" message: "serviceaddress must target the in-cluster monitoring service"
Or use a TokenRequest API call with audience bound to prometheus-k8s so the token is not replayable against kube-apiserver.