Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions internal/metrics/logfilemetricexporter/daemonset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
loggingv1alpha1 "github.com/openshift/cluster-logging-operator/api/logging/v1alpha1"
"github.com/openshift/cluster-logging-operator/internal/auth"
"github.com/openshift/cluster-logging-operator/internal/constants"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -63,6 +64,54 @@ var _ = Describe("Reconcile LogFileMetricExporter Daemonset", func() {
Expect(dsInstance.Spec.Template.Spec.Containers[0].Resources.Requests).To(BeNil())
})

It("should run the exporter container with a minimal, non-root security context", func() {

// Reconcile the exporter daemonset
Expect(ReconcileDaemonset(*lfmeInstance,
reqClient,
constants.OpenshiftNS,
constants.LogfilesmetricexporterName, dsOwner)).To(Succeed())

Expect(reqClient.Get(context.TODO(), dsKey, dsInstance)).Should(Succeed())
Expect(dsInstance.Spec.Template.Spec.Containers).To(HaveLen(1))

container := dsInstance.Spec.Template.Spec.Containers[0]

// The exporter binary is invoked directly (not via a shell) with each flag as a separate arg.
Expect(container.Command).To(Equal([]string{"/usr/local/bin/log-file-metric-exporter"}))
Expect(container.Args).To(HaveLen(9))
Expect(container.Args).To(ContainElements(
"-verbosity=2",
"-dir=/var/log/pods",
"-http=:2112",
"-keyFile=/etc/logfilemetricexporter/metrics/tls.key",
"-crtFile=/etc/logfilemetricexporter/metrics/tls.crt",
"-secureMetrics",
))
Expect(container.Args).To(ContainElement(HavePrefix("-tlsMinVersion=")))
Expect(container.Args).To(ContainElement(HavePrefix("-cipherSuites=")))
Expect(container.Args).To(ContainElement(HavePrefix("-groups=")))

sc := container.SecurityContext
Expect(sc).ToNot(BeNil())
Expect(sc.SELinuxOptions).ToNot(BeNil())
Expect(sc.SELinuxOptions.Type).To(Equal("container_logwriter_t"))
Expect(sc.RunAsUser).ToNot(BeNil())
Expect(*sc.RunAsUser).To(Equal(int64(1000)))
Expect(sc.RunAsNonRoot).ToNot(BeNil())
Expect(*sc.RunAsNonRoot).To(BeTrue())
Expect(sc.ReadOnlyRootFilesystem).ToNot(BeNil())
Expect(*sc.ReadOnlyRootFilesystem).To(BeTrue())
Expect(sc.AllowPrivilegeEscalation).ToNot(BeNil())
Expect(*sc.AllowPrivilegeEscalation).To(BeFalse())
Expect(sc.Capabilities).ToNot(BeNil())
Expect(sc.Capabilities.Drop).To(Equal(auth.RequiredDropCapabilities))
Expect(sc.SeccompProfile).ToNot(BeNil())
Expect(sc.SeccompProfile.Type).To(Equal(corev1.SeccompProfileTypeRuntimeDefault))
// The exporter only stats world-traversable log dirs, so no elevated group access is needed.
Expect(dsInstance.Spec.Template.Spec.SecurityContext).To(BeNil())
})

It("should reconcile successfully a daemonset with specified resources.requests", func() {
lfmeInstance.Spec = loggingv1alpha1.LogFileMetricExporterSpec{
Resources: &corev1.ResourceRequirements{
Expand Down
55 changes: 48 additions & 7 deletions internal/metrics/logfilemetricexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

configv1 "github.com/openshift/api/config/v1"
loggingv1a1 "github.com/openshift/cluster-logging-operator/api/logging/v1alpha1"
"github.com/openshift/cluster-logging-operator/internal/collector"
"github.com/openshift/cluster-logging-operator/internal/auth"
"github.com/openshift/cluster-logging-operator/internal/constants"
coreFactory "github.com/openshift/cluster-logging-operator/internal/factory"
"github.com/openshift/cluster-logging-operator/internal/utils"
Expand All @@ -28,6 +28,17 @@ const (
logPods = "varlogpods"
logPodsValue = "/var/log/pods"
metricsVolumePath = "/etc/logfilemetricexporter/metrics"

// lfmeRunAsUser is the fixed non-root UID the exporter runs as. The exporter reads the
// hostPath log directories via group 0 (the default GID granted by OpenShift), which
// satisfies the 0750 root:root permissions on /var/log/pods without joining extra groups.
lfmeRunAsUser int64 = 1000
// selinuxTypeLogWriter (container_logwriter_t) is an MCS-constrained container domain that
// grants read plus the inotify "watch"/"watch_reads" permissions on container_log_t, which
// the exporter requires to watch /var/log/pods. It is far more restrictive than the
// super-privileged spc_t; the otherwise-preferable container_logreader_t domain is not
// usable because it denies the inotify "watch" permission.
selinuxTypeLogWriter = "container_logwriter_t"
)

var (
Expand Down Expand Up @@ -112,18 +123,48 @@ func newLogMetricsExporterContainer(exporter loggingv1a1.LogFileMetricExporter,
Protocol: v1.ProtocolTCP,
},
}
exporterContainer.Command = []string{"/bin/bash"}
exporterContainer.Args = []string{"-c",
"/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=" +
tls.MinTLSVersion(tlsProfileSpec) + " -cipherSuites=" + strings.Join(tls.TLSCiphers(tlsProfileSpec), ",") +
" -groups=" + strings.Join(tls.TLSGroups(tlsProfileSpec), ",")}
exporterContainer.Command = []string{"/usr/local/bin/log-file-metric-exporter"}
exporterContainer.Args = []string{
"-verbosity=2",
"-dir=/var/log/pods",
"-http=:2112",
"-keyFile=/etc/logfilemetricexporter/metrics/tls.key",
"-crtFile=/etc/logfilemetricexporter/metrics/tls.crt",
"-secureMetrics",
"-tlsMinVersion=" + tls.MinTLSVersion(tlsProfileSpec),
"-cipherSuites=" + strings.Join(tls.TLSCiphers(tlsProfileSpec), ","),
"-groups=" + strings.Join(tls.TLSGroups(tlsProfileSpec), ","),
}

exporterContainer.VolumeMounts = []v1.VolumeMount{
{Name: logContainers, ReadOnly: true, MountPath: logContainersValue},
{Name: logPods, ReadOnly: true, MountPath: logPodsValue},
{Name: exporterMetricsVolumeName, ReadOnly: true, MountPath: metricsVolumePath},
}

collector.AddSecurityContextTo(exporterContainer)
exporterContainer.SecurityContext = securityContext()
return exporterContainer
}

// securityContext returns the minimal security context required by the log-file-metric-exporter.
// The exporter runs as a fixed non-root UID with all capabilities dropped, a read-only root
// filesystem, no privilege escalation and the default seccomp profile. It runs under the
// MCS-constrained container_logwriter_t SELinux domain, which grants read plus the inotify
// watch the exporter needs on the host log tree while remaining far more restrictive than spc_t.
func securityContext() *v1.SecurityContext {
return &v1.SecurityContext{
Capabilities: &v1.Capabilities{
Drop: auth.RequiredDropCapabilities,
},
SELinuxOptions: &v1.SELinuxOptions{
Type: selinuxTypeLogWriter,
},
RunAsUser: utils.GetPtr(lfmeRunAsUser),
RunAsNonRoot: utils.GetPtr(true),
ReadOnlyRootFilesystem: utils.GetPtr(true),
AllowPrivilegeEscalation: utils.GetPtr(false),
SeccompProfile: &v1.SeccompProfile{
Type: v1.SeccompProfileTypeRuntimeDefault,
},
}
}
Loading