> ## 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.

# Pods Should Be Managed By A Controller

### More Info:

Verifies pods are owned by a controller (Deployment, StatefulSet, DaemonSet, Job). A naked pod is not rescheduled if its node dies.

### Risk Level

Low

### 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 naked 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
             | "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)
               + " is_compliant=\(if $own == null then "false" else "true" end)"
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end' \
           | grep 'is_compliant=false'
           ```

        2. For each naked pod, export its manifest so you can base a controller on it (replace NAMESPACE and PODNAME; run on any machine with kubectl access):
           ```bash theme={null}
           kubectl get pod PODNAME -n NAMESPACE -o yaml > PODNAME-pod.yaml
           ```

        3. Create an appropriate controller manifest from the pod spec (run on any machine with kubectl access; edit locally with your editor):
           * For stateless workloads, convert to a Deployment:
             ```bash theme={null}
             cat PODNAME-pod.yaml | sed '1,/^spec:/d' > PODNAME-pod-spec.yaml
             ```
             Then create `PODNAME-deploy.yaml` with content like:
             ```yaml theme={null}
             apiVersion: apps/v1
             kind: Deployment
             metadata:
               name: PODNAME
               namespace: NAMESPACE
             spec:
               replicas: 1
               selector:
                 matchLabels:
                   app: PODNAME
               template:
                 metadata:
                   labels:
                     app: PODNAME
                 spec:
             ```
             Now paste the contents of `PODNAME-pod-spec.yaml` under the `spec:` line above (indented two spaces) and adjust labels, selectors, and any fields that must be unique (for example, remove `nodeName` if you don’t want pinning).
           * For workloads that must run on every node, build a DaemonSet instead by changing `kind: Deployment` to `kind: DaemonSet` and removing the `replicas:` field.
           * For stateful or single-instance workloads that need stable identities, use `kind: StatefulSet` and add a `serviceName` and volumeClaimTemplates as appropriate.

        4. Apply the new controller to the cluster (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl apply -f PODNAME-deploy.yaml
           ```
           Wait for the new managed pod(s) to be ready:
           ```bash theme={null}
           kubectl get pods -n NAMESPACE -l app=PODNAME
           ```

        5. Once the controller-managed pod is running and serving traffic, delete the original naked pod (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl delete pod PODNAME -n NAMESPACE
           ```
           If the pod was fronted by a Service, confirm the Service’s selector matches the labels used by the controller (for example, `app=PODNAME`):
           ```bash theme={null}
           kubectl get service -n NAMESPACE
           kubectl get service SERVICENAME -n NAMESPACE -o yaml | grep -A3 'selector:'
           ```

        6. Verify no remaining naked pods exist (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
             | "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)
               + " is_compliant=\(if $own == null then "false" else "true" end)"
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```
           Confirm that either the output is `is_compliant=true` or that no lines contain `is_compliant=false`.
      </Accordion>

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

        1. Identify “naked” pods (no controller ownerReference) outside system namespaces:

        ```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
          | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
          | select($own == null)
          | "\($m.namespace) \($m.name)"
          ][]'
        ```

        2. For each listed pod, export its spec to base a controller on (example for namespace `app-namespace`, pod `my-app-pod`):

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

        3. Create a matching controller manifest. For a typical stateless app, prefer a Deployment. Strip pod fields that must not be templated (status, metadata.uid, resourceVersion, etc.) and wrap `spec` under `template`. Example `deployment-my-app.yaml`:

        ```yaml theme={null}
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: my-app
          namespace: app-namespace
        spec:
          replicas: 2
          selector:
            matchLabels:
              app: my-app
          template:
            metadata:
              labels:
                app: my-app
            spec:
              containers:
                - name: my-app
                  image: your-registry.example.com/my-app:1.0.0
                  ports:
                    - containerPort: 8080
                  env:
                    - name: ENVIRONMENT
                      value: "prod"
                  # copy over other needed fields from the original pod spec
                  # (resources, probes, volumeMounts, etc.)
              # copy over volumes, node selectors, tolerations, etc. as required
        ```

        Apply it:

        ```bash theme={null}
        kubectl apply -f deployment-my-app.yaml
        ```

        4. If the naked pod is a singleton system-style agent that should run on all nodes, instead create a DaemonSet. Example `daemonset-my-agent.yaml`:

        ```yaml theme={null}
        apiVersion: apps/v1
        kind: DaemonSet
        metadata:
          name: my-agent
          namespace: app-namespace
        spec:
          selector:
            matchLabels:
              app: my-agent
          template:
            metadata:
              labels:
                app: my-agent
            spec:
              containers:
                - name: my-agent
                  image: your-registry.example.com/my-agent:1.0.0
                  # copy other required fields from the original pod spec
        ```

        Apply it:

        ```bash theme={null}
        kubectl apply -f daemonset-my-agent.yaml
        ```

        5. After the new controller-created pods are running and traffic is confirmed, delete the original naked pod(s), one at a time, by name:

        ```bash theme={null}
        kubectl delete pod my-app-pod -n app-namespace
        ```

        6. Repeat steps 2–5 for each naked pod, choosing Deployment/StatefulSet/DaemonSet/Job as appropriate to the workload semantics (stateful storage, one-shot jobs, per-node agents).

        7. Verification (same audit as the check, run after all conversions):

        ```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
          | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
          | "kind=Pod ns=\($m.namespace) name=\($m.name) is_compliant=\(if $own == null then "false" else "true" 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
        #
        # Remediation: Ensure pods are managed by a controller (C5.2) on OKE
        #
        # Usage:
        #   1) Ensure kubectl is configured (on any machine with kubectl access).
        #   2) Run: bash fix-naked-pods.sh
        #
        # What this does:
        #   - Detects "naked" pods (no controlling ownerReference) outside
        #     kube-system, kube-public, kube-node-lease.
        #   - Generates equivalent controller manifests (Deployment by default)
        #     into ./generated-controllers/.
        #   - Applies those manifests.
        #   - Deletes the naked pods (so the controllers take over).
        #   - Re-runs the audit at the end to verify.
        #
        # IMPORTANT:
        #   - This script cannot infer the correct controller type (Deployment vs
        #     StatefulSet vs DaemonSet vs Job) from a single pod. It uses
        #     Deployment by default.
        #   - Review the generated manifests before applying in production.
        #   - For stateful or singleton workloads, you may want StatefulSet or
        #     Job instead; adjust the generated YAML accordingly before apply.
        #   - This is best-effort automation and may not be appropriate for all
        #     naked pods. You are responsible for reviewing and approving changes.
        set -euo pipefail

        # -------- configuration --------

        # Default controller kind used to manage previously naked pods.
        # Options you might set after review: Deployment / StatefulSet / Job.
        DEFAULT_CONTROLLER_KIND="Deployment"

        # Directory to store generated manifests
        OUT_DIR="./generated-controllers"
        mkdir -p "${OUT_DIR}"

        # -------- helper functions --------

        error() { printf 'ERROR: %s\n' "$*" >&2; }
        info()  { printf 'INFO: %s\n' "$*"; }

        require_bin() {
          if ! command -v "$1" >/dev/null 2>&1; then
            error "Required binary '$1' not found in PATH"
            exit 1
          fi
        }

        # -------- prerequisites --------

        require_bin kubectl
        require_bin jq

        # Simple cluster access check
        if ! kubectl version --request-timeout=10s >/dev/null 2>&1; then
          error "kubectl cannot reach the cluster or is not authenticated"
          exit 1
        fi

        info "Detecting naked pods (no controller) in all non-system namespaces..."

        # -------- discover naked pods --------

        # JSON list of naked pods with minimal fields needed to reconstruct a spec
        NAKED_PODS_JSON=$(
          kubectl get pods --all-namespaces -o json \
          | jq '
            .items[]
            | select(.metadata.namespace as $n
                     | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
            | . as $pod
            | ((.metadata.ownerReferences // []) | map(select(.controller)) | length) as $ctlCount
            | select($ctlCount == 0)
            | {
                namespace: .metadata.namespace,
                name: .metadata.name,
                labels: (.metadata.labels // {}),
                annotations: (.metadata.annotations // {}),
                containers: .spec.containers,
                initContainers: (.spec.initContainers // []),
                nodeSelector: (.spec.nodeSelector // {}),
                tolerations: (.spec.tolerations // []),
                affinity: (.spec.affinity // {}),
                volumes: (.spec.volumes // []),
                serviceAccountName: (.spec.serviceAccountName // ""),
                restartPolicy: (.spec.restartPolicy // "Always")
              }'
        )

        if [[ -z "${NAKED_PODS_JSON}" ]] || [[ "${NAKED_PODS_JSON}" == "null" ]]; then
          info "No naked pods found. Cluster is already compliant."
          exit 0
        fi

        # Count naked pods
        NAKED_COUNT=$(printf '%s\n' "${NAKED_PODS_JSON}" | jq -s 'length')
        if [[ "${NAKED_COUNT}" -eq 0 ]]; then
          info "No naked pods found. Cluster is already compliant."
          exit 0
        fi

        info "Found ${NAKED_COUNT} naked pod(s). Generating ${DEFAULT_CONTROLLER_KIND} manifests in ${OUT_DIR}..."

        # -------- generate controller manifests --------

        # Note: Safe to re-run; files are overwritten.
        printf '%s\n' "${NAKED_PODS_JSON}" | jq -c '.' | while read -r POD; do
          NS=$(printf '%s\n' "${POD}" | jq -r '.namespace')
          NAME=$(printf '%s\n' "${POD}" | jq -r '.name')

          # Controller name (avoid clash with existing controller)
          CTRL_NAME="${NAME}-managed"

          FILE="${OUT_DIR}/${NS}-${NAME}-${DEFAULT_CONTROLLER_KIND,,}.yaml"

          info "Generating ${DEFAULT_CONTROLLER_KIND} for Pod ${NS}/${NAME} -> ${CTRL_NAME} (${FILE})"

          # Convert pod labels into a selector; if none, fabricate one.
          LABELS_JSON=$(printf '%s\n' "${POD}" | jq '.labels')
          HAVE_LABELS=$(printf '%s\n' "${LABELS_JSON}" | jq 'length')
          if [[ "${HAVE_LABELS}" -eq 0 ]]; then
            LABELS_JSON='{ "app": "'"${CTRL_NAME}"'" }'
          fi

          # Restart policy only relevant for some controllers; Deployments must use Always.
          RESTART_POLICY=$(printf '%s\n' "${POD}" | jq -r '.restartPolicy')
          if [[ "${DEFAULT_CONTROLLER_KIND}" == "Deployment" ]]; then
            RESTART_POLICY="Always"
          fi

          # Build YAML
          cat > "${FILE}" <<EOF
        apiVersion: apps/v1
        kind: ${DEFAULT_CONTROLLER_KIND}
        metadata:
          name: ${CTRL_NAME}
          namespace: ${NS}
          labels: $(printf '%s\n' "${LABELS_JSON}" | jq -c '.' )
        spec:
          replicas: 1
          selector:
            matchLabels: $(printf '%s\n' "${LABELS_JSON}" | jq -c '.' )
          template:
            metadata:
              labels: $(printf '%s\n' "${LABELS_JSON}" | jq -c '.' )
              annotations: $(printf '%s\n' "${POD}" | jq -c '.annotations' )
            spec:
              serviceAccountName: $(printf '%s\n' "${POD}" | jq -r '.serviceAccountName')
              restartPolicy: ${RESTART_POLICY}
              containers: $(printf '%s\n' "${POD}" | jq -c '.containers')
              initContainers: $(printf '%s\n' "${POD}" | jq -c '.initContainers')
              nodeSelector: $(printf '%s\n' "${POD}" | jq -c '.nodeSelector')
              tolerations: $(printf '%s\n' "${POD}" | jq -c '.tolerations')
              affinity: $(printf '%s\n' "${POD}" | jq -c '.affinity')
              volumes: $(printf '%s\n' "${POD}" | jq -c '.volumes')
        EOF

        done

        info "Generated manifests. Review them in ${OUT_DIR} before proceeding."
        info "Applying generated controller manifests..."

        kubectl apply -f "${OUT_DIR}"

        info "Deleting original naked pods so controllers can recreate them..."

        # Delete only if pods still exist (idempotent)
        printf '%s\n' "${NAKED_PODS_JSON}" | jq -r '.namespace + " " + .name' | while read -r NS NAME; do
          if kubectl get pod "${NAME}" -n "${NS}" >/dev/null 2>&1; then
            info "Deleting naked pod ${NS}/${NAME}"
            kubectl delete pod "${NAME}" -n "${NS}" --wait=false
          else
            info "Pod ${NS}/${NAME} already gone; skipping delete"
          fi
        done

        info "Waiting briefly for controllers to create replacement pods..."
        sleep 15

        # -------- verification (re-run audit) --------

        info "Re-running compliance check to verify pods are managed by controllers..."

        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
          | (if $own == null then "non_compliant" else empty end)
          ] as $rows
          | if ($rows | length) == 0 then "All non-system pods are now managed by controllers (is_compliant=true)" else "Some naked pods remain (is_compliant=false)" end
        '
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
