Skip to content
Merged
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
7 changes: 5 additions & 2 deletions observer-operator/pkg/adjuster/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import "time"

const (
// Component names
DiagnosticFluentdName = "diagnostics-fluentd"
VSystemVrepStsName = "vsystem-vrep"
DiagnosticFluentdName = "diagnostics-fluentd"
DiagnosticFluentdInitName = "fix-permissions"
DiagnosticFluentdPosFile = "/var/tmp/diagnostics-fluentd-sdi.log.pos"
DiagnosticFluentdVarTmpVolume = "vartmp"
VSystemVrepStsName = "vsystem-vrep"

// Volume and mount names
VolumeName = "exports-mask"
Expand Down
6 changes: 5 additions & 1 deletion observer-operator/pkg/adjuster/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,15 @@ func (a *Adjuster) updateExistingRouteCA(ctx context.Context, ns, name string, r
}

if route.Spec.TLS.DestinationCACertificate == caBundle {
a.logger.Info(fmt.Sprintf("Route %s destination CA certificate is unchanged", name))
a.logger.V(1).Info("Route: CA certificate unchanged", "name", name)
return nil
}

a.logger.Info("Route: updating CA certificate", "name", name)
route.Spec.TLS.DestinationCACertificate = caBundle
if err := a.Client.Update(ctx, route); err != nil {
return fmt.Errorf("failed to update route %s CA certificate: %w", name, err)
}
return nil
}

Expand Down
245 changes: 218 additions & 27 deletions observer-operator/pkg/adjuster/sdiconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand Down Expand Up @@ -87,57 +88,247 @@ func (a *Adjuster) AdjustSDIDiagnosticsFluentdDaemonsetContainerPrivilege(ns str
return err
}

// Check if vartmp volume uses hostPath - only apply patches in that case
// SDI 3.3.290+ uses emptyDir which doesn't have permission issues
if !a.isVartmpHostPath(&ds.Spec.Template.Spec) {
// emptyDir detected - clean up any previous patches we may have applied
cleaned := a.cleanupFluentdPatches(&ds.Spec.Template.Spec)
if cleaned {
a.logger.Info("DaemonSet: removing patches (emptyDir detected)", "name", DiagnosticFluentdName)
if err := a.Client.Update(ctx, ds); err != nil {
return err
}
} else {
a.logger.V(1).Info("DaemonSet: no patches needed (emptyDir)", "name", DiagnosticFluentdName)
}
return nil
}

// hostPath detected - apply security patches for permission fix
podUpdated := a.patchPodSecurityContext(&ds.Spec.Template.Spec)
containerUpdated := a.patchContainerSecurityContext(&ds.Spec.Template.Spec, DiagnosticFluentdName)
initContainerUpdated := a.ensureFluentdInitContainer(&ds.Spec.Template.Spec)
updated := podUpdated || containerUpdated || initContainerUpdated

if updated {
a.logger.Info("DaemonSet: patching security context (hostPath)", "name", DiagnosticFluentdName)
if err := a.Client.Update(ctx, ds); err != nil {
return err
}
} else {
a.logger.V(1).Info("DaemonSet: already configured", "name", DiagnosticFluentdName)
}
return nil
}

// isVartmpHostPath checks if the vartmp volume uses hostPath.
// Returns true if hostPath, false if emptyDir or not found.
func (a *Adjuster) isVartmpHostPath(podSpec *corev1.PodSpec) bool {
for i := range podSpec.Volumes {
if podSpec.Volumes[i].Name == DiagnosticFluentdVarTmpVolume {
return podSpec.Volumes[i].HostPath != nil
}
}
return false
}

// cleanupFluentdPatches removes patches previously applied by this operator.
// This is needed when upgrading from hostPath to emptyDir (SDI 3.3.290+).
// Returns true if any changes were made.
func (a *Adjuster) cleanupFluentdPatches(podSpec *corev1.PodSpec) bool {
updated := false

// Patch pod-level securityContext to allow running as root
podSecCtx := ds.Spec.Template.Spec.SecurityContext
if podSecCtx == nil {
ds.Spec.Template.Spec.SecurityContext = &corev1.PodSecurityContext{}
podSecCtx = ds.Spec.Template.Spec.SecurityContext
// Remove our init container if present
for i := range podSpec.InitContainers {
if podSpec.InitContainers[i].Name == DiagnosticFluentdInitName {
podSpec.InitContainers = append(podSpec.InitContainers[:i], podSpec.InitContainers[i+1:]...)
updated = true
break
}
}

// Revert container security context to non-privileged defaults
for i := range podSpec.Containers {
if podSpec.Containers[i].Name != DiagnosticFluentdName {
continue
}
secCtx := podSpec.Containers[i].SecurityContext
if secCtx == nil {
break
}
// Revert privileged mode if we set it
if secCtx.Privileged != nil && *secCtx.Privileged {
secCtx.Privileged = ptr.To(false)
updated = true
}
// Revert allowPrivilegeEscalation if we set it
if secCtx.AllowPrivilegeEscalation != nil && *secCtx.AllowPrivilegeEscalation {
secCtx.AllowPrivilegeEscalation = ptr.To(false)
updated = true
}
// Revert runAsNonRoot if we changed it
if secCtx.RunAsNonRoot != nil && !*secCtx.RunAsNonRoot {
secCtx.RunAsNonRoot = ptr.To(true)
updated = true
}
// Remove SELinux options if we set them
if secCtx.SELinuxOptions != nil && secCtx.SELinuxOptions.Type == "spc_t" {
secCtx.SELinuxOptions = nil
updated = true
}
break
}

// Revert pod security context
if podSpec.SecurityContext != nil {
podSecCtx := podSpec.SecurityContext
if podSecCtx.RunAsNonRoot != nil && !*podSecCtx.RunAsNonRoot {
podSecCtx.RunAsNonRoot = ptr.To(true)
updated = true
}
}

return updated
}

// patchPodSecurityContext patches the pod-level security context.
// Returns true if any changes were made.
func (a *Adjuster) patchPodSecurityContext(podSpec *corev1.PodSpec) bool {
updated := false
if podSpec.SecurityContext == nil {
podSpec.SecurityContext = &corev1.PodSecurityContext{}
}
podSecCtx := podSpec.SecurityContext
// Allow running as any user (not restricted to non-root)
if podSecCtx.RunAsNonRoot == nil || *podSecCtx.RunAsNonRoot {
podSecCtx.RunAsNonRoot = ptr.To(false)
updated = true
}
if podSecCtx.RunAsUser == nil || *podSecCtx.RunAsUser != 0 {
podSecCtx.RunAsUser = ptr.To(int64(0))
// Remove runAsUser if it was previously set to 0 by this operator
// Let the container run as the original UID to maintain compatibility
// with files created by previous versions
if podSecCtx.RunAsUser != nil && *podSecCtx.RunAsUser == 0 {
podSecCtx.RunAsUser = nil
updated = true
}
return updated
}

// Patch container-level securityContext
for i := range ds.Spec.Template.Spec.Containers {
if ds.Spec.Template.Spec.Containers[i].Name != DiagnosticFluentdName {
// patchContainerSecurityContext patches the container-level security context for privileged execution.
// Returns true if any changes were made.
func (a *Adjuster) patchContainerSecurityContext(podSpec *corev1.PodSpec, containerName string) bool {
updated := false
for i := range podSpec.Containers {
if podSpec.Containers[i].Name != containerName {
continue
}
containerSecCtx := ds.Spec.Template.Spec.Containers[i].SecurityContext
if containerSecCtx == nil {
ds.Spec.Template.Spec.Containers[i].SecurityContext = &corev1.SecurityContext{}
containerSecCtx = ds.Spec.Template.Spec.Containers[i].SecurityContext
if podSpec.Containers[i].SecurityContext == nil {
podSpec.Containers[i].SecurityContext = &corev1.SecurityContext{}
}
secCtx := podSpec.Containers[i].SecurityContext
if secCtx.Privileged == nil || !*secCtx.Privileged {
secCtx.Privileged = ptr.To(true)
updated = true
}
if secCtx.AllowPrivilegeEscalation == nil || !*secCtx.AllowPrivilegeEscalation {
secCtx.AllowPrivilegeEscalation = ptr.To(true)
updated = true
}
if secCtx.RunAsNonRoot == nil || *secCtx.RunAsNonRoot {
secCtx.RunAsNonRoot = ptr.To(false)
updated = true
}
if containerSecCtx.Privileged == nil || !*containerSecCtx.Privileged {
containerSecCtx.Privileged = ptr.To(true)
// Remove runAsUser if it was previously set to 0 by this operator
// Let the container run as the original UID to maintain compatibility
// with files created by previous versions
if secCtx.RunAsUser != nil && *secCtx.RunAsUser == 0 {
secCtx.RunAsUser = nil
updated = true
}
if containerSecCtx.AllowPrivilegeEscalation == nil || !*containerSecCtx.AllowPrivilegeEscalation {
containerSecCtx.AllowPrivilegeEscalation = ptr.To(true)
// Clear capabilities.drop when running privileged to avoid interference
if secCtx.Capabilities != nil && len(secCtx.Capabilities.Drop) > 0 {
secCtx.Capabilities.Drop = nil
updated = true
}
if containerSecCtx.RunAsNonRoot == nil || *containerSecCtx.RunAsNonRoot {
containerSecCtx.RunAsNonRoot = ptr.To(false)
// Set SELinux type to spc_t (super privileged container) to bypass SELinux restrictions on host paths
if secCtx.SELinuxOptions == nil || secCtx.SELinuxOptions.Type != "spc_t" {
secCtx.SELinuxOptions = &corev1.SELinuxOptions{Type: "spc_t"}
updated = true
}
break
}
return updated
}

if updated {
a.logger.Info("DaemonSet: patching security context", "name", DiagnosticFluentdName)
if err := a.Client.Update(ctx, ds); err != nil {
return err
// ensureFluentdInitContainer adds an init container to fix permissions on the pos file.
// This handles the upgrade case where the pos file was created by a previous version
// running as a different UID. Returns true if any changes were made.
func (a *Adjuster) ensureFluentdInitContainer(podSpec *corev1.PodSpec) bool {
expectedCommand := "if [ -f " + DiagnosticFluentdPosFile + " ]; then chmod 666 " + DiagnosticFluentdPosFile + " || true; fi"

// Check if init container already exists with correct command
for i := range podSpec.InitContainers {
if podSpec.InitContainers[i].Name == DiagnosticFluentdInitName {
// Check if command matches expected
if len(podSpec.InitContainers[i].Command) == 3 &&
podSpec.InitContainers[i].Command[2] == expectedCommand {
return false // Already configured correctly
}
// Update command to use chmod instead of chown
podSpec.InitContainers[i].Command = []string{"/bin/sh", "-c", expectedCommand}
return true
}
} else {
a.logger.V(1).Info("DaemonSet: already configured", "name", DiagnosticFluentdName)
}
return nil

// Get the main container's image to use for the init container
var fluentdImage string
for i := range podSpec.Containers {
if podSpec.Containers[i].Name == DiagnosticFluentdName {
fluentdImage = podSpec.Containers[i].Image
break
}
}
if fluentdImage == "" {
return false
}

// Create init container that fixes file permissions
// Use chmod 666 to make the file writable by any UID, avoiding hardcoded UID dependencies
initContainer := corev1.Container{
Name: DiagnosticFluentdInitName,
Image: fluentdImage,
Command: []string{
"/bin/sh",
"-c",
expectedCommand,
},
VolumeMounts: []corev1.VolumeMount{
{
Name: DiagnosticFluentdVarTmpVolume,
MountPath: "/var/tmp",
},
},
SecurityContext: &corev1.SecurityContext{
RunAsUser: ptr.To(int64(0)),
RunAsNonRoot: ptr.To(false),
Privileged: ptr.To(true),
AllowPrivilegeEscalation: ptr.To(true),
SELinuxOptions: &corev1.SELinuxOptions{Type: "spc_t"},
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("10m"),
corev1.ResourceMemory: resource.MustParse("10Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("50m"),
corev1.ResourceMemory: resource.MustParse("50Mi"),
},
},
}

podSpec.InitContainers = append(podSpec.InitContainers, initContainer)
return true
}

func (a *Adjuster) AdjustSDIVSystemVrepStatefulSets(ns string, obs *sdiv1alpha1.SDIObserver, ctx context.Context) error {
Expand Down
Loading