Most Kubernetes access control still comes down to who holds which kubeconfig. Formal moves Kubernetes access management onto the request path: a Connector sits in front of the API server, engineers reach it with kubectl through the Formal Endpoint and their SSO identity, and every request passes a policy before the cluster sees it.
Kubernetes access control: RBAC plus per-request policy
Kubernetes RBAC answers one question: can this subject run this verb on this resource? Roles and ClusterRoles list permissions; RoleBindings and ClusterRoleBindings attach them to users, groups or service accounts. The Kubernetes RBAC good practices are the right baseline: least privilege, namespace-scoped bindings, no casual cluster-admin.
What RBAC doesn’t see is context. It can’t ask for MFA before an exec into production, it doesn’t record the shell, and a binding stays until someone deletes it. Formal adds that layer at the Connector: a Rego policy runs on every Kubernetes API request, with its group, namespace, kind, name, verb and subresource in input.kubernetes.
RBAC keeps the final say. With the impersonate action, the Connector forwards each request with Impersonate-User and Impersonate-Group headers, so the API server authorizes it against that user’s own bindings. Formal group names should match the groups your bindings use.
package formal.v2
import future.keywords.if
session := {
"action": "impersonate",
"user": input.user.username,
"groups": input.user.groups
} if {
input.resource.technology == "kubernetes"
}
The cluster needs a ClusterRole granting the Connector the impersonate verb on users and groups; the Kubernetes guide has the YAML. Treat that Connector identity as privileged: Kubernetes warns that impersonate lets the holder gain other users’ rights.
Which policies can you set on namespace, kind and verb?
Each policy sees the fields kubectl sends to the API server, so rules read like the access you mean:
- Block a namespace prefix. Refuse every request to namespaces that start with
kube-. - Protect a kind. Block a Kind such as
secrets. Kubernetes notes thatlistandwatchon Secrets reveal their contents, not onlyget. - Step up for exec. Require MFA when the subresource is
exec. No answer before the timeout means the request is blocked.
package formal.v2
import future.keywords.if
session := { "action": "mfa" } if {
input.kubernetes.subresource == "exec"
}
Every field and action is listed in policy evaluation.
How does Kubernetes session recording work?
The Connector records kubectl exec and kubectl attach sessions. Other requests, including kubectl port-forward, are logged but not recorded.
In Sessions, each exec shows the pod, container, commands and user, with an AI summary and a risk score from 1 (lowest) to 5 (highest). Terminating an active session disconnects the user and aborts the running command.
With Session Monitors, you describe a scenario in plain English. When it triggers, a policy can suspend the session, require MFA, or quarantine the user: all their sessions end and new SSH, Kubernetes and RDP sessions stay blocked until an admin unblocks them. SSH sessions get the same recording and monitors.
How do Kubernetes audit logs show the real user?
Without impersonation, the cluster sees the Connector’s identity on every request, and RBAC errors name the Connector’s role, not the engineer. With it, the API server’s audit events record the Connector as user and the engineer as impersonatedUser, so the Kubernetes audit log ties each action to a person.
Formal keeps its own Kubernetes access logs too: each request with the Formal user and resource, searchable with resource.technology:kubernetes, and forwardable to S3, GCS, Azure Blob Storage, Datadog, Splunk or Panther. For a Kubernetes security audit, that’s one access trail across clusters, databases and SSH hosts.
How does just-in-time Kubernetes access work?
Standing cluster-admin is usually the first access teams want gone. You write the policy that blocks the risky action; when an engineer needs it, an approved request creates a time-bound policy suspension for that user or group. Just-in-time access explains the model, and Workflows covers approvals in Slack.
Cursor works this way. Formal secures SSH, PostgreSQL, Redis, ClickHouse, and Kubernetes at Cursor, and engineers request just-in-time access for a specific operational need instead of holding permanent production permissions. Read the Cursor story.
What do you need to connect a cluster?
Each engineer runs the authenticated Formal Endpoint. On the Connector, the Kubernetes Resource targets the API endpoint on port 443; with AWS IAM Native Users, its name must match the EKS cluster name exactly. Without impersonation, every request runs with the Native User’s RBAC permissions, so scope them. On EKS, the Connector role also needs an access entry.
This page covers people reaching the cluster API through Formal PAM. The Formal Endpoint for Kubernetes (a sidecar for traffic leaving your pods) and the Kubernetes operator (which registers in-cluster services as Formal resources) are separate.
