> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Containers Should Not Run In Privileged Mode

### More Info:

Verifies no container sets securityContext.privileged=true. A privileged container can compromise the node and every other pod scheduled on it.

### Risk Level

Critical

### Address

Security

### Compliance Standards

* Cloudanix Best Practice

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. Identify all privileged pods (run on any machine with kubectl access):
           ```sh theme={null}
           kubectl get pods --all-namespaces -o json | jq -r '
             [ .items[]
             | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
             | .metadata as $m
             | (.spec.nodeName // "") as $node
             | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
             | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
             | ((.spec.containers // []) + (.spec.initContainers // []))[]
             | (.securityContext.privileged // false) as $priv
             | select($priv == true)
             | "ns=\($m.namespace) pod=\($m.name) container=\(.name) ownerKind=\($own.kind) ownerName=\($own.name)"
             ][]'
           ```

        2. For each affected pod that is part of a higher‑level controller (Deployment, DaemonSet, StatefulSet, Job, CronJob), edit the controller manifest to remove privileged mode (run on any machine with kubectl access, replace KIND/NS/NAME accordingly):
           ```sh theme={null}
           kubectl -n NAMESPACE edit KIND NAME
           ```
           In the opened spec, under each affected `containers` or `initContainers` entry:
           * Remove the line:
             ```yaml theme={null}
             securityContext:
               privileged: true
             ```
             or, if `securityContext` has multiple fields, remove only:
             ```yaml theme={null}
             privileged: true
             ```
           * If the workload truly needs some low‑level access, add only the required capabilities instead:
             ```yaml theme={null}
             securityContext:
               capabilities:
                 add:
                   - NET_ADMIN    # example, adjust as needed
                   - SYS_TIME
             ```

        3. For affected standalone Pods without an owning controller, edit the Pod spec directly (run on any machine with kubectl access):
           ```sh theme={null}
           kubectl -n NAMESPACE edit pod POD_NAME
           ```
           In each affected container’s `securityContext`, remove `privileged: true` as in step 2, optionally replacing it with minimal `capabilities.add` entries if required. Save and exit to apply.

        4. If any privileged setting comes from a shared Pod template (e.g., Helm chart or GitOps manifest), locate and update that source manifest so changes persist (run where your manifests live):
           * Open the manifest defining the controller (e.g., `deployment.yaml`, `daemonset.yaml`).
           * Edit the container spec to remove `securityContext.privileged: true` or replace with minimal `securityContext.capabilities.add` as in step 2.
           * Re‑apply if you manage manifests manually:
             ```sh theme={null}
             kubectl apply -f path/to/manifest.yaml
             ```

        5. Wait for non‑privileged replacements to be running and old pods terminated (run on any machine with kubectl access):
           ```sh theme={null}
           kubectl get pods --all-namespaces
           ```
           Confirm newly created pods for each modified workload are in `Running` (or expected) state without `privileged` enabled in their spec.

        6. Verify remediation (run on any machine with kubectl access):
           ```sh theme={null}
           kubectl get pods --all-namespaces -o json | jq -r '
             [ .items[]
             | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
             | .metadata as $m
             | (.spec.nodeName // "") as $node
             | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
             | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
             | ((.spec.containers // []) + (.spec.initContainers // []))[]
             | (.securityContext.privileged // false) as $priv
             | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
               + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
               + (if $node   == ""   then "" else " node=\($node)" end)
               + (if $labels == ""   then "" else " labels=\($labels)" end)
               + (if $own    == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
               + " container=\(.name) image=\(.image) privileged=\($priv)"
               + " is_compliant=\(if $priv then "false" else "true" end)"
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```
           Ensure the output is `is_compliant=true` and no line shows `privileged=true is_compliant=false` for non‑system namespaces.
      </Accordion>

      <Accordion title="Using kubectl">
        On any machine with kubectl access to the cluster:

        1. Identify the owning controller (from the audit output’s `owner=` field). If the pod is controlled by a Deployment/DaemonSet/StatefulSet/etc., you must edit that controller, not the pod.

        2. Edit the controller manifest to remove privileged mode and (optionally) add only required capabilities.

           Example for a Deployment (replace values as needed):

           ```bash theme={null}
           kubectl -n my-namespace get deploy my-app -o yaml > /tmp/my-app-deploy.yaml
           ```

           In `/tmp/my-app-deploy.yaml`, under each affected container (and/or initContainer), change:

           ```yaml theme={null}
           securityContext:
             privileged: true
           ```

           to either remove `privileged` entirely:

           ```yaml theme={null}
           securityContext: {}
           ```

           or, if specific capabilities are required, replace with:

           ```yaml theme={null}
           securityContext:
             privileged: false
             capabilities:
               add:
                 - NET_ADMIN
                 - SYS_TIME
           ```

           (Adjust capability names to the minimal set actually needed, or remove `capabilities` entirely if none are required.)

           Then apply:

           ```bash theme={null}
           kubectl -n my-namespace apply -f /tmp/my-app-deploy.yaml
           ```

        3. If the pod is not controlled by any higher-level object (`owner=` is empty), edit the pod spec directly and recreate it:

           ```bash theme={null}
           kubectl -n my-namespace get pod my-pod -o yaml > /tmp/my-pod.yaml
           ```

           Edit `/tmp/my-pod.yaml`:

           * Remove `metadata.resourceVersion`, `metadata.uid`, `metadata.creationTimestamp`, `metadata.managedFields`, `metadata.ownerReferences`, and `status` sections.
           * Under each affected container/initContainer, change/remove `securityContext.privileged: true` as described above.

           Then delete and recreate:

           ```bash theme={null}
           kubectl -n my-namespace delete pod my-pod
           kubectl -n my-namespace apply -f /tmp/my-pod.yaml
           ```

        4. Verification (on any machine with kubectl access):

           ```bash theme={null}
           kubectl get pods --all-namespaces -o json | jq -r '
             [ .items[]
             | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
             | .metadata as $m
             | (.spec.nodeName // "") as $node
             | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
             | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
             | ((.spec.containers // []) + (.spec.initContainers // []))[]
             | (.securityContext.privileged // false) as $priv
             | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
               + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
               + (if $node   == ""   then "" else " node=\($node)" end)
               + (if $labels == ""   then "" else " labels=\($labels)" end)
               + (if $own    == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
               + " container=\(.name) image=\(.image) privileged=\($priv)"
               + " is_compliant=\(if $priv then "false" else "true" end)"
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```

           Ensure no line shows `privileged=true` and overall output is `is_compliant=true`.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Remediate privileged containers in an EKS cluster by updating their owning workload manifests.
        # Runs from: any machine with kubectl access and jq installed.
        # Requirements: kubectl, jq, bash 4+
        #
        # Behavior:
        # - Scans all namespaces except kube-system, kube-public, kube-node-lease
        # - Identifies pods with containers or initContainers having securityContext.privileged=true
        # - Determines the controlling owner (Deployment, StatefulSet, DaemonSet, Job, CronJob, ReplicaSet, ReplicationController)
        # - Patches the owner spec templates to remove privileged: true and optionally add explicit capabilities instead
        # - Is idempotent: safe to run multiple times
        # - Verifies: re-runs the benchmark audit filter at the end

        set -euo pipefail

        # ---- configuration ----

        # If you want to also explicitly drop privileged securityContext blocks entirely (not just privileged: true),
        # set this to "true". Otherwise, only privileged: true will be removed.
        STRIP_FULL_PRIV_BLOCK="${STRIP_FULL_PRIV_BLOCK:-false}"

        # Optionally define specific capabilities to add instead of full privilege
        # Example:
        #   DEFAULT_CAPS='["NET_ADMIN","SYS_PTRACE"]'
        # Leave empty ("") to skip adding capabilities.
        DEFAULT_CAPS="${DEFAULT_CAPS:-""}"

        # ---- helper functions ----

        need_cmd() {
          if ! command -v "$1" >/dev/null 2>&1; then
            echo "ERROR: '$1' is required but not found in PATH" >&2
            exit 1
          fi
        }

        need_cmd kubectl
        need_cmd jq

        echo "Discovering pods with privileged containers (excluding kube-system, kube-public, kube-node-lease)..."

        # Collect pods with privileged containers and their owners in a stable JSON array
        PODS_JSON="$(kubectl get pods --all-namespaces -o json | jq -c '
          [
            .items[]
            | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
            | .metadata as $m
            | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
            | ((.spec.containers // []) + (.spec.initContainers // []))[]
            | select((.securityContext.privileged // false) == true)
            | {
                namespace: $m.namespace,
                podName: $m.name,
                ownerKind: ($own.kind // "Pod"),
                ownerName: ($own.name // $m.name),
                ownerUID:  ($own.uid // $m.uid)
              }
          ] | unique
        ')"

        if [[ "$(jq 'length' <<<"$PODS_JSON")" -eq 0 ]]; then
          echo "No privileged containers found; cluster is already compliant."
          exit 0
        fi

        echo "Found $(jq 'length' <<<"$PODS_JSON") pods with privileged containers. Identifying owning workloads..."

        # Build a unique list of owners to patch
        OWNERS_JSON="$(jq -c '
          [
            .[]
            | {namespace, kind: .ownerKind, name: .ownerName}
          ] | unique
        ' <<<"$PODS_JSON")"

        echo "Unique owners to patch:"
        jq -r '.[] | "\(.kind) \(.namespace)/\(.name)"' <<<"$OWNERS_JSON"

        patch_owner() {
          local ns="$1"
          local kind="$2"
          local name="$3"

          echo "Patching ${kind} ${ns}/${name}..."

          # Get the current manifest
          if ! kubectl get "$kind" "$name" -n "$ns" -o json >/tmp/owner.json 2>/dev/null; then
            echo "  WARNING: Unable to fetch ${kind} ${ns}/${name}, skipping." >&2
            return
          fi

          # Work out where pod templates may live: spec.template (for most controllers),
          # jobTemplate.spec.template (for CronJobs), etc.
          # We patch all known template locations defensively.
          PATCH="$(jq -c --argjson caps "${DEFAULT_CAPS:-null}" --arg stripFull "${STRIP_FULL_PRIV_BLOCK}" '
            def strip_priv(sc):
              if sc == null then null
              else
                (if (.privileged? == true) then del(.privileged) end)
                | (if ($stripFull == "true" and (.privileged? // null) == null and (.capabilities? // null) == null and (.runAsUser? // null) == null and (.runAsGroup? // null) == null and (.runAsNonRoot? // null) == null and (.seLinuxOptions? // null) == null and (.windowsOptions? // null) == null) then empty else . end);
              end;

            def fix_containers(obj):
              if obj == null then null
              else obj
                | (if has("containers") then .containers |=
                      map(
                        if (.securityContext.privileged? // false) == true then
                          .securityContext |= (
                            strip_priv(.)
                            | (if $caps != null and ($caps | length) > 0 then
                                 .capabilities |= (. // {}) | .capabilities.add |= (($caps | unique) // [])
                               else .
                               end)
                          )
                        else . end
                      )
                   else . end)
                | (if has("initContainers") then .initContainers |=
                      map(
                        if (.securityContext.privileged? // false) == true then
                          .securityContext |= (
                            strip_priv(.)
                            | (if $caps != null and ($caps | length) > 0 then
                                 .capabilities |= (. // {}) | .capabilities.add |= (($caps | unique) // [])
                               else .
                               end)
                          )
                        else . end
                      )
                   else . end)
              end;

            {
              "spec": (
                .spec
                # Deployment, StatefulSet, DaemonSet, ReplicaSet, ReplicationController, Job
                | (if has("template") then .template |=
                     (. // {} | .spec |= fix_containers(.spec // {}))
                   else . end)
                # CronJob: spec.jobTemplate.spec.template
                | (if has("jobTemplate") then .jobTemplate |=
                     (. // {} | .spec |=
                        (. // {} | .template |=
                            (. // {} | .spec |= fix_containers(.spec // {}))
                        )
                      )
                   else . end)
              )
            }
          ' /tmp/owner.json)"

          # If patch does not change anything (no privileged fields), skip
          if [[ "$(jq -S '.' /tmp/owner.json)" == "$(kubectl get "$kind" "$name" -n "$ns" -o json | jq -S '.')" ]]; then
            echo "  No change detected for ${kind} ${ns}/${name}, skipping apply."
            return
          fi

          # Apply patch
          kubectl patch "$kind" "$name" -n "$ns" --type merge -p "$PATCH" >/dev/null
          echo "  Patched ${kind} ${ns}/${name}."
        }

        # Iterate owners and patch
        echo "$OWNERS_JSON" | jq -c '.[]' | while read -r owner; do
          ns="$(jq -r '.namespace' <<<"$owner")"
          kind="$(jq -r '.kind' <<<"$owner")"
          name="$(jq -r '.name' <<<"$owner")"

          # Normalize common owner kinds to their canonical resources
          case "$kind" in
            ReplicaSet) kind="ReplicaSet" ;;
            ReplicationController) kind="ReplicationController" ;;
            Deployment) kind="Deployment" ;;
            StatefulSet) kind="StatefulSet" ;;
            DaemonSet) kind="DaemonSet" ;;
            Job) kind="Job" ;;
            CronJob) kind="CronJob" ;;
            Pod) kind="Pod" ;; # bare pods (will be patched directly)
            *)
              echo "  WARNING: Unsupported owner kind '$kind' for ${ns}/${name}, skipping." >&2
              continue
              ;;
          esac

          patch_owner "$ns" "$kind" "$name"
        done

        echo "Waiting for workloads to roll out updated, non-privileged pods..."

        # For deployments, statefulsets, daemonsets, and cronjobs/jobs we can at least wait for rollout where supported.
        # This is best-effort and will not block forever due to timeout (300s) per resource.
        echo "$OWNERS_JSON" | jq -c '.[]' | while read -r owner; do
          ns="$(jq -r '.namespace' <<<"$owner")"
          kind="$(jq -r '.kind' <<<"$owner")"
          name="$(jq -r '.name' <<<"$owner")"

          case "$kind" in
            Deployment|StatefulSet|DaemonSet)
              echo "  Waiting for ${kind} ${ns}/${name} rollout..."
              kubectl rollout status "$kind" "$name" -n "$ns" --timeout=300s || \
                echo "  WARNING: Rollout status for ${kind} ${ns}/${name} did not complete within timeout." >&2
              ;;
            *)
              # Jobs/CronJobs/Pods/RS/RC – no generic rollout wait
              :
              ;;
          esac
        done

        echo "Re-running privileged container audit for verification..."

        kubectl get pods --all-namespaces -o json | jq -r '
          [ .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | .metadata as $m
          | (.spec.nodeName // "") as $node
          | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
          | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
          | ((.spec.containers // []) + (.spec.initContainers // []))[]
          | (.securityContext.privileged // false) as $priv
          | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
            + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
            + (if $node   == ""   then "" else " node=\($node)" end)
            + (if $labels == ""   then "" else " labels=\($labels)" end)
            + (if $own    == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
            + " container=\(.name) image=\(.image) privileged=\($priv)"
            + " is_compliant=\(if $priv then "false" else "true" end)"
          ] as $rows
          | if ($rows | map(select(. | contains(" privileged=true "))) | length) == 0
            then "is_compliant=true"
            else $rows[]
            end
        '
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
