From fad8c397d5b35a4d095f07ac88a94782f752ca65 Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Sat, 3 Oct 2026 18:24:24 +0300 Subject: [PATCH 1/2] fix(security): harden collector with container_logwriter_t and non-root execution (LOG-9754) Replace super-privileged spc_t SELinux domain and root execution on the Vector collector container with container_logwriter_t and UID 1000, addressing CWE-668 (exposure of resource to wrong sphere). What changed: - Collector execution: Converted to run as non-root (RunAsUser: 1000, RunAsGroup: 0, RunAsNonRoot: true). Access to host logs (/var/log/pods) is granted via GID 0 group read permissions. - SELinux confinement: Replaced spc_t with MCS-constrained container_logwriter_t domain on the collector container. - Pod Security Context: Added FSGroup: 0 to PodSecurityContext so K8s-mounted volumes (Secrets, ConfigMaps, ServiceAccount tokens) are readable by GID 0. - Capabilities: All dangerous capabilities dropped (CHOWN, DAC_OVERRIDE, FOWNER, etc.). - Hardening defaults: Enforced ReadOnlyRootFilesystem: true, AllowPrivilegeEscalation: false, and seccomp RuntimeDefault across all containers. - Host volume handling: Configured hostPath volume type to DirectoryOrCreate. - Init container (data-dir-init): Added an unprivileged (Privileged: false) init container running as UID 0 with spc_t to create and relabel the state directory (chmod 2770, chcon container_file_t) prior to Vector startup. Injection is safely appended (preventing overrides) and restricted to DaemonSets. - SCC compliance: Kept logging-scc strict (AllowPrivilegedContainer: false, AllowPrivilegeEscalation: false). Configured SELinuxContext.Type: RunAsAny to support both container_logwriter_t and spc_t. - Entrypoint script (run-vector.sh): Converted script to POSIX sh. Replaced dynamic script generation (fmt.Sprintf) with a static script that reads the VECTOR_DATA_DIR environment variable to manage directory creation. - Tests: Updated unit tests to enforce UID 1000/GID 0 non-root assertions and container_logwriter_t verification. Updated the functional test framework to mock the data directory using an isolated EmptyDir volume instead of a HostPath. JIRA: LOG-9754 Signed-off-by: Vitalii Parfonov --- Dockerfile.macos-dev | 47 ++++++++----------- internal/auth/securitycontextconstraint.go | 13 ++--- internal/collector/collector.go | 23 ++++++++- internal/collector/collector_test.go | 44 ++++++++++------- internal/collector/common/constants.go | 12 +++++ internal/collector/config.go | 3 +- internal/collector/vector/run-vector.sh | 13 ++--- internal/collector/vector/visitors.go | 44 +++++++++++++++-- .../logfilemetricexporter/daemonset_test.go | 3 +- .../metrics/logfilemetricexporter/factory.go | 11 ++--- test/framework/e2e/http_vector.go | 4 +- test/framework/functional/framework.go | 20 ++++++++ test/framework/functional/vector/deploy.go | 3 +- 13 files changed, 166 insertions(+), 74 deletions(-) diff --git a/Dockerfile.macos-dev b/Dockerfile.macos-dev index ff2909e533..a7a1c11a0d 100644 --- a/Dockerfile.macos-dev +++ b/Dockerfile.macos-dev @@ -3,44 +3,37 @@ # The original Dockerfile compiles Go inside the container, which requires # QEMU amd64 emulation on ARM Macs. QEMU crashes with segfaults during # `go mod download` (network syscall emulation issue). This Dockerfile skips -# the in-container Go build and instead expects a pre-built binary at -# bin/cluster-logging-operator, cross-compiled natively on the host. +# the in-container Go build and instead expects pre-built binaries at +# bin/cluster-logging-operator and bin/must-gather, cross-compiled natively +# on the host. # # Usage: # GOOS=linux GOARCH=amd64 make build # podman build -t $(IMAGE_TAG) . -f Dockerfile.macos-dev -FROM --platform=linux/amd64 quay.io/openshift/origin-cli-artifacts:latest AS origincli +FROM registry.access.redhat.com/ubi9/ubi:latest AS packages -RUN case $(uname -m) in \ - x86_64) cp /usr/share/openshift/linux_amd64/oc.rhel9 /tmp/oc ;; \ - aarch64) cp /usr/share/openshift/linux_arm64/oc.rhel9 /tmp/oc ;; \ - ppc64le) cp /usr/share/openshift/linux_ppc64le/oc.rhel9 /tmp/oc ;; \ - s390x) cp /usr/share/openshift/linux_s390x/oc /tmp/oc ;; \ - *) echo "Unsupported architecture"; exit 1 ;; \ -esac - -FROM --platform=linux/amd64 registry.access.redhat.com/ubi9/ubi-minimal - -ENV SRC_DIR=./ - -RUN INSTALL_PKGS=" \ - openssl \ +RUN mkdir -p /mnt/rootfs && \ + dnf install -y --installroot=/mnt/rootfs \ + --releasever=9 \ + --setopt=install_weak_deps=false \ + --nodocs \ + openssl-libs \ + ca-certificates \ rsync \ file \ - xz \ - " && \ - microdnf install -y $INSTALL_PKGS && \ - rpm -V $INSTALL_PKGS && \ - microdnf clean all && \ - mkdir /tmp/ocp-clo && \ - chmod og+w /tmp/ocp-clo + xz && \ + dnf --installroot=/mnt/rootfs clean all && \ + mkdir -p /mnt/rootfs/tmp/ocp-clo && \ + chmod og+w /mnt/rootfs/tmp/ocp-clo -COPY bin/cluster-logging-operator /usr/bin/ +FROM --platform=linux/amd64 registry.access.redhat.com/ubi9/ubi-micro -COPY --from=origincli /tmp/oc /usr/bin/oc +COPY --from=packages /mnt/rootfs/ / +COPY bin/cluster-logging-operator /usr/bin/ +COPY bin/must-gather /usr/bin/ -COPY $SRC_DIR/must-gather/collection-scripts/* /usr/bin/ +RUN ln -s /usr/bin/must-gather /usr/bin/gather USER 1000 WORKDIR /usr/bin diff --git a/internal/auth/securitycontextconstraint.go b/internal/auth/securitycontextconstraint.go index b0f5ee3d48..9642d941d6 100644 --- a/internal/auth/securitycontextconstraint.go +++ b/internal/auth/securitycontextconstraint.go @@ -9,7 +9,7 @@ import ( "github.com/openshift/cluster-logging-operator/internal/utils" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" - client "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client" ) const sccName = "logging-scc" @@ -27,13 +27,12 @@ var ( "KILL", } - DesiredSCCVolumes = []security.FSType{"configMap", "secret", "emptyDir", "projected"} + DesiredSCCVolumes = []security.FSType{"configMap", "secret", "emptyDir", "projected", "hostPath"} ) func NewSCC() *security.SecurityContextConstraints { - scc := runtime.NewSCC(sccName) - scc.AllowPrivilegedContainer = false + scc.AllowPrivilegedContainer = false // Forbids privileged containers scc.RequiredDropCapabilities = RequiredDropCapabilities scc.AllowHostDirVolumePlugin = true scc.Volumes = DesiredSCCVolumes @@ -43,11 +42,13 @@ func NewSCC() *security.SecurityContextConstraints { Type: security.RunAsUserStrategyRunAsAny, } scc.SELinuxContext = security.SELinuxContextStrategyOptions{ - Type: security.SELinuxStrategyRunAsAny, + Type: security.SELinuxStrategyRunAsAny, // Permits spc_t on init container } scc.ReadOnlyRootFilesystem = true scc.ForbiddenSysctls = []string{"*"} - scc.SeccompProfiles = []string{"runtime/default"} + scc.SeccompProfiles = []string{ + "runtime/default", + } return scc } diff --git a/internal/collector/collector.go b/internal/collector/collector.go index 519de5296c..68a701490a 100644 --- a/internal/collector/collector.go +++ b/internal/collector/collector.go @@ -51,6 +51,15 @@ const ( sourceKubeAPIServerPath = "/var/log/kube-apiserver" tmpVolumeName = "tmp" tmpPath = "/tmp" + + // collectorRunAsUser is set to 1000 to enforce RunAsNonRoot. + // Read access to host logs (/var/log/pods) is achieved via collectorRunAsGroup (GID 0), + // which satisfies the group-read permissions on the host filesystem. + // The security improvement comes from replacing spc_t with the MCS-constrained + // container_logwriter_t SELinux domain. + collectorRunAsUser = common.NonRootUser + // collectorRunAsGroup is the primary GID. + collectorRunAsGroup int64 = 0 ) type Visitor func(collector *v1.Container, podSpec *v1.PodSpec, resNames *factory.ForwarderResourceNames, namespace, logLevel string) @@ -153,6 +162,9 @@ func (f *Factory) NewPodSpec(trustedCABundle *v1.ConfigMap, spec obs.ClusterLogF } podSpec := &v1.PodSpec{ + SecurityContext: &v1.PodSecurityContext{ + FSGroup: utils.GetPtr(int64(0)), + }, NodeSelector: utils.EnsureLinuxNodeSelector(f.NodeSelector()), PriorityClassName: clusterLoggingPriorityClassName, ServiceAccountName: f.ResourceNames.ServiceAccount, @@ -214,6 +226,12 @@ func (f *Factory) NewPodSpec(trustedCABundle *v1.ConfigMap, spec obs.ClusterLogF f.Visit(collector, podSpec, f.ResourceNames, namespace, LogLevel(f.annotations)) + // Add init container for daemonsets to prepare the data directory with proper SELinux labeling + if f.isDaemonset { + dataPath := vector.GetDataPath(namespace, f.ResourceNames.ForwarderName) + podSpec.InitContainers = append(podSpec.InitContainers, vector.NewDataDirInitContainer(dataPath)) + } + podSpec.Containers = []v1.Container{ *collector, } @@ -373,8 +391,11 @@ func AddSecurityContextTo(container *v1.Container) *v1.Container { Drop: auth.RequiredDropCapabilities, }, SELinuxOptions: &v1.SELinuxOptions{ - Type: "spc_t", + Type: common.SelinuxTypeLogWriter, // container_logwriter_t enforces SELinux isolation }, + RunAsUser: utils.GetPtr(collectorRunAsUser), // 1000 + RunAsGroup: utils.GetPtr(collectorRunAsGroup), // 0 + RunAsNonRoot: utils.GetPtr(true), ReadOnlyRootFilesystem: utils.GetPtr(true), AllowPrivilegeEscalation: utils.GetPtr(false), SeccompProfile: &v1.SeccompProfile{ diff --git a/internal/collector/collector_test.go b/internal/collector/collector_test.go index 19a35f9ecc..b794b8580e 100644 --- a/internal/collector/collector_test.go +++ b/internal/collector/collector_test.go @@ -156,14 +156,17 @@ var _ = Describe("Factory#Daemonset", func() { FieldRef: &v1.ObjectFieldSelector{ APIVersion: "v1", FieldPath: "status.podIP"}}})) }) - It("should set a security context", func() { + It("should set a strict security context with container_logwriter_t and enforce non-root", func() { Expect(collector.SecurityContext).To(Equal(&v1.SecurityContext{ Capabilities: &v1.Capabilities{ Drop: auth.RequiredDropCapabilities, }, SELinuxOptions: &v1.SELinuxOptions{ - Type: "spc_t", + Type: common.SelinuxTypeLogWriter, }, + RunAsUser: utils.GetPtr[int64](common.NonRootUser), + RunAsGroup: utils.GetPtr[int64](0), + RunAsNonRoot: utils.GetPtr(true), ReadOnlyRootFilesystem: utils.GetPtr(true), AllowPrivilegeEscalation: utils.GetPtr(false), SeccompProfile: &v1.SeccompProfile{ @@ -172,6 +175,25 @@ var _ = Describe("Factory#Daemonset", func() { })) }) + It("should not use spc_t SELinux type (regression guard)", func() { + Expect(collector.SecurityContext.SELinuxOptions.Type).ToNot(Equal(common.SelinuxTypeSpc)) + }) + + It("should include init container to prepare data directory", func() { + Expect(podSpec.InitContainers).To(HaveLen(1)) + initContainer := podSpec.InitContainers[0] + Expect(initContainer.Name).To(Equal("data-dir-init")) + Expect(initContainer.SecurityContext.SELinuxOptions.Type).To(Equal(common.SelinuxTypeSpc)) + Expect(*initContainer.SecurityContext.RunAsUser).To(Equal(int64(0))) + Expect(initContainer.SecurityContext.Privileged).To(BeNil()) + Expect(initContainer.Command).To(Equal([]string{"/bin/sh", "-c"})) + Expect(initContainer.Args).To(HaveLen(3)) + Expect(initContainer.Args[0]).To(ContainSubstring("mkdir -p")) + Expect(initContainer.Args[0]).To(ContainSubstring("chmod -R 2770")) + Expect(initContainer.Args[0]).To(ContainSubstring("chcon -R -t container_file_t")) + Expect(initContainer.Args[1]).To(Equal("--")) + }) + It("should set VECTOR_LOG env variable with debug value", func() { logLevelDebug := "debug" factory.annotations = map[string]string{ @@ -597,20 +619,10 @@ var _ = Describe("Factory#Deployment", func() { FieldRef: &v1.ObjectFieldSelector{ APIVersion: "v1", FieldPath: "status.podIP"}}})) }) - It("should not set security context", func() { - Expect(collector.SecurityContext).ToNot(Equal(&v1.SecurityContext{ - Capabilities: &v1.Capabilities{ - Drop: auth.RequiredDropCapabilities, - }, - SELinuxOptions: &v1.SELinuxOptions{ - Type: "spc_t", - }, - ReadOnlyRootFilesystem: utils.GetPtr(true), - AllowPrivilegeEscalation: utils.GetPtr(false), - SeccompProfile: &v1.SeccompProfile{ - Type: v1.SeccompProfileTypeRuntimeDefault, - }, - })) + It("should not set explicit security context (uses pod/namespace defaults)", func() { + // Deployments don't call AddSecurityContextTo, so SecurityContext is nil + // They rely on pod and namespace-level security policies + Expect(collector.SecurityContext).To(BeNil()) }) }) diff --git a/internal/collector/common/constants.go b/internal/collector/common/constants.go index b2e2377529..55ce35ef1f 100644 --- a/internal/collector/common/constants.go +++ b/internal/collector/common/constants.go @@ -11,4 +11,16 @@ const ( // TrustedCABundleName is the name of the configmap to inject the trusted CA bundle TrustedCABundleName = "trusted-ca-bundle" + // SelinuxTypeLogWriter (container_logwriter_t) is an MCS-constrained container domain that + // grants read on /var/log/** plus inotify watch permissions on container_log_t, and write + // on /var/lib/vector/**. Far more restrictive than spc_t (super-privileged container). + SelinuxTypeLogWriter = "container_logwriter_t" + // SelinuxTypeSpc (spc_t) is the Super-Privileged Container SELinux domain. + // It is used exclusively by short-lived init containers (e.g., data-dir-init) to + // create host directories and set SELinux labels (chcon) before the main collector + // launches under container_logwriter_t. + SelinuxTypeSpc = "spc_t" + + // NonRootUser is the fixed non-root UID + NonRootUser int64 = 1000 ) diff --git a/internal/collector/config.go b/internal/collector/config.go index b3d90bf8fa..e8db95fc61 100644 --- a/internal/collector/config.go +++ b/internal/collector/config.go @@ -1,7 +1,6 @@ package collector import ( - "fmt" log "github.com/ViaQ/logerr/v2/log/static" "github.com/openshift/cluster-logging-operator/internal/collector/vector" "github.com/openshift/cluster-logging-operator/internal/reconcile" @@ -20,7 +19,7 @@ func (f *Factory) ReconcileCollectorConfig(k8sClient client.Client, reader clien f.ResourceNames.ConfigMap, map[string]string{ vector.ConfigFile: collectorConfig, - vector.RunVectorFile: fmt.Sprintf(vector.RunVectorScript, vector.GetDataPath(namespace, f.ResourceNames.ForwarderName)), + vector.RunVectorFile: vector.RunVectorScript, }, f.CommonLabelInitializer) diff --git a/internal/collector/vector/run-vector.sh b/internal/collector/vector/run-vector.sh index 15e862e071..d33d1a6e1b 100644 --- a/internal/collector/vector/run-vector.sh +++ b/internal/collector/vector/run-vector.sh @@ -1,11 +1,12 @@ -#!/bin/bash +#!/bin/sh -set -uo pipefail +set -u -# The directory used for persisting Vector state, such as on-disk buffers, file checkpoints, and more. -VECTOR_DATA_DIR=%s -echo "Creating the directory used for persisting Vector state $VECTOR_DATA_DIR" -mkdir -p ${VECTOR_DATA_DIR} +# Ensure data directory exists (init container handles this in production, but needed for tests) +if [ -n "${VECTOR_DATA_DIR:-}" ]; then + # Try to create the directory; ignore errors if filesystem is read-only or inaccessible + mkdir -p "$VECTOR_DATA_DIR" 2>/dev/null || true +fi echo "Starting Vector process..." exec /usr/bin/vector --config-toml /etc/vector/vector.toml diff --git a/internal/collector/vector/visitors.go b/internal/collector/vector/visitors.go index 61cf052ebe..71d7927cef 100644 --- a/internal/collector/vector/visitors.go +++ b/internal/collector/vector/visitors.go @@ -2,15 +2,21 @@ package vector import ( "github.com/openshift/cluster-logging-operator/internal/collector/common" + "github.com/openshift/cluster-logging-operator/internal/constants" "github.com/openshift/cluster-logging-operator/internal/factory" "github.com/openshift/cluster-logging-operator/internal/runtime" "github.com/openshift/cluster-logging-operator/internal/utils" corev1 "k8s.io/api/core/v1" ) +const initContainerName = "data-dir-init" + func CollectorVisitor(collectorContainer *corev1.Container, podSpec *corev1.PodSpec, resNames *factory.ForwarderResourceNames, namespace, logLevel string) { + dataPath := GetDataPath(namespace, resNames.ForwarderName) + collectorContainer.Env = append(collectorContainer.Env, corev1.EnvVar{Name: "VECTOR_LOG", Value: logLevel}, + corev1.EnvVar{Name: "VECTOR_DATA_DIR", Value: dataPath}, corev1.EnvVar{Name: "KUBERNETES_SERVICE_HOST", Value: "kubernetes.default.svc"}, corev1.EnvVar{ Name: "VECTOR_SELF_NODE_NAME", @@ -22,7 +28,6 @@ func CollectorVisitor(collectorContainer *corev1.Container, podSpec *corev1.PodS }, ) - dataPath := GetDataPath(namespace, resNames.ForwarderName) collectorContainer.VolumeMounts = append(collectorContainer.VolumeMounts, corev1.VolumeMount{Name: common.ConfigVolumeName, ReadOnly: true, MountPath: vectorConfigPath}, corev1.VolumeMount{Name: common.DataDir, ReadOnly: false, MountPath: dataPath}, @@ -32,14 +37,47 @@ func CollectorVisitor(collectorContainer *corev1.Container, podSpec *corev1.PodS collectorContainer.Command = []string{"sh"} collectorContainer.Args = []string{entrypointValue} + hostPathDirOrCreate := corev1.HostPathDirectoryOrCreate podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{Name: common.ConfigVolumeName, VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: resNames.ConfigMap}}}}, - corev1.Volume{Name: common.DataDir, VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: dataPath}}}, + corev1.Volume{Name: common.DataDir, VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: dataPath, Type: &hostPathDirOrCreate}}}, corev1.Volume{Name: common.EntrypointVolumeName, VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: resNames.ConfigMap}}}}, ) } -// PodLogExcludeLabel by default, the kubernetes_logs source will skip logs from the Pods that have a vector.dev/exclude: "true" label. +// PodLogExcludeLabel by default, the kubernetes_logs source will skip logs from the +// Pods that have a vector.dev/exclude: "true" label. func PodLogExcludeLabel(o runtime.Object) { utils.AddLabels(runtime.Meta(o), map[string]string{"vector.dev/exclude": "true"}) } + +// NewDataDirInitContainer creates an init container that prepares the collector's data directory. +// The init container runs as root with spc_t to create the directory and relabel it to +// container_file_t (which container_logwriter_t can write to). Group permissions (2770) grant +// Vector (UID 1000, GID 0) full read/write access. +func NewDataDirInitContainer(dataPath string) corev1.Container { + return corev1.Container{ + Name: initContainerName, + Image: utils.GetComponentImage(constants.VectorName), + Command: []string{"/bin/sh", "-c"}, + Args: []string{ + "set -e; " + + "mkdir -p \"$1\" && " + + "chmod -R 2770 \"$1\" && " + + "(chcon -R -t container_file_t \"$1\" || true)", + "--", + dataPath, + }, + VolumeMounts: []corev1.VolumeMount{ + {Name: common.DataDir, MountPath: dataPath}, + }, + SecurityContext: &corev1.SecurityContext{ + AllowPrivilegeEscalation: utils.GetPtr(false), + ReadOnlyRootFilesystem: utils.GetPtr(true), + SELinuxOptions: &corev1.SELinuxOptions{ + Type: common.SelinuxTypeSpc, + }, + RunAsUser: utils.GetPtr[int64](0), + }, + } +} diff --git a/internal/metrics/logfilemetricexporter/daemonset_test.go b/internal/metrics/logfilemetricexporter/daemonset_test.go index c8b082adf6..e3de889210 100644 --- a/internal/metrics/logfilemetricexporter/daemonset_test.go +++ b/internal/metrics/logfilemetricexporter/daemonset_test.go @@ -7,6 +7,7 @@ import ( . "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/collector/common" "github.com/openshift/cluster-logging-operator/internal/constants" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -95,7 +96,7 @@ var _ = Describe("Reconcile LogFileMetricExporter Daemonset", func() { sc := container.SecurityContext Expect(sc).ToNot(BeNil()) Expect(sc.SELinuxOptions).ToNot(BeNil()) - Expect(sc.SELinuxOptions.Type).To(Equal("container_logwriter_t")) + Expect(sc.SELinuxOptions.Type).To(Equal(common.SelinuxTypeLogWriter)) Expect(sc.RunAsUser).ToNot(BeNil()) Expect(*sc.RunAsUser).To(Equal(int64(1000))) Expect(sc.RunAsNonRoot).ToNot(BeNil()) diff --git a/internal/metrics/logfilemetricexporter/factory.go b/internal/metrics/logfilemetricexporter/factory.go index bde44a6748..ce4cdba3d6 100644 --- a/internal/metrics/logfilemetricexporter/factory.go +++ b/internal/metrics/logfilemetricexporter/factory.go @@ -3,6 +3,7 @@ package logfilemetricexporter import ( "strings" + "github.com/openshift/cluster-logging-operator/internal/collector/common" "github.com/openshift/cluster-logging-operator/internal/runtime" "github.com/openshift/cluster-logging-operator/internal/tls" @@ -32,13 +33,7 @@ const ( // 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" + lfmeRunAsUser = common.NonRootUser ) var ( @@ -157,7 +152,7 @@ func securityContext() *v1.SecurityContext { Drop: auth.RequiredDropCapabilities, }, SELinuxOptions: &v1.SELinuxOptions{ - Type: selinuxTypeLogWriter, + Type: common.SelinuxTypeLogWriter, }, RunAsUser: utils.GetPtr(lfmeRunAsUser), RunAsNonRoot: utils.GetPtr(true), diff --git a/test/framework/e2e/http_vector.go b/test/framework/e2e/http_vector.go index baf2772f5d..b3ff1e8cd5 100644 --- a/test/framework/e2e/http_vector.go +++ b/test/framework/e2e/http_vector.go @@ -131,7 +131,7 @@ func (tc *E2ETestFramework) DeployHttpReceiver(ns string) (deployment *VectorHtt opts := metav1.CreateOptions{} config := runtime.NewConfigMap(ns, container.Name, map[string]string{ vector.ConfigFile: vectorHttpConf, - vector.RunVectorFile: fmt.Sprintf(vector.RunVectorScript, path.Join("/tmp/vector", ns, container.Name)), + vector.RunVectorFile: vector.RunVectorScript, }) config, err = tc.KubeClient.CoreV1().ConfigMaps(ns).Create(context.TODO(), config, opts) if err != nil { @@ -502,7 +502,7 @@ func (tc *E2ETestFramework) DeployHttpReceiverWithTLS(ns string, profileSpec con vectorConf := vectorHttpConfWithTLSProfile(profileSpec) config := runtime.NewConfigMap(ns, container.Name, map[string]string{ vector.ConfigFile: vectorConf, - vector.RunVectorFile: fmt.Sprintf(vector.RunVectorScript, path.Join("/tmp/vector", ns, container.Name)), + vector.RunVectorFile: vector.RunVectorScript, }) log.V(2).Info("Creating configmap", "namespace", config.Namespace, "name", config.Name, "vector.toml", vectorConf) diff --git a/test/framework/functional/framework.go b/test/framework/functional/framework.go index b3cb736054..b0122b19ef 100644 --- a/test/framework/functional/framework.go +++ b/test/framework/functional/framework.go @@ -10,6 +10,7 @@ import ( "time" "github.com/openshift/cluster-logging-operator/internal/api/initialize" + "github.com/openshift/cluster-logging-operator/internal/collector/vector" obs "github.com/openshift/cluster-logging-operator/api/observability/v1" @@ -330,6 +331,25 @@ func (f *CollectorFunctionalFramework) DeployWithVisitors(visitors []runtime.Pod WithImagePullPolicy(corev1.PullIfNotPresent). End() + dataPath := vector.GetDataPath(f.Namespace, f.Forwarder.Name) + + f.Pod.Spec.Volumes = append(f.Pod.Spec.Volumes, corev1.Volume{ + Name: common.DataDir, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }) + + for i, c := range f.Pod.Spec.Containers { + if c.Name == constants.CollectorName { + f.Pod.Spec.Containers[i].VolumeMounts = append(f.Pod.Spec.Containers[i].VolumeMounts, corev1.VolumeMount{ + Name: common.DataDir, + MountPath: dataPath, + ReadOnly: false, + }) + } + } + for _, visit := range visitors { if visit != nil { if err = visit(b); err != nil { diff --git a/test/framework/functional/vector/deploy.go b/test/framework/functional/vector/deploy.go index 562572f990..baaaa7cd51 100644 --- a/test/framework/functional/vector/deploy.go +++ b/test/framework/functional/vector/deploy.go @@ -1,7 +1,6 @@ package vector import ( - "fmt" "github.com/openshift/cluster-logging-operator/internal/utils" "regexp" "strings" @@ -29,7 +28,7 @@ func (c *VectorCollector) DeployConfigMapForConfig(name, config, clfName, clfYam runtime.NewConfigMapBuilder(configmap). Add(vector.ConfigFile, config). Add("clfyaml", clfYaml). - Add("run.sh", fmt.Sprintf(vector.RunVectorScript, vector.GetDataPath(c.NS.Name, clfName))) + Add("run.sh", vector.RunVectorScript) if err := c.Create(configmap); err != nil { return err } From 6f412f87d268e92b20e9e2b80f5da48f293464b5 Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Wed, 28 Oct 2026 18:52:45 +0200 Subject: [PATCH 2/2] address PR review comments for init container implementation - use environment variable instead of positional argument - simplify script to single multi-line string - remove excessive test assertions Signed-off-by: Vitalii Parfonov --- internal/collector/collector_test.go | 8 +------- internal/collector/vector/visitors.go | 13 +++++++------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/internal/collector/collector_test.go b/internal/collector/collector_test.go index b794b8580e..39195fceb0 100644 --- a/internal/collector/collector_test.go +++ b/internal/collector/collector_test.go @@ -185,13 +185,7 @@ var _ = Describe("Factory#Daemonset", func() { Expect(initContainer.Name).To(Equal("data-dir-init")) Expect(initContainer.SecurityContext.SELinuxOptions.Type).To(Equal(common.SelinuxTypeSpc)) Expect(*initContainer.SecurityContext.RunAsUser).To(Equal(int64(0))) - Expect(initContainer.SecurityContext.Privileged).To(BeNil()) - Expect(initContainer.Command).To(Equal([]string{"/bin/sh", "-c"})) - Expect(initContainer.Args).To(HaveLen(3)) - Expect(initContainer.Args[0]).To(ContainSubstring("mkdir -p")) - Expect(initContainer.Args[0]).To(ContainSubstring("chmod -R 2770")) - Expect(initContainer.Args[0]).To(ContainSubstring("chcon -R -t container_file_t")) - Expect(initContainer.Args[1]).To(Equal("--")) + Expect(initContainer.Env).To(ContainElement(v1.EnvVar{Name: "VECTOR_DATA_DIR", Value: "/var/lib/vector"})) }) It("should set VECTOR_LOG env variable with debug value", func() { diff --git a/internal/collector/vector/visitors.go b/internal/collector/vector/visitors.go index 71d7927cef..0b516b9070 100644 --- a/internal/collector/vector/visitors.go +++ b/internal/collector/vector/visitors.go @@ -61,12 +61,13 @@ func NewDataDirInitContainer(dataPath string) corev1.Container { Image: utils.GetComponentImage(constants.VectorName), Command: []string{"/bin/sh", "-c"}, Args: []string{ - "set -e; " + - "mkdir -p \"$1\" && " + - "chmod -R 2770 \"$1\" && " + - "(chcon -R -t container_file_t \"$1\" || true)", - "--", - dataPath, + `set -e + mkdir -p "$VECTOR_DATA_DIR" + chmod -R 2770 "$VECTOR_DATA_DIR" + chcon -R -t container_file_t "$VECTOR_DATA_DIR" || true`, + }, + Env: []corev1.EnvVar{ + {Name: "VECTOR_DATA_DIR", Value: dataPath}, }, VolumeMounts: []corev1.VolumeMount{ {Name: common.DataDir, MountPath: dataPath},