1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# Copyright (c) 2025 Erick Bourgeois, firestoned
# SPDX-License-Identifier: MIT
#
# Pod/container hardening reference for the bindcar sidecar (K-3) plus an egress
# companion to the ingress NetworkPolicy (N-1).
#
# bindcar runs as a sidecar inside the BIND9 pods created by the bindy operator
# and serves a privileged mutate API. Without a runtime securityContext, a single
# RCE in the HTTP handler lands in a container that can read its auto-mounted
# ServiceAccount token and pivot. These controls are independent defense-in-depth
# layers; apply them to the bindcar container in the bindy pod template.
#
# This file is REFERENCE configuration: the snippet below shows the exact fields
# to set on the bindy-managed pod (it is not directly applyable on its own).
---
# --- Container securityContext (apply to the bindcar sidecar container) -------
# containers:
# - name: bindcar
# securityContext:
# allowPrivilegeEscalation: false # no setuid escalation
# readOnlyRootFilesystem: true # immutable rootfs; mount an
# # emptyDir at $TMPDIR for the
# # 0600 TSIG key file (nsupdate -k)
# runAsNonRoot: true
# runAsUser: 65532 # matches the distroless/chainguard image
# capabilities:
# drop: ["ALL"] # bindcar needs no Linux capabilities
# seccompProfile:
# type: RuntimeDefault
#
# --- Pod-level settings -------------------------------------------------------
# spec:
# serviceAccountName: bindcar # the least-privilege SA (deploy/rbac.yaml)
# automountServiceAccountToken: false # withhold the token unless TokenReview
# # is in use; in drone/explicit mode
# # bindcar needs no cluster token
# securityContext:
# seccompProfile:
# type: RuntimeDefault
#
# NOTE: if readOnlyRootFilesystem is true, give bindcar a writable $TMPDIR so the
# TSIG key file can be created (it is 0600 and removed immediately after nsupdate
# exits). Mount a small tmpfs/emptyDir and set TMPDIR to it:
# volumeMounts: [{ name: tmp, mountPath: /tmp }]
# volumes: [{ name: tmp, emptyDir: { medium: Memory } }]
---
# ENFORCED control (A5). The securityContext block above is a reference an
# operator must transcribe into the bindy pod template — nothing stops a pod that
# omits it. Pod Security Admission is the applyable backstop: with these labels
# the API server REJECTS any pod admitted to bindy-system that is not
# "restricted" (non-root, no privilege escalation, seccomp RuntimeDefault, all
# capabilities dropped). "restricted" still permits adding back only
# NET_BIND_SERVICE, so the co-located `named` container can bind port 53.
#
# If the bindy operator owns/creates this namespace, apply these labels to ITS
# manifest instead of shipping a competing Namespace object, or run:
# kubectl label ns bindy-system \
# pod-security.kubernetes.io/enforce=restricted --overwrite
apiVersion: v1
kind: Namespace
metadata:
name: bindy-system
labels:
app.kubernetes.io/name: bindcar
app.kubernetes.io/component: pod-security
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
---
# Egress companion to deploy/networkpolicy.yaml (N-1). The ingress policy is
# Ingress-only, which leaves egress wide open — a compromised pod could exfiltrate
# the SA token or zone data anywhere. This restricts egress to what bindcar/named
# actually need: DNS resolution, the Kubernetes API server (TokenReview), and the
# RNDC/zone-transfer peers. Tighten the CIDRs/selectors to your environment.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: bindcar-egress-restrict
namespace: bindy-system
labels:
app.kubernetes.io/name: bindcar
app.kubernetes.io/component: network-policy
spec:
podSelector:
matchLabels:
app: bind9
app.kubernetes.io/part-of: bindy
policyTypes:
- Egress
egress:
# DNS resolution — scoped to the in-cluster DNS service (CoreDNS/kube-dns in
# kube-system). This removes the "DNS to anywhere" tunneling/exfil channel
# (A6). If `named` must reach EXTERNAL zone-transfer peers or upstream
# forwarders on 53, add their explicit CIDRs to the `to:` list below — do NOT
# drop the selector to make it open again.
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
# - ipBlock: { cidr: <secondary-or-forwarder-CIDR> } # zone transfers / forwarding
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Kubernetes API server for TokenReview. This MUST be scoped to your API
# server endpoint. The placeholder below is intentionally restrictive
# (fail-closed): left unedited, TokenReview egress is denied and auth fails
# loudly, rather than the pod being able to reach ANY 443/6443 host on the
# network — which was the SA-token exfil / lateral-movement channel (A6).
# Replace the CIDR with your kube-apiserver address (or use a
# namespaceSelector for a private-endpoint service).
- to:
- ipBlock:
cidr: 10.0.0.1/32 # REPLACE: kube-apiserver endpoint/CIDR
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 6443