# Secret Has Excessive Keys

> Flags Kubernetes Secrets on EKS, GKE and AKS with more than 30 keys, where one leak or one broad grant exposes every credential inside.

Source: https://zop.dev/integrations/kubernetes/recommendations/secret-has-excessive-keys

---

## Why credential count per Secret matters

Kubernetes RBAC can narrow access down to individual objects through `resourceNames`, as the
[RBAC reference](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) shows, but no
further. There is no rule that grants one key of a Secret and withholds another. A role that
can `get` a Secret with 40 keys can read all 40.

Mounting behaves the same way by default. The
[distribute credentials task](https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/)
explains that each key in the Secret's data becomes a file in the mounted directory unless the
volume lists specific `items`. A pod that needs one database password therefore receives the
payment API key and the SMTP password too. The
[Secrets documentation](https://kubernetes.io/docs/concepts/configuration/secret/) also caps
individual Secrets at 1 MiB, which a bundle of certificates can approach.

This is a governance and security posture finding. Secrets have no cloud charge of their own,
so there is no savings figure.

## Listing crowded Secrets

The command below needs `list` on Secrets, which returns the encoded values as well as the
names, so run it with an identity that is already trusted with that data:

```bash
kubectl get secrets -A -o json | jq -r '
  .items[]
  | ((.data // {}) | length) as $n
  | select($n > 30)
  | "\(.metadata.namespace)/\(.metadata.name) type=\(.type) keys=\($n)"'
```

`kubectl get secrets -A` without `-o json` prints a `DATA` column with the same count if you
prefer not to pull values at all.

## When the finding appears

ZopNight reads the number of keys it recorded for each Secret and raises the finding once that
number passes 30. The threshold is stricter than the 50 used for ConfigMaps because the contents
are credentials. The check is a point-in-time read with no lookback window, and it clears as soon
as the Secret is back to 30 keys or fewer.

## Secrets it leaves alone

A Secret without a recorded key count is skipped. The rule does not inspect Secret types, so
TLS, Docker registry and Opaque Secrets are all judged by the same count. Legacy
service-account token Secrets are handled by a separate check,
[Legacy service-account-token Secret](https://zop.dev/integrations/kubernetes/recommendations/legacy-service-account-token-secret).

## Splitting credentials by consumer

1. For each key, find the workloads that reference it through `secretKeyRef`, `envFrom` or a
   volume mount.
2. Create one Secret per consumer, or per trust boundary, holding only the keys that consumer
   uses.
3. Update each workload to reference its own Secret, or, as a stopgap, list only the needed keys
   under `.spec.volumes[].secret.items`.
4. Tighten RBAC with `resourceNames` so each service account can read only its own Secret.
5. Rotate any credential that was broadly readable before the split, then delete the old
   bundle.

**Warning**
Move every consumer to its new Secret before deleting keys from the old one, and roll
each workload once to prove it starts cleanly without the old bundle.
