Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/generate-vulnerability-report.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Generate Vulnerability Report

on:
workflow_dispatch:
inputs:
imageTag:
description: 'Image Tag to scan'
required: true
default: 'latest'
type: string

permissions:
contents: read
issues: write

jobs:
run-scan:
runs-on: ubuntu-latest
environment: dockerhub
steps:
- uses: actions/checkout@v6

- uses: actions/setup-node@v6
with:
node-version: 26

- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Get sha256 digest of requested tag
id: digest
run: |
digest=$(docker buildx imagetools inspect openintegrationengine/engine:${{ inputs.imageTag }} --raw \
| jq -r '.manifests[] | select(.platform.os == "linux" and .platform.architecture == "amd64") | .digest')

echo "Got digest: $digest"
echo "imageDigest=$digest" >> $GITHUB_OUTPUT
echo "startTime=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT

- name: Docker Scout
id: docker-scout
uses: docker/scout-action@v1
with:
command: cves
image: openintegrationengine/engine@${{ steps.digest.outputs.imageDigest }}
platform: linux/amd64
only-severities: critical,high
ignore-base: true
sarif-file: reports/sarif.json

- name: Render report
working-directory: ./reports
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node render-report.ts ${{ inputs.imageTag }} ${{ steps.digest.outputs.imageDigest }} ${{ steps.digest.outputs.startTime }}
gh issue create --title "OpenIntegrationEngine ${{ inputs.imageTag }} vulnerabilities" --body-file report.md
63 changes: 63 additions & 0 deletions .github/workflows/render-docker-templates.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Render Docker templates

on:
workflow_dispatch:
pull_request:
paths:
- releases.yaml
- render_docker_templates.sh
- templates/**
- dockerfiles/**
- .github/workflows/render-docker-templates.yaml

jobs:
render-dockerfiles:
runs-on: ubuntu-latest
env:
GOMPLATE_VERSION: 4.3.2
GOMPLATE_SHA512: ed77ea781cc2b63fbb325dbb29bbe3041f5158ccaec555fdc79b6ecca1b4c48fb6be2fee6612469c715b77fb9179be6e263ee1d58773232dfc346249afe660ab
GOMPLATE_DIR: ${{ github.workspace }}/.cache/gomplate
GOMPLATE_BIN: ${{ github.workspace }}/.cache/gomplate/gomplate
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Check for duplicate slugs
run: |
duplicates=$(yq -o=json -I=0 '.versions | map(.slug) | group_by(.) | map(select(length > 1) | .[0])' releases.yaml)
if [ "$duplicates" != "[]" ]; then
echo "::error file=releases.yaml::Duplicate version slugs: $duplicates"
exit 1
fi

- name: Cache gomplate binary
id: cache
uses: actions/cache@v5
with:
path: ${{ env.GOMPLATE_DIR }}
key: |
gomplate-${{ env.GOMPLATE_VERSION }}-${{ env.GOMPLATE_SHA512 }}

- name: Download gomplate
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
run: |
mkdir -p "${GOMPLATE_DIR}"
curl -fsSL --output "${GOMPLATE_BIN}" "https://github.com/hairyhenderson/gomplate/releases/download/v$GOMPLATE_VERSION/gomplate_linux-amd64"
echo "${{ env.GOMPLATE_SHA512 }} ${GOMPLATE_BIN}" | sha512sum --check --status
chmod +x "${GOMPLATE_BIN}"

- name: Add gomplate to PATH
shell: bash
run: echo "${GOMPLATE_DIR}" >> "${GITHUB_PATH}"

- name: Render Dockerfiles
run: ./render_docker_templates.sh

- name: Check rendered files are up to date
run: |
git add --intent-to-add dockerfiles
if ! git diff --exit-code -- dockerfiles; then
echo "::error::dockerfiles/ is out of date. Run ./render_docker_templates.sh and commit the result."
exit 1
fi
25 changes: 25 additions & 0 deletions .github/workflows/update-dockerhub-description.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Update description in Docker Hub

on:
workflow_dispatch:
push:
branches:
- main
paths:
- README.md

concurrency: build-images

jobs:
update-description:
runs-on: ubuntu-latest
environment: dockerhub
steps:
- uses: actions/checkout@v6

- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v5
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: openintegrationengine/engine
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.idea/
.idea/
reports/sarif.json
reports/report.md
19 changes: 19 additions & 0 deletions reports/local.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
#
# SPDX-License-Identifier: MPL-2.0
# SPDX-FileCopyrightText: 2026 BrightCodeCompany OÜ
#

imageRepo=openintegrationengine/engine
imageTag=latest

digest=$(docker buildx imagetools inspect "$imageRepo:$imageTag" --raw \
| jq -r '.manifests[] | select(.platform.os == "linux" and .platform.architecture == "amd64") | .digest')

echo "Got digest: $digest"
startTime=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

docker scout cves "$imageRepo@$digest" --ignore-base --only-severity critical,high --format sarif -o sarif.json

# Requires at least Node 26
node render-report.ts "$imageTag" "$digest" "$startTime"
94 changes: 94 additions & 0 deletions reports/render-report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 BrightCodeCompany OÜ

import fs from 'fs'

const severityToColor = (severity: string) => {
switch (severity.toLowerCase()) {
case 'critical':
return '🔴'
case 'high':
return ' 🟠'
case 'medium':
return '🟡'
case 'low':
return '⚪'
}
}

const getReportJson = () => {
try {
const report = fs.readFileSync('sarif.json', 'utf8')
return JSON.parse(report)
} catch (err) {
console.error(err)
process.exit(1)
}
}

const writeReportToFile = (markdown: string) => {
try {
fs.writeFileSync('report.md', markdown)
} catch (err) {
console.error(err)
process.exit(1)
}
}

const renderCve = (cve: object) => {
const pUrl = cve.properties.purls[0]
const packageName = pUrl.replace("pkg:", "").split("@")[0]
const installedVersion = pUrl.split("@")[1]
const severity = cve.properties.cvssV3_severity
const sevScore = cve.properties.cvssV3
const fixedVersion = cve.properties.fixed_version
return `| [${cve.id}](${cve.helpUri}) | ${severityToColor(severity)} | ${sevScore} | \`${packageName}\` | \`${installedVersion}\` | \`${fixedVersion}\` | |`
}

const main = (imageTag: string, imageDigest: string, startTime: Instant) => {
const report = getReportJson().runs[0]

const sortedCve = report.tool.driver.rules.sort((a: object, b: object) => {
return b.properties.cvssV3 - a.properties.cvssV3
})

let cveStrings: string[] = []
let cvssMap: { [severity: string]: number } = {}
for (const cve of sortedCve) {
const cveString = renderCve(cve)

cvssMap[cve.properties.cvssV3_severity] = (cvssMap[cve.properties.cvssV3_severity] || 0) + 1
cveStrings.push(cveString)
}
const driver = report.tool.driver

const markdown = `
## Scan

| | |
|-------------|-----------------------------------------------------------------|
| **Target** | \`${imageTag}\` |
| **Digest** | \`${imageDigest}\` |
| **Scanner** | \`${driver.fullName} ${driver.version}\` |
| **Timestamp** | \`${startTime.toString()}\` |\` |
| **Filter** | \`severity ≥ High; base-image vulns excluded; fixed only\` |

**Totals:** 🔴 Critical \`${cvssMap['CRITICAL'] || 0}\` · 🟠 High \`${cvssMap['HIGH'] || 0}\` · 🟡 Medium \`${cvssMap['MEDIUM'] || 0}\` · ⚪ Low \`${cvssMap['LOW'] || 0}\`

## Findings

| CVE | Sev | CVSS | Package | Installed | Fixed in |
|------|-----|------|---------|-----------|----------|
${cveStrings.join('\n')}
`
writeReportToFile(markdown)
}

const args = process.argv.slice(2)
console.log(args)

const imageTag = `openintegrationengine/engine:${args[0]}`
const imageDigest = args[1]
const startInstant = Temporal.Instant.from(args[2])

main(imageTag, imageDigest, startInstant)
Loading