Skip to content

Commit 335e249

Browse files
authored
Merge pull request #15 from turenlabs/feat/coverage-batch
Add test coverage batch: safe fixtures, taint sinks, rule categories
2 parents 5541450 + b7a70d4 commit 335e249

16 files changed

Lines changed: 2780 additions & 0 deletions

File tree

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
package container
2+
3+
import (
4+
"testing"
5+
6+
"github.com/turenlabs/batou/internal/testutil"
7+
)
8+
9+
// --- BATOU-CTR-001: Dockerfile running as root ---
10+
11+
func TestCTR001_NoUser(t *testing.T) {
12+
content := `FROM node:20-alpine
13+
WORKDIR /app
14+
COPY . .
15+
RUN npm install
16+
CMD ["node", "server.js"]
17+
`
18+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
19+
testutil.MustFindRule(t, result, "BATOU-CTR-001")
20+
}
21+
22+
func TestCTR001_UserRoot(t *testing.T) {
23+
content := `FROM python:3.12
24+
USER root
25+
COPY . .
26+
CMD ["python", "app.py"]
27+
`
28+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
29+
testutil.MustFindRule(t, result, "BATOU-CTR-001")
30+
}
31+
32+
func TestCTR001_Safe_NonRootUser(t *testing.T) {
33+
content := `FROM node:20-alpine
34+
RUN adduser -D appuser
35+
USER appuser
36+
COPY . .
37+
CMD ["node", "server.js"]
38+
`
39+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
40+
testutil.MustNotFindRule(t, result, "BATOU-CTR-001")
41+
}
42+
43+
// --- BATOU-CTR-002: Docker COPY/ADD with wildcard ---
44+
45+
func TestCTR002_CopyDot(t *testing.T) {
46+
content := `FROM node:20-alpine
47+
COPY . /app/
48+
USER appuser
49+
`
50+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
51+
testutil.MustFindRule(t, result, "BATOU-CTR-002")
52+
}
53+
54+
func TestCTR002_CopyGlob(t *testing.T) {
55+
content := `FROM python:3.12
56+
COPY * /app/
57+
USER appuser
58+
`
59+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
60+
testutil.MustFindRule(t, result, "BATOU-CTR-002")
61+
}
62+
63+
func TestCTR002_Safe_SpecificCopy(t *testing.T) {
64+
content := `FROM node:20-alpine
65+
COPY package.json package-lock.json ./
66+
RUN npm install
67+
COPY src/ ./src/
68+
USER appuser
69+
`
70+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
71+
testutil.MustNotFindRule(t, result, "BATOU-CTR-002")
72+
}
73+
74+
// --- BATOU-CTR-003: Docker latest tag ---
75+
76+
func TestCTR003_LatestTag(t *testing.T) {
77+
content := `FROM node:latest
78+
USER appuser
79+
CMD ["node", "server.js"]
80+
`
81+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
82+
testutil.MustFindRule(t, result, "BATOU-CTR-003")
83+
}
84+
85+
func TestCTR003_NoTag(t *testing.T) {
86+
content := `FROM python
87+
USER appuser
88+
CMD ["python", "app.py"]
89+
`
90+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
91+
testutil.MustFindRule(t, result, "BATOU-CTR-003")
92+
}
93+
94+
func TestCTR003_Safe_PinnedVersion(t *testing.T) {
95+
content := `FROM node:20.11-alpine
96+
USER appuser
97+
CMD ["node", "server.js"]
98+
`
99+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
100+
testutil.MustNotFindRule(t, result, "BATOU-CTR-003")
101+
}
102+
103+
// --- BATOU-CTR-004: Docker SSH port ---
104+
105+
func TestCTR004_ExposeSSH(t *testing.T) {
106+
content := `FROM ubuntu:22.04
107+
EXPOSE 22
108+
USER appuser
109+
CMD ["/usr/sbin/sshd"]
110+
`
111+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
112+
testutil.MustFindRule(t, result, "BATOU-CTR-004")
113+
}
114+
115+
func TestCTR004_Safe_ExposeHTTP(t *testing.T) {
116+
content := `FROM node:20-alpine
117+
EXPOSE 3000
118+
USER appuser
119+
CMD ["node", "server.js"]
120+
`
121+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
122+
testutil.MustNotFindRule(t, result, "BATOU-CTR-004")
123+
}
124+
125+
// --- BATOU-CTR-005: Docker privileged mode ---
126+
127+
func TestCTR005_Privileged(t *testing.T) {
128+
content := `docker run --privileged myimage`
129+
result := testutil.ScanContent(t, "/app/deploy.sh", content)
130+
testutil.MustFindRule(t, result, "BATOU-CTR-005")
131+
}
132+
133+
func TestCTR005_CapAddAll(t *testing.T) {
134+
content := `docker run --cap-add=ALL myimage`
135+
result := testutil.ScanContent(t, "/app/deploy.sh", content)
136+
testutil.MustFindRule(t, result, "BATOU-CTR-005")
137+
}
138+
139+
func TestCTR005_CapAddSysAdmin(t *testing.T) {
140+
content := `docker run --cap-add=SYS_ADMIN myimage`
141+
result := testutil.ScanContent(t, "/app/deploy.sh", content)
142+
testutil.MustFindRule(t, result, "BATOU-CTR-005")
143+
}
144+
145+
func TestCTR005_Safe_NoCaps(t *testing.T) {
146+
content := `docker run --read-only myimage`
147+
result := testutil.ScanContent(t, "/app/deploy.sh", content)
148+
testutil.MustNotFindRule(t, result, "BATOU-CTR-005")
149+
}
150+
151+
// --- BATOU-CTR-006: Docker secrets in ENV/ARG ---
152+
153+
func TestCTR006_EnvPassword(t *testing.T) {
154+
content := `FROM node:20-alpine
155+
ENV DB_PASSWORD=supersecret123
156+
USER appuser
157+
`
158+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
159+
testutil.MustFindRule(t, result, "BATOU-CTR-006")
160+
}
161+
162+
func TestCTR006_ArgSecret(t *testing.T) {
163+
content := `FROM python:3.12
164+
ARG API_KEY=sk-abc123def456
165+
USER appuser
166+
`
167+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
168+
testutil.MustFindRule(t, result, "BATOU-CTR-006")
169+
}
170+
171+
func TestCTR006_Safe_NoValue(t *testing.T) {
172+
content := `FROM node:20-alpine
173+
ARG DB_PASSWORD
174+
USER appuser
175+
`
176+
result := testutil.ScanContent(t, "/app/Dockerfile", content)
177+
testutil.MustNotFindRule(t, result, "BATOU-CTR-006")
178+
}
179+
180+
// --- BATOU-CTR-007: Kubernetes privileged container ---
181+
182+
func TestCTR007_K8sPrivileged(t *testing.T) {
183+
content := `apiVersion: v1
184+
kind: Pod
185+
spec:
186+
containers:
187+
- name: app
188+
securityContext:
189+
privileged: true
190+
`
191+
result := testutil.ScanContent(t, "/app/pod.yaml", content)
192+
testutil.MustFindRule(t, result, "BATOU-CTR-007")
193+
}
194+
195+
func TestCTR007_K8sAllowPrivEsc(t *testing.T) {
196+
content := `apiVersion: v1
197+
kind: Pod
198+
spec:
199+
containers:
200+
- name: app
201+
securityContext:
202+
allowPrivilegeEscalation: true
203+
`
204+
result := testutil.ScanContent(t, "/app/pod.yaml", content)
205+
testutil.MustFindRule(t, result, "BATOU-CTR-007")
206+
}
207+
208+
func TestCTR007_Safe_K8sNotPrivileged(t *testing.T) {
209+
content := `apiVersion: v1
210+
kind: Pod
211+
spec:
212+
containers:
213+
- name: app
214+
securityContext:
215+
privileged: false
216+
allowPrivilegeEscalation: false
217+
`
218+
result := testutil.ScanContent(t, "/app/pod.yaml", content)
219+
testutil.MustNotFindRule(t, result, "BATOU-CTR-007")
220+
}
221+
222+
// --- BATOU-CTR-008: Kubernetes hostNetwork/hostPID ---
223+
224+
func TestCTR008_HostNetwork(t *testing.T) {
225+
content := `apiVersion: v1
226+
kind: Pod
227+
spec:
228+
hostNetwork: true
229+
containers:
230+
- name: app
231+
`
232+
result := testutil.ScanContent(t, "/app/pod.yaml", content)
233+
testutil.MustFindRule(t, result, "BATOU-CTR-008")
234+
}
235+
236+
func TestCTR008_HostPID(t *testing.T) {
237+
content := `apiVersion: v1
238+
kind: Pod
239+
spec:
240+
hostPID: true
241+
containers:
242+
- name: app
243+
`
244+
result := testutil.ScanContent(t, "/app/pod.yaml", content)
245+
testutil.MustFindRule(t, result, "BATOU-CTR-008")
246+
}
247+
248+
func TestCTR008_Safe_NoHostNamespace(t *testing.T) {
249+
content := `apiVersion: v1
250+
kind: Pod
251+
spec:
252+
hostNetwork: false
253+
containers:
254+
- name: app
255+
`
256+
result := testutil.ScanContent(t, "/app/pod.yaml", content)
257+
testutil.MustNotFindRule(t, result, "BATOU-CTR-008")
258+
}
259+
260+
// --- BATOU-CTR-009: Kubernetes no resource limits ---
261+
262+
func TestCTR009_NoLimits(t *testing.T) {
263+
content := `apiVersion: v1
264+
kind: Pod
265+
spec:
266+
containers:
267+
- name: app
268+
image: nginx:1.25
269+
`
270+
result := testutil.ScanContent(t, "/app/pod.yaml", content)
271+
testutil.MustFindRule(t, result, "BATOU-CTR-009")
272+
}
273+
274+
func TestCTR009_Safe_WithLimits(t *testing.T) {
275+
content := `apiVersion: v1
276+
kind: Pod
277+
spec:
278+
containers:
279+
- name: app
280+
image: nginx:1.25
281+
resources:
282+
limits:
283+
cpu: "500m"
284+
memory: "512Mi"
285+
`
286+
result := testutil.ScanContent(t, "/app/pod.yaml", content)
287+
testutil.MustNotFindRule(t, result, "BATOU-CTR-009")
288+
}
289+
290+
// --- BATOU-CTR-010: Terraform open ingress ---
291+
292+
func TestCTR010_TF_OpenCIDR(t *testing.T) {
293+
content := `resource "aws_security_group" "web" {
294+
ingress {
295+
from_port = 0
296+
to_port = 65535
297+
protocol = "tcp"
298+
cidr_blocks = ["0.0.0.0/0"]
299+
}
300+
}
301+
`
302+
result := testutil.ScanContent(t, "/infra/main.tf", content)
303+
testutil.MustFindRule(t, result, "BATOU-CTR-010")
304+
}
305+
306+
func TestCTR010_Safe_RestrictedCIDR(t *testing.T) {
307+
content := `resource "aws_security_group" "web" {
308+
ingress {
309+
from_port = 443
310+
to_port = 443
311+
protocol = "tcp"
312+
cidr_blocks = ["10.0.0.0/8"]
313+
}
314+
}
315+
`
316+
result := testutil.ScanContent(t, "/infra/main.tf", content)
317+
testutil.MustNotFindRule(t, result, "BATOU-CTR-010")
318+
}

0 commit comments

Comments
 (0)