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
47 changes: 20 additions & 27 deletions Dockerfile.macos-dev
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 7 additions & 6 deletions internal/auth/securitycontextconstraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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
}

Expand Down
23 changes: 22 additions & 1 deletion internal/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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{
Expand Down
44 changes: 28 additions & 16 deletions internal/collector/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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{
Expand Down Expand Up @@ -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())
})
})

Expand Down
12 changes: 12 additions & 0 deletions internal/collector/common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
3 changes: 1 addition & 2 deletions internal/collector/config.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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)

Expand Down
13 changes: 7 additions & 6 deletions internal/collector/vector/run-vector.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#!/bin/bash
#!/bin/sh
Comment thread
vparfonov marked this conversation as resolved.

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
44 changes: 41 additions & 3 deletions internal/collector/vector/visitors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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},
Expand All @@ -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.
Comment thread
vparfonov marked this conversation as resolved.
// 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),
Comment thread
vparfonov marked this conversation as resolved.
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,
Comment thread
vparfonov marked this conversation as resolved.
},
VolumeMounts: []corev1.VolumeMount{
{Name: common.DataDir, MountPath: dataPath},
},
SecurityContext: &corev1.SecurityContext{
AllowPrivilegeEscalation: utils.GetPtr(false),
ReadOnlyRootFilesystem: utils.GetPtr(true),
Comment thread
jcantrill marked this conversation as resolved.
SELinuxOptions: &corev1.SELinuxOptions{
Type: common.SelinuxTypeSpc,
},
RunAsUser: utils.GetPtr[int64](0),
},
}
}
3 changes: 2 additions & 1 deletion internal/metrics/logfilemetricexporter/daemonset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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())
Expand Down
Loading
Loading