Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
63 changes: 63 additions & 0 deletions .github/workflows/test_multi_arch_images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Test Multi-arch images

on:
schedule:
- cron: '0 */6 * * *' # runs every 6 hours
push:
branches: #
- '*'

permissions:
contents: read
id-token: write

jobs:
test_multi_arch:
runs-on: ubuntu-latest
environment:
name: plugin-development
strategy:
matrix:
platform:
- "linux/386"
- "linux/amd64"
- "linux/arm/v5"
- "linux/arm/v7"
- "linux/arm64/v8"
- "linux/ppc64le"
- "linux/riscv64"
- "linux/s390x"

steps:

- name: Checkout this repository
uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.AWS_IAM_ROLE }}

- name: Test multi-arch image - ${{ matrix.platform }}
id: inspector
uses: aws-actions/vulnerability-scan-github-action-for-amazon-inspector@investigate_multi_arch
with:
artifact_type: 'container'
artifact_path: 'debian:trixie'
platform: ${{ matrix.platform }}
display_vulnerability_findings: "enabled"
sbomgen_version: "latest"

- name: Demonstrate SBOM Output (JSON)
run: cat ${{ steps.inspector.outputs.artifact_sbom }}

- name: Display scan results
run: cat ${{ steps.inspector.outputs.inspector_scan_results }}

- name: Validate multi-arch - ${{ matrix.platform }}
run: python3 validator/validate_multi_platform_image_support.py --platform "${{ matrix.platform }}" --sbom "${{ steps.inspector.outputs.artifact_sbom }}"


1 change: 1 addition & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ runs:
- --thresholds
- ${{ inputs.threshold_fixable_only == 'true' && '--threshold-fixable-only' || '--no-op' }}
- ${{ inputs.show_only_fixable_vulns == 'true' && '--show-only-fixable-vulns'|| '--no-op' }}
- --platform=${{ inputs.platform || '' }}
Copy link
Member

Choose a reason for hiding this comment

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

Could you tell me how this works when inputs.platform is not given?
I think '' is passed to the inspector-sbomgen container as --platform option. Is '' correct option?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When the user does not specify a platform, the action passes the argument: --platform "".
This causes sbomgen to default to the host runner's CPU arch.
Docker exhibits the same behavior, so I believe this is correct.

Copy link
Member

@s-kenji s-kenji Sep 23, 2025

Choose a reason for hiding this comment

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

[Non-blocking] I checked inspector-sbomgen's source code, and confirmed empty string is treated as platform is not set. However, since this depends on inspector-sbomgen's internal implementation, would it be possible to avoid passing the --platform option to inspector-sbomgen's container or inspector-sbomgen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Notionally speaking, if the user did not specify platform, we could avoid sending the platform flag to the Python layer by sending a no-op, similar to this line:

    - ${{ inputs.show_only_fixable_vulns == 'true' && '--show-only-fixable-vulns'|| '--no-op' }}

This pattern works well for bool CLI variables, but its less effective for string variables, as in this case, because then you have to write extra code to handle no-op strings.

Since --platform "" doesn't change intended behavior, I am inclined to keep it as-is.

- --critical=${{ inputs.critical_threshold }}
- --high=${{ inputs.high_threshold }}
- --medium=${{ inputs.medium_threshold }}
Expand Down
74 changes: 74 additions & 0 deletions validator/validate_multi_platform_image_support.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env python3

import argparse
import json
import sys


def get_expected_arch(platform):
"""Map platform string to expected architecture value in SBOM"""
platform_to_arch = {
"linux/386": "386",
"linux/amd64": "amd64",
"linux/arm/v5": "arm",
"linux/arm/v7": "arm",
"linux/arm64/v8": "arm64",
"linux/ppc64le": "ppc64le",
"linux/riscv64": "riscv64",
"linux/s390x": "s390x"
}

if platform not in platform_to_arch:
raise ValueError(f"Unknown platform: {platform}")

return platform_to_arch[platform]


def extract_arch_from_sbom(sbom_file):
"""Extract architecture from SBOM metadata"""
try:
with open(sbom_file, 'r') as f:
sbom = json.load(f)

properties = sbom.get('metadata', {}).get('component', {}).get('properties', [])

for prop in properties:
if prop.get('name') == 'amazon:inspector:sbom_generator:image_arch':
return prop.get('value')

raise ValueError("Architecture property not found in SBOM")

except Exception as e:
raise ValueError(f"Failed to parse SBOM: {e}")


def main():
parser = argparse.ArgumentParser(description='Validate SBOM architecture matches expected platform')
parser.add_argument('--platform', required=True, help='Expected platform (e.g., linux/amd64)')
parser.add_argument('--sbom', required=True, help='Path to SBOM file')

args = parser.parse_args()

try:
expected_arch = get_expected_arch(args.platform)
actual_arch = extract_arch_from_sbom(args.sbom)

print(f"Platform: {args.platform}")
print(f"Expected arch: {expected_arch}")
print(f"Actual arch: {actual_arch}")

if actual_arch != expected_arch:
print(f"❌ Architecture mismatch for platform {args.platform}")
print(f" Expected: {expected_arch}")
print(f" Found: {actual_arch}")
sys.exit(1)

print(f"βœ… Architecture validation passed: {actual_arch} matches expected {expected_arch}")

except Exception as e:
print(f"❌ Validation failed: {e}")
sys.exit(1)


if __name__ == '__main__':
main()