Privileged or Host-Namespace Containers
Looks for containers running privileged, sharing the host's PID, network or IPC namespace, or with dangerous added capabilities.
- Why It Matters
- A privileged container, or one with CAP_SYS_ADMIN or host namespaces, has almost no isolation: a compromise of the application inside is a compromise of the host.
- How It Detects
- Reads /var/lib/docker/containers/*/hostconfig.json (needs root) and reports Privileged true, PidMode/NetworkMode/IpcMode host, or CapAdd containing ALL, SYS_ADMIN, SYS_PTRACE, SYS_MODULE or NET_ADMIN.
- How to Fix
- Recreate each container without --privileged, --pid=host, --net=host and --ipc=host; drop all capabilities and add back only the specific ones needed (--cap-drop=ALL --cap-add=...).
- How to Verify
docker inspect --format '{{.Name}} {{.HostConfig.Privileged}} {{.HostConfig.PidMode}} {{.HostConfig.NetworkMode}} {{.HostConfig.CapAdd}}' $(docker ps -aq)- CVSS v3.1 Vector
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H- Control Category
- Secure Configuration: CIS Controls v8 4; NIST CSF 2.0 PR.PS
- Fix Effort
- Medium
- CIS Controls v8 Safeguard
- 4.1
- NIST CSF 2.0 Subcategory
- PR.PS-01
- CWE
- CWE-250, CWE-653
- NIST SP 800-53 Rev. 5
- AC-6, SC-39
- References
- NIST SP 800-190, Application Container Security Guide
OWASP Docker Security Cheat Sheet
Source Code
The complete file that defines this check. Run it with openvapt scan --only HOST-CTR-003, or read it offline with openvapt explain HOST-CTR-003.
internal/checks/host/containers.go · Lines 1-322View on GitHub at This Commit
| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "path/filepath" |
| 7 | "regexp" |
| 8 | "strconv" |
| 9 | "strings" |
| 10 | |
| 11 | "github.com/openvapt/openvapt/internal/check" |
| 12 | "github.com/openvapt/openvapt/internal/sys" |
| 13 | ) |
| 14 | |
| 15 | var ( |
| 16 | refDockerSec = check.Ref{Title: "Docker documentation, Docker Engine security", URL: "https://docs.docker.com/engine/security/"} |
| 17 | refDockerSock = check.Ref{Title: "Docker documentation, Protect the Docker daemon socket", URL: "https://docs.docker.com/engine/security/protect-access/"} |
| 18 | refOWASPDock = check.Ref{Title: "OWASP Docker Security Cheat Sheet", URL: "https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html"} |
| 19 | refKubelet = check.Ref{Title: "Kubernetes documentation, Kubelet authentication and authorization", URL: "https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/"} |
| 20 | refK8sSec = check.Ref{Title: "Kubernetes documentation, Securing a cluster", URL: "https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/"} |
| 21 | refNIST190 = check.Ref{Title: "NIST SP 800-190, Application Container Security Guide", URL: "https://csrc.nist.gov/pubs/sp/800/190/final"} |
| 22 | ) |
| 23 | |
| 24 | // dockerDaemon is the subset of /etc/docker/daemon.json these checks read. |
| 25 | type dockerDaemon struct { |
| 26 | Hosts []string `json:"hosts"` |
| 27 | TLSVerify bool `json:"tlsverify"` |
| 28 | NoNewPrivileges bool `json:"no-new-privileges"` |
| 29 | ICC *bool `json:"icc"` |
| 30 | UsernsRemap string `json:"userns-remap"` |
| 31 | LiveRestore bool `json:"live-restore"` |
| 32 | } |
| 33 | |
| 34 | // hostConfig is the subset of a container's hostconfig.json these checks read. |
| 35 | type hostConfig struct { |
| 36 | Privileged bool `json:"Privileged"` |
| 37 | PidMode string `json:"PidMode"` |
| 38 | NetworkMode string `json:"NetworkMode"` |
| 39 | IpcMode string `json:"IpcMode"` |
| 40 | CapAdd []string `json:"CapAdd"` |
| 41 | Binds []string `json:"Binds"` |
| 42 | } |
| 43 | |
| 44 | // containerConfigs reads every container's hostconfig.json (needs root). The |
| 45 | // container id is shortened to 12 characters in evidence, like docker ps. |
| 46 | func containerConfigs(ctx context.Context, s sys.System) (map[string]hostConfig, bool) { |
| 47 | paths, _ := s.Glob("/var/lib/docker/containers/*/hostconfig.json") |
| 48 | out := map[string]hostConfig{} |
| 49 | complete := true |
| 50 | for _, p := range paths { |
| 51 | r, err := s.ReadFile(ctx, p) |
| 52 | if err != nil { |
| 53 | complete = false |
| 54 | continue |
| 55 | } |
| 56 | var hc hostConfig |
| 57 | if json.Unmarshal(r.Data, &hc) != nil || !r.Complete { |
| 58 | complete = false |
| 59 | continue |
| 60 | } |
| 61 | id := filepath.Base(filepath.Dir(p)) |
| 62 | if len(id) > 12 { |
| 63 | id = id[:12] |
| 64 | } |
| 65 | out[id] = hc |
| 66 | } |
| 67 | return out, complete |
| 68 | } |
| 69 | |
| 70 | func init() { |
| 71 | check.Register(&check.Check{ |
| 72 | ID: "HOST-CTR-001", Title: "Docker API Exposed Over TCP Without TLS", Domain: check.DomainHost, |
| 73 | Severity: check.Critical, Category: check.CatNetwork, Effort: check.EffortLow, |
| 74 | CVSS: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", |
| 75 | Summary: "Checks whether the Docker daemon is configured to listen on a TCP socket without TLS client verification.", |
| 76 | Rationale: "The Docker API is equivalent to root on the host. Exposed over TCP without mutual TLS, anyone who can reach the port can start a privileged container and take over the machine; this is a common cause of cryptomining infections.", |
| 77 | Detection: "Reads /etc/docker/daemon.json (hosts and tlsverify) and Docker systemd unit files and drop-ins for -H tcp:// flags.", |
| 78 | Fix: "Remove tcp:// listeners and use the local Unix socket or SSH (docker context with ssh://). If remote access is unavoidable, enable tlsverify with client certificates and restrict the port in the firewall.", |
| 79 | Verify: "ss -tlnp | grep -E ':(2375|2376)' and grep -r 'tcp://' /etc/docker /etc/systemd/system/docker.service.d", |
| 80 | Refs: []check.Ref{refDockerSock, refOWASPDock}, |
| 81 | Standards: check.Standards{CWE: []string{"CWE-306", "CWE-319"}, OWASP: []string{"A05:2021", "A07:2021"}, NIST: []string{"AC-3", "SC-8"}}, |
| 82 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 83 | var tcp []string |
| 84 | tlsOn := false |
| 85 | found := false |
| 86 | if t, _, ok := readText(ctx, s, "/etc/docker/daemon.json"); ok { |
| 87 | found = true |
| 88 | var d dockerDaemon |
| 89 | if json.Unmarshal([]byte(t), &d) == nil { |
| 90 | tlsOn = d.TLSVerify |
| 91 | for _, h := range d.Hosts { |
| 92 | if strings.HasPrefix(h, "tcp://") { |
| 93 | tcp = append(tcp, "daemon.json hosts: "+h) |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | } |
| 98 | units := []string{"/lib/systemd/system/docker.service", "/usr/lib/systemd/system/docker.service"} |
| 99 | more, _ := s.Glob("/etc/systemd/system/docker.service.d/*.conf") |
| 100 | units = append(units, more...) |
| 101 | for _, u := range units { |
| 102 | t, _, ok := readText(ctx, s, u) |
| 103 | if !ok { |
| 104 | continue |
| 105 | } |
| 106 | found = true |
| 107 | for _, line := range strings.Split(t, "\n") { |
| 108 | if strings.Contains(line, "tcp://") && !strings.HasPrefix(strings.TrimSpace(line), "#") { |
| 109 | tcp = append(tcp, u+": "+strings.TrimSpace(line)) |
| 110 | if strings.Contains(line, "--tlsverify") { |
| 111 | tlsOn = true |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | if !found { |
| 117 | return []check.Result{check.SkipR("docker", "Docker is not installed")} |
| 118 | } |
| 119 | if len(tcp) > 0 && !tlsOn { |
| 120 | return []check.Result{check.FailR("docker", "Docker API listens on TCP without TLS verification", tcp...)} |
| 121 | } |
| 122 | return []check.Result{check.PassR("docker", "Docker API is not exposed over unauthenticated TCP")} |
| 123 | }, |
| 124 | }) |
| 125 | |
| 126 | check.Register(&check.Check{ |
| 127 | ID: "HOST-CTR-002", Title: "Docker Daemon Not Hardened", Domain: check.DomainHost, |
| 128 | Severity: check.Medium, Category: check.CatConfig, Effort: check.EffortMedium, |
| 129 | CVSS: "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:L", |
| 130 | Summary: "Checks the Docker daemon defaults: no-new-privileges, inter-container communication, user namespace remapping and live restore.", |
| 131 | Rationale: "Hardened daemon defaults limit what a compromised container can do: no-new-privileges blocks setuid escalation, disabling icc isolates containers, and user namespaces map container root to an unprivileged host user.", |
| 132 | Detection: "Reads /etc/docker/daemon.json and lists each hardening setting that is missing: no-new-privileges true, icc false, userns-remap set, live-restore true.", |
| 133 | Fix: "Add to /etc/docker/daemon.json: \"no-new-privileges\": true, \"icc\": false, \"userns-remap\": \"default\", \"live-restore\": true then restart Docker after testing your containers.", |
| 134 | Verify: "cat /etc/docker/daemon.json ; docker info | grep -Ei 'security|userns'", |
| 135 | Refs: []check.Ref{refDockerSec, refNIST190}, |
| 136 | Standards: check.Standards{CWE: []string{"CWE-250", "CWE-1188"}, NIST: []string{"CM-6", "AC-6"}, CISTopic: "Docker daemon configuration"}, |
| 137 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 138 | t, complete, ok := readText(ctx, s, "/etc/docker/daemon.json") |
| 139 | if !ok { |
| 140 | if _, err := s.Stat("/var/lib/docker"); err != nil { |
| 141 | return []check.Result{check.SkipR("docker", "Docker is not installed")} |
| 142 | } |
| 143 | return []check.Result{check.FailR("/etc/docker/daemon.json", "No daemon.json: all hardening options are at their defaults")} |
| 144 | } |
| 145 | var d dockerDaemon |
| 146 | if json.Unmarshal([]byte(t), &d) != nil || !complete { |
| 147 | return []check.Result{check.IncompleteR("/etc/docker/daemon.json", "daemon.json could not be parsed")} |
| 148 | } |
| 149 | var miss []string |
| 150 | if !d.NoNewPrivileges { |
| 151 | miss = append(miss, "no-new-privileges is not true") |
| 152 | } |
| 153 | if d.ICC == nil || *d.ICC { |
| 154 | miss = append(miss, "icc is not false") |
| 155 | } |
| 156 | if d.UsernsRemap == "" { |
| 157 | miss = append(miss, "userns-remap is not set") |
| 158 | } |
| 159 | if !d.LiveRestore { |
| 160 | miss = append(miss, "live-restore is not true") |
| 161 | } |
| 162 | if len(miss) > 0 { |
| 163 | return []check.Result{check.FailR("/etc/docker/daemon.json", "Docker daemon hardening options are missing", miss...)} |
| 164 | } |
| 165 | return []check.Result{check.PassR("/etc/docker/daemon.json", "Docker daemon hardening options are set")} |
| 166 | }, |
| 167 | }) |
| 168 | |
| 169 | check.Register(&check.Check{ |
| 170 | ID: "HOST-CTR-003", Title: "Privileged or Host-Namespace Containers", Domain: check.DomainHost, |
| 171 | Severity: check.High, Category: check.CatConfig, Effort: check.EffortMedium, |
| 172 | CVSS: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", |
| 173 | Summary: "Looks for containers running privileged, sharing the host's PID, network or IPC namespace, or with dangerous added capabilities.", |
| 174 | Rationale: "A privileged container, or one with CAP_SYS_ADMIN or host namespaces, has almost no isolation: a compromise of the application inside is a compromise of the host.", |
| 175 | Detection: "Reads /var/lib/docker/containers/*/hostconfig.json (needs root) and reports Privileged true, PidMode/NetworkMode/IpcMode host, or CapAdd containing ALL, SYS_ADMIN, SYS_PTRACE, SYS_MODULE or NET_ADMIN.", |
| 176 | Fix: "Recreate each container without --privileged, --pid=host, --net=host and --ipc=host; drop all capabilities and add back only the specific ones needed (--cap-drop=ALL --cap-add=...).", |
| 177 | Verify: "docker inspect --format '{{.Name}} {{.HostConfig.Privileged}} {{.HostConfig.PidMode}} {{.HostConfig.NetworkMode}} {{.HostConfig.CapAdd}}' $(docker ps -aq)", |
| 178 | Refs: []check.Ref{refNIST190, refOWASPDock}, |
| 179 | Standards: check.Standards{CWE: []string{"CWE-250", "CWE-653"}, NIST: []string{"AC-6", "SC-39"}}, |
| 180 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 181 | cfgs, complete := containerConfigs(ctx, s) |
| 182 | if len(cfgs) == 0 { |
| 183 | if !complete { |
| 184 | return []check.Result{check.IncompleteR("containers", "Container configuration could not be read (run as root)")} |
| 185 | } |
| 186 | return []check.Result{check.SkipR("containers", "No Docker containers found")} |
| 187 | } |
| 188 | danger := map[string]bool{"ALL": true, "SYS_ADMIN": true, "SYS_PTRACE": true, "SYS_MODULE": true, "NET_ADMIN": true, "CAP_SYS_ADMIN": true} |
| 189 | var bad []string |
| 190 | for id, hc := range cfgs { |
| 191 | var why []string |
| 192 | if hc.Privileged { |
| 193 | why = append(why, "privileged") |
| 194 | } |
| 195 | for name, mode := range map[string]string{"pid": hc.PidMode, "network": hc.NetworkMode, "ipc": hc.IpcMode} { |
| 196 | if mode == "host" { |
| 197 | why = append(why, name+"=host") |
| 198 | } |
| 199 | } |
| 200 | for _, c := range hc.CapAdd { |
| 201 | if danger[strings.ToUpper(c)] { |
| 202 | why = append(why, "cap-add "+c) |
| 203 | } |
| 204 | } |
| 205 | if len(why) > 0 { |
| 206 | bad = append(bad, id+": "+strings.Join(why, ", ")) |
| 207 | } |
| 208 | } |
| 209 | if len(bad) > 0 { |
| 210 | return []check.Result{check.FailR("containers", "Containers with weak isolation", bad...)} |
| 211 | } |
| 212 | if !complete { |
| 213 | return []check.Result{check.IncompleteR("containers", "Some container configurations could not be read")} |
| 214 | } |
| 215 | return []check.Result{check.PassR("containers", strconv.Itoa(len(cfgs))+" container(s) checked; none privileged or sharing host namespaces")} |
| 216 | }, |
| 217 | }) |
| 218 | |
| 219 | check.Register(&check.Check{ |
| 220 | ID: "HOST-CTR-004", Title: "Containers Mount the Docker Socket or Host Root", Domain: check.DomainHost, |
| 221 | Severity: check.High, Category: check.CatAccess, Effort: check.EffortMedium, |
| 222 | CVSS: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", |
| 223 | Summary: "Looks for containers that bind-mount the Docker socket, the host root filesystem or sensitive host paths.", |
| 224 | Rationale: "A container with /var/run/docker.sock can control Docker and therefore the host. Mounting / or /etc gives direct write access to host configuration.", |
| 225 | Detection: "Reads Binds from /var/lib/docker/containers/*/hostconfig.json (needs root) and reports mounts of /var/run/docker.sock, /run/docker.sock, /, /etc, /root, /proc or /sys from the host.", |
| 226 | Fix: "Remove these mounts. If a tool genuinely needs the Docker API, use a restricted socket proxy that allows only the required read-only calls.", |
| 227 | Verify: "docker inspect --format '{{.Name}} {{.HostConfig.Binds}}' $(docker ps -aq)", |
| 228 | Refs: []check.Ref{refDockerSock, refNIST190}, |
| 229 | Standards: check.Standards{CWE: []string{"CWE-250", "CWE-732"}, NIST: []string{"AC-6", "SC-39"}}, |
| 230 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 231 | cfgs, complete := containerConfigs(ctx, s) |
| 232 | if len(cfgs) == 0 { |
| 233 | if !complete { |
| 234 | return []check.Result{check.IncompleteR("containers", "Container configuration could not be read (run as root)")} |
| 235 | } |
| 236 | return []check.Result{check.SkipR("containers", "No Docker containers found")} |
| 237 | } |
| 238 | sensitive := map[string]bool{"/var/run/docker.sock": true, "/run/docker.sock": true, "/": true, "/etc": true, "/root": true, "/proc": true, "/sys": true} |
| 239 | var bad []string |
| 240 | for id, hc := range cfgs { |
| 241 | for _, b := range hc.Binds { |
| 242 | src := strings.SplitN(b, ":", 2)[0] |
| 243 | if sensitive[filepath.Clean(src)] { |
| 244 | bad = append(bad, id+": "+b) |
| 245 | } |
| 246 | } |
| 247 | } |
| 248 | if len(bad) > 0 { |
| 249 | return []check.Result{check.FailR("containers", "Containers mount the Docker socket or sensitive host paths", bad...)} |
| 250 | } |
| 251 | return []check.Result{check.PassR("containers", "No containers mount the Docker socket or host root")} |
| 252 | }, |
| 253 | }) |
| 254 | |
| 255 | check.Register(&check.Check{ |
| 256 | ID: "HOST-K8S-001", Title: "Kubelet Allows Anonymous or Unauthorised Access", Domain: check.DomainHost, |
| 257 | Severity: check.Critical, Category: check.CatIdentity, Effort: check.EffortMedium, |
| 258 | CVSS: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", |
| 259 | Summary: "Checks the kubelet configuration for anonymous authentication, AlwaysAllow authorisation or the unauthenticated read-only port.", |
| 260 | Rationale: "The kubelet API can run commands in every pod on the node. With anonymous access or AlwaysAllow, anyone who can reach port 10250 controls the node's workloads; the read-only port 10255 leaks pod and environment details.", |
| 261 | Detection: "Reads /var/lib/kubelet/config.yaml and looks for authentication.anonymous.enabled: true, authorization.mode: AlwaysAllow, or a non-zero readOnlyPort.", |
| 262 | Fix: "Set authentication.anonymous.enabled: false, authorization.mode: Webhook and readOnlyPort: 0 in the kubelet configuration, then restart the kubelet.", |
| 263 | Verify: "grep -A2 -E 'anonymous|authorization|readOnlyPort' /var/lib/kubelet/config.yaml", |
| 264 | Refs: []check.Ref{refKubelet, refK8sSec}, |
| 265 | Standards: check.Standards{CWE: []string{"CWE-306", "CWE-862"}, OWASP: []string{"A01:2021", "A07:2021"}, NIST: []string{"AC-3", "IA-2"}}, |
| 266 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 267 | t, _, ok := readText(ctx, s, "/var/lib/kubelet/config.yaml") |
| 268 | if !ok { |
| 269 | return []check.Result{check.SkipR("kubelet", "No kubelet configuration found")} |
| 270 | } |
| 271 | var bad []string |
| 272 | if regexp.MustCompile(`(?m)anonymous:\s*\n\s+enabled:\s*true`).MatchString(t) { |
| 273 | bad = append(bad, "authentication.anonymous.enabled: true") |
| 274 | } |
| 275 | if regexp.MustCompile(`(?m)^\s*mode:\s*AlwaysAllow`).MatchString(t) { |
| 276 | bad = append(bad, "authorization.mode: AlwaysAllow") |
| 277 | } |
| 278 | if m := regexp.MustCompile(`(?m)^\s*readOnlyPort:\s*(\d+)`).FindStringSubmatch(t); m != nil && m[1] != "0" { |
| 279 | bad = append(bad, "readOnlyPort: "+m[1]) |
| 280 | } |
| 281 | if len(bad) > 0 { |
| 282 | return []check.Result{check.FailR("/var/lib/kubelet/config.yaml", "Kubelet allows unauthenticated or unauthorised access", bad...)} |
| 283 | } |
| 284 | return []check.Result{check.PassR("/var/lib/kubelet/config.yaml", "Kubelet requires authentication and authorisation")} |
| 285 | }, |
| 286 | }) |
| 287 | |
| 288 | check.Register(&check.Check{ |
| 289 | ID: "HOST-K8S-002", Title: "Kubernetes Data Directory Accessible to Others", Domain: check.DomainHost, |
| 290 | Severity: check.High, Category: check.CatAccess, Effort: check.EffortLow, |
| 291 | CVSS: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N", |
| 292 | Summary: "Checks that the etcd data directory is accessible only by its owner.", |
| 293 | Rationale: "etcd stores every Kubernetes object, including Secrets, usually unencrypted at rest. Read access to its data directory exposes all cluster credentials.", |
| 294 | Detection: "Stats /var/lib/etcd and fails if group or others have any permission.", |
| 295 | Fix: "chmod 700 /var/lib/etcd && chown etcd:etcd /var/lib/etcd (or root:root for kubeadm static pods). Enable encryption at rest for Secrets.", |
| 296 | Verify: "stat -c '%a %U:%G %n' /var/lib/etcd", |
| 297 | Refs: []check.Ref{{Title: "Kubernetes documentation, Operating etcd clusters for Kubernetes", URL: "https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/"}, refK8sSec}, |
| 298 | Standards: check.Standards{CWE: []string{"CWE-732"}, NIST: []string{"AC-3", "SC-28"}}, |
| 299 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 300 | st, err := s.Stat("/var/lib/etcd") |
| 301 | if err != nil { |
| 302 | return []check.Result{check.SkipR("/var/lib/etcd", "No etcd data directory found")} |
| 303 | } |
| 304 | if st.Mode.Perm()&0o077 != 0 { |
| 305 | return []check.Result{check.FailR("/var/lib/etcd", "etcd data directory is accessible by group or others", "mode "+perm(st.Mode))} |
| 306 | } |
| 307 | return []check.Result{check.PassR("/var/lib/etcd", "etcd data directory is owner-only", "mode "+perm(st.Mode))} |
| 308 | }, |
| 309 | }) |
| 310 | |
| 311 | registerFileMode(fileModeRule{ |
| 312 | id: "HOST-K8S-003", title: "Kubernetes Credential Files Readable by Others", sev: check.High, cat: check.CatIdentity, |
| 313 | cvss: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", |
| 314 | summary: "Checks that kubeconfig files and Kubernetes PKI private keys are not readable by other users.", |
| 315 | rationale: "admin.conf and the cluster CA key grant full control of the cluster; any local user who can read them becomes a cluster administrator.", |
| 316 | fix: "chmod 600 /etc/kubernetes/*.conf /etc/kubernetes/pki/*.key /etc/kubernetes/pki/etcd/*.key && chown root:root on each.", |
| 317 | verify: "stat -c '%a %n' /etc/kubernetes/*.conf /etc/kubernetes/pki/*.key", |
| 318 | patterns: []string{"/etc/kubernetes/admin.conf", "/etc/kubernetes/super-admin.conf", "/etc/kubernetes/kubelet.conf", "/etc/kubernetes/controller-manager.conf", "/etc/kubernetes/scheduler.conf", "/etc/kubernetes/pki/*.key", "/etc/kubernetes/pki/etcd/*.key"}, |
| 319 | bad: 0o044 | 0o022, why: "readable or writable by group or others", cwe: "CWE-732", |
| 320 | refs: []check.Ref{refK8sSec}, |
| 321 | }) |
| 322 | } |
Also available as plain Markdown for notes, scripts and AI tools. Same facts, same source, no styling.
Cite This Page
If this page helped your work, please cite it. The version and web address are filled in; the access date is today's date in your browser. Every other style, and when credit is required, is on How to Cite OpenVAPT.
OpenVAPT. (2026). HOST-CTR-003 Privileged or Host-Namespace Containers (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/HOST-CTR-003.html
In text: (OpenVAPT, 2026). Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
@misc{openvapt_host_ctr_003,
author = {{OpenVAPT}},
title = {{HOST-CTR-003 Privileged or Host-Namespace Containers}},
year = {2026},
howpublished = {\url{https://openvapt.com/checks/HOST-CTR-003.html}},
note = {OpenVAPT Check Catalogue, version 0.1.0-alpha.6. Accessed 16 October 2026}
}
Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
Citing OpenVAPT does not mean OpenVAPT endorses, approves or has reviewed your work.