← Policy catalog
</> ValidatingAdmissionPolicy · Apache-2.0

Grants Access to Secrets

clusterrole-secrets-access

severityCritical resourceClusterRoles productKubernetes bundles1

Reading Secrets exposes the credentials they hold in every namespace, including service account tokens, TLS private keys and registry passwords, and the write verbs let those values be replaced with attacker-controlled ones.

Rejects unless

!has(object.rules) || object.rules.all(r,
  !(has(r.apiGroups) && has(r.resources) && has(r.verbs) &&
    r.apiGroups.exists(ag, ag == "") &&
    r.resources.exists(res, res == "secrets") &&
    r.verbs.exists(v, variables.secretVerbs.exists(sv, sv == v))
  )
)

ClusterRoles must not grant broad read/write access to Secrets without tight scoping.

Variables

secretVerbs

["get","list","watch","create","update","patch","delete"]

Remediation

Remove get, list, watch, create, update, patch and delete from every rule covering secrets in the core API group, and deliver credentials through a projected volume or an external secret store instead. Fields: rules[*].apiGroups, rules[*].resources, rules[*].verbs.

Applies to

  • clusterroles · rbac.authorization.k8s.io/v1 · CREATE, UPDATE

Manifest

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: clusterrole-secrets-access
  annotations:
    kubeapt.io/uuid: "8c614113-78ad-4b17-9d7c-c046baa2be90"
    security.kubeapt.io/displayName: "Grants Access to Secrets"
    security.kubeapt.io/description: "Reading Secrets exposes the credentials they hold in every namespace, including service account tokens, TLS private keys and registry passwords, and the write verbs let those values be replaced with attacker-controlled ones."
    security.kubeapt.io/resource: "ClusterRoles"
    security.kubeapt.io/severity: "Critical"
    security.kubeapt.io/remediation: "Remove get, list, watch, create, update, patch and delete from every rule covering secrets in the core API group, and deliver credentials through a projected volume or an external secret store instead. Fields: rules[*].apiGroups, rules[*].resources, rules[*].verbs."
    security.kubeapt.io/product: "Kubernetes"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups:
      - rbac.authorization.k8s.io
      apiVersions:
      - v1
      operations:
      - CREATE
      - UPDATE
      resources:
      - clusterroles
  variables:
  - name: secretVerbs
    expression: |
      ["get","list","watch","create","update","patch","delete"]
  validations:
  - expression: |
      !has(object.rules) || object.rules.all(r,
        !(has(r.apiGroups) && has(r.resources) && has(r.verbs) &&
          r.apiGroups.exists(ag, ag == "") &&
          r.resources.exists(res, res == "secrets") &&
          r.verbs.exists(v, variables.secretVerbs.exists(sv, sv == v))
        )
      )
    message: |
      ClusterRoles must not grant broad read/write access to Secrets without tight scoping.

Save it as clusterrole-secrets-access.yaml — the commands below assume that name.

Validate with kubeapt

Check a workload against this one policy, before anything is installed on a cluster.

$ kubeapt validate -p ./clusterrole-secrets-access.yaml -r ./workload.yaml

Or without the file, from the published policy set — -r takes a directory too:

$ kubeapt policies download && kubeapt validate --policy-name clusterrole-secrets-access -r ./manifests

Or against every namespace in the cluster your kubeconfig points at:

$ kubeapt validate --policy-name clusterrole-secrets-access -A