-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjava_security.yml.tmpl
More file actions
275 lines (235 loc) · 10.8 KB
/
java_security.yml.tmpl
File metadata and controls
275 lines (235 loc) · 10.8 KB
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
name: Security Scan
on:
push:
branches: ["main"]
pull_request:
permissions:
contents: read
issues: write
pull-requests: write
jobs:
security:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Prepare artifacts directory
run: mkdir -p artifacts/security
- name: Extract exclude paths from config
id: exclude-paths
run: |
python3 << 'EOF'
import yaml
import json
import os
exclude_paths = []
try:
if os.path.exists('security-config.yml'):
with open('security-config.yml') as f:
config = yaml.safe_load(f)
if config and 'exclude_paths' in config:
exclude_paths = config['exclude_paths'] or []
except Exception as e:
print(f"Error reading config: {e}")
semgrep_excludes = ' '.join([f'--exclude {p}' for p in exclude_paths])
gitleaks_excludes = ','.join(exclude_paths) if exclude_paths else ''
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
f.write(f"semgrep_excludes={semgrep_excludes}\n")
f.write(f"gitleaks_excludes={gitleaks_excludes}\n")
f.write(f"exclude_paths={json.dumps(exclude_paths)}\n")
EOF
pip install pyyaml
- name: Set up Java
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
- name: Cache Maven packages
if: hashFiles('pom.xml') != ''
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{"{{"}} runner.os {{ "}}" }}-m2-${{"{{"}} hashFiles('**/pom.xml') {{ "}}" }}
- name: Cache Gradle packages
if: hashFiles('build.gradle') != '' || hashFiles('build.gradle.kts') != ''
uses: actions/cache@v4
with:
path: ~/.gradle/caches
key: ${{"{{"}} runner.os {{ "}}" }}-gradle-${{"{{"}} hashFiles('**/*.gradle*') {{ "}}" }}
- name: Install dependencies (Maven)
if: hashFiles('pom.xml') != ''
run: mvn dependency:resolve -q
- name: Install dependencies (Gradle)
if: hashFiles('build.gradle') != '' || hashFiles('build.gradle.kts') != ''
run: ./gradlew dependencies -q || gradle dependencies -q
{{- if .Tools.Semgrep }}
- name: Set up Python for Semgrep
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Run Semgrep
run: |
pip install semgrep
semgrep --config p/ci ${{"{{"}} steps.exclude-paths.outputs.semgrep_excludes {{ "}}" }} --json > artifacts/security/semgrep-report.json || true
{{- end }}
{{- if .Tools.Gitleaks }}
- name: Create Gitleaks config with exclusions
if: steps.exclude-paths.outputs.gitleaks_excludes != ''
run: |
cat > .gitleaks.toml << 'EOF'
[allowlist]
paths = [
'''${{"{{"}} steps.exclude-paths.outputs.gitleaks_excludes {{ "}}" }}'''
]
EOF
sed -i "s/'''/\"/g" .gitleaks.toml
sed -i 's/,/",\n "/g' .gitleaks.toml
- name: Install Gitleaks
run: |
curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.18.1/gitleaks_8.18.1_linux_x64.tar.gz | tar -xz
sudo mv gitleaks /usr/local/bin/
- name: Run Gitleaks
run: |
if [ -f .gitleaks.toml ]; then
gitleaks detect --report-format json --report-path artifacts/security/gitleaks-report.json --config .gitleaks.toml --exit-code 0 || true
else
gitleaks detect --report-format json --report-path artifacts/security/gitleaks-report.json --exit-code 0 || true
fi
{{- end }}
{{- if .Tools.Trivy }}
- name: Run Trivy FS Scan
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
format: json
output: artifacts/security/trivy-fs.json
severity: HIGH,CRITICAL
skip-dirs: ${{"{{"}} steps.exclude-paths.outputs.gitleaks_excludes {{ "}}" }}
- name: Check for Dockerfile
id: docker-check
run: |
if [ -f "Dockerfile" ]; then
echo "has_docker=true" >> $GITHUB_OUTPUT
else
echo "has_docker=false" >> $GITHUB_OUTPUT
fi
- name: Build Docker image for scanning
if: steps.docker-check.outputs.has_docker == 'true'
run: docker build -t devsecops-scan-temp:latest .
- name: Run Trivy Image Scan
if: steps.docker-check.outputs.has_docker == 'true'
uses: aquasecurity/trivy-action@master
with:
scan-type: image
image-ref: devsecops-scan-temp:latest
format: json
output: artifacts/security/trivy-image.json
severity: HIGH,CRITICAL
{{- end }}
- name: Extract fail_on thresholds from config
if: always()
run: |
pip install pyyaml
python3 << 'EOF'
import yaml
import json
import os
fail_on = {
'gitleaks': 0, 'semgrep': 10,
'trivy_critical': 0, 'trivy_high': 5,
'trivy_medium': -1, 'trivy_low': -1
}
try:
if os.path.exists('security-config.yml'):
with open('security-config.yml') as f:
config = yaml.safe_load(f)
if config and 'fail_on' in config:
fail_on.update(config['fail_on'])
except Exception as e:
print(f"Error reading config, using defaults: {e}")
os.makedirs('artifacts/security', exist_ok=True)
with open('artifacts/security/fail-on.json', 'w') as out:
json.dump(fail_on, out)
EOF
- name: Generate security summary (JSON)
if: always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const dir = 'artifacts/security';
function readJson(file) {
if (!fs.existsSync(file)) return null;
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
}
const failOn = readJson(path.join(dir, 'fail-on.json')) || { gitleaks: 0, semgrep: 10, trivy_critical: 0, trivy_high: 5, trivy_medium: -1, trivy_low: -1 };
const result = { version: "0.3.0", status: "PASS", blocking_count: 0, summary: {} };
const gitleaks = readJson(path.join(dir, 'gitleaks-report.json'));
if (gitleaks) {
const count = Array.isArray(gitleaks) ? gitleaks.length : (gitleaks.findings?.length || 0);
result.summary.gitleaks = { total: count };
if (failOn.gitleaks >= 0 && count > failOn.gitleaks) result.blocking_count += count - failOn.gitleaks;
}
const trivy = readJson(path.join(dir, 'trivy-fs.json'));
if (trivy?.Results) {
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
for (const r of trivy.Results) for (const v of (r.Vulnerabilities || [])) { const s = v.Severity?.toLowerCase(); if (s in counts) counts[s]++; }
result.summary.trivy_fs = counts;
for (const [s, k] of [['critical','trivy_critical'],['high','trivy_high'],['medium','trivy_medium'],['low','trivy_low']]) {
if (failOn[k] >= 0 && counts[s] > failOn[k]) result.blocking_count += counts[s] - failOn[k];
}
}
const semgrep = readJson(path.join(dir, 'semgrep-report.json'));
if (semgrep) {
const count = (Array.isArray(semgrep) ? semgrep : (semgrep.results || [])).length;
result.summary.semgrep = { total: count };
if (failOn.semgrep >= 0 && count > failOn.semgrep) result.blocking_count += count - failOn.semgrep;
}
result.status = result.blocking_count > 0 ? "FAIL" : "PASS";
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, 'summary.json'), JSON.stringify(result, null, 2));
- name: Post PR Security Summary
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
github-token: ${{"{{"}} secrets.GITHUB_TOKEN {{ "}}" }}
script: |
const fs = require('fs');
const marker = '<!-- devsecops-kit-security-summary -->';
let summary = null;
try { summary = JSON.parse(fs.readFileSync('artifacts/security/summary.json', 'utf8')); } catch {}
let body = `${marker}\n### 🔐 DevSecOps Kit Security Summary\n\n`;
if (!summary) { body += "_No summary available._\n"; } else {
body += `- **Gitleaks:** ${summary.summary?.gitleaks?.total ?? 0} leak(s)\n`;
const trivyFs = summary.summary?.trivy_fs ?? {};
if (Object.keys(trivyFs).length > 0) { body += `- **Trivy FS:**\n`; for (const s of Object.keys(trivyFs)) body += ` - ${s.toUpperCase()}: ${trivyFs[s]}\n`; }
if (summary.summary?.semgrep) body += `- **Semgrep:** ${summary.summary.semgrep.total} finding(s)\n`;
body += `\n**Status:** ${summary.status === "FAIL" ? '🚨 **FAIL**' : '✅ **PASS**'}\n`;
if (summary.blocking_count > 0) body += `_${summary.blocking_count} issue(s) exceed configured thresholds_\n`;
}
const { owner, repo } = context.repo;
const pr = context.issue.number;
const comments = await github.rest.issues.listComments({ owner, repo, issue_number: pr });
const existing = comments.data.find(c => c.body?.includes(marker));
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: pr, body });
}
- name: Upload security artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: security-reports
path: artifacts/security/
- name: Check fail gates
if: always()
run: |
STATUS=$(cat artifacts/security/summary.json | python3 -c "import sys, json; print(json.load(sys.stdin).get('status', 'UNKNOWN'))")
BLOCKING=$(cat artifacts/security/summary.json | python3 -c "import sys, json; print(json.load(sys.stdin).get('blocking_count', 0))")
echo "Status: $STATUS | Blocking: $BLOCKING"
if [ "$STATUS" = "FAIL" ]; then echo "❌ Security scan FAILED: $BLOCKING issue(s) exceed thresholds"; exit 1; fi
echo "✅ Security scan PASSED"