Skip to content

Commit a86633c

Browse files
committed
Create docker ci reusable workflow
1 parent 391e31b commit a86633c

1 file changed

Lines changed: 395 additions & 0 deletions

File tree

.github/workflows/docker-ci.yml

Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
---
2+
name: 'ci'
3+
on:
4+
push:
5+
branches:
6+
- 'main'
7+
pull_request:
8+
branches:
9+
- '*'
10+
paths-ignore:
11+
- '**.md'
12+
workflow_dispatch:
13+
14+
permissions:
15+
contents: 'read'
16+
issues: 'write'
17+
pull-requests: 'write'
18+
19+
env:
20+
IMAGE_NAME: 'actions-runner'
21+
LOCAL_SCAN_TAG: 'ci-scan'
22+
23+
jobs:
24+
ApplyCommonLinting:
25+
uses: 'icariohealth/.github/.github/workflows/common-linting.yml@main'
26+
secrets:
27+
ci_token: '${{ secrets.NOVU_CI_TOKEN }}'
28+
29+
build_scan_docker_image_packer:
30+
name: 'build-scan-docker-image-packer'
31+
needs:
32+
- 'hadolint_packer'
33+
# - 'ApplyCommonLinting'
34+
runs-on: 'ubuntu-latest'
35+
steps:
36+
- name: 'checkout'
37+
uses: 'actions/checkout@v2'
38+
39+
- name: 'build and tag image'
40+
id: 'build-tag-image'
41+
run: |
42+
docker build --file ami/Dockerfile --tag ${IMAGE_NAME}-packer:${LOCAL_SCAN_TAG} .
43+
44+
- name: 'Download lacework'
45+
run: |
46+
curl -L --output ./lw_scanner https://github.com/lacework/lacework-vulnerability-scanner/releases/latest/download/lw-scanner-linux-amd64
47+
sudo chmod a+x ./lw_scanner
48+
49+
- name: 'Scan the image'
50+
run: |
51+
./lw_scanner image evaluate --access-token ${{secrets.LW_ACCESS_TOKEN}} --account-name "icario" --pretty -w ${IMAGE_NAME}-packer:${LOCAL_SCAN_TAG}
52+
53+
push_docker_image_packer:
54+
name: 'push-image-packer'
55+
needs:
56+
- 'hadolint_packer'
57+
# - 'ApplyCommonLinting'
58+
- 'build_scan_docker_image_packer'
59+
runs-on: 'ubuntu-latest'
60+
steps:
61+
- name: 'checkout'
62+
uses: 'actions/checkout@v2'
63+
64+
- name: 'configure aws credentials'
65+
uses: 'aws-actions/configure-aws-credentials@v1.5.5'
66+
with:
67+
aws-access-key-id: '${{ secrets.DEP_ACCESS_KEY }}'
68+
aws-secret-access-key: '${{ secrets.DEP_SECRET_KEY }}'
69+
aws-region: '${{ secrets.DEP_AWS_REGION }}'
70+
71+
- name: 'Setup QEMU'
72+
uses: 'docker/setup-qemu-action@v1'
73+
74+
- name: 'Setup Docker Buildx'
75+
id: 'buildx'
76+
uses: 'docker/setup-buildx-action@v1'
77+
with:
78+
install: true
79+
80+
- name: 'Login to Amazon ECR'
81+
id: 'login-ecr'
82+
uses: 'aws-actions/amazon-ecr-login@v1'
83+
84+
- name: 'tag Image'
85+
id: 'vars'
86+
run: 'echo ::set-output name=TAG::$(cat VERSION.txt)'
87+
88+
- name: 'build-push-image'
89+
if: "github.event_name == 'push'"
90+
id: 'push-image-ecr'
91+
env:
92+
ECR_REGISTRY: '${{ steps.login-ecr.outputs.registry }}'
93+
ECR_REPOSITORY: 'cloud/actions-runner-packer'
94+
run: |
95+
export IMAGE_TAG="${ECR_REGISTRY}/${ECR_REPOSITORY}:${{ steps.vars.outputs.TAG }}"
96+
docker build -f ami/Dockerfile --push --no-cache --platform linux/amd64 -t ${IMAGE_TAG} -t ${ECR_REGISTRY}/${ECR_REPOSITORY}:latest .
97+
echo "::set-output name=IMAGE_TAG::${IMAGE_TAG}"
98+
99+
build_scan_docker_image:
100+
name: 'build-scan-docker-image'
101+
needs:
102+
- 'hadolint'
103+
# - 'ApplyCommonLinting'
104+
runs-on: 'ubuntu-latest'
105+
steps:
106+
- name: 'checkout'
107+
uses: 'actions/checkout@v2'
108+
109+
- name: 'build and tag image'
110+
id: 'build-tag-image'
111+
run: |
112+
docker build --file Dockerfile --tag ${IMAGE_NAME}:${LOCAL_SCAN_TAG} .
113+
114+
- name: 'Download lacework'
115+
run: |
116+
curl -L --output ./lw_scanner https://github.com/lacework/lacework-vulnerability-scanner/releases/latest/download/lw-scanner-linux-amd64
117+
sudo chmod a+x ./lw_scanner
118+
119+
- name: 'Scan the image'
120+
run: |
121+
./lw_scanner image evaluate --access-token ${{secrets.LW_ACCESS_TOKEN}} --account-name "icario" --pretty -w ${IMAGE_NAME}:${LOCAL_SCAN_TAG}
122+
123+
- name: 'configure aws credentials for inspector'
124+
uses: 'aws-actions/configure-aws-credentials@v1.5.5'
125+
with:
126+
aws-access-key-id: '${{ secrets.DEP_ACCESS_KEY }}'
127+
aws-secret-access-key: '${{ secrets.DEP_SECRET_KEY }}'
128+
aws-region: '${{ secrets.DEP_AWS_REGION }}'
129+
130+
- name: 'Inspector scan'
131+
id: 'inspector'
132+
uses: 'aws-actions/vulnerability-scan-github-action-for-amazon-inspector@v1'
133+
with:
134+
artifact_type: 'container'
135+
artifact_path: '${{ env.IMAGE_NAME }}:${{ env.LOCAL_SCAN_TAG }}'
136+
display_vulnerability_findings: 'enabled'
137+
138+
- name: 'Upload Inspector artifacts'
139+
if: 'always()'
140+
uses: 'actions/upload-artifact@v4'
141+
with:
142+
name: 'inspector-results-actions-runner'
143+
path: |
144+
${{ steps.inspector.outputs.inspector_scan_results }}
145+
${{ steps.inspector.outputs.inspector_scan_results_csv }}
146+
${{ steps.inspector.outputs.inspector_scan_results_markdown }}
147+
148+
- name: 'Comment Inspector findings on PR'
149+
if: 'github.event_name == ''pull_request'' && always()'
150+
uses: 'actions/github-script@v7'
151+
env:
152+
REPORT_PATH: '${{ steps.inspector.outputs.inspector_scan_results_markdown }}'
153+
THRESHOLD_EXCEEDED: '${{ steps.inspector.outputs.vulnerability_threshold_exceeded }}'
154+
COMMENT_TITLE: '## Amazon Inspector results'
155+
NORMAL_ARTIFACT_LABEL: '${{ env.IMAGE_NAME }}:${{ env.LOCAL_SCAN_TAG }}'
156+
PACKER_ARTIFACT_LABEL: '${{ env.IMAGE_NAME }}-packer:${{ env.LOCAL_SCAN_TAG }}'
157+
with:
158+
github-token: '${{ secrets.GITHUB_TOKEN }}'
159+
script: |
160+
const fs = require('fs');
161+
162+
const reportPath = process.env.REPORT_PATH;
163+
const thresholdExceeded = process.env.THRESHOLD_EXCEEDED === '1';
164+
const commentTitle = process.env.COMMENT_TITLE;
165+
const normalArtifactLabel = process.env.NORMAL_ARTIFACT_LABEL;
166+
const packerArtifactLabel = process.env.PACKER_ARTIFACT_LABEL;
167+
168+
function summarizeReport(raw) {
169+
const cveRegex = /CVE-\d{4}-\d+/g;
170+
const matches = [...raw.matchAll(cveRegex)];
171+
if (!matches.length) {
172+
return raw;
173+
}
174+
175+
const severityCounts = {
176+
critical: 0,
177+
high: 0,
178+
medium: 0,
179+
low: 0,
180+
other: 0
181+
};
182+
183+
const rows = [];
184+
for (let i = 0; i < matches.length; i++) {
185+
const start = matches[i].index;
186+
const end = i + 1 < matches.length ? matches[i + 1].index : raw.length;
187+
const section = raw.slice(start, end);
188+
189+
const cve = matches[i][0];
190+
const sevMatch = section.match(/\b(critical|high|medium|low)\b/i);
191+
const severity = sevMatch ? sevMatch[1].toLowerCase() : 'other';
192+
severityCounts[severity] += 1;
193+
194+
const pkgMatches = [...section.matchAll(/pkg:[^\s<)]+/g)].map((m) => m[0]);
195+
const uniquePkgs = [...new Set(pkgMatches)];
196+
197+
const fixedMatch = section.match(/\b\d+(?:\.\d+)+(?:[-+._a-zA-Z0-9]*)?\b/g);
198+
const fixVersion = fixedMatch && fixedMatch.length ? fixedMatch[fixedMatch.length - 1] : 'N/A';
199+
200+
const sevEmoji = {
201+
critical: '🔴',
202+
high: '🟠',
203+
medium: '🟡',
204+
low: '🔵',
205+
other: '⚪'
206+
}[severity] || '⚪';
207+
208+
rows.push(
209+
`| ${cve} | ${sevEmoji} ${severity} | ${uniquePkgs.length} | ${fixVersion} | ${uniquePkgs.slice(0, 2).map((p) => '`' + p + '`').join(', ')}${uniquePkgs.length > 2 ? ` (+${uniquePkgs.length - 2} more)` : ''} |`
210+
);
211+
}
212+
213+
const summaryTable = [
214+
'### Vulnerability counts by severity',
215+
'',
216+
'| Severity | Count |',
217+
'| --- | ---: |',
218+
`| 🔴 Critical | ${severityCounts.critical} |`,
219+
`| 🟠 High | ${severityCounts.high} |`,
220+
`| 🟡 Medium | ${severityCounts.medium} |`,
221+
`| 🔵 Low | ${severityCounts.low} |`,
222+
`| ⚪ Other | ${severityCounts.other} |`,
223+
''
224+
].join('\n');
225+
226+
const findingsTable = [
227+
'### Findings',
228+
'',
229+
'| CVE | Severity | Packages | Fix Version | Sample Packages |',
230+
'| --- | --- | ---: | --- | --- |',
231+
...rows,
232+
''
233+
].join('\n');
234+
235+
return `${summaryTable}\n${findingsTable}`;
236+
}
237+
238+
let report = 'No Inspector markdown report was generated.';
239+
if (reportPath && fs.existsSync(reportPath)) {
240+
const raw = fs.readFileSync(reportPath, 'utf8');
241+
report = summarizeReport(raw);
242+
}
243+
244+
const body = [
245+
commentTitle,
246+
'',
247+
thresholdExceeded
248+
? ':warning: Vulnerabilities detected. Review Inspector findings before merging.'
249+
: ':white_check_mark: No findings crossed the configured threshold.',
250+
'',
251+
'## actions-runner',
252+
'',
253+
`Artifact: \`${normalArtifactLabel}\``,
254+
'',
255+
'<details><summary>Show findings</summary>',
256+
'',
257+
report,
258+
'',
259+
'</details>',
260+
'',
261+
'## actions-runner-packer',
262+
'',
263+
`Artifact: \`${packerArtifactLabel}\``,
264+
'',
265+
'Packer image is also scanned in CI and will fail independently if findings are present.',
266+
'',
267+
'Full raw reports are available in workflow artifacts:',
268+
'- `inspector-results-actions-runner`',
269+
'- `inspector-results-actions-runner-packer`'
270+
].join('\n');
271+
272+
const { owner, repo } = context.repo;
273+
const issue_number = context.payload.pull_request.number;
274+
275+
const comments = await github.rest.issues.listComments({
276+
owner,
277+
repo,
278+
issue_number,
279+
per_page: 100
280+
});
281+
282+
const existing = comments.data.find((c) =>
283+
c.user.type === 'Bot' && c.body.includes(commentTitle)
284+
);
285+
286+
if (existing) {
287+
await github.rest.issues.updateComment({
288+
owner,
289+
repo,
290+
comment_id: existing.id,
291+
body
292+
});
293+
} else {
294+
await github.rest.issues.createComment({
295+
owner,
296+
repo,
297+
issue_number,
298+
body
299+
});
300+
}
301+
302+
- name: 'Report Inspector findings'
303+
if: 'always()'
304+
run: |
305+
if [ "${{ steps.inspector.outputs.vulnerability_threshold_exceeded }}" = "1" ]; then
306+
echo "::warning::Inspector found vulnerabilities above threshold. Review findings in the PR comment before merging."
307+
fi
308+
309+
push_docker_image:
310+
name: 'push-image'
311+
needs:
312+
- 'hadolint'
313+
# - 'ApplyCommonLinting'
314+
- 'build_scan_docker_image'
315+
runs-on: 'ubuntu-latest'
316+
steps:
317+
- name: 'checkout'
318+
uses: 'actions/checkout@v2'
319+
320+
- name: 'configure aws credentials'
321+
uses: 'aws-actions/configure-aws-credentials@v1.5.5'
322+
with:
323+
aws-access-key-id: '${{ secrets.DEP_ACCESS_KEY }}'
324+
aws-secret-access-key: '${{ secrets.DEP_SECRET_KEY }}'
325+
aws-region: '${{ secrets.DEP_AWS_REGION }}'
326+
327+
- name: 'Setup QEMU'
328+
uses: 'docker/setup-qemu-action@v1'
329+
330+
- name: 'Setup Docker Buildx'
331+
id: 'buildx'
332+
uses: 'docker/setup-buildx-action@v1'
333+
with:
334+
install: true
335+
336+
- name: 'Login to Amazon ECR'
337+
id: 'login-ecr'
338+
uses: 'aws-actions/amazon-ecr-login@v1'
339+
340+
- name: 'tag Image'
341+
id: 'vars'
342+
run: 'echo ::set-output name=TAG::$(cat VERSION.txt)'
343+
344+
- name: 'build-push-image'
345+
if: "github.event_name == 'push'"
346+
id: 'push-image-ecr'
347+
env:
348+
ECR_REGISTRY: '${{ steps.login-ecr.outputs.registry }}'
349+
ECR_REPOSITORY: 'cloud/actions-runner'
350+
run: |
351+
export IMAGE_TAG="${ECR_REGISTRY}/${ECR_REPOSITORY}:${{ steps.vars.outputs.TAG }}"
352+
docker build -f Dockerfile --push --no-cache --platform linux/amd64,linux/arm64,linux/arm64/v8 -t ${IMAGE_TAG} -t ${ECR_REGISTRY}/${ECR_REPOSITORY}:latest .
353+
echo "::set-output name=IMAGE_TAG::${IMAGE_TAG}"
354+
355+
hadolint_packer:
356+
name: 'hadolint-packer'
357+
runs-on: 'ubuntu-latest'
358+
steps:
359+
- name: 'Clone App Repo'
360+
uses: 'actions/checkout@v2'
361+
362+
- name: 'Clone Github-Actions repo'
363+
uses: 'actions/checkout@v2'
364+
with:
365+
repository: 'icariohealth/github-actions'
366+
token: '${{ secrets.NOVU_CI_TOKEN }}'
367+
path: './github-actions'
368+
ref: 'main'
369+
370+
- name: 'Run Hadolint'
371+
uses: './github-actions/hadolint'
372+
env:
373+
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
374+
HADOLINT_ACTION_DOCKERFILE_FOLDER: "./ami"
375+
376+
hadolint:
377+
name: 'hadolint'
378+
runs-on: 'ubuntu-latest'
379+
steps:
380+
- name: 'Clone App Repo'
381+
uses: 'actions/checkout@v2'
382+
383+
- name: 'Clone Github-Actions repo'
384+
uses: 'actions/checkout@v2'
385+
with:
386+
repository: 'icariohealth/github-actions'
387+
token: '${{ secrets.NOVU_CI_TOKEN }}'
388+
path: './github-actions'
389+
ref: 'main'
390+
391+
- name: 'Run Hadolint'
392+
uses: './github-actions/hadolint'
393+
env:
394+
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
395+
HADOLINT_ACTION_DOCKERFILE_FOLDER: "./"

0 commit comments

Comments
 (0)