Skip to content
Open
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
84 changes: 84 additions & 0 deletions .github/actions/run-test-suite/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: "Run test suite"
description: |
Composite action that runs a single test type (unit/contract/schema/integration/acceptance),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Composite action that can run any one of the test types: unit/contract/schema/integration/acceptance

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or

Composite action that is used to run the test types:

uploads the test artefacts, and publishes a JUnit summary to the GitHub Actions job.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

junit and html

Delegates to `make test-<type>` which invokes scripts/tests/run-test.sh.
For remote tests, pass apigee-access-token to authenticate with the APIM proxy.

inputs:
test-type:
description: "Type of test to run"
required: true
apigee-access-token:
description: "Apigee access token"
required: false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the env defaults to remote:

env:
description: "Environment: local or remote"
required: false
default: "remote"

as you have further down - should this 'apigee-access-token' default to true as you say above that this token is required for a remote run?

base-url:
description: "The URL of the environment to test"
required: false
mtls-cert:
description: "Path to mTLS certificate"
required: false
mtls-key:
description: "Path to mTLS key"
required: false
stub-sds:
description: "Whether to stub SDS"
required: false
default: "false"
stub-pds:
description: "Whether to stub PDS"
required: false
default: "false"
stub-provider:
description: "Whether to stub Provider"
required: false
default: "false"
env:
description: "Environment: local or remote"
required: false
default: "remote"

runs:
using: composite
steps:
- name: "Run ${{ inputs.test-type }} tests"
shell: bash
env:
APIGEE_ACCESS_TOKEN: ${{ inputs.apigee-access-token }}
BASE_URL: ${{ inputs.base-url }}
MTLS_CERT: ${{ inputs.mtls-cert }}
MTLS_KEY: ${{ inputs.mtls-key }}
STUB_SDS: ${{ inputs.stub-sds }}
STUB_PDS: ${{ inputs.stub-pds }}
STUB_PROVIDER: ${{ inputs.stub-provider }}
ENV: ${{ inputs.env }}
run: |
if [[ -n "${APIGEE_ACCESS_TOKEN}" ]]; then
echo "::add-mask::${APIGEE_ACCESS_TOKEN}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this do?

if it masks the log when why log it out?

fi
make test-${{ inputs.test-type }}

- name: "Upload ${{ inputs.test-type }} test results"
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ inputs.test-type }}-test-results
path: gateway-api/test-artefacts/
retention-days: 30

- name: "Check ${{ inputs.test-type }}-tests.xml exists"
id: check
if: always()
shell: bash
run: |
if [[ -f "gateway-api/test-artefacts/${{ inputs.test-type }}-tests.xml" ]]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: "Publish ${{ inputs.test-type }} test results to summary"
if: ${{ always() && steps.check.outputs.exists == 'true' }}
uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86
with:
paths: gateway-api/test-artefacts/${{ inputs.test-type }}-tests.xml
25 changes: 20 additions & 5 deletions .github/actions/setup-python-project/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,33 @@ inputs:
python-version:
description: "Python version to install"
required: true

runs:
using: "composite"
steps:
- name: "Install system dependencies"
shell: bash
run: |
if ! dpkg -s libxslt1-dev >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y libxml2-dev libxslt1-dev
fi

- name: "Install Poetry"
shell: bash
run: |
if ! command -v poetry &> /dev/null; then
curl --proto "=https" -sSL https://install.python-poetry.org | python3 -
echo "$HOME/.local/bin" >> $GITHUB_PATH
fi

- name: "Set up Python"
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- name: "Install Poetry"
shell: bash
run: |
curl --proto "=https" -sSL https://install.python-poetry.org | python3 -
echo "$HOME/.local/bin" >> $GITHUB_PATH
cache: 'poetry'
cache-dependency-path: '**/poetry.lock'

- name: "Install dependencies"
shell: bash
run: make dependencies
130 changes: 130 additions & 0 deletions .github/actions/trivy-fs-sbom/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
name: SBOM Scan
description: Performs SBOM scanning and reporting with optional dependency graph upload

inputs:
image-ref:
description: 'Docker image reference to scan'
required: false
repo-path:
description: "Path to git repo to scan (local or remote)"
required: false
fs-path:
description: 'Path to filesystem to scan'
required: false
github-token:
description: 'GitHub token for dependency graph upload'
required: false
publish-to-dependency-graph:
description: 'Whether to publish SBOM to GitHub Dependency Graph'
required: false
default: 'false'
artifact-name:
description: 'Name for the uploaded SBOM artifact'
required: false
default: 'sbom'

outputs:
sbom-path:
description: 'Path to the generated SBOM file'
value: 'sbom.spdx.json'

runs:
using: "composite"
steps:
- name: Validate inputs
shell: bash
env:
IMAGE_REF: ${{ inputs.image-ref }}
REPO_PATH: ${{ inputs.repo-path }}
FS_PATH: ${{ inputs.fs-path }}
run: |
echo "IMAGE_REF:" $IMAGE_REF
echo "REPO_PATH:" $REPO_PATH
echo "FS_PATH:" $FS_PATH
if [[ -z $IMAGE_REF && -z $REPO_PATH && -z $FS_PATH ]]; then
echo "Must define one of IMAGE_REF or REPO_PATH or FS_PATH"
exit 1
elif [[ -n $IMAGE_REF && -n $REPO_PATH && -n $FS_PATH ]]; then
echo "Must define only one of IMAGE_REF or REPO_PATH or FS_PATH, not multiple."
exit 1
fi
ls -l $FS_PATH || true

- name: Trivy SBOM SPDX Scan - Docker Image
if: ${{ inputs.image-ref != '' }}
uses: aquasecurity/trivy-action@0.34.2
with:
scan-type: image
image-ref: ${{ inputs.image-ref }}
format: spdx-json
output: sbom.spdx.json

- name: Trivy SBOM SPDX Scan - Repo
if: ${{ inputs.repo-path != '' }}
uses: aquasecurity/trivy-action@0.34.2
with:
scan-type: repo
scan-ref: ${{ inputs.repo-path }}
format: spdx-json
output: sbom.spdx.json

- name: Trivy SBOM SPDX Scan - Filesystem
if: ${{ inputs.fs-path != '' }}
uses: aquasecurity/trivy-action@0.34.2
with:
scan-type: fs
scan-ref: ${{ inputs.fs-path }}
format: spdx-json
output: sbom.spdx.json

- name: Trivy SBOM Dependency Graph Upload
if: ${{ inputs.publish-to-dependency-graph == 'true' && inputs.github-token != '' }}
uses: aquasecurity/trivy-action@0.34.2
with:
scan-type: image
image-ref: ${{ inputs.image-ref }}
format: github
output: sbom.github.json
github-pat: ${{ inputs.github-token }}

- name: Upload SBOM artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ inputs.artifact-name }}
path: sbom.spdx.json

- name: Append SBOM inventory to summary
if: always()
shell: bash
run: |
cat > sbom_to_summary.jq <<'JQ'
def clean: (.|tostring) | gsub("\\|"; "\\|") | gsub("\r?\n"; " ");
def purl: ((.externalRefs[]? | select(.referenceType=="purl") | .referenceLocator) // "");
def license: (.licenseConcluded // .licenseDeclared // "");
def supplier: ((.supplier // "") | sub("^Person: *|^Organization: *";""));

if (has("spdxVersion") | not) then
"### SBOM Inventory (SPDX)\n\nSBOM is not SPDX JSON."
else
.packages as $pkgs
| "### SBOM Inventory (SPDX)\n\n"
+ "| Metric | Value |\n|---|---|\n"
+ "| Packages | " + ($pkgs|length|tostring) + " |\n\n"
+ "<details><summary>Full inventory</summary>\n\n"
+ "| Package | Version | Supplier | License | PURL |\n|---|---|---|---|---|\n"
+ (
$pkgs
| map("| "
+ ((.name // .SPDXID) | clean)
+ " | " + ((.versionInfo // "") | clean)
+ " | " + (supplier | clean)
+ " | " + (license | clean)
+ " | " + (purl | clean)
+ " |")
| join("\n")
)
+ "\n\n</details>\n"
end
JQ

jq -r -f sbom_to_summary.jq sbom.spdx.json >> "$GITHUB_STEP_SUMMARY"
143 changes: 143 additions & 0 deletions .github/actions/trivy-fs-scan/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: Trivy fs Scan
description: Performs Trivy security scanning for filesystems with comprehensive reporting

inputs:
filesystem-ref:
description: 'Filesystem reference to scan (e.g., /path/to/filesystem)'
required: true
severity:
description: 'Comma-separated list of severity levels to report'
required: false
default: 'HIGH,CRITICAL,MEDIUM,LOW,UNKNOWN'
trivy-config:
description: 'Path to Trivy configuration file'
required: false
default: 'trivy.yaml'
artifact-name:
description: 'Name for the uploaded artifact'
required: false
default: 'trivy-fs-scan-results'
fail-on-critical-high:
description: 'Whether to fail the action on critical/high findings'
required: false
default: 'true'
ignore-unfixed:
description: 'Ignore unfixed vulnerabilities'
required: false
default: 'true'

outputs:
critical-count:
description: 'Number of critical severity findings'
value: ${{ steps.report.outputs.crit }}
high-count:
description: 'Number of high severity findings'
value: ${{ steps.report.outputs.high }}
report-path:
description: 'Path to the generated markdown report'
value: 'trivy_fs_report.md'

runs:
using: "composite"
steps:
- name: Check if Trivy config exists
id: trivy-config-check
shell: bash
run: |
if [[ -f "${{ inputs.trivy-config }}" ]]; then
echo "config-exists=true" >> "$GITHUB_OUTPUT"
echo "config-arg=${{ inputs.trivy-config }}" >> "$GITHUB_OUTPUT"
else
echo "config-exists=false" >> "$GITHUB_OUTPUT"
echo "config-arg=" >> "$GITHUB_OUTPUT"
fi

- name: Trivy fs scan
uses: aquasecurity/trivy-action@0.34.2
with:
scan-type: 'fs'
scan-ref: ${{ inputs.filesystem-ref }}
format: json
output: trivy-fs-scan.json
exit-code: 0
ignore-unfixed: ${{ inputs.ignore-unfixed }}
severity: ${{ inputs.severity }}
trivy-config: ${{ steps.trivy-config-check.outputs.config-arg }}

- name: Upload Trivy Scan Artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ inputs.artifact-name }}
path: trivy-fs-scan.json

- name: Build summary & counts
id: report
shell: bash
run: |
# Filesystem scan report
jq -r '
def clean: (.|tostring) | gsub("\\|";"\\|") | gsub("\r?\n";" ");
def sev: ["CRITICAL","HIGH","MEDIUM","LOW","UNKNOWN"];
def counts:
([.Results[]? | .Vulnerabilities[]? | .Severity // "UNKNOWN"]
| reduce .[] as $s ({CRITICAL:0,HIGH:0,MEDIUM:0,LOW:0,UNKNOWN:0}; .[$s]+=1));
. as $root
| (counts) as $c
|
# ---- TOP BANNER (only if High/Critical present) ----
(
if (($c.CRITICAL + $c.HIGH) > 0) then
"🚫 **Trivy gate:** **\($c.CRITICAL) Critical**, **\($c.HIGH) High** vulnerability(s) found.\n\n"
else
"✅ **Trivy gate:** no Critical/High vulnerabilities.\n\n"
end
)
# ---- SUMMARY REPORT ----
+ "### Trivy Filesystem Scan Summary\n\n"
+ "**Filesystem:** " + ($root.ArtifactName // "'"'"'${{ inputs.filesystem-ref }}'"'"'") + "\n\n"
+ "| Severity | Count |\n|---|---|\n"
+ (sev | map("| " + . + " | " + ($c[.]|tostring) + " |") | join("\n"))
+ (if ([.Results[]? | .Vulnerabilities[]?] | length) == 0
then "\n\n✅ No vulnerabilities found.\n"
else
"\n\n<details><summary>Findings (top 50)</summary>\n\n"
+ "| Severity | ID | Package | Installed | Fixed | Source |\n|---|---|---|---|---|---|\n"
+ (
[ .Results[]? as $r
| $r.Vulnerabilities[]?
| "| \(.Severity) | \(.VulnerabilityID) | \(.PkgName) | \(.InstalledVersion) | \((.FixedVersion // "") | clean) | \(($r.Target) | clean) |"
] | .[:50] | join("\n")
)
+ "\n\n</details>\n"
end)
' trivy-fs-scan.json > trivy_fs_report.md

# Extract counts for gating/other steps
read CRIT HIGH < <(jq -r '
[.Results[]? | .Vulnerabilities[]? | .Severity // "UNKNOWN"]
| reduce .[] as $s ({CRITICAL:0,HIGH:0,MEDIUM:0,LOW:0,UNKNOWN:0}; .[$s]+=1)
| "\(.CRITICAL) \(.HIGH)"
' trivy-fs-scan.json)

echo "crit=$CRIT" >> "$GITHUB_OUTPUT"
echo "high=$HIGH" >> "$GITHUB_OUTPUT"

- name: Publish Trivy Summary
if: always()
shell: bash
run: cat trivy_fs_report.md >> "$GITHUB_STEP_SUMMARY"

- name: Update Trivy PR comment
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }}
uses: marocchino/sticky-pull-request-comment@v2
with:
header: ${{ inputs.artifact-name }}
path: trivy_fs_report.md

- name: Check Trivy Issue Thresholds
if: ${{ inputs.fail-on-critical-high == 'true' && (steps.report.outputs.crit != '0' || steps.report.outputs.high != '0') }}
shell: bash
run: |
echo "Critical vulnerabilities detected: ${{ steps.report.outputs.crit }}"
echo "High vulnerabilities detected: ${{ steps.report.outputs.high }}"
exit 1
Loading
Loading