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

# Ensure Use Google Managed Ssl Certificates

### More Info:

Encrypt traffic to HTTPS load balancers using TLS certificates.

### Risk Level

Low

### Address

Security

### Compliance Standards

* CIS GKE

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. On any machine with `gcloud` and `kubectl` access, list current LoadBalancer Services and Ingresses to decide what to migrate and secure:
           ```bash theme={null}
           kubectl get svc -A | grep LoadBalancer || echo "No LoadBalancer Services"
           kubectl get ingress -A
           ```

        2. For each public `LoadBalancer` Service you want to front with HTTPS, create or reuse an Ingress and backend Service/ports (GCE HTTP(S) Load Balancer only works with NodePort/NEG backends). Example (edit names/namespaces/ports as needed, run from any machine with kubectl access):
           ```bash theme={null}
           cat <<'EOF' > ingress-backend-svc.yaml
           apiVersion: v1
           kind: Service
           metadata:
             name: my-app
             namespace: default
           spec:
             type: NodePort
             selector:
               app: my-app
             ports:
             - port: 80
               targetPort: 8080
           ---
           apiVersion: networking.k8s.io/v1
           kind: Ingress
           metadata:
             name: my-app-ing
             namespace: default
             annotations:
               kubernetes.io/ingress.class: "gce"
           spec:
             rules:
             - host: myapp.example.com
               http:
                 paths:
                 - path: /
                   pathType: Prefix
                   backend:
                     service:
                       name: my-app
                       port:
                         number: 80
           EOF

           kubectl apply -f ingress-backend-svc.yaml
           ```

        3. Create a Google-managed SSL certificate resource in the same namespace as the Ingress (any machine with kubectl access). Use DNS names that point to the load balancer IP you will get:
           ```bash theme={null}
           cat <<'EOF' > managed-cert.yaml
           apiVersion: networking.gke.io/v1
           kind: ManagedCertificate
           metadata:
             name: my-app-cert
             namespace: default
           spec:
             domains:
             - myapp.example.com
           EOF

           kubectl apply -f managed-cert.yaml
           ```

        4. Attach the ManagedCertificate to the Ingress via annotation (any machine with kubectl access):
           ```bash theme={null}
           kubectl annotate ingress -n default my-app-ing \
             "networking.gke.io/managed-certificates=my-app-cert" \
             --overwrite
           ```

        5. Wait for the managed certificate and Ingress to become active, then decommission old public LoadBalancer Services once traffic is confirmed on HTTPS (any machine with kubectl access):
           ```bash theme={null}
           # Watch certificate status
           kubectl describe managedcertificate -n default my-app-cert | egrep 'Status:|Domain Status'

           # Watch Ingress IP and provisioning
           kubectl get ingress -n default my-app-ing -w

           # When stable and tested over HTTPS, delete obsolete LoadBalancer Services
           kubectl get svc -A | grep LoadBalancer
           # Example:
           kubectl delete svc -n default old-lb-service-name
           ```

        6. Verify compliance from any machine with `kubectl` and `jq` installed:
           ```bash theme={null}
           svc_json="$(kubectl get svc -A -o json 2>/dev/null                || echo '{"items":[],"__err":"SVC_FORBIDDEN"}')"
           ing_json="$(kubectl get ingress -A -o json 2>/dev/null            || echo '{"items":[],"__err":"INGRESS_FORBIDDEN"}')"
           mc_json="$(kubectl get managedcertificates -A -o json 2>/dev/null || echo '{"items":[],"__err":"MC_FORBIDDEN"}')"

           printf '%s\n%s\n%s\n' "$svc_json" "$ing_json" "$mc_json" \
           | jq -rs '
             (.[0] // {}) as $svcsRaw |
             (.[1] // {}) as $ingsRaw |
             (.[2] // {}) as $mcsRaw |
             if ($svcsRaw.__err or $ingsRaw.__err or $mcsRaw.__err) then
               "ERROR_KUBECTL_LIST:" +
               ([
                 ($svcsRaw.__err // empty),
                 ($ingsRaw.__err // empty),
                 ($mcsRaw.__err // empty)
               ] | join(","))
             else
               ($svcsRaw.items // []) as $svcs |
               ($ingsRaw.items // []) as $ings |
               ($mcsRaw.items // []) as $mcs |
               def trim: gsub("^\\s+|\\s+$";"");
               def hasmc($ns;$name): any($mcs[]?; .metadata.namespace==$ns and .metadata.name==$name);
               ([
                 $svcs[]? | select(.spec.type=="LoadBalancer")
                 | "FOUND_PUBLIC_LB_SERVICE:\(.metadata.namespace // "default"):\(.metadata.name)"
               ] + [
                 $ings[]? as $i
                 | ($i.metadata.annotations."networking.gke.io/managed-certificates" // "") as $ann
                 | select($ann=="")
                 | "FOUND_INGRESS_WITHOUT_MANAGED_CERT:\($i.metadata.namespace // "default"):\($i.metadata.name)"
               ] + [
                 $ings[]? as $i
                 | ($i.metadata.annotations."networking.gke.io/managed-certificates" // "") as $ann
                 | select($ann!="")
                 | ($i.metadata.namespace // "default") as $ns
                 | ($ann | split(",") | map(trim) | map(select(length>0)) | .[]) as $mc
                 | select(hasmc($ns;$mc) | not)
                 | "FOUND_MISSING_MANAGED_CERT_RESOURCE:\($ns):\($i.metadata.name):cert=\($mc)"
               ]) as $f
               | if ($f|length)>0
                   then $f[]
                   else "ALL_INGRESSES_USE_MANAGED_CERTS_AND_NO_PUBLIC_LB_SERVICES"
                 end
             end
           '
           ```
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot configure Google-managed SSL Certificates or convert LoadBalancer Services into Ingress resources; those changes must be made in the GCP console, gcloud CLI, or your IaC (GKE/Cloud Load Balancing configuration). Refer to the Manual Steps section for the exact cloud-side changes to replace public LoadBalancer Services with Ingress objects that use Google-managed certificates.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # automate_managed_ssl_gke.sh
        #
        # Scope: run on any machine with:
        #  - gcloud CLI configured for the target project
        #  - kubectl with access to the target GKE cluster/context
        #
        # What it does (idempotent):
        #  - Detects Services of type LoadBalancer (does NOT auto-convert them; prints guidance)
        #  - For each Ingress that:
        #       * has no managed-certificates annotation, OR
        #       * has an annotation but some referenced ManagedCertificates are missing
        #    it:
        #       * creates/keeps one Google ManagedCertificate per Ingress
        #         named "<ingress-name>-mc" in the same namespace
        #       * annotates the Ingress with that single managed-cert reference
        #  - At the end, re-runs the benchmark audit logic to show remaining findings.
        #
        # IMPORTANT: You MUST provide a domain for each Ingress.
        # This script expects a mapping file to avoid guessing domains.
        #
        # DOMAIN MAPPING FILE FORMAT (YAML-ish, parsed via grep/sed, keep it simple):
        #   # lines starting with # are comments
        #   namespace/ingress-name: example.com
        #   namespace/another-ing: app.example.org
        #   default/web: web.example.com
        #
        # Example usage:
        #   PROJECT_ID="my-proj" CLUSTER="my-cluster" LOCATION="us-central1" \
        #   DOMAIN_MAP="ingress_domains.map" ./automate_managed_ssl_gke.sh
        #

        set -euo pipefail

        PROJECT_ID="${PROJECT_ID:-}"
        CLUSTER="${CLUSTER:-}"
        LOCATION="${LOCATION:-}"
        DOMAIN_MAP="${DOMAIN_MAP:-ingress_domains.map}"

        if [[ -z "$PROJECT_ID" || -z "$CLUSTER" || -z "$LOCATION" ]]; then
          echo "ERROR: Set PROJECT_ID, CLUSTER, and LOCATION environment variables."
          exit 1
        fi

        if [[ ! -f "$DOMAIN_MAP" ]]; then
          echo "ERROR: DOMAIN_MAP file '$DOMAIN_MAP' not found."
          echo "Create it with lines like: namespace/ingress-name: example.com"
          exit 1
        fi

        # Ensure kubectl context is set to the target GKE cluster
        echo "Configuring kubectl for cluster '${CLUSTER}' in '${LOCATION}' (project '${PROJECT_ID}')..."
        gcloud container clusters get-credentials "$CLUSTER" --zone "$LOCATION" --project "$PROJECT_ID"

        # Helper: trim whitespace
        trim() { sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'; }

        # Helper: lookup domain for namespace/ingress from DOMAIN_MAP
        lookup_domain() {
          local ns="$1"
          local ing="$2"
          local key="${ns}/${ing}"
          # Match "ns/ing:" at line start; ignore comments and blank lines
          local line
          line="$(grep -E "^[[:space:]]*${key}:" "$DOMAIN_MAP" 2>/dev/null || true)"
          if [[ -z "$line" ]]; then
            return 1
          fi
          # Extract substring after first colon
          local dom
          dom="$(printf '%s\n' "$line" | sed 's/^[^:]*:[[:space:]]*//')"
          dom="$(printf '%s\n' "$dom" | trim)"
          if [[ -z "$dom" ]]; then
            return 1
          fi
          printf '%s\n' "$dom"
        }

        # 1) Detect public LoadBalancer Services (read-only; no auto-conversion)
        echo "Scanning for Services of type LoadBalancer..."
        lb_svcs_json="$(kubectl get svc -A -o json 2>/dev/null || echo '{"items":[]}')"
        lb_count="$(printf '%s\n' "$lb_svcs_json" | jq '[.items[]? | select(.spec.type=="LoadBalancer")] | length')"

        if [[ "$lb_count" -gt 0 ]]; then
          echo "WARNING: Found $lb_count Service(s) of type LoadBalancer."
          printf '%s\n' "$lb_svcs_json" \
            | jq -r '.items[]? | select(.spec.type=="LoadBalancer") | "  - " + (.metadata.namespace//"default") + "/" + .metadata.name'
          echo "Per CIS GKE 5.6.8: consider replacing these with Ingress resources."
        else
          echo "No Services of type LoadBalancer found."
        fi

        # 2) Process Ingresses and ManagedCertificate resources
        echo "Scanning Ingresses and ManagedCertificates..."

        ings_json="$(kubectl get ingress -A -o json 2>/dev/null || echo '{"items":[],"__err":"INGRESS_FORBIDDEN"}')"
        mcs_json="$(kubectl get managedcertificates -A -o json 2>/dev/null || echo '{"items":[]}')"

        if echo "$ings_json" | jq -e 'has("__err")' >/dev/null 2>&1; then
          echo "ERROR: Unable to list Ingresses with kubectl. Check permissions."
          exit 1
        fi

        # Get array of Ingresses as lines "ns ing annotations_json"
        mapfile -t INGS < <(
          printf '%s\n' "$ings_json" | jq -r '
            .items[]? |
            (.metadata.namespace // "default") + " " +
            .metadata.name + " " +
            ( (.metadata.annotations // {}) | @json )
          '
        )

        # Existing ManagedCertificates: "ns name"
        mapfile -t EXISTING_MC < <(
          printf '%s\n' "$mcs_json" | jq -r '.items[]? | (.metadata.namespace // "default") + " " + .metadata.name'
        )

        has_mc() {
          local ns="$1" name="$2"
          for l in "${EXISTING_MC[@]}"; do
            if [[ "$l" == "$ns $name" ]]; then
              return 0
            fi
          done
          return 1
        }

        # For each ingress, ensure it uses a single ManagedCertificate "<ingress-name>-mc"
        for line in "${INGS[@]}"; do
          ns="$(printf '%s\n' "$line" | awk '{print $1}')"
          ing="$(printf '%s\n' "$line" | awk '{print $2}')"
          ann_json="$(printf '%s\n' "$line" | cut -d' ' -f3-)"

          mc_name="${ing}-mc"

          # Desired annotation value: single entry "<ingress-name>-mc"
          desired_ann="$mc_name"

          # Parse current annotation value (if any)
          current_ann="$(printf '%s\n' "$ann_json" | jq -r '."networking.gke.io/managed-certificates" // ""')"

          # Determine if current annotation already references our mc_name and mc exists
          need_update="true"
          if [[ -n "$current_ann" ]]; then
            # Split by comma, trim, and check if our mc_name is present and exists
            if printf '%s\n' "$current_ann" \
              | jq -r --arg mc "$mc_name" '
                  split(",")
                  | map(gsub("^\\s+|\\s+$";""))
                  | map(select(length>0))
                  | any(. == $mc)
                ' | grep -qx "true"; then
              if has_mc "$ns" "$mc_name"; then
                # Already using our desired ManagedCertificate
                need_update="false"
              fi
            fi
          fi

          # Ensure ManagedCertificate resource exists
          if ! has_mc "$ns" "$mc_name"; then
            domain="$(lookup_domain "$ns" "$ing" || true)"
            if [[ -z "$domain" ]]; then
              echo "SKIP: Ingress ${ns}/${ing} has no domain mapping in ${DOMAIN_MAP}; cannot create ManagedCertificate."
              continue
            fi

            echo "Creating/refreshing ManagedCertificate ${ns}/${mc_name} for domain ${domain}..."
            cat <<EOF | kubectl apply -n "$ns" -f -
        apiVersion: networking.gke.io/v1
        kind: ManagedCertificate
        metadata:
          name: ${mc_name}
        spec:
          domains:
          - ${domain}
        EOF

            # Refresh EXISTING_MC cache entry for idempotence in same run
            EXISTING_MC+=("$ns $mc_name")
          fi

          if [[ "$need_update" == "true" ]]; then
            echo "Annotating Ingress ${ns}/${ing} with networking.gke.io/managed-certificates=${desired_ann}..."
            kubectl annotate ingress "${ing}" \
              -n "${ns}" \
              "networking.gke.io/managed-certificates=${desired_ann}" \
              --overwrite
          else
            echo "Ingress ${ns}/${ing} already references existing ManagedCertificate ${mc_name}; no change."
          fi
        done

        echo
        echo "Waiting briefly for Kubernetes API to reflect changes..."
        sleep 5

        # 3) Verification: re-run the benchmark audit logic
        echo
        echo "=== Verification (CIS GKE 5.6.8 check) ==="
        svc_json="$(kubectl get svc -A -o json 2>/dev/null                || echo '{"items":[],"__err":"SVC_FORBIDDEN"}')"
        ing_json="$(kubectl get ingress -A -o json 2>/dev/null            || echo '{"items":[],"__err":"INGRESS_FORBIDDEN"}')"
        mc_json="$(kubectl get managedcertificates -A -o json 2>/dev/null || echo '{"items":[],"__err":"MC_FORBIDDEN"}')"

        printf '%s\n%s\n%s\n' "$svc_json" "$ing_json" "$mc_json" \
        | jq -rs '
          (.[0] // {}) as $svcsRaw |
          (.[1] // {}) as $ingsRaw |
          (.[2] // {}) as $mcsRaw |

          if ($svcsRaw.__err or $ingsRaw.__err or $mcsRaw.__err) then
            "ERROR_KUBECTL_LIST:" +
            ([
              ($svcsRaw.__err // empty),
              ($ingsRaw.__err // empty),
              ($mcsRaw.__err // empty)
            ] | join(","))
          else
            ($svcsRaw.items // []) as $svcs |
            ($ingsRaw.items // []) as $ings |
            ($mcsRaw.items // []) as $mcs |

            def trim: gsub("^\\s+|\\s+$";"");
            def hasmc($ns;$name): any($mcs[]?; .metadata.namespace==$ns and .metadata.name==$name);

            ([
              $svcs[]? | select(.spec.type=="LoadBalancer")
              | "FOUND_PUBLIC_LB_SERVICE:\(.metadata.namespace // "default"):\(.metadata.name)"
            ] + [
              $ings[]? as $i
              | ($i.metadata.annotations."networking.gke.io/managed-certificates" // "") as $ann
              | select($ann=="")
              | "FOUND_INGRESS_WITHOUT_MANAGED_CERT:\($i.metadata.namespace // "default"):\($i.metadata.name)"
            ] + [
              $ings[]? as $i
              | ($i.metadata.annotations."networking.gke.io/managed-certificates" // "") as $ann
              | select($ann!="")
              | ($i.metadata.namespace // "default") as $ns
              | ($ann | split(",") | map(trim) | map(select(length>0)) | .[]) as $mc
              | select(hasmc($ns;$mc) | not)
              | "FOUND_MISSING_MANAGED_CERT_RESOURCE:\($ns):\($i.metadata.name):cert=\($mc)"
            ]) as $f
            | if ($f|length)>0
                then $f[]
                else "ALL_INGRESSES_USE_MANAGED_CERTS_AND_NO_PUBLIC_LB_SERVICES"
              end
          end
        '

        echo "=== End of verification ==="
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

### Additional Reading:

* [https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/data-protection.html](https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/data-protection.html)
