Skip to content

    Back to articles

    6 min read

    Test Kubernetes security defaults at the workload level

    A safer mechanism can still change workload behavior

    Kubernetes v1.36 makes faster SELinux volume labeling generally available, replacing recursive relabeling with a mount option that applies a label to the volume at mount time. The Kubernetes v1.36 release notes describe the change and note a potential risk when a volume is shared between privileged and unprivileged Pods.

    That is a security improvement, but I would not translate “more secure by default” into “safe to upgrade without workload tests.” The mechanism that applies a label can affect how a workload accesses a volume, especially when multiple Pods or different privilege levels are involved. An API compatibility check will not tell me whether a database can still start, whether a sidecar can read its files, or whether a shared volume behaves as intended.

    I would treat the release as a reason to test security behavior, not only whether manifests still apply. The point is not to preserve every old assumption. It is to discover which assumptions exist, decide which are legitimate, and make the intended posture visible in configuration and policy.

    Test the relationships, not just the manifest

    A Pod becoming Ready is useful evidence, but it does not cover every security boundary. I would start by identifying workloads that use persistent volumes and asking how those volumes are mounted and shared. Then I would test the combinations that matter to the application: the workload’s configured identity, the volume’s access mode, any sidecars or init containers that touch the files, and whether another Pod uses the same storage.

    For each representative workload, I want tests that answer practical questions:

    • Can the main container read and write only the paths it is meant to use?
    • Can sidecars and init containers access the files they need without gaining broader access?
    • Does a restart, reschedule, or remount preserve the expected behavior?
    • Does sharing a volume across Pods with different privilege levels still work, or does the new behavior correctly expose an unsafe design?
    • Do admission policies, security policies, and monitoring still report the posture I intend?

    I would test both the expected path and a deliberately disallowed one. A successful write by the application is not enough if an unrelated container can also write to that data. Conversely, a denied access is not automatically a regression: it may be the security boundary doing its job. The test needs to say which result is intended.

    A small smoke-test sequence can make the operational intent explicit. The manifests below stand for the workload and its test fixture; I would run equivalent checks against the actual release candidate and storage driver, rather than assume a generic fixture represents every cluster.

    kubectl apply -f workload.yaml
    kubectl wait --for=condition=Ready pod -l app=workload --timeout=5m
    kubectl exec deploy/workload -- sh -c 'test -r /data/expected-file'
    kubectl exec deploy/workload -- sh -c 'test -w /data/application-owned'
    kubectl logs deploy/workload

    Those checks are only a starting point. I would add a negative test for paths the process should not access, and an integration test for any volume sharing the application relies on. I would also run the same tests after a restart and with the storage configuration used in the target cluster. If a test only passes when the workload runs with broader permissions than its intended posture allows, I want that failure before promotion, not as an incident after the upgrade.

    Make the intended posture explicit

    Defaults are useful because they reduce the amount of configuration teams must repeat. They are a poor long-term record of a workload’s security requirements. If a team relies on a particular identity, label, access boundary, or sharing pattern, I want that requirement represented deliberately and reviewed with the workload.

    That does not mean copying every default into every manifest. Explicitness should clarify an important decision, not create a forest of redundant settings. I would make a setting explicit when its absence makes the intended behavior ambiguous, when a policy depends on it, or when an upgrade could change the assumption. Then I would validate it in the same place as the workload: its deployment tests, admission checks, or policy tests.

    For the SELinux volume behavior in v1.36, I would record which workloads depend on shared-volume access and which are expected to keep their data isolated. I would check whether the relevant Pod and storage configurations express that intent, and verify the result on the target node and storage path. If the design depends on a specific compatibility behavior, I would document why and test it as an exception rather than leaving it implicit.

    This is also where policy needs workload-level tests. A cluster-wide rule can be syntactically correct and still reject an important workload, or allow a workload that violates the intended boundary. I would test representative Pods through the same admission and deployment path used in production. The output should tell the team whether a failure comes from the Kubernetes change, the storage integration, or its own policy—not just report that an upgrade test failed.

    Apply the same discipline to self-hosted clusters

    Self-hosted does not mean exempt from upstream behavior. The Kubernetes release notes describe a change in Kubernetes’ volume-labeling behavior; whether the resulting workload behavior is acceptable still depends on the node’s security enforcement, storage driver, and workload design. I would not assume a cloud-managed control plane is the deciding factor.

    For a self-hosted upgrade, I would run the tests on representative nodes with the actual kernel, container runtime, SELinux mode, and storage setup. I would include the node replacement or maintenance path used by the cluster, because a test on one prepared node cannot establish that every worker behaves the same way. If the cluster has more than one storage class or node configuration, I would include the combinations that are materially different.

    My upgrade gate would therefore include more than API discovery and deployment health. It would require workload checks for expected and forbidden access, policy checks for the intended security posture, and a review of any exception that preserves older behavior. I would roll forward only when those results are understood. If the change reveals that a workload relies on an unsafe shortcut, I would fix that dependency rather than treat the old behavior as the compatibility contract.

    A tighter default is valuable when it makes the safer path easier. It still needs to be tested where it meets a real workload. I want the upgrade to expose assumptions while there is time to change them, and I want the final security posture to come from an explicit decision—not from whichever implicit default happened to be in force.

    Drafted with AI assistance, reviewed and edited by Ali.