spottersec 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spottersec-0.6.0/LICENSE +21 -0
- spottersec-0.6.0/PKG-INFO +491 -0
- spottersec-0.6.0/README.md +458 -0
- spottersec-0.6.0/pyproject.toml +55 -0
- spottersec-0.6.0/setup.cfg +4 -0
- spottersec-0.6.0/spottersec.egg-info/PKG-INFO +491 -0
- spottersec-0.6.0/spottersec.egg-info/SOURCES.txt +10 -0
- spottersec-0.6.0/spottersec.egg-info/dependency_links.txt +1 -0
- spottersec-0.6.0/spottersec.egg-info/entry_points.txt +2 -0
- spottersec-0.6.0/spottersec.egg-info/requires.txt +6 -0
- spottersec-0.6.0/spottersec.egg-info/top_level.txt +1 -0
- spottersec-0.6.0/tests/test_scanner.py +105 -0
spottersec-0.6.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SpotterSec
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spottersec
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Kubernetes security scanner — attack paths, RBAC blast radius, interactive TUI
|
|
5
|
+
Author-email: SpotterSec <hello@spottersec.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://spottersec.com
|
|
8
|
+
Project-URL: Repository, https://github.com/spottersec/spotter
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/spottersec/spotter/issues
|
|
10
|
+
Keywords: kubernetes,security,k8s,rbac,scanner,devsecops,pentest,audit,attack-path
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: PyYAML>=6.0
|
|
28
|
+
Requires-Dist: click>=8.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# SpotterSec
|
|
35
|
+
|
|
36
|
+
**Kubernetes security scanner that traces attack paths, not just misconfigs.**
|
|
37
|
+
|
|
38
|
+
SpotterSec goes beyond linting — it finds exploitable chains from internet exposure through SA token theft to cluster-admin, scores RBAC blast radius per service account, and gives you the exact `kubectl patch` command to fix each finding. Built for security engineers and operators who want to understand *what an attacker would actually do*.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
spottersec audit --interactive
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
23 CRITICAL · 65 HIGH · 137 MEDIUM · 84 LOW (309 findings)
|
|
46
|
+
Risk Score 99/100 F Critical Risk
|
|
47
|
+
|
|
48
|
+
⚡ Dangerous combinations detected:
|
|
49
|
+
+10 hostPath + hostNetwork = node filesystem + network sniff
|
|
50
|
+
+12 hostPID + SYS_PTRACE = dump memory of any process
|
|
51
|
+
+20 Privileged pod + cluster-admin = instant full cluster
|
|
52
|
+
|
|
53
|
+
▶ ClusterRoleBinding/cluster-reconciler-flux-system
|
|
54
|
+
💀 CRITICAL cluster-admin binding → ServiceAccount/kustomize-controller
|
|
55
|
+
|
|
56
|
+
▶ Pod/monitoring/node-exporter (11 findings)
|
|
57
|
+
💀 CRITICAL hostPath mount on dangerous path: /
|
|
58
|
+
💀 CRITICAL hostPath mount on dangerous path: /proc
|
|
59
|
+
🔴 HIGH hostNetwork: true — host network namespace
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install spotter
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or from source:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/spottersec/spotter
|
|
74
|
+
cd cli
|
|
75
|
+
pip install .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Requirements:** Python 3.10+, `kubectl` in PATH for live cluster commands
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Scan a YAML file
|
|
86
|
+
spottersec scan pod.yaml
|
|
87
|
+
|
|
88
|
+
# Scan your live cluster and browse findings interactively
|
|
89
|
+
spottersec audit --interactive
|
|
90
|
+
|
|
91
|
+
# Trace all attack paths from internet to cluster-admin
|
|
92
|
+
spottersec attack
|
|
93
|
+
|
|
94
|
+
# What can an attacker do with this pod's SA token?
|
|
95
|
+
spottersec blast-radius my-pod-name
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Commands
|
|
101
|
+
|
|
102
|
+
### `spottersec scan` — Scan YAML files
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
spottersec scan pod.yaml
|
|
106
|
+
spottersec scan ./k8s/ --explain --group
|
|
107
|
+
spottersec scan - --explain # stdin — pipe from kubectl
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Flags:**
|
|
111
|
+
|
|
112
|
+
| Flag | Description |
|
|
113
|
+
|------|-------------|
|
|
114
|
+
| `--explain` | Show full attack chains and fix snippets |
|
|
115
|
+
| `--group` | Group findings by resource |
|
|
116
|
+
| `--output` | `table` (default) \| `json` \| `sarif` |
|
|
117
|
+
| `--fail-on` | Exit 1 if findings at this severity or above exist |
|
|
118
|
+
| `--severity` | Only show findings at or above this level |
|
|
119
|
+
| `--ignore` | Comma-separated rule IDs to suppress |
|
|
120
|
+
| `--fix` | Print auto-patched YAML to stdout |
|
|
121
|
+
| `--diff` | Show unified diff of what `--fix` would change |
|
|
122
|
+
| `--write-fix` | Overwrite file(s) with patched YAML in place |
|
|
123
|
+
|
|
124
|
+
**Pipe from kubectl:**
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
kubectl get pod mypod -o yaml | spottersec scan -
|
|
128
|
+
kubectl get deployments -A -o yaml | spottersec scan - --explain
|
|
129
|
+
kubectl get clusterrolebindings,clusterroles -A -o yaml | spottersec scan -
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### `spottersec audit` — Live cluster scan
|
|
135
|
+
|
|
136
|
+
Fetches 18 resource types via `kubectl` and runs the full ruleset against your live cluster.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
spottersec audit # all namespaces
|
|
140
|
+
spottersec audit -n production # single namespace
|
|
141
|
+
spottersec audit --explain # full attack chains + fixes
|
|
142
|
+
spottersec audit --skip-system-roles # hide built-in k8s system roles
|
|
143
|
+
spottersec audit --interactive # scan then drop into TUI
|
|
144
|
+
spottersec audit --output json > scan.json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### `spottersec attack` — Attack path analysis
|
|
150
|
+
|
|
151
|
+
Builds a full cluster graph and traces every exploitable path from internet-exposed entry points through SA token theft to cluster-admin. Three categories of paths:
|
|
152
|
+
|
|
153
|
+
1. **Internet-exposed** — Ingress hosts + LoadBalancer/NodePort services with dangerous SAs
|
|
154
|
+
2. **Internal** — any pod with RCE and score ≥50, traced to full impact
|
|
155
|
+
3. **Residual SA risk** — completed Jobs whose SA still has dangerous RBAC bindings
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
spottersec attack
|
|
159
|
+
spottersec attack -n production
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Example output:**
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
INTERNET-EXPOSED PATHS (1 found)
|
|
166
|
+
|
|
167
|
+
Path 1 · Score 85/100
|
|
168
|
+
Entry: Ingress/monitoring/grafana [grafana.example.com]
|
|
169
|
+
├─▶ SA/monitoring/grafana (token theft — 85/100)
|
|
170
|
+
├─▶ kubectl get secrets -A (Step 1 — exfil DB creds, TLS keys)
|
|
171
|
+
└─▶ kubectl run pwn --overrides hostPID+hostNetwork (Step 2 — node escape)
|
|
172
|
+
|
|
173
|
+
INTERNAL PATHS (11 found)
|
|
174
|
+
|
|
175
|
+
Path 1 · Score 100/100
|
|
176
|
+
Entry: Pod/flux-system/kustomize-controller (RCE)
|
|
177
|
+
├─▶ SA/flux-system/kustomize-controller (token theft — 100/100)
|
|
178
|
+
└─▶ cluster-admin (Full cluster compromise)
|
|
179
|
+
|
|
180
|
+
RESIDUAL SA RISK (1 found)
|
|
181
|
+
Job helm-install-traefik (completed) → SA still has cluster-admin
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### `spottersec blast-radius` — RBAC blast radius
|
|
187
|
+
|
|
188
|
+
Given any pod or service account: what can an attacker do with that SA token? Resolves all RBAC bindings, scores 0–100, and shows the exact exploit steps.
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
spottersec blast-radius my-app # auto-detect pod/SA
|
|
192
|
+
spottersec blast-radius pod/production/api-server-abc123 # specific pod
|
|
193
|
+
spottersec blast-radius sa/flux-system/kustomize-controller # specific SA
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Example output:**
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
Target: ServiceAccount/flux-system/kustomize-controller
|
|
200
|
+
Score: 100/100 CRITICAL — cluster compromise likely
|
|
201
|
+
|
|
202
|
+
Capabilities if compromised:
|
|
203
|
+
💀 FULL CLUSTER-ADMIN (verbs:* resources:*)
|
|
204
|
+
|
|
205
|
+
Active RBAC bindings:
|
|
206
|
+
▶ ClusterRoleBinding/cluster-reconciler-flux-system
|
|
207
|
+
→ ClusterRole/cluster-admin (scope: cluster)
|
|
208
|
+
|
|
209
|
+
Attacker steps after RCE:
|
|
210
|
+
1. cat /var/run/secrets/kubernetes.io/serviceaccount/token
|
|
211
|
+
2. export TOKEN=$(cat ...)
|
|
212
|
+
3. kubectl get secrets -A --token=$TOKEN
|
|
213
|
+
4. kubectl create clusterrolebinding pwned \
|
|
214
|
+
--clusterrole=cluster-admin \
|
|
215
|
+
--serviceaccount=flux-system:kustomize-controller
|
|
216
|
+
5. Full cluster owned
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### `spottersec tui` — Interactive finding browser
|
|
222
|
+
|
|
223
|
+
Browse all findings interactively. Per finding: press `f` for the exact `kubectl patch` fix, `e` for the exploit chain with runnable commands.
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
spottersec tui # scan live cluster, then browse
|
|
227
|
+
spottersec tui results.json # load from saved scan
|
|
228
|
+
spottersec audit --interactive # scan then drop into TUI
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Navigation:**
|
|
232
|
+
|
|
233
|
+
| Key | Action |
|
|
234
|
+
|-----|--------|
|
|
235
|
+
| `↑↓` / number | Navigate findings |
|
|
236
|
+
| `Enter` | View finding detail |
|
|
237
|
+
| `f` | Fix view — exact kubectl patch for this resource |
|
|
238
|
+
| `e` | Exploit view — attacker commands |
|
|
239
|
+
| `←→` | Previous / next finding in detail view |
|
|
240
|
+
| `b` | Back to list |
|
|
241
|
+
| `q` | Quit |
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
### `spottersec fix` — Interactive patch approval
|
|
246
|
+
|
|
247
|
+
Review and apply fixes with a diff before each change.
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
spottersec fix ./k8s/ # review each fix before applying
|
|
251
|
+
spottersec fix ./k8s/ --yes # apply all without prompting (CI)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### `spottersec watch` — Re-scan on save
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
spottersec watch pod.yaml
|
|
260
|
+
spottersec watch ./k8s/ --explain
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### `spottersec trend` — Compare two scans
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
spottersec scan ./k8s/ --output json > baseline.json
|
|
269
|
+
# ... make changes ...
|
|
270
|
+
spottersec scan ./k8s/ --output json > current.json
|
|
271
|
+
spottersec trend baseline.json current.json
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
↓ 3 fixed ↑ 1 new = 18 unchanged
|
|
276
|
+
Risk: 72 → 61 (-11) C → C
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### `spottersec rules` — List all rules
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
spottersec rules
|
|
285
|
+
spottersec rules --severity high
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Rule Reference
|
|
291
|
+
|
|
292
|
+
50 rules across pods, RBAC, network, ingress, secrets, webhooks, and Helm.
|
|
293
|
+
|
|
294
|
+
### Container Security (SC)
|
|
295
|
+
|
|
296
|
+
| Rule | Severity | Description |
|
|
297
|
+
|------|----------|-------------|
|
|
298
|
+
| SC001 | 💀 CRITICAL | `privileged: true` — full container escape path |
|
|
299
|
+
| SC002 | 🔴 HIGH | `runAsUser: 0` — explicit root |
|
|
300
|
+
| SC003 | 🔴 HIGH | `runAsNonRoot: false` |
|
|
301
|
+
| SC004 | 🟡 MEDIUM | `runAsNonRoot` not set |
|
|
302
|
+
| SC005 | 🔴 HIGH | `allowPrivilegeEscalation: true` |
|
|
303
|
+
| SC006 | 🔵 LOW | `allowPrivilegeEscalation` not set |
|
|
304
|
+
| SC007 | 🔵 LOW | `readOnlyRootFilesystem` not set |
|
|
305
|
+
| SC008 | 🔵 LOW | No seccomp profile |
|
|
306
|
+
|
|
307
|
+
### Volumes (VOL)
|
|
308
|
+
|
|
309
|
+
| Rule | Severity | Description |
|
|
310
|
+
|------|----------|-------------|
|
|
311
|
+
| VOL001 | 💀 CRITICAL | `hostPath` mount on dangerous path (`/`, `/etc`, `/proc`, `/sys`, `/var/run`) |
|
|
312
|
+
|
|
313
|
+
### Namespace / Host (NS)
|
|
314
|
+
|
|
315
|
+
| Rule | Severity | Description |
|
|
316
|
+
|------|----------|-------------|
|
|
317
|
+
| NS001 | 🔴 HIGH | `hostPID: true` — process namespace sharing |
|
|
318
|
+
| NS002 | 🔴 HIGH | `hostNetwork: true` — host network namespace |
|
|
319
|
+
| NS003 | 🟡 MEDIUM | `hostIPC: true` — host IPC namespace |
|
|
320
|
+
| NS010 | 🔴 HIGH | Namespace missing Pod Security Admission enforce label |
|
|
321
|
+
| NS011 | 🔴 HIGH | Namespace PSA enforce level is `privileged` |
|
|
322
|
+
|
|
323
|
+
### RBAC
|
|
324
|
+
|
|
325
|
+
| Rule | Severity | Description |
|
|
326
|
+
|------|----------|-------------|
|
|
327
|
+
| RBAC001 | 💀 CRITICAL | `cluster-admin` binding |
|
|
328
|
+
| RBAC002 | 🔴 HIGH | Wildcard verbs on pod/workload resources |
|
|
329
|
+
| RBAC003 | 🔴 HIGH | Wildcard resources in RBAC rule |
|
|
330
|
+
| RBAC004 | 🔴 HIGH | Role grants Secrets read access |
|
|
331
|
+
| RBAC005 | 🔴 HIGH | Binding to built-in privileged role |
|
|
332
|
+
| RBAC006 | 💀 CRITICAL | ClusterRole with full wildcard (`verbs:* resources:* apiGroups:*`) |
|
|
333
|
+
| RBAC007 | 🔴 HIGH | Role can create/modify pods — escalation path |
|
|
334
|
+
| RBAC008 | 💀 CRITICAL | Role can write RBAC — self-grant escalation |
|
|
335
|
+
| RBAC009 | 💀 CRITICAL | Role grants impersonation |
|
|
336
|
+
|
|
337
|
+
### Service Accounts (SA)
|
|
338
|
+
|
|
339
|
+
| Rule | Severity | Description |
|
|
340
|
+
|------|----------|-------------|
|
|
341
|
+
| SA001 | 🟡 MEDIUM | SA token auto-mounted |
|
|
342
|
+
| SA002 | 🟡 MEDIUM | Uses `default` ServiceAccount |
|
|
343
|
+
|
|
344
|
+
### Capabilities (CAP)
|
|
345
|
+
|
|
346
|
+
| Rule | Severity | Description |
|
|
347
|
+
|------|----------|-------------|
|
|
348
|
+
| CAP001_* | 💀/🔴 varies | Dangerous Linux capability added (NET_ADMIN, SYS_PTRACE, etc.) |
|
|
349
|
+
| CAP002 | 🟡 MEDIUM | `capabilities.drop: [ALL]` missing |
|
|
350
|
+
|
|
351
|
+
### Network (NET)
|
|
352
|
+
|
|
353
|
+
| Rule | Severity | Description |
|
|
354
|
+
|------|----------|-------------|
|
|
355
|
+
| NET001 | 🟡 MEDIUM | No NetworkPolicy covers workload |
|
|
356
|
+
| NET002 | 🟡 MEDIUM | NetworkPolicy has catch-all ingress |
|
|
357
|
+
| NET003 | 🟡 MEDIUM | NetworkPolicy has no egress restriction |
|
|
358
|
+
|
|
359
|
+
### Services (SVC)
|
|
360
|
+
|
|
361
|
+
| Rule | Severity | Description |
|
|
362
|
+
|------|----------|-------------|
|
|
363
|
+
| SVC001 | 🟡 MEDIUM | Service type LoadBalancer — public IP |
|
|
364
|
+
| SVC002 | 🔴 HIGH | `externalIPs` set — CVE-2020-8554 |
|
|
365
|
+
| SVC003 | 🔵 LOW | Service type NodePort |
|
|
366
|
+
|
|
367
|
+
### Ingress (ING)
|
|
368
|
+
|
|
369
|
+
| Rule | Severity | Description |
|
|
370
|
+
|------|----------|-------------|
|
|
371
|
+
| ING001 | 🟡 MEDIUM | Ingress host with no TLS |
|
|
372
|
+
| ING002 | 🟡 MEDIUM | Ingress wildcard/missing host |
|
|
373
|
+
| ING003 | 🔵 LOW | Ingress has no auth annotation |
|
|
374
|
+
|
|
375
|
+
### Admission Webhooks (WHK)
|
|
376
|
+
|
|
377
|
+
| Rule | Severity | Description |
|
|
378
|
+
|------|----------|-------------|
|
|
379
|
+
| WHK001 | 🔴 HIGH | Webhook `failurePolicy: Ignore` — misconfigs bypass admission |
|
|
380
|
+
| WHK002 | 🔵 LOW | Webhook high timeout |
|
|
381
|
+
| WHK003 | 🟡 MEDIUM | Webhook intercepts all resources and operations |
|
|
382
|
+
|
|
383
|
+
### Environment / ConfigMap (ENV)
|
|
384
|
+
|
|
385
|
+
| Rule | Severity | Description |
|
|
386
|
+
|------|----------|-------------|
|
|
387
|
+
| ENV001 | 🔴 HIGH | Hardcoded credential in container env var |
|
|
388
|
+
| ENV002 | 🔴 HIGH | Credential stored in ConfigMap (should be a Secret) |
|
|
389
|
+
|
|
390
|
+
### etcd (ETCD)
|
|
391
|
+
|
|
392
|
+
| Rule | Severity | Description |
|
|
393
|
+
|------|----------|-------------|
|
|
394
|
+
| ETCD001 | 💀 CRITICAL | etcd running without TLS |
|
|
395
|
+
| ETCD002 | 💀 CRITICAL | etcd bound to `0.0.0.0` — publicly accessible |
|
|
396
|
+
| ETCD003 | 🟡 MEDIUM | Secrets not encrypted at rest |
|
|
397
|
+
|
|
398
|
+
### Images (IMG)
|
|
399
|
+
|
|
400
|
+
| Rule | Severity | Description |
|
|
401
|
+
|------|----------|-------------|
|
|
402
|
+
| IMG001 | 🔵 LOW | Mutable image tag (`:latest` or no tag) |
|
|
403
|
+
|
|
404
|
+
### Resources (RES)
|
|
405
|
+
|
|
406
|
+
| Rule | Severity | Description |
|
|
407
|
+
|------|----------|-------------|
|
|
408
|
+
| RES001 | 🔵 LOW | No resource limits set |
|
|
409
|
+
|
|
410
|
+
### Helm (HLM)
|
|
411
|
+
|
|
412
|
+
| Rule | Severity | Description |
|
|
413
|
+
|------|----------|-------------|
|
|
414
|
+
| HLM001 | 🔴 HIGH | Hardcoded secret in Helm values |
|
|
415
|
+
| HLM002 | 🔴 HIGH | `auth.enabled: false` in Helm values |
|
|
416
|
+
|
|
417
|
+
### Availability (PDB)
|
|
418
|
+
|
|
419
|
+
| Rule | Severity | Description |
|
|
420
|
+
|------|----------|-------------|
|
|
421
|
+
| PDB001 | 🟡 MEDIUM | Single replica — no redundancy |
|
|
422
|
+
| PDB002 | 🔵 LOW | No PodDisruptionBudget |
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## Config File
|
|
427
|
+
|
|
428
|
+
Commit a `.spottersec.yaml` to your repo to set defaults for the whole team.
|
|
429
|
+
|
|
430
|
+
```yaml
|
|
431
|
+
# .spottersec.yaml
|
|
432
|
+
scan:
|
|
433
|
+
fail_on: high # CI: exit 1 on HIGH or CRITICAL
|
|
434
|
+
severity: medium # show MEDIUM+ findings
|
|
435
|
+
ignore:
|
|
436
|
+
- NET001 # NetworkPolicy managed externally
|
|
437
|
+
- SC008 # seccomp set by admission controller
|
|
438
|
+
paths:
|
|
439
|
+
- ./k8s/
|
|
440
|
+
- ./helm/templates/
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
SpotterSec checks `.spottersec.yaml` in the current directory, then `~/.spottersec/config.yaml`. CLI flags always override.
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## CI/CD Integration
|
|
448
|
+
|
|
449
|
+
### GitHub Actions
|
|
450
|
+
|
|
451
|
+
```yaml
|
|
452
|
+
# .github/workflows/k8s-security.yml
|
|
453
|
+
name: K8s Security Scan
|
|
454
|
+
on: [pull_request]
|
|
455
|
+
|
|
456
|
+
jobs:
|
|
457
|
+
scan:
|
|
458
|
+
runs-on: ubuntu-latest
|
|
459
|
+
steps:
|
|
460
|
+
- uses: actions/checkout@v4
|
|
461
|
+
- name: Install SpotterSec
|
|
462
|
+
run: pip install spotter
|
|
463
|
+
- name: Scan K8s manifests
|
|
464
|
+
run: spottersec scan ./k8s/ --fail-on high --output sarif
|
|
465
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
466
|
+
with:
|
|
467
|
+
sarif_file: spottersec.sarif
|
|
468
|
+
# Findings appear as annotations in GitHub Security tab
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### GitLab CI
|
|
472
|
+
|
|
473
|
+
```yaml
|
|
474
|
+
k8s-security:
|
|
475
|
+
image: python:3.12-slim
|
|
476
|
+
script:
|
|
477
|
+
- pip install spotter
|
|
478
|
+
- spottersec scan ./k8s/ --fail-on high
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Learn More
|
|
484
|
+
|
|
485
|
+
SpotterSec CLI is the companion tool to [spottersec.com](https://spottersec.com) — an interactive Kubernetes attack path learning platform with 10 scenarios covering the most common K8s compromise chains.
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## License
|
|
490
|
+
|
|
491
|
+
MIT
|