Skip to content

CD grype for downloads.chef.io #5

CD grype for downloads.chef.io

CD grype for downloads.chef.io #5

# CD pipeline to download packages from Commercial and/or Community downloads API and run Grype security scan
# test inputs:
# product: chef (or chef-workstation)
# product-version: 18.8.54 (for chef), 25.12.1102 (for chef-workstation)
# channel: stable
# architecture: x86_64
# os-platform: both (ubuntu and windows)
# os-platform-version: 24.04 (TODO: make this selectable by Windows and linux separately later)
# download-site: commercial
# license-id: (not specified, uses secret)
# called ad-hoc (dispatch) only
#
# performs the following actions:
# 1. create the specified runner (Windows or ubuntu latest, later add specific OS versions to matrix)
# 2. download grype
# 3. download the specified product from https://chefdownload-community.chef.io/stable/<PRODUCT>/download?p=<PLATFORM>&pv=<PLATFORM_VERSION>&m=<ARCHITECTURE>&v=<PRODUCT_VERSION>
# OR https://chefdownload-commercial.chef.io/stable/chef/download?p=windows&pv=11&m=x86_64&v=25.12.1102&license_id=LICENSE_ID
# 4. unzip or c:/tmp (Windows) or untar the downloaded package to to /tmp
# 5. grype dir:.
# 6. upload the vulnerability scan artifact
# secrets required (by step)
# DOWNLOAD_LICENSE_TOKEN (provided in common-github-actions repo as organization level secret GA_DOWNLOAD_GRYPE_LICENSE_ID,
# but can be overridden on inputs)
# product versions can be listed with https://chefdownload-commercial.chef.io/<CHANNEL>/<PRODUCT>/versions/all?license_id=<LICENSE_ID>, or
# get latest with https://chefdownload-commercial.chef.io/<CHANNEL>/<PRODUCT>/versions/latest?license_id=<LICENSE_ID> (default)
# platforms are hardcoded as a choice list but not tied to platform-version, so user must ensure they are compatible
# see samples at https://chefdownload-commercial.chef.io/stable/chef/packages?license_id=LICENSE_ID
# chef-client platform versions (broadest subset?) with architectures are
# {
# "amazon": {
# "2": ["aarch64", "x86_64"],
# "2023": [ "aarch64", "x86_64"]
# },
# "debian": {
# "10": [ "x86_64" ],
# "11": [ "x86_64" ],
# "9": [ "x86_64" ]
# },
# "el": {
# "7": [ "aarch64", "ppc64", "ppc64le", "s390x", "x86_64"],
# "8": [ "aarch64", "s390x", "x86_64"],
# "9": [ "aarch64", "x86_64"]
# },
# "freebsd": {
# "13": [ "x86_64" ]
# },
# "mac_os_x": {
# "12": [ "aarch64", "x86_64" ],
# "13": [ "aarch64" ],
# "14": [ "aarch64" ]
# },
# "rocky": {
# "8": [ "x86_64" ],
# "9": [ "x86_64" ]
# },
# "sles": {
# "12": [ "s390x", "x86_64" ],
# "15": [ "aarch64", "s390x", "x86_64" ]
# },
# "solaris2": {
# "5.11": [ "i386", "sparc"]
# },
# "ubuntu": {
# "16.04": [ "x86_64" ],
# "18.04": [ "aarch64", "x86_64" ],
# "20.04": [ "aarch64", "x86_64" ],
# "22.04": [ "aarch64", "x86_64" ],
# "24.04": [ "aarch64", "x86_64" ]
# },
# "windows": {
# "10": [ "x86_64" ],
# "11": [ "x86_64" ],
# "2016": [ "x86_64" ],
# "2019": [ "x86_64" ],
# "2022": [ "x86_64" ],
# "2025": [ "x86_64" ]
# }
# }
name: CD Scan downloadable customer packages with grype
on:
workflow_dispatch:
inputs:
download-site:
description: 'Download site to use (commercial or community)'
required: false
type: choice
options:
- community
- commercial
default: 'commercial'
# https://chefdownload-commercial.chef.io/ (default)
# https://chefdownload-community.chef.io/
# GA_DOWNLOAD_GRYPE_LICENSE_ID
license-id:
description: 'License ID for commercial downloads'
required: false
type: string
product:
description: 'Specific product to download (e.g., chef, chef-infra-server, chef-automate, habitat, inspec, chef-workstation)'
required: true
type: choice
options:
- automate
- chef
- chef-backend
- chef-server
- chef-workstation
- habitat
- inspec
- manage
- supermarket
- chef-360
default: 'chef'
product-version:
description: 'Product version to download (e.g., 25.12.1102 for chef workstation, 4.0.0 for automate, etc.)'
required: false
type: string
channel:
description: 'Release channel to use (stable or current)'
required: false
type: choice
options:
- stable
- current
default: 'stable'
architecture:
description: 'Hardware architecture'
required: false
type: choice
options:
- aarch64
- armv7l
- i386
- powerpc
- ppc64
- ppc64le
- s390x
- sparc
- universal
- x86_64
default: 'x86_64'
os-platform:
description: 'OS platform'
required: false
type: choice
options:
- aix
- amazon
- darwin
- debian
- el
- freebsd
- ios_xr
- linux
- linux-kernel2
- mac_os_x
- nexus
- rocky
- sles
- solaris2
- suse
- ubuntu
- windows
default: 'ubuntu'
os-platform-version:
description: 'OS platform version'
required: false
type: string
test-runner:
description: 'Test runner OS (windows-latest, ubuntu-latest, or both)'
required: false
type: choice
options:
- ubuntu-latest
- windows-latest
- both
default: 'ubuntu-latest'
env:
REPO_VISIBILITY: ${{ github.event.repository.visibility }}
REPO_NAME: ${{ github.event.repository.name }}
PIPELINE_VERSION: '1.0.0' # version of this CD pipeline
# PRIMARY_APPLICATION: 'default' # Custom repo property [primaryApplication]: chef360, automate, infra-server, habitat, supermarket, licensing, downloads, chef-client, inspec, chef-workstation (or derivatives like habitat-builder)
# GA_BUILD_LANGUAGE: ${{ inputs.language}} # Custom repo property [GABuildLanguage]: go, ruby, erlang, rust (replaces Language input)
# GA_BUILD_PROFILE: ${{ inputs.build-profile }} # Custom repo property [GABuildProfile]: TBD
# # APP_VERSION: $(cat VERSION)
FILE_PREFIX: $(echo "${{ github.repository }}" | sed 's|/|-|g')-$(date +%Y%m%d%H%M%S)
DEFAULT_FILE_EXTENSION: ".json"
DEFAULT_SEPARATOR: "-"
jobs:
precompilation-checks:
name: 'echo action parameters'
runs-on: ubuntu-latest
steps:
- run: |
echo "CD Download Customer Packages and Grype Scan Pipeline version $PIPELINE_VERSION"
echo "** INPUTS ***********************************************"
echo " Download site: ${{ inputs.download-site }}"
echo " Product: ${{ inputs.product }}"
echo " Product version: ${{ inputs.product-version }}"
echo " Channel: ${{ inputs.channel }}"
echo " Architecture: ${{ inputs.architecture }}"
echo " OS Platform: ${{ inputs.os-platform }}"
echo " OS Platform Version: ${{ inputs.os-platform-version }}"
echo " Test runner: ${{ inputs.test-runner }}"
echo "*************************************************************"
grype-scan-linux:
name: 'Linux grype scan of customer-downloadable packages'
runs-on: ubuntu-latest # TODO: make this a versioned OS strategy later
if: ${{ success() && (inputs.test-runner == 'ubuntu-latest' || inputs.test-runner == 'both') }}
steps:
- name: Generate filename prefix, download URL and license-id as environment variables for later steps
run: |
FILE_PREFIX=$(echo "grype${{ env.DEFAULT_SEPARATOR }}${{ inputs.product }}${{ env.DEFAULT_SEPARATOR }}${{ inputs.product-version }}${{ env.DEFAULT_SEPARATOR }}ubuntu" | sed 's|/|-|g')-$(date +%Y%m%d%H%M%S)
echo "FILE_PREFIX=${FILE_PREFIX}" >> $GITHUB_ENV
echo "Generated FILE_PREFIX: ${FILE_PREFIX}"
DOWNLOAD_URL="https://chefdownload-commercial.chef.io"
if [ ${{ inputs.download-site }} = "community" ]; then
DOWNLOAD_URL="https://chefdownload-community.chef.io"
fi
echo "DOWNLOAD_URL=${DOWNLOAD_URL}" >> $GITHUB_ENV
echo "DOWNLOAD_URL is set to ${DOWNLOAD_URL}"
# get the license_id from input or secret
LICENSE_ID="${{ inputs.license-id }}"
if [ -z "${LICENSE_ID}" ]; then
LICENSE_ID="${{ secrets.GA_DOWNLOAD_GRYPE_LICENSE_ID }}"
echo "Using license ID from repository secret"
else
echo "Using license ID from workflow input"
fi
echo "LICENSE_ID=${LICENSE_ID}" >> $GITHUB_ENV
- name: Install Grype (ubuntu-latest)
continue-on-error: true
run: |
curl -sSfL https://get.anchore.io/grype | sh -s -- -b /usr/local/bin
- name: Download package under test (ubuntu-latest)
run: |
# Example download URL construction
DOWNLOAD_URL="${{ env.DOWNLOAD_URL }}/${{ inputs.channel }}/${{ inputs.product }}/download?p=${{ inputs.os-platform }}&pv=${{ inputs.os-platform-version }}&m=${{ inputs.architecture }}"
if [ -n "${{ inputs.product-version }}" ]; then
DOWNLOAD_URL="${DOWNLOAD_URL}&v=${{ inputs.product-version }}"
fi
echo "Downloading package from: ${DOWNLOAD_URL}"
if [ "${{ inputs.download-site }}" = "commercial" ]; then
LICENSE_ID="${{ env.LICENSE_ID }}"
if [ -n "${LICENSE_ID}" ]; then
DOWNLOAD_URL="${DOWNLOAD_URL}&license_id=${LICENSE_ID}"
echo "Using license ID in download URL"
fi
fi
echo "Full DOWNLOAD_URL: ${DOWNLOAD_URL}"
echo "DOWNLOAD_URL=${DOWNLOAD_URL}" >> $GITHUB_ENV
# Download the package
curl -L -o /tmp/package_downloaded "${DOWNLOAD_URL}"
# Handle package extraction based on platform
if [ "${{ inputs.os-platform }}" = "ubuntu" ]; then
# Ubuntu downloads are .deb packages - install them
echo "Installing .deb package..."
sudo dpkg -i /tmp/package_downloaded
# Echo where it was installed
echo "Package installed. Listing installed files:"
dpkg -L ${{ inputs.product }} || dpkg -L $(dpkg -I /tmp/package_downloaded | grep Package: | awk '{print $2}')
# Set extraction path for grype scan
mkdir -p /tmp/extracted_packages
# Copy installed files to extraction directory for scanning
echo "Copying installed files for scanning..."
if [ "${{ inputs.product }}" = "chef" ]; then
sudo cp -r /opt/chef /tmp/extracted_packages/ 2>/dev/null || true
elif [ "${{ inputs.product }}" = "chef-workstation" ]; then
sudo cp -r /opt/chef-workstation /tmp/extracted_packages/ 2>/dev/null || true
else
sudo cp -r /opt/${{ inputs.product }} /tmp/extracted_packages/ 2>/dev/null || true
fi
else
# Extract the package based on its type (assuming .tar.gz for non-Ubuntu)
mkdir -p /tmp/extracted_packages
tar -xzf /tmp/package_downloaded -C /tmp/extracted_packages
echo "Package downloaded and extracted to /tmp/extracted_packages"
fi
ls -l /tmp/extracted_packages
- name: Run Grype scan on extracted directory
timeout-minutes: 15 # Sets a 15-minute timeout for this specific step
run: |
# run grype in runner
grype dir:/tmp/extracted_packages --name ${{ inputs.product }}
# run grype to output to file (which is uploaded to the job as an artifact)
OUTPUT_FILE="${{ env.FILE_PREFIX }}.txt"
OUTPUT_FILE="${OUTPUT_FILE//\//-}"
echo $OUTPUT_FILE
grype dir:/tmp/extracted_packages --name ${{ inputs.product }} > $OUTPUT_FILE
echo "OUTPUT_FILE=$OUTPUT_FILE" >> $GITHUB_ENV
- name: Upload Grype Scan Results
uses: actions/upload-artifact@v4
with:
name: ${{ env.OUTPUT_FILE }}
path: ${{ env.OUTPUT_FILE }}
grype-scan-windows:
name: 'Windows grype scan of customer-downloadable packages'
runs-on: windows-latest # TODO: make this a versioned OS strategy later
if: ${{ success() && (inputs.test-runner == 'windows-latest' || inputs.test-runner == 'both') }}
steps:
- name: Generate filename prefix, download URL and license-id as environment variables for later steps
shell: pwsh
run: |
$FilePrefix = "grype${{ env.DEFAULT_SEPARATOR }}${{ inputs.product }}${{ env.DEFAULT_SEPARATOR }}${{ inputs.product-version }}${{ env.DEFAULT_SEPARATOR }}windows" -replace '/', '-'
$FilePrefix = "${FilePrefix}-$(Get-Date -Format 'yyyyMMddHHmmss')"
echo "FILE_PREFIX=$FilePrefix" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Host "Generated FILE_PREFIX: $FilePrefix"
$DownloadUrl = "https://chefdownload-commercial.chef.io"
if ("${{ inputs.download-site }}" -eq "community") {
$DownloadUrl = "https://chefdownload-community.chef.io"
}
echo "DOWNLOAD_URL=$DownloadUrl" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Host "DOWNLOAD_URL is set to $DownloadUrl"
# get the license_id from input or secret
$LicenseId = "${{ inputs.license-id }}"
if ([string]::IsNullOrEmpty($LicenseId)) {
$LicenseId = "${{ secrets.GA_DOWNLOAD_GRYPE_LICENSE_ID }}"
Write-Host "Using license ID from repository secret"
} else {
Write-Host "Using license ID from workflow input"
}
echo "LICENSE_ID=$LicenseId" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Install Grype (windows-latest)
continue-on-error: true
run: |
$ErrorActionPreference = 'Stop'
# Download and install Grype for Windows
$grypeVersion = (Invoke-RestMethod -Uri "https://api.github.com/repos/anchore/grype/releases/latest").tag_name
$grypeUrl = "https://github.com/anchore/grype/releases/download/$grypeVersion/grype_$($grypeVersion.TrimStart('v'))_windows_amd64.zip"
$grypeZip = "$env:TEMP\grype.zip"
$grypeDir = "$env:TEMP\grype"
# Download Grype
Invoke-WebRequest -Uri $grypeUrl -OutFile $grypeZip
# Extract Grype
Expand-Archive -Path $grypeZip -DestinationPath $grypeDir -Force
# Add Grype to PATH for subsequent steps
echo "$grypeDir" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
# Verify installation
& "$grypeDir\grype.exe" version
- name: Download package under test (windows-latest)
run: |
# Example download URL construction
$DownloadUrl = "${{ env.DOWNLOAD_URL }}/${{ inputs.channel }}/${{ inputs.product }}/download?p=${{ inputs.os-platform }}&pv=${{ inputs.os-platform-version }}&m=${{ inputs.architecture }}"
if ("${{ inputs.product-version }}" -ne "") {
$DownloadUrl = "${DownloadUrl}&v=${{ inputs.product-version }}"
}
Write-Host "Downloading package from: ${DownloadUrl}"
if ("${{ inputs.download-site }}" -eq "commercial") {
$LicenseId = "${{ env.LICENSE_ID }}"
if (-not [string]::IsNullOrEmpty($LicenseId)) {
$DownloadUrl = "${DownloadUrl}&license_id=${LicenseId}"
Write-Host "Using license ID in download URL"
}
}
# Download the package
Invoke-WebRequest -Uri $DownloadUrl -OutFile "$env:TEMP\package_downloaded" -FollowRelocation
# Handle package extraction/installation based on platform
$ExtractPath = "$env:TEMP\extracted_packages"
New-Item -ItemType Directory -Force -Path $ExtractPath
if ("${{ inputs.os-platform }}" -eq "windows") {
# Windows downloads are .msi packages - install them
Write-Host "Installing .msi package..."
Start-Process msiexec.exe -ArgumentList "/i", "$env:TEMP\package_downloaded", "/qn", "/norestart" -Wait
# Echo where it was installed
Write-Host "Package installed. Common installation paths:"
if (Test-Path "C:\opscode\chef") {
Write-Host "Found installation at: C:\opscode\chef"
Get-ChildItem -Path "C:\opscode\chef" -Recurse -Depth 1
Copy-Item -Path "C:\opscode\chef" -Destination $ExtractPath -Recurse -Force
}
if (Test-Path "C:\opscode\chef-workstation") {
Write-Host "Found installation at: C:\opscode\chef-workstation"
Get-ChildItem -Path "C:\opscode\chef-workstation" -Recurse -Depth 1
Copy-Item -Path "C:\opscode\chef-workstation" -Destination $ExtractPath -Recurse -Force
}
if (Test-Path "C:\opscode\${{ inputs.product }}") {
Write-Host "Found installation at: C:\opscode\${{ inputs.product }}"
Get-ChildItem -Path "C:\opscode\${{ inputs.product }}" -Recurse -Depth 1
Copy-Item -Path "C:\opscode\${{ inputs.product }}" -Destination $ExtractPath -Recurse -Force
}
} else {
# Extract the package based on its type (assuming .zip for non-Windows platforms)
Expand-Archive -Path "$env:TEMP\package_downloaded" -DestinationPath $ExtractPath -Force
Write-Host "Package downloaded and extracted to $ExtractPath"
}
Get-ChildItem -Path $ExtractPath
- name: Run Grype scan on extracted directory
timeout-minutes: 15 # Sets a 15-minute timeout for this specific step
run: |
$ExtractPath = "$env:TEMP\extracted_packages"
# run grype in runner
grype dir:$ExtractPath --name ${{ inputs.product }}
# run grype to output to file (which is uploaded to the job as an artifact)
$OutputFile = "${{ env.FILE_PREFIX }}.txt"
$OutputFile = $OutputFile -replace '/', '-'
Write-Host $OutputFile
grype dir:$ExtractPath --name ${{ inputs.product }} | Out-File -FilePath $OutputFile -Encoding utf8
echo "OUTPUT_FILE=$OutputFile" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Upload Grype Scan Results
uses: actions/upload-artifact@v4
with:
name: ${{ env.OUTPUT_FILE }}
path: ${{ env.OUTPUT_FILE }}