> ## 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 Use A Read-Only Root Filesystem

### More Info:

Verifies readOnlyRootFilesystem is true. A writable root filesystem lets an attacker persist tools or modify binaries inside a running container.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* Cloudanix Best Practice

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. Identify noncompliant Pods (run 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.readOnlyRootFilesystem == true) as $ok
             | select($ok | not)
             | "ns=\($m.namespace) pod=\($m.name) ownerKind=\($own.kind // "Pod") ownerName=\($own.name // $m.name)"
             ] | unique[]'
           ```

        2. For each listed Pod, edit the owning workload manifest (e.g., Deployment) to set a read-only root filesystem (run on any machine with kubectl access; example for a Deployment):
           ```bash theme={null}
           kubectl -n NAMESPACE edit deployment DEPLOYMENT_NAME
           ```
           In the opened spec, under each `.spec.template.spec.containers[]` (and `.initContainers[]` if present) add or modify:
           ```yaml theme={null}
           securityContext:
             readOnlyRootFilesystem: true
           ```
           Save and exit to apply the change.

        3. If the container needs a writable path, add an `emptyDir` volume and mount it there (run on any machine with kubectl access; continue in the same edit):
           * Under `spec.template.spec.volumes`:
             ```yaml theme={null}
             - name: writable-tmp
               emptyDir: {}
             ```
           * Under the container that needs write access:
             ```yaml theme={null}
             volumeMounts:
               - name: writable-tmp
                 mountPath: /path/that/must/be/writable
             ```
           Adjust `name` and `mountPath` to match the application’s needs. Do not remove `readOnlyRootFilesystem: true`.

        4. For Pods created directly (no controller), update or recreate their manifests (run on any machine with kubectl access):
           * Export the Pod spec:
             ```bash theme={null}
             kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/pod-NAMESPACE-POD_NAME.yaml
             ```
           * Edit the file, adding:
             ```yaml theme={null}
             securityContext:
               readOnlyRootFilesystem: true
             ```
             under each container and initContainer, and optionally add `emptyDir` + `volumeMounts` as in step 3.
           * Delete and recreate:
             ```bash theme={null}
             kubectl -n NAMESPACE delete pod POD_NAME
             kubectl apply -f /tmp/pod-NAMESPACE-POD_NAME.yaml
             ```

        5. Allow the workloads to roll out the updated Pods and confirm all Pods are running (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl get pods --all-namespaces
           ```

        6. Verification (run 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.containers // []) + (.spec.initContainers // []))[]
             | (.securityContext.readOnlyRootFilesystem == true) as $ok
             | select($ok | not)
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```
           The output should be `is_compliant=true` when all applicable containers use a read-only root filesystem.
      </Accordion>

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

        1. Edit the Pod’s controller manifest (Deployment example)

        ```bash theme={null}
        kubectl -n YOUR_NAMESPACE get deploy YOUR_DEPLOYMENT -o yaml > /tmp/deploy.yaml
        ```

        In `/tmp/deploy.yaml`, under each container (and initContainer) that should be read‑only, add or update:

        ```yaml theme={null}
        spec:
          template:
            spec:
              containers:
                - name: your-container
                  image: your-image
                  securityContext:
                    readOnlyRootFilesystem: true
                  # If the app needs write access:
                  volumeMounts:
                    - name: writable-tmp
                      mountPath: /path/that/must/be/writable
              volumes:
                - name: writable-tmp
                  emptyDir: {}
        ```

        Apply the updated manifest:

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

        Repeat the same pattern for other controllers (StatefulSet, DaemonSet, CronJob, Job) by replacing `deploy` and the kind in the `kubectl get` call.

        2. Directly patch a running Pod (only if it is not controlled by a Deployment/StatefulSet/etc.)

        ```bash theme={null}
        kubectl -n YOUR_NAMESPACE patch pod YOUR_POD \
          --type='json' \
          -p='[
            {"op":"add","path":"/spec/containers/0/securityContext","value":{"readOnlyRootFilesystem":true}}
          ]'
        ```

        Adjust the container index `/0/` or patch additional containers as needed.

        If a Pod-level initContainer also needs the setting:

        ```bash theme={null}
        kubectl -n YOUR_NAMESPACE patch pod YOUR_POD \
          --type='json' \
          -p='[
            {"op":"add","path":"/spec/initContainers/0/securityContext","value":{"readOnlyRootFilesystem":true}}
          ]'
        ```

        3. Verification

        Run the benchmark audit command again from 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.readOnlyRootFilesystem == true) as $ok
          | "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)"
            + " readOnlyRootFilesystem=\(if .securityContext.readOnlyRootFilesystem == null then "unset" else .securityContext.readOnlyRootFilesystem end)"
            + " is_compliant=\(if $ok then "true" else "false" end)"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
        ```
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Purpose: Ensure all non-system Pods use readOnlyRootFilesystem=true on every container
        # Scope:   Any machine with kubectl access to the AKS cluster
        # Impact:  Pods will be deleted and recreated as updated Deployments/Workloads roll out

        set -euo pipefail

        # REQUIREMENTS:
        # - kubectl configured to point to the AKS cluster
        # - jq installed

        timestamp="$(date +%Y%m%d-%H%M%S)"
        backup_dir="./readonly-rootfs-backups-${timestamp}"
        mkdir -p "${backup_dir}"

        echo "==> Discovering non-compliant Pods and their owning workloads..."

        # Fetch pods JSON once
        pods_json="$(kubectl get pods --all-namespaces -o json)"

        # Function: list unique owning controllers (kind, namespace, name, apiVersion)
        # for pods with any container that has readOnlyRootFilesystem unset/false
        echo "${pods_json}" | jq -r '
          .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
          | select($own != null)
          | (.spec.containers // [] + .spec.initContainers // []) as $cs
          | select(any($cs[];
                       (.securityContext.readOnlyRootFilesystem // "unset") != true))
          | "\($own.apiVersion),\($own.kind),\($m.namespace),\($own.name)"
        ' \
        | sort -u > "${backup_dir}/non_compliant_owners.list"

        if [[ ! -s "${backup_dir}/non_compliant_owners.list" ]]; then
          echo "No non-compliant workloads found. Cluster is already compliant."
          exit 0
        fi

        echo "Found non-compliant owning workloads:"
        cat "${backup_dir}/non_compliant_owners.list"
        echo

        patch_count=0

        while IFS=',' read -r apiVersion kind namespace name; do
          # Skip types we cannot confidently patch generically
          case "${kind}" in
            Deployment|StatefulSet|DaemonSet|ReplicaSet|Job|CronJob)
              ;;
            *)
              echo "Skipping ${kind}/${namespace}/${name} (not a supported controller type). Review manually."
              continue
              ;;
          esac

          res="${kind,,}"   # lowercase resource type for kubectl
          # Special pluralization
          case "${res}" in
            deployment) res="deployments" ;;
            statefulset) res="statefulsets" ;;
            daemonset) res="daemonsets" ;;
            replicaset) res="replicasets" ;;
            job) res="jobs" ;;
            cronjob) res="cronjobs" ;;
          esac

          echo "==> Processing ${kind}/${namespace}/${name}"

          # Backup full manifest
          backup_file="${backup_dir}/${namespace}-${kind}-${name}.yaml"
          kubectl -n "${namespace}" get "${res}" "${name}" -o yaml > "${backup_file}"
          echo "    Backup saved to ${backup_file}"

          # Build a strategic merge patch that enforces readOnlyRootFilesystem: true
          # for every container and initContainer in the pod spec.
          # We fetch the current pod template, then construct a patch dynamically.
          tmpl_json="$(kubectl -n "${namespace}" get "${res}" "${name}" -o json)"

          containers_patch="$(echo "${tmpl_json}" | jq '
            .spec.template.spec.containers
            | map({
                name: .name,
                securityContext: (
                  .securityContext // {} | .readOnlyRootFilesystem = true
                )
              })
          ')"

          init_containers_patch="$(echo "${tmpl_json}" | jq '
            (.spec.template.spec.initContainers // [])
            | map({
                name: .name,
                securityContext: (
                  .securityContext // {} | .readOnlyRootFilesystem = true
                )
              })
          ')"

          patch_json="$(jq -n --argjson c "${containers_patch}" --argjson ic "${init_containers_patch}" '
            {
              "spec": {
                "template": {
                  "spec": {
                    "containers": $c
                  }
                }
              }
              + if ($ic | length) > 0 then
                  { "spec": { "template": { "spec": { "initContainers": $ic } } } }
                else {} end
            }
          ')"

          echo "    Applying patch to set readOnlyRootFilesystem=true..."
          echo "${patch_json}" | kubectl -n "${namespace}" patch "${res}" "${name}" \
            --type merge -p "$(cat)"

          patch_count=$((patch_count + 1))
        done < "${backup_dir}/non_compliant_owners.list"

        if [[ "${patch_count}" -eq 0 ]]; then
          echo "No supported controller types were patched. Remaining resources must be reviewed and fixed manually."
        else
          echo "Patched ${patch_count} workload(s). Waiting for Pods to be updated..."
          # Give controllers time to roll out new Pods
          sleep 10
        fi

        echo
        echo "==> Verifying compliance (excluding kube-system, kube-public, kube-node-lease)..."

        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
          | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
          | ((.spec.containers // []) + (.spec.initContainers // []))[]
          | (.securityContext.readOnlyRootFilesystem == true) as $ok
          | select($ok | not)
          | "ns=\($m.namespace) pod=\($m.name) container=\(.name) readOnlyRootFilesystem=\(if .securityContext.readOnlyRootFilesystem == null then "unset" else .securityContext.readOnlyRootFilesystem end)"
          ] as $rows
          | if ($rows | length) == 0 then
              "All evaluated containers have readOnlyRootFilesystem=true (is_compliant=true)"
            else
              "Non-compliant containers remain:\n" + ($rows | join("\n"))
            end
        '

        echo
        echo "Script completed. Review backups in ${backup_dir} if rollback is needed."
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
