551 lines
13 KiB
Markdown
551 lines
13 KiB
Markdown
# Kubernetes Security Policies
|
|
|
|
Comprehensive OPA policies for Kubernetes security best practices and admission control.
|
|
|
|
## Table of Contents
|
|
|
|
- [Pod Security](#pod-security)
|
|
- [RBAC Security](#rbac-security)
|
|
- [Network Security](#network-security)
|
|
- [Image Security](#image-security)
|
|
- [Secret Management](#secret-management)
|
|
|
|
## Pod Security
|
|
|
|
### Privileged Containers
|
|
|
|
Deny privileged containers:
|
|
|
|
```rego
|
|
package kubernetes.admission.privileged_containers
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.containers[_]
|
|
container.securityContext.privileged == true
|
|
|
|
msg := sprintf("Privileged container is not allowed: %v", [container.name])
|
|
}
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.initContainers[_]
|
|
container.securityContext.privileged == true
|
|
|
|
msg := sprintf("Privileged init container is not allowed: %v", [container.name])
|
|
}
|
|
```
|
|
|
|
### Run as Non-Root
|
|
|
|
Enforce containers run as non-root:
|
|
|
|
```rego
|
|
package kubernetes.admission.non_root
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.containers[_]
|
|
not container.securityContext.runAsNonRoot
|
|
|
|
msg := sprintf("Container must run as non-root user: %v", [container.name])
|
|
}
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.containers[_]
|
|
container.securityContext.runAsUser == 0
|
|
|
|
msg := sprintf("Container cannot run as UID 0 (root): %v", [container.name])
|
|
}
|
|
```
|
|
|
|
### Read-Only Root Filesystem
|
|
|
|
Require read-only root filesystem:
|
|
|
|
```rego
|
|
package kubernetes.admission.readonly_root
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.containers[_]
|
|
not container.securityContext.readOnlyRootFilesystem
|
|
|
|
msg := sprintf("Container must use read-only root filesystem: %v", [container.name])
|
|
}
|
|
```
|
|
|
|
### Capabilities
|
|
|
|
Restrict Linux capabilities:
|
|
|
|
```rego
|
|
package kubernetes.admission.capabilities
|
|
|
|
# Denied capabilities
|
|
denied_capabilities := [
|
|
"CAP_SYS_ADMIN",
|
|
"CAP_NET_ADMIN",
|
|
"CAP_SYS_PTRACE",
|
|
"CAP_SYS_MODULE",
|
|
]
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.containers[_]
|
|
capability := container.securityContext.capabilities.add[_]
|
|
denied_capabilities[_] == capability
|
|
|
|
msg := sprintf("Capability %v is not allowed for container: %v", [capability, container.name])
|
|
}
|
|
|
|
# Require dropping ALL capabilities by default
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.containers[_]
|
|
not drops_all_capabilities(container)
|
|
|
|
msg := sprintf("Container must drop ALL capabilities: %v", [container.name])
|
|
}
|
|
|
|
drops_all_capabilities(container) {
|
|
container.securityContext.capabilities.drop[_] == "ALL"
|
|
}
|
|
```
|
|
|
|
### Host Namespaces
|
|
|
|
Prevent use of host namespaces:
|
|
|
|
```rego
|
|
package kubernetes.admission.host_namespaces
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
input.request.object.spec.hostPID == true
|
|
|
|
msg := "Sharing the host PID namespace is not allowed"
|
|
}
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
input.request.object.spec.hostIPC == true
|
|
|
|
msg := "Sharing the host IPC namespace is not allowed"
|
|
}
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
input.request.object.spec.hostNetwork == true
|
|
|
|
msg := "Sharing the host network namespace is not allowed"
|
|
}
|
|
```
|
|
|
|
### Host Paths
|
|
|
|
Restrict hostPath volumes:
|
|
|
|
```rego
|
|
package kubernetes.admission.host_path
|
|
|
|
# Allowed host paths (if any)
|
|
allowed_host_paths := [
|
|
"/var/log/pods", # Example: log collection
|
|
]
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
volume := input.request.object.spec.volumes[_]
|
|
volume.hostPath
|
|
not is_allowed_host_path(volume.hostPath.path)
|
|
|
|
msg := sprintf("hostPath volume is not allowed: %v", [volume.hostPath.path])
|
|
}
|
|
|
|
is_allowed_host_path(path) {
|
|
allowed_host_paths[_] == path
|
|
}
|
|
```
|
|
|
|
### Security Context
|
|
|
|
Comprehensive pod security context validation:
|
|
|
|
```rego
|
|
package kubernetes.admission.security_context
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
not input.request.object.spec.securityContext
|
|
|
|
msg := "Pod must define a security context"
|
|
}
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
pod_security := input.request.object.spec.securityContext
|
|
not pod_security.runAsNonRoot
|
|
|
|
msg := "Pod security context must set runAsNonRoot: true"
|
|
}
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
pod_security := input.request.object.spec.securityContext
|
|
not pod_security.seccompProfile
|
|
|
|
msg := "Pod must define a seccomp profile"
|
|
}
|
|
```
|
|
|
|
## RBAC Security
|
|
|
|
### Wildcard Permissions
|
|
|
|
Prevent wildcard RBAC permissions:
|
|
|
|
```rego
|
|
package kubernetes.rbac.wildcards
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Role"
|
|
rule := input.request.object.rules[_]
|
|
rule.verbs[_] == "*"
|
|
|
|
msg := sprintf("Role contains wildcard verb permission in rule: %v", [rule])
|
|
}
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Role"
|
|
rule := input.request.object.rules[_]
|
|
rule.resources[_] == "*"
|
|
|
|
msg := sprintf("Role contains wildcard resource permission in rule: %v", [rule])
|
|
}
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "ClusterRole"
|
|
rule := input.request.object.rules[_]
|
|
rule.verbs[_] == "*"
|
|
|
|
msg := sprintf("ClusterRole contains wildcard verb permission in rule: %v", [rule])
|
|
}
|
|
```
|
|
|
|
### Cluster Admin
|
|
|
|
Restrict cluster-admin usage:
|
|
|
|
```rego
|
|
package kubernetes.rbac.cluster_admin
|
|
|
|
# System accounts allowed to use cluster-admin
|
|
allowed_system_accounts := [
|
|
"system:kube-controller-manager",
|
|
"system:kube-scheduler",
|
|
]
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "ClusterRoleBinding"
|
|
input.request.object.roleRef.name == "cluster-admin"
|
|
subject := input.request.object.subjects[_]
|
|
not is_allowed_system_account(subject)
|
|
|
|
msg := sprintf("cluster-admin binding not allowed for subject: %v", [subject.name])
|
|
}
|
|
|
|
is_allowed_system_account(subject) {
|
|
allowed_system_accounts[_] == subject.name
|
|
}
|
|
```
|
|
|
|
### Service Account Token Mounting
|
|
|
|
Control service account token auto-mounting:
|
|
|
|
```rego
|
|
package kubernetes.rbac.service_account_tokens
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
input.request.object.spec.automountServiceAccountToken == true
|
|
not requires_service_account(input.request.object)
|
|
|
|
msg := "Pod should not auto-mount service account token unless required"
|
|
}
|
|
|
|
requires_service_account(pod) {
|
|
pod.metadata.annotations["requires-service-account"] == "true"
|
|
}
|
|
```
|
|
|
|
## Network Security
|
|
|
|
### Network Policies Required
|
|
|
|
Require network policies for namespaces:
|
|
|
|
```rego
|
|
package kubernetes.network.policies_required
|
|
|
|
# Check if namespace has network policies (requires admission controller data)
|
|
deny[msg] {
|
|
input.request.kind.kind == "Namespace"
|
|
not has_network_policy_annotation(input.request.object)
|
|
|
|
msg := sprintf("Namespace must have network policy annotation: %v", [input.request.object.metadata.name])
|
|
}
|
|
|
|
has_network_policy_annotation(namespace) {
|
|
namespace.metadata.annotations["network-policy.enabled"] == "true"
|
|
}
|
|
```
|
|
|
|
### Deny Default Network Policy
|
|
|
|
Implement default-deny network policy:
|
|
|
|
```rego
|
|
package kubernetes.network.default_deny
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "NetworkPolicy"
|
|
not is_default_deny(input.request.object)
|
|
input.request.object.metadata.labels["policy-type"] == "default"
|
|
|
|
msg := "Default network policy must be deny-all"
|
|
}
|
|
|
|
is_default_deny(network_policy) {
|
|
# Check for empty ingress rules (deny all ingress)
|
|
not network_policy.spec.ingress
|
|
# Check for ingress type
|
|
network_policy.spec.policyTypes[_] == "Ingress"
|
|
}
|
|
```
|
|
|
|
### Service Type LoadBalancer
|
|
|
|
Restrict external LoadBalancer services:
|
|
|
|
```rego
|
|
package kubernetes.network.loadbalancer
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Service"
|
|
input.request.object.spec.type == "LoadBalancer"
|
|
not is_approved_for_external_exposure(input.request.object)
|
|
|
|
msg := sprintf("LoadBalancer service requires approval annotation: %v", [input.request.object.metadata.name])
|
|
}
|
|
|
|
is_approved_for_external_exposure(service) {
|
|
service.metadata.annotations["external-exposure.approved"] == "true"
|
|
}
|
|
```
|
|
|
|
## Image Security
|
|
|
|
### Image Registry Whitelist
|
|
|
|
Allow only approved image registries:
|
|
|
|
```rego
|
|
package kubernetes.images.registry_whitelist
|
|
|
|
approved_registries := [
|
|
"gcr.io/my-company",
|
|
"docker.io/my-company",
|
|
"quay.io/my-company",
|
|
]
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.containers[_]
|
|
not is_approved_registry(container.image)
|
|
|
|
msg := sprintf("Image from unapproved registry: %v", [container.image])
|
|
}
|
|
|
|
is_approved_registry(image) {
|
|
startswith(image, approved_registries[_])
|
|
}
|
|
```
|
|
|
|
### Image Tags
|
|
|
|
Prevent latest tag and require specific tags:
|
|
|
|
```rego
|
|
package kubernetes.images.tags
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.containers[_]
|
|
endswith(container.image, ":latest")
|
|
|
|
msg := sprintf("Container uses 'latest' tag: %v", [container.name])
|
|
}
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.containers[_]
|
|
not contains(container.image, ":")
|
|
|
|
msg := sprintf("Container image must specify a tag: %v", [container.name])
|
|
}
|
|
```
|
|
|
|
### Image Vulnerability Scanning
|
|
|
|
Require vulnerability scan results:
|
|
|
|
```rego
|
|
package kubernetes.images.vulnerability_scanning
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
not has_scan_annotation(input.request.object)
|
|
|
|
msg := "Pod must have vulnerability scan results annotation"
|
|
}
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
scan_result := input.request.object.metadata.annotations["vulnerability-scan.result"]
|
|
scan_result == "failed"
|
|
|
|
msg := "Pod image failed vulnerability scan"
|
|
}
|
|
|
|
has_scan_annotation(pod) {
|
|
pod.metadata.annotations["vulnerability-scan.result"]
|
|
}
|
|
```
|
|
|
|
## Secret Management
|
|
|
|
### Environment Variable Secrets
|
|
|
|
Prevent secrets in environment variables:
|
|
|
|
```rego
|
|
package kubernetes.secrets.env_vars
|
|
|
|
sensitive_keywords := [
|
|
"password",
|
|
"token",
|
|
"apikey",
|
|
"secret",
|
|
"credential",
|
|
]
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
container := input.request.object.spec.containers[_]
|
|
env := container.env[_]
|
|
is_sensitive_name(env.name)
|
|
env.value # Direct value, not from secret
|
|
|
|
msg := sprintf("Sensitive data in environment variable: %v in container %v", [env.name, container.name])
|
|
}
|
|
|
|
is_sensitive_name(name) {
|
|
lower_name := lower(name)
|
|
contains(lower_name, sensitive_keywords[_])
|
|
}
|
|
```
|
|
|
|
### Secret Volume Permissions
|
|
|
|
Restrict secret volume mount permissions:
|
|
|
|
```rego
|
|
package kubernetes.secrets.volume_permissions
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Pod"
|
|
volume := input.request.object.spec.volumes[_]
|
|
volume.secret
|
|
volume_mount := input.request.object.spec.containers[_].volumeMounts[_]
|
|
volume_mount.name == volume.name
|
|
not volume_mount.readOnly
|
|
|
|
msg := sprintf("Secret volume mount must be read-only: %v", [volume.name])
|
|
}
|
|
```
|
|
|
|
### External Secrets
|
|
|
|
Require use of external secret management:
|
|
|
|
```rego
|
|
package kubernetes.secrets.external
|
|
|
|
deny[msg] {
|
|
input.request.kind.kind == "Secret"
|
|
input.request.object.metadata.labels["environment"] == "production"
|
|
not input.request.object.metadata.annotations["external-secret.enabled"] == "true"
|
|
|
|
msg := sprintf("Production secrets must use external secret management: %v", [input.request.object.metadata.name])
|
|
}
|
|
```
|
|
|
|
## Admission Control Integration
|
|
|
|
Example OPA Gatekeeper ConstraintTemplate:
|
|
|
|
```yaml
|
|
apiVersion: templates.gatekeeper.sh/v1beta1
|
|
kind: ConstraintTemplate
|
|
metadata:
|
|
name: k8spodsecsecurity
|
|
spec:
|
|
crd:
|
|
spec:
|
|
names:
|
|
kind: K8sPodSecSecurity
|
|
targets:
|
|
- target: admission.k8s.gatekeeper.sh
|
|
rego: |
|
|
package k8spodsecurity
|
|
|
|
violation[{"msg": msg}] {
|
|
container := input.review.object.spec.containers[_]
|
|
container.securityContext.privileged == true
|
|
msg := sprintf("Privileged container not allowed: %v", [container.name])
|
|
}
|
|
|
|
violation[{"msg": msg}] {
|
|
container := input.review.object.spec.containers[_]
|
|
not container.securityContext.runAsNonRoot
|
|
msg := sprintf("Container must run as non-root: %v", [container.name])
|
|
}
|
|
```
|
|
|
|
Example Constraint:
|
|
|
|
```yaml
|
|
apiVersion: constraints.gatekeeper.sh/v1beta1
|
|
kind: K8sPodSecSecurity
|
|
metadata:
|
|
name: pod-security-policy
|
|
spec:
|
|
match:
|
|
kinds:
|
|
- apiGroups: [""]
|
|
kinds: ["Pod"]
|
|
namespaces:
|
|
- "production"
|
|
- "staging"
|
|
```
|
|
|
|
## References
|
|
|
|
- [Kubernetes Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/)
|
|
- [OPA Gatekeeper Library](https://github.com/open-policy-agent/gatekeeper-library)
|
|
- [NSA Kubernetes Hardening Guide](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/)
|
|
- [CIS Kubernetes Benchmark](https://www.cisecurity.org/benchmark/kubernetes)
|