Skip to content

Commit 3c28f59

Browse files
Clee2691openshift-merge-bot[bot]
authored andcommitted
LOG-8978: Enhance log-file-metrics-exporter to only allow scraping of metrics from a secure endpoint
1 parent 57bdaf6 commit 3c28f59

9 files changed

Lines changed: 117 additions & 31 deletions

File tree

internal/auth/rbac.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package auth
22

33
import (
44
"fmt"
5+
56
"github.com/openshift/cluster-logging-operator/internal/reconcile"
67
"github.com/openshift/cluster-logging-operator/internal/runtime"
78
"github.com/openshift/cluster-logging-operator/internal/utils"
@@ -10,6 +11,8 @@ import (
1011
"sigs.k8s.io/controller-runtime/pkg/client"
1112
)
1213

14+
const systemAuthDelegatorClusterRoleName = "system:auth-delegator"
15+
1316
// ReconcileRBAC reconciles the RBAC specifically for the service account and SCC
1417
func ReconcileRBAC(k8sClient client.Client, rbacName, saNamespace, saName string, owner metav1.OwnerReference) error {
1518
desiredCRB := NewMetaDataReaderClusterRoleBinding(saNamespace, saName, owner)
@@ -25,6 +28,30 @@ func ReconcileRBAC(k8sClient client.Client, rbacName, saNamespace, saName string
2528
return reconcile.RoleBinding(k8sClient, desiredSCCRoleBinding)
2629
}
2730

31+
// ReconcileMetricsAuthRBAC reconciles the ClusterRoleBinding that binds system:auth-delegator to the service account
32+
func ReconcileMetricsAuthRBAC(k8sClient client.Client, commonName, saNamespace, saName string) error {
33+
name := fmt.Sprintf("%s-metrics-auth", commonName)
34+
desiredMetricsAuthRoleBinding := NewMetricsAuthClusterRoleBinding(name, saNamespace, saName)
35+
return reconcile.ClusterRoleBinding(k8sClient, desiredMetricsAuthRoleBinding.Name, func() *rbacv1.ClusterRoleBinding { return desiredMetricsAuthRoleBinding })
36+
}
37+
38+
// NewMetricsAuthClusterRoleBinding binds the system:auth-delegator ClusterRole to the given service account.
39+
func NewMetricsAuthClusterRoleBinding(name, saNamespace, saName string) *rbacv1.ClusterRoleBinding {
40+
return runtime.NewClusterRoleBinding(
41+
name,
42+
rbacv1.RoleRef{
43+
APIGroup: rbacv1.GroupName,
44+
Kind: "ClusterRole",
45+
Name: systemAuthDelegatorClusterRoleName,
46+
},
47+
rbacv1.Subject{
48+
Kind: "ServiceAccount",
49+
Name: saName,
50+
Namespace: saNamespace,
51+
},
52+
)
53+
}
54+
2855
// NewMetaDataReaderClusterRoleBinding stubs a clusterrolebinding to allow reading of pod metadata (i.e. labels)
2956
func NewMetaDataReaderClusterRoleBinding(saNamespace, saName string, owner metav1.OwnerReference) *rbacv1.ClusterRoleBinding {
3057
name := fmt.Sprintf("metadata-reader-%s-%s", saNamespace, saName)

internal/metrics/logfilemetricexporter/factory.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,8 @@ func newLogMetricsExporterContainer(exporter loggingv1a1.LogFileMetricExporter,
116116
// TODO: When openshift/api is updated with the Groups field in TLSProfileSpec,
117117
// pass groups from the profile spec instead of nil.
118118
exporterContainer.Args = []string{"-c",
119-
"/usr/local/bin/log-file-metric-exporter -verbosity=2 -dir=/var/log/pods -http=:2112 -keyFile=/etc/logfilemetricexporter/metrics/tls.key -crtFile=/etc/logfilemetricexporter/metrics/tls.crt -tlsMinVersion=" +
120-
tls.MinTLSVersion(tlsProfileSpec) + " -cipherSuites=" + strings.Join(tls.TLSCiphers(tlsProfileSpec), ",") +
121-
" -groups=" + strings.Join(tls.TLSGroups(nil), ",")}
119+
"/usr/local/bin/log-file-metric-exporter -verbosity=2 -dir=/var/log/pods -http=:2112 -keyFile=/etc/logfilemetricexporter/metrics/tls.key -crtFile=/etc/logfilemetricexporter/metrics/tls.crt -secureMetrics -tlsMinVersion=" +
120+
tls.MinTLSVersion(tlsProfileSpec) + " -cipherSuites=" + strings.Join(tls.TLSCiphers(tlsProfileSpec), ",")}
122121

123122
exporterContainer.VolumeMounts = []v1.VolumeMount{
124123
{Name: logContainers, ReadOnly: true, MountPath: logContainersValue},

internal/metrics/logfilemetricexporter/metric_exporter.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ func Reconcile(lfmeInstance *loggingv1alpha1.LogFileMetricExporter,
5151
return err
5252
}
5353

54+
if err := auth.ReconcileMetricsAuthRBAC(requestClient, resNames.CommonName, lfmeInstance.Namespace, resNames.ServiceAccount); err != nil {
55+
log.Error(err, "logfilemetricexporter.ReconcileMetricsRBAC")
56+
return err
57+
}
58+
5459
if err := network.ReconcileService(requestClient, lfmeInstance.Namespace, resNames.CommonName, lfmeInstance.Name, constants.LogfilesmetricexporterName, constants.MetricsPortName, ExporterMetricsSecretName, exporterPort, owner, commonLabels); err != nil {
5560
log.Error(err, "logfilemetricexporter.ReconcileService")
5661
return err

internal/metrics/logfilemetricexporter/metric_exporter_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
appsv1 "k8s.io/api/apps/v1"
1818
corev1 "k8s.io/api/core/v1"
1919
networkingv1 "k8s.io/api/networking/v1"
20+
rbacv1 "k8s.io/api/rbac/v1"
2021
"k8s.io/apimachinery/pkg/api/errors"
2122
"k8s.io/apimachinery/pkg/api/resource"
2223
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -128,6 +129,17 @@ var _ = Describe("Reconcile LogFileMetricExporter", func() {
128129
svcURL := fmt.Sprintf("%s.openshift-logging.svc", constants.LogfilesmetricexporterName)
129130
Expect(smInstance.Spec.Endpoints[0].TLSConfig.SafeTLSConfig.ServerName).
130131
To(Equal(svcURL))
132+
133+
Expect(smInstance.Spec.Endpoints[0].BearerTokenFile).
134+
To(Equal("/var/run/secrets/kubernetes.io/serviceaccount/token"))
135+
136+
// Metrics Auth RBAC
137+
// Verify the metrics auth ClusterRoleBinding exists and references system:auth-delegator
138+
metricsAuthBinding := &rbacv1.ClusterRoleBinding{}
139+
Expect(reqClient.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf("%s-metrics-auth", constants.LogfilesmetricexporterName)}, metricsAuthBinding)).Should(Succeed())
140+
Expect(metricsAuthBinding.RoleRef.Name).To(Equal("system:auth-delegator"))
141+
Expect(metricsAuthBinding.Subjects).To(HaveLen(1))
142+
Expect(metricsAuthBinding.Subjects[0].Name).To(Equal(constants.LogfilesmetricexporterName))
131143
})
132144

133145
Context("when the logfilemetricexporter NetworkPolicy is reconciled", func() {

internal/metrics/service_monitor.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@ import (
1313
)
1414

1515
const (
16-
prometheusCAFile = "/etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt"
16+
prometheusCAFile = "/etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt"
17+
prometheusBearerTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
1718
)
1819

1920
func newServiceMonitor(namespace, name string, owner metav1.OwnerReference, selector map[string]string, portName string) *monitoringv1.ServiceMonitor {
2021
var endpoint = []monitoringv1.Endpoint{
2122
{
22-
Port: portName,
23-
Path: "/metrics",
24-
Scheme: "https",
23+
Port: portName,
24+
Path: "/metrics",
25+
Scheme: "https",
26+
BearerTokenFile: prometheusBearerTokenFile,
2527
TLSConfig: &monitoringv1.TLSConfig{
2628
CAFile: prometheusCAFile,
2729
SafeTLSConfig: monitoringv1.SafeTLSConfig{

internal/metrics/service_monitor_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,8 @@ var _ = Describe("Reconcile ServiceMonitor", func() {
6969
svcURL := fmt.Sprintf("%s.openshift-logging.svc", serviceName)
7070
Expect(smInstance.Spec.Endpoints[0].TLSConfig.SafeTLSConfig.ServerName).
7171
To(Equal(svcURL))
72+
73+
Expect(smInstance.Spec.Endpoints[0].BearerTokenFile).
74+
To(Equal("/var/run/secrets/kubernetes.io/serviceaccount/token"))
7275
})
7376
})

internal/reconcile/rbac.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
log "github.com/ViaQ/logerr/v2/log/static"
88
"github.com/openshift/cluster-logging-operator/internal/runtime"
99
rbacv1 "k8s.io/api/rbac/v1"
10+
apierrors "k8s.io/apimachinery/pkg/api/errors"
1011
"sigs.k8s.io/controller-runtime/pkg/client"
1112
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1213
)
@@ -43,25 +44,29 @@ func RoleBinding(k8Client client.Client, desired *rbacv1.RoleBinding) error {
4344
}
4445

4546
func ClusterRoleBinding(k8sClient client.Client, name string, generator func() *rbacv1.ClusterRoleBinding) error {
46-
wantRoleBinding := generator()
47-
crb := runtime.NewClusterRoleBinding(name, rbacv1.RoleRef{})
48-
op, err := controllerutil.CreateOrUpdate(context.TODO(), k8sClient, crb, func() error {
49-
// Update the clusterrolebinding with our desired state
50-
crb.RoleRef = wantRoleBinding.RoleRef
51-
crb.Subjects = wantRoleBinding.Subjects
52-
return nil
53-
})
47+
desired := generator()
48+
existing := runtime.NewClusterRoleBinding(name, rbacv1.RoleRef{})
49+
err := k8sClient.Get(context.TODO(), client.ObjectKeyFromObject(existing), existing)
50+
if apierrors.IsNotFound(err) {
51+
log.V(3).Info("Creating clusterRoleBinding", "name", name)
52+
return k8sClient.Create(context.TODO(), desired)
53+
}
54+
if err != nil {
55+
return err
56+
}
5457

55-
if err == nil {
56-
log.V(3).Info(fmt.Sprintf("reconciled clusterRoleBinding - operation: %s", op))
58+
if existing.RoleRef != desired.RoleRef {
59+
log.V(3).Info("Deleting clusterRoleBinding due to roleRef change", "name", name)
60+
if err := k8sClient.Delete(context.TODO(), existing); err != nil {
61+
return err
62+
}
63+
log.V(3).Info("Recreating clusterRoleBinding", "name", name)
64+
return k8sClient.Create(context.TODO(), desired)
5765
}
58-
return err
59-
}
6066

61-
func DeleteClusterRole(k8sClient client.Client, name string) error {
62-
object := runtime.NewClusterRole(name)
63-
log.V(3).Info("Deleting", "object", object)
64-
return k8sClient.Delete(context.TODO(), object)
67+
existing.Subjects = desired.Subjects
68+
log.V(3).Info("Updating clusterRoleBinding", "name", name)
69+
return k8sClient.Update(context.TODO(), existing)
6570
}
6671

6772
func DeleteClusterRoleBinding(k8sClient client.Client, name string) error {

internal/reconcile/service_account.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func ServiceAccount(k8Client client.Client, desired *v1.ServiceAccount) (*v1.Ser
1616
op, err := controllerutil.CreateOrUpdate(context.TODO(), k8Client, sa, func() error {
1717
sa.Annotations = desired.Annotations
1818
sa.OwnerReferences = desired.OwnerReferences
19+
sa.Finalizers = desired.Finalizers
1920
return nil
2021
})
2122

test/e2e/logfilesmetricexporter/lfme_test.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import (
44
"context"
55
_ "embed"
66
"fmt"
7+
"path/filepath"
8+
"regexp"
9+
"strings"
10+
"time"
11+
712
log "github.com/ViaQ/logerr/v2/log/static"
813
. "github.com/onsi/ginkgo/v2"
914
. "github.com/onsi/gomega"
@@ -13,9 +18,6 @@ import (
1318
framework "github.com/openshift/cluster-logging-operator/test/framework/e2e"
1419
"github.com/openshift/cluster-logging-operator/test/helpers/oc"
1520
"k8s.io/apimachinery/pkg/util/wait"
16-
"regexp"
17-
"strings"
18-
"time"
1921
)
2022

2123
//go:embed valid.yaml
@@ -47,24 +49,54 @@ var _ = Describe("[e2e][logfilemetricexporter] LogFileMetricsExporter", func() {
4749
Expect(err.Error()).To(MatchRegexp("is invalid.*supported values.*instance"), "exp. the CR to be rejected because it is not THE singleton")
4850
})
4951

50-
It("should be deployed by the operator and producing metrics", func() {
52+
It("should serve metrics to authorized clients providing a valid bearer token", func() {
5153
e2e.AddCleanup(func() error {
5254
return oc.Literal().From("oc -n openshift-logging delete --ignore-not-found logfilemetricexporter instance").Output()
5355
})
56+
metricsReaderRoleName := fmt.Sprintf("%s-metrics-reader", constants.ClusterLoggingOperator)
57+
metricsReaderBindingName := fmt.Sprintf("%s-metrics-reader", constants.LogfilesmetricexporterName)
58+
metricsAuthRoleName := fmt.Sprintf("%s-metrics-auth", constants.LogfilesmetricexporterName)
59+
e2e.AddCleanup(func() error {
60+
return oc.Literal().From("oc delete --ignore-not-found clusterrole %s", metricsReaderRoleName).Output()
61+
})
62+
e2e.AddCleanup(func() error {
63+
return oc.Literal().From("oc delete --ignore-not-found clusterrolebinding %s", metricsReaderBindingName).Output()
64+
})
65+
// Delete the metrics auth ClusterRoleBinding
66+
// The LFME reconciles the ClusterRoleBinding and ClusterRole for metrics auth
67+
e2e.AddCleanup(func() error {
68+
return oc.Literal().From("oc delete --ignore-not-found clusterrolebinding %s", metricsAuthRoleName).Output()
69+
})
70+
5471
err = createLFME(validCR)
5572
Expect(err).ToNot(HaveOccurred())
5673
Expect(e2e.WaitForDaemonSet(constants.OpenshiftNS, constants.LogfilesmetricexporterName)).To(Succeed())
5774

58-
args := []string{"-k", "-s", fmt.Sprintf("https://%s.%s.svc:2112/metrics", constants.LogfilesmetricexporterName, constants.OpenshiftNS)}
75+
By("creating the metrics-reader ClusterRole")
76+
roleFilePath, err := filepath.Abs(filepath.Join("..", "..", "..", "config", "rbac", "metrics_reader_role.yaml"))
77+
Expect(err).ToNot(HaveOccurred(), "Failed to construct role file path")
78+
_, err = oc.Literal().From("oc apply -f %s", roleFilePath).Run()
79+
Expect(err).ToNot(HaveOccurred(), "Failed to create metrics-reader ClusterRole")
80+
81+
By("creating a ClusterRoleBinding for the service account to allow access to metrics")
82+
_, err = oc.Literal().From("oc create clusterrolebinding %s --clusterrole=%s --serviceaccount=%s:%s",
83+
metricsReaderBindingName, metricsReaderRoleName, constants.OpenshiftNS, constants.LogfilesmetricexporterName).Run()
84+
Expect(err).ToNot(HaveOccurred(), "Failed to create ClusterRoleBinding")
85+
86+
metricsURL := fmt.Sprintf("https://%s.%s.svc:2112/metrics", constants.LogfilesmetricexporterName, constants.OpenshiftNS)
87+
curlCmd := fmt.Sprintf(`curl -s -k -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" %s`, metricsURL)
88+
5989
err = wait.PollUntilContextTimeout(context.TODO(), time.Second, time.Second*30, true, func(context.Context) (done bool, err error) {
60-
out, err := oc.Exec().WithNamespace(constants.OpenshiftNS).Pod(fmt.Sprintf("ds/%s", constants.LogfilesmetricexporterName)).WithCmd("curl", args...).Run()
90+
out, err := oc.Exec().WithNamespace(constants.OpenshiftNS).
91+
Pod(fmt.Sprintf("ds/%s", constants.LogfilesmetricexporterName)).
92+
WithCmd("sh", "-c", curlCmd).Run()
6193
Expect(err).ToNot(HaveOccurred(), out)
62-
log.V(5).Info("Polling metrics", "result", out)
94+
log.V(5).Info("Polling secure metrics", "result", out)
6395
if !strings.Contains(out, "log_logged_bytes_total") {
6496
return false, nil
6597
}
6698
return regexp.MatchString(`log_logged_bytes_total{.*} [1-9][0-9]*`, out)
6799
})
68-
Expect(err).ToNot(HaveOccurred(), "Exp. to find log_logged_bytes_total being calculated")
100+
Expect(err).ToNot(HaveOccurred(), "Exp. to scrape metrics with a bearer token")
69101
})
70102
})

0 commit comments

Comments
 (0)