Securing Exec and Attach Access
Required knowledge for the CKS certification.
The pods/exec and pods/attach subresources provide powerful debugging capabilities but also represent significant security risks. Unrestricted access allows attackers to execute arbitrary commands inside containers, steal credentials, and pivot to connected systems.
Issue: By default, many RBAC roles grant broad exec and attach permissions, allowing users to access any container in authorized namespaces and extract sensitive data.
Fix: Apply strict RBAC controls, implement admission policies, enable audit logging, and use alternative debugging methods that don't require exec access.
1. Restrict RBAC Permissions for Exec and Attach
Issue: Overly permissive roles grant exec and attach access to all pods, enabling credential theft from any container.
Fix: Grant exec/attach permissions only when necessary and restrict to specific pods or namespaces.
Deny Exec and Attach by Default
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer-readonly
namespace: production
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list"]
# Explicitly exclude pods/exec and pods/attach
Grant Exec Access to Specific Pods Only
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: debug-specific-app
namespace: production
rules:
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
resourceNames:
- "debug-pod-12345"
- "troubleshooting-pod-67890"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: debug-binding
namespace: production
subjects:
- kind: User
name: operator@company.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: debug-specific-app
apiGroup: rbac.authorization.k8s.io
Audit Who Has Exec/Attach Access
# Find all roles granting exec access
kubectl get roles,clusterroles -A -o json | \
jq '.items[] | select(.rules[]? | select(.resources[]? | contains("exec")))'
# Find all role bindings with exec permissions
kubectl get rolebindings,clusterrolebindings -A -o json | \
jq '.items[] | select(.roleRef.name as $role |
[kubectl get role/$role -o json | select(.rules[]? |
select(.resources[]? | contains("exec")))] | length > 0)'
# Check specific user's exec permissions
kubectl auth can-i create pods/exec -n production --as user@company.com
2. Implement Admission Control for Exec Operations
Issue: Even with RBAC restrictions, there's no real-time visibility or blocking of suspicious exec commands.
Fix: Use admission webhooks to log, audit, and optionally block exec/attach operations.
Create Validating Webhook for Exec
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validate-exec
webhooks:
- name: exec-validator.company.com
admissionReviewVersions: ["v1"]
clientConfig:
service:
name: exec-validator
namespace: security
path: "/validate"
caBundle: LS0tLS1CRUdJTi...
rules:
- operations: ["CONNECT"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods/exec", "pods/attach"]
failurePolicy: Fail
sideEffects: None
timeoutSeconds: 5
Example Webhook Logic
// Deny exec to pods with sensitive labels
func validateExec(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
pod := getPod(ar.Request.Namespace, ar.Request.Name)
if pod.Labels["env"] == "production" &&
pod.Labels["sensitive"] == "true" {
return &admissionv1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Message: "Exec not allowed on sensitive production pods",
},
}
}
return &admissionv1.AdmissionResponse{Allowed: true}
}
3. Enable Comprehensive Audit Logging
Issue: Exec and attach operations leave no trace without audit logging, making forensics impossible.
Fix: Enable detailed audit logging for all exec/attach operations.
Configure Audit Policy for Exec/Attach
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Log all exec and attach operations with full request/response
- level: RequestResponse
verbs: ["create"]
resources:
- group: ""
resources: ["pods/exec", "pods/attach"]
omitStages:
- RequestReceived
# Log portforward as well
- level: RequestResponse
verbs: ["create"]
resources:
- group: ""
resources: ["pods/portforward"]
Query Audit Logs for Exec Activity
# Search for all exec operations
kubectl logs -n kube-system kube-apiserver-master | \
grep -E "pods/exec.*CONNECT" | \
jq '.user.username, .objectRef.namespace, .objectRef.name, .requestURI'
# Find exec operations by specific user
kubectl logs -n kube-system kube-apiserver-master | \
jq 'select(.objectRef.resource == "pods" and
.objectRef.subresource == "exec" and
.user.username == "suspicious@company.com")'
# Check what commands were executed (if container logging enabled)
kubectl logs -n kube-system kube-apiserver-master | \
jq 'select(.objectRef.subresource == "exec") | .requestURI'
4. Use Ephemeral Debug Containers Instead
Issue: Traditional exec requires containers to have shells and debugging tools, increasing attack surface.
Fix: Use Kubernetes ephemeral debug containers that don't require modifying existing pods.
Create Ephemeral Debug Container
# Debug a pod without exec
kubectl debug -it webapp-pod-abc123 \
--image=busybox:1.28 \
--target=webapp \
--namespace=production
# Debug with specific tools
kubectl debug webapp-pod-abc123 \
--image=nicolaka/netshoot:latest \
--target=webapp \
-n production
Grant Debug Permissions Instead of Exec
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: debug-only
namespace: production
rules:
- apiGroups: [""]
resources: ["pods/ephemeralcontainers"]
verbs: ["update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get"]
# No exec or attach permissions
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: debug-binding
namespace: production
subjects:
- kind: User
name: developer@company.com
roleRef:
kind: Role
name: debug-only
apiGroup: rbac.authorization.k8s.io
5. Remove Shells and Debugging Tools from Production Images
Issue: Production containers with shells and debugging tools enable attackers to use exec for reconnaissance.
Fix: Build minimal distroless or scratch-based images without shells.
Use Distroless Base Images
# Before: Full image with shell
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl wget
COPY app /app
CMD ["/app"]
# After: Distroless image
FROM gcr.io/distroless/static-debian11:nonroot
COPY app /app
CMD ["/app"]
Verify No Shell in Container
# This should fail if no shell
kubectl exec -n production webapp-pod-abc123 -- /bin/sh
# Error: OCI runtime exec failed: exec failed: unable to start container process: exec: "/bin/sh": stat /bin/sh: no such file or directory
Build Multi-Stage Images
# Build stage with tools
FROM golang:1.21 AS builder
WORKDIR /src
COPY . .
RUN go build -o /app main.go
# Production stage without tools
FROM gcr.io/distroless/base-debian11:nonroot
COPY --from=builder /app /app
CMD ["/app"]
6. Implement Network Segmentation
Issue: Containers with exec access can reach internal services and databases, enabling lateral movement.
Fix: Use network policies to restrict container connectivity.
Deny Egress to Internal Services
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: restrict-database-access
namespace: production
spec:
podSelector:
matchLabels:
app: webapp
policyTypes:
- Egress
egress:
# Allow DNS
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
# Allow only specific services
- to:
- podSelector:
matchLabels:
app: api-backend
ports:
- protocol: TCP
port: 8080
# Deny database access (not listed = denied)
7. Monitor and Alert on Exec Usage
Issue: Without real-time monitoring, malicious exec activity goes unnoticed until damage is done.
Fix: Implement monitoring and alerting for suspicious exec patterns.
Create Falco Rules for Exec Detection
- rule: Suspicious Exec Command
desc: Detect potentially malicious exec commands
condition: >
kevt and ka.verb = create and
ka.target.subresource = exec and
(ka.uri contains "env" or
ka.uri contains "cat /var/run/secrets" or
ka.uri contains ".ssh" or
ka.uri contains "credentials")
output: >
Suspicious exec command detected (user=%ka.user.name
pod=%ka.target.name ns=%ka.target.namespace
command=%ka.uri)
priority: WARNING
tags: [k8s, exec, credential_access]
- rule: Exec to Production Pod
desc: Alert on any exec to production pods
condition: >
kevt and ka.verb = create and
ka.target.subresource = exec and
ka.target.namespace = production
output: >
Exec to production pod (user=%ka.user.name
pod=%ka.target.name ns=%ka.target.namespace)
priority: WARNING
tags: [k8s, exec, production]
Create Prometheus Alerts
- alert: ExecToProductionPod
expr: |
increase(apiserver_audit_event_total{
verb="create",
subresource="exec",
namespace="production"
}[5m]) > 0
labels:
severity: warning
annotations:
summary: "kubectl exec used in production namespace"
description: "User attempted exec in production (check audit logs)"
Query Metrics
# Count exec operations per namespace
kubectl top nodes
kubectl get events -A --field-selector reason=ExecStarted
# Check Prometheus metrics
curl -s 'http://prometheus:9090/api/v1/query?query=apiserver_audit_event_total{subresource="exec"}'
8. Use Pod Security Standards
Issue: Privileged pods allow easier credential extraction and system compromise via exec.
Fix: Enforce restricted Pod Security Standards to limit container capabilities.
Apply Restricted Profile
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Deny Privileged Containers
apiVersion: v1
kind: Pod
metadata:
name: webapp
namespace: production
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:1.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
9. Implement Break-Glass Procedures
Issue: Completely blocking exec access prevents legitimate emergency debugging.
Fix: Implement time-bound break-glass access with approval workflows.
Create Just-in-Time Access Request
apiVersion: v1
kind: ServiceAccount
metadata:
name: emergency-debug
namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: emergency-exec
namespace: production
rules:
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
---
# Initially no binding - created on-demand
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: emergency-access-user123
namespace: production
annotations:
expires: "2024-12-31T23:59:59Z"
approver: "manager@company.com"
ticket: "INC-12345"
subjects:
- kind: User
name: user@company.com
roleRef:
kind: Role
name: emergency-exec
apiGroup: rbac.authorization.k8s.io
Automated Expiration Script
#!/bin/bash
# Remove expired emergency access
kubectl get rolebindings -A -o json | \
jq -r '.items[] |
select(.metadata.annotations.expires != null) |
select(.metadata.annotations.expires < now | strftime("%Y-%m-%dT%H:%M:%SZ")) |
"\(.metadata.namespace) \(.metadata.name)"' | \
while read ns name; do
echo "Removing expired access: $ns/$name"
kubectl delete rolebinding -n "$ns" "$name"
done
Security Checklist
- Deny exec and attach permissions by default in all RBAC roles
- Grant exec/attach access only to specific pods when absolutely necessary
- Implement admission webhooks to validate and audit exec operations
- Enable comprehensive audit logging for pods/exec and pods/attach
- Use ephemeral debug containers instead of exec for troubleshooting
- Build distroless images without shells for production workloads
- Implement network policies to restrict container connectivity
- Deploy Falco rules to detect suspicious exec activity
- Set up alerts for exec operations in production namespaces
- Apply restricted Pod Security Standards to all production namespaces
- Implement just-in-time access with automatic expiration
- Regularly audit who has exec permissions and why
- Document legitimate use cases for exec access