TT Lab
Get started
Learn Learning paths Courses

KCSA — Kubernetes Security Associate

Secret Access, Pod Creation, and Runtime Security Are Separate Boundaries

Continue in TT Lab

In one line

The permission to read the Secret API, the permission to create Pods, and a Pod's runtime security settings are different boundaries. A Restricted Pod can also read a Secret in the same namespace as a volume.

Why this was needed

Here is an example scenario. get secrets was removed from a deployment service account. kubectl auth can-i also answered no, so it was reported that the secret could not be accessed. But that account still had create pods. Instead of the API that reads Secrets, the attacker chose the API that creates a Pod with a Secret volume. Once the kubelet prepared the volume, the container obtained the value through an ordinary file read. A direct access denial and blocking indirect access are different.

How it works

Three questions to ask

Boundary The question it asks What this lab looks at
Authentication Who is the requester? Distinguishing the administrator certificate from the runner SA requested by proxy
Authorization Does it allow this verb, resource, and scope? Secret lookup is denied, and the app's Pod creation is allowed
Admission Will it accept the object of an allowed create request? Checks whether the Pod meets the Restricted runtime conditions

The subject of a Pod creation request and the Pod's serviceAccountName are not the same concept. Just because an administrator created a Pod that specifies the runner does not mean the runner's create permission was used. The next lab sends an actual create request with --as=system:serviceaccount:app:runner. This is a proxy request for teaching that uses the administrator's impersonate permission, not theft or forgery of an SA token.

What Restricted blocks and what it leaves

Restricted in the Pod Security Standards limits container runtime settings such as privilege escalation and running as root. From baseline on, it limits dangerous settings such as hostPath, hostPID, hostNetwork, and privileged. But the volume types Restricted allows include secret and configMap. This policy does not judge whether a given user may use a specific Secret for business purposes.

Even if you put in all of runAsNonRoot: true, capabilities drop ALL, allowPrivilegeEscalation: false, and seccompProfile: RuntimeDefault, reading a file that the container legitimately had mounted is not forbidden. automountServiceAccountToken: false is also an option that turns off automatic API token mounting, not an option that blocks Secret volumes.

So the explanation that create pods is always node root is wrong too. Even with the same permission, the reach differs depending on the admission policy, the SAs that can be selected, the namespace scope, and the volume types. pods/exec is affected by the permissions and data of the existing container you can get into. bind, escalate, and impersonate also require checking the allowed resources and subject scope, and you must not flatly conclude that it is unconditionally cluster-admin just because one of these verbs is present.

Defense sets the boundary of data and workloads together

The starting point is not to put mutually untrusting workloads in the same namespace as sensitive Secrets. Minimize the permission to create workloads and the service accounts that can be selected, and if needed, restrict the allowed Secret references and SA selection with a separate admission policy. Even if you put resourceNames on the direct get permission, the volumes referenced through Pod creation are not automatically restricted. A NetworkPolicy is a control on network connections, so it does not block reading local files that are already mounted.

PSA is still needed on top of that. You must apply together the control that reduces dangerous runtime settings that lead to the node and the control that decides who can use business data inside a namespace. The key is not to skip verifying another boundary just because you have a checkmark that one was turned on.

Encryption at rest is also a different boundary

The API data value of a Secret is a base64 representation and is not encryption. If encryption at rest is not configured, the data in the store has no confidentiality guarantee. In this k3s/kine environment, we check for a synthetic marker in the serialized Secret inside SQLite. This does not mean that every distribution uses the same file or serialization format, and you must separately check the encryption defaults of managed services.

EncryptionConfiguration writes new values with the first provider and reads old values with the later providers. If you put identity first, even new data is not encrypted. Turning on the setting or changing the key also does not automatically resave existing objects. You need rewriting, verification, and removal of the old read path. A properly authorized API request receives the decrypted value, so encryption at rest does not block the mount path above. If you expose the key and backups together, you can also lose the effect of encryption.

What it looks like in the field

An example inspection order is as follows. First, for a specific SA, specify the namespace and ask can-i get secrets and can-i create pods separately. Next, look at which Pods can be created through the admission policy and the SA selection scope. Finally, check what data the created workloads mount. can-i --list is a starting point and is not evidence that fully explains every external authorization and admission result. You must look at the actual request and the cause of failure together.

Even if you turn off automatic token mounting on a service account, settings explicitly specified in the Pod may take precedence. Fixing this option does not immediately change every Pod already running, so check the impact and apply it with new Pods. Judge separately whether a token is unneeded and whether a Secret is unneeded.

Read 401 and 403 separately as well. An unqualified request on a path where anonymous authentication is allowed may be processed as the system:anonymous user and the system:unauthenticated group. So an anonymous request is not always a 401. It varies with per-path anonymous allowance, invalid credentials, and the authorization result. Do not conclude from a 403 alone which admission controls it passed; check the reason in the response and the audit record together.

What you will do in the next lab

After confirming encryption at rest and rewriting with a synthetic Secret, you compare the hashes of the Secret file in a non-root Pod that the runner created in the Restricted-enforced app. The actual secret value is not printed. You record the API direct lookup denial, the Pod creation success, and the Secret volume read success as separate pieces of evidence. Then you check the authentication boundary and an audit log without bodies. This VM is a single node for teaching, and you must not extend these results into a security guarantee for other tenants or an entire production cluster.

Check further in the official documentation