Skip to content

fix: add restricted-compliant securityContext to Java init container - #360

Open
ab0utbla-k wants to merge 1 commit into
aws:mainfrom
ab0utbla-k:fix/java-init-container-restricted-security-context
Open

fix: add restricted-compliant securityContext to Java init container#360
ab0utbla-k wants to merge 1 commit into
aws:mainfrom
ab0utbla-k:fix/java-init-container-restricted-security-context

Conversation

@ab0utbla-k

@ab0utbla-k ab0utbla-k commented Mar 10, 2026

Copy link
Copy Markdown

Fixes #361

Problem

The Java auto-instrumentation init container (opentelemetry-auto-instrumentation-java) is created without any securityContext, causing pod creation to fail in namespaces enforcing pod-security.kubernetes.io/enforce: restricted.

Error creating: pods "app-789564bdf9-c6wm4" is forbidden: violates PodSecurity
"restricted:latest": allowPrivilegeEscalation != false (container
"opentelemetry-auto-instrumentation-java" must set
securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container
"opentelemetry-auto-instrumentation-java" must set
securityContext.capabilities.drop=["ALL"]), seccompProfile (pod or container
"opentelemetry-auto-instrumentation-java" must set
securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")

Other languages (NodeJS, Python, DotNet, Apache) are not affected, because they all call setInitContainerSecurityContext, which copies the app container's security context onto the init container.

Root Cause

In pkg/instrumentation/sdk.go, setInitContainerSecurityContext was commented out for Java to avoid a runAsNonRoot conflict with the root-based Java agent image (opentelemetry-operator#2272). However, this left the init container with no securityContext at all, violating the restricted Pod Security Standard.

Fix

Added setJavaInitContainerSecurityContext, which derives the init container's context from the instrumented container instead of hardcoding one:

  1. Deep-copy the instrumented container's securityContext, so user-specified fields (readOnlyRootFilesystem, seLinuxOptions, runAsGroup, capabilities.add, and so on) are preserved and behavior stays consistent with the other languages.
  2. Strip runAsNonRoot and runAsUser, which are the fields that conflict with the root-based Java agent image (#2272).
  3. Default the fields required by the restricted Pod Security Standard (allowPrivilegeEscalation: false, capabilities.drop: ["ALL"], seccompProfile.type: RuntimeDefault), only when the instrumented container leaves them unset, so user values always win and a container with no securityContext at all still gets a compliant init container.

setInitContainerSecurityContext now deep-copies as well. It previously assigned the same pointer to both the init container and the instrumented container; stripping fields from a shared pointer would silently mutate the instrumented container. No behavior change today, since nothing mutated it before.

Reproduction

  1. Create namespace with pod-security.kubernetes.io/enforce: restricted
  2. Install amazon-cloudwatch-observability EKS add-on
  3. Deploy a Java app with instrumentation.opentelemetry.io/inject-java: "true" and a restricted-compliant securityContext
  4. Observe FailedCreate event on the ReplicaSet

Testing

  • New TestInjectJavaSecurityContext covering three cases: no securityContext on the instrumented container (minimal restricted context applied), a full restricted context (runAsNonRoot/runAsUser dropped, everything else preserved), and a partial context (missing restricted fields defaulted). Each case also asserts the instrumented container's securityContext is not modified.
  • Updated existing test cases that assert on the Java init container's expected pod spec (3 in sdk_test.go, 4 in podmutator_test.go) to include the resulting securityContext.
  • go build ./..., go vet ./... and go test ./pkg/instrumentation/... pass.

@mrowken

mrowken commented May 5, 2026

Copy link
Copy Markdown

Hi, we need this feature, kindly assign reviewer please.

Comment thread pkg/instrumentation/sdk.go Outdated
// Set a minimal restricted-compliant securityContext without runAsNonRoot/runAsUser
// to avoid the runAsNonRoot conflict (https://github.com/open-telemetry/opentelemetry-operator/issues/2272)
// while still satisfying the restricted Pod Security Standard.
pod = i.setInitContainerRestrictedSecurityContext(pod, javaInitContainerName)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like this security context is missing in NGINX instrumentation as well - https://github.com/ab0utbla-k/amazon-cloudwatch-agent-operator/blob/3969c9f684e19dbd5fb5be421c6d72183b95036e/pkg/instrumentation/sdk.go#L231 mind adding there as well ?

@ab0utbla-k ab0utbla-k Jul 31, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch, but I think NGINX is already covered. It just sets the context inline in nginx.go rather than through the sdk.go helper, which is why it looks missing at the line linked:

  • otel-agent-attach-nginx: nginx.go#L169 sets SecurityContext: pod.Spec.Containers[index].SecurityContext
  • otel-agent-source-container-clone: created via container.DeepCopy() at nginx.go#L78, so it inherits the instrumented container's context

Happy to move both to the shared helper for consistency if you'd prefer, but that felt out of scope for this fix. Java was the only language ending up with no context at all.

@mitali-salvi

Copy link
Copy Markdown
Contributor

The other languages (Node.js, Python, .NET, Apache) use setInitContainerSecurityContext which copies the app container's full securityContext onto the init container. This PR introduces a separate
setInitContainerRestrictedSecurityContext that applies a hardcoded minimal context instead.

Have you considered modifying the existing setInitContainerSecurityContext to copy the app container's securityContext but strip runAsNonRoot and runAsUser for the Java case? That would preserve any
additional user-specified fields (e.g. readOnlyRootFilesystem, seLinuxOptions, custom capabilities.add) while still avoiding the #2272 conflict — and keep the behavior consistent across all languages.

… container

Copy the instrumented container's securityContext onto the Java init
container and drop only runAsNonRoot/runAsUser, instead of applying a
hardcoded minimal context. User-specified fields are preserved and the
behavior is consistent with the other languages.

Fields required by the restricted Pod Security Standard are defaulted
when the container leaves them unset, so injection stays valid in
namespaces enforcing "restricted".

setInitContainerSecurityContext now deep-copies rather than sharing the
pointer with the instrumented container, so per-language adjustments
cannot leak back into it.
@ab0utbla-k
ab0utbla-k force-pushed the fix/java-init-container-restricted-security-context branch from 3969c9f to 8b90785 Compare July 31, 2026 09:12
@ab0utbla-k

ab0utbla-k commented Jul 31, 2026

Copy link
Copy Markdown
Author

@mitali-salvi good call, done, the PR now does exactly that.

setJavaInitContainerSecurityContext copies the instrumented container's securityContext, strips runAsNonRoot/runAsUser, and keeps everything else (readOnlyRootFilesystem, seLinuxOptions, capabilities.add, and so on).

Two things worth flagging:

  1. The copy has to be deep. setInitContainerSecurityContext assigned the same pointer to the init container and the instrumented container. Stripping runAsNonRoot on that shared pointer would also remove it from the instrumented container. I changed the shared helper to DeepCopy(). No behavior change today since nothing mutated it, but it makes the strip safe. There is a test asserting the instrumented container is untouched.
  2. Nil fallback. If the instrumented container has no securityContext, a plain copy reproduces the original bug. The restricted fields (allowPrivilegeEscalation, capabilities.drop, seccompProfile) are therefore defaulted only when unset, so user values always win.

Added TestInjectJavaSecurityContext covering all three paths, and rebased on latest main.

@ab0utbla-k
ab0utbla-k requested a review from mitali-salvi July 31, 2026 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Java init container missing securityContext breaks restricted Pod Security Standard

4 participants