Skip to content

Security Scanning

Security Scanning #37

name: Security Scanning
env:
CODE_EDITOR_TARGETS: '["code-editor-sagemaker-server"]'
on:
# Trigger 1: PR created on main or version branches (*.*)
pull_request:
branches:
- main
- '*.*'
types: [opened, reopened, synchronize]
# Trigger 2: Daily scheduled run at 00:13 UTC
# Schedule it a random minute because most Github Actions are scheduled
# at the start of the hour and their invocation can get delayed.
# Ref: https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#schedule
schedule:
- cron: '13 0 * * *'
# Trigger 3: Manual trigger
workflow_dispatch:
jobs:
get-branches-to-scan:
runs-on: ubuntu-latest
outputs:
security-scan-branches: ${{ steps.determine-pr-branches.outputs.branches || steps.determine-scheduled-security-scan-branches.outputs.branches }}
global-dependencies-branches: ${{ steps.determine-pr-branches.outputs.branches || steps.determine-scheduled-global-dependencies-branches.outputs.branches }}
output-branch-name: ${{ steps.determine-pr-branches.outputs.output-branch-name || steps.get-upstream-branches.outputs.output-branch-name }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine branches for PR events
id: determine-pr-branches
if: github.event_name == 'pull_request'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# For PR events, validate base branch and use head ref if valid
base_ref="${{ github.base_ref }}"
head_ref="${{ github.head_ref }}"
echo "Base branch: $base_ref"
echo "Head branch: $head_ref"
if [[ "$base_ref" =~ ^[0-9]+\.[0-9]+$ ]] || [[ "$base_ref" == "main" ]]; then
echo "Base branch matches allowed pattern (main or digit.digit)"
echo "branches=[\"$head_ref\"]" >> $GITHUB_OUTPUT
echo "output-branch-name=$base_ref" >> $GITHUB_OUTPUT
echo "Branches to scan: [$head_ref]"
echo "Output files will use branch name: $base_ref"
else
echo "Base branch does not match allowed pattern - no branches to scan"
echo "branches=[]" >> $GITHUB_OUTPUT
echo "output-branch-name=" >> $GITHUB_OUTPUT
fi
- name: Get all upstream branches
id: get-upstream-branches
if: github.event_name != 'pull_request'
run: |
# Get main branch and all version branches (*.*)
branches=$(git branch -r | grep -E 'origin/(main|[0-9]+\.[0-9]+)' | sed 's/origin\///' | tr '\n' ' ')
echo "Found upstream branches: $branches"
echo "upstream-branches=$branches" >> $GITHUB_OUTPUT
echo "output-branch-name=scheduled" >> $GITHUB_OUTPUT
- name: Get completed workflows from previous day
id: get-completed-workflows
if: github.event_name != 'pull_request'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
workflow_name="Security Scanning"
# Get workflows from 1 hour ago to now (for testing)
previous_day_start=$(date -d '1 hour ago' -u +%Y-%m-%dT%H:%M:%SZ)
previous_day_end=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "Getting completed workflows from 1 hour ago: $previous_day_start to $previous_day_end"
# Get all completed workflow runs from 1 hour ago
completed_runs=$(gh run list --workflow="$workflow_name" --json databaseId,startedAt,conclusion,headBranch --status completed --limit 100)
previous_day_runs=$(echo "$completed_runs" | jq --arg start "$previous_day_start" --arg end "$previous_day_end" '.[] | select(.startedAt >= $start and .startedAt <= $end)')
echo "Found completed workflow runs from 1 hour ago:"
echo "$previous_day_runs" | jq -r '.databaseId'
# Store workflow run IDs for artifact checking
run_ids=$(echo "$previous_day_runs" | jq -r '.databaseId' | tr '\n' ' ')
echo "workflow-run-ids=$run_ids" >> $GITHUB_OUTPUT
- name: Check for successful scan artifacts from previous day
id: check-scan-artifacts
if: github.event_name != 'pull_request'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
run_ids="${{ steps.get-completed-workflows.outputs.workflow-run-ids }}"
successful_security_scan_branches=""
successful_global_dependencies_branches=""
echo "Checking for successful scan artifacts from workflow runs (1 hour ago): $run_ids"
for run_id in $run_ids; do
if [ -n "$run_id" ]; then
echo "Checking artifacts for run ID: $run_id"
# Get artifacts for this run using GitHub API
artifacts=$(gh api /repos/${{ github.repository }}/actions/runs/$run_id/artifacts --jq '.artifacts[].name')
# Check for scan-success-branch-* artifacts
security_scan_artifacts=$(echo "$artifacts" | grep "^scan-success-branch-" || true)
global_dependencies_artifacts=$(echo "$artifacts" | grep "^global-scan-success-" || true)
# Extract branch names from artifact names
for artifact in $security_scan_artifacts; do
branch_name=$(echo "$artifact" | sed 's/scan-success-branch-files//' | sed 's/scan-success-branch-//')
if [ -n "$branch_name" ]; then
successful_security_scan_branches="$successful_security_scan_branches $branch_name"
fi
done
for artifact in $global_dependencies_artifacts; do
branch_name=$(echo "$artifact" | sed 's/global-scan-success-//')
if [ -n "$branch_name" ]; then
successful_global_dependencies_branches="$successful_global_dependencies_branches $branch_name"
fi
done
fi
done
# Remove duplicates and clean up
successful_security_scan_branches=$(echo $successful_security_scan_branches | tr ' ' '\n' | sort -u | tr '\n' ' ')
successful_global_dependencies_branches=$(echo $successful_global_dependencies_branches | tr ' ' '\n' | sort -u | tr '\n' ' ')
echo "Branches with successful security scans from 1 hour ago: $successful_security_scan_branches"
echo "Branches with successful global dependency scans from 1 hour ago: $successful_global_dependencies_branches"
echo "successful-security-scan-branches=$successful_security_scan_branches" >> $GITHUB_OUTPUT
echo "successful-global-dependencies-branches=$successful_global_dependencies_branches" >> $GITHUB_OUTPUT
- name: Determine security scan branches for scheduled runs
id: determine-scheduled-security-scan-branches
if: github.event_name != 'pull_request'
run: |
upstream_branches="${{ steps.get-upstream-branches.outputs.upstream-branches }}"
successful_branches="${{ steps.check-scan-artifacts.outputs.successful-security-scan-branches }}"
branches_to_scan=""
echo "Upstream branches: $upstream_branches"
echo "Successfully scanned branches from 1 hour ago: $successful_branches"
# Check each upstream branch
for branch in $upstream_branches; do
branch=$(echo $branch | xargs) # trim whitespace
if [ -n "$branch" ]; then
# Check if this branch was successfully scanned yesterday
if echo "$successful_branches" | grep -q "\b$branch\b"; then
echo "Skipping branch $branch - found successful scan from 1 hour ago"
else
echo "Adding branch $branch to security scan list - no successful scan from 1 hour ago"
branches_to_scan="$branches_to_scan $branch"
fi
fi
done
# Clean up and convert to JSON array
branches_to_scan=$(echo $branches_to_scan | xargs)
if [ -n "$branches_to_scan" ]; then
json_branches=$(echo "$branches_to_scan" | tr ' ' '\n' | jq -R . | jq -s -c .)
echo "branches=$json_branches" >> $GITHUB_OUTPUT
echo "Security scan branches to scan: $json_branches"
else
echo "branches=[]" >> $GITHUB_OUTPUT
echo "No security scan branches to scan - all have successful scans from 1 hour ago"
fi
- name: Determine global dependencies branches for scheduled runs
id: determine-scheduled-global-dependencies-branches
if: github.event_name != 'pull_request'
run: |
upstream_branches="${{ steps.get-upstream-branches.outputs.upstream-branches }}"
successful_branches="${{ steps.check-scan-artifacts.outputs.successful-global-dependencies-branches }}"
branches_to_scan=""
echo "Upstream branches: $upstream_branches"
echo "Successfully scanned global dependencies branches from 1 hour ago: $successful_branches"
# Check each upstream branch
for branch in $upstream_branches; do
branch=$(echo $branch | xargs) # trim whitespace
if [ -n "$branch" ]; then
# Check if this branch was successfully scanned yesterday
if echo "$successful_branches" | grep -q "\b$branch\b"; then
echo "Skipping branch $branch - found successful global dependencies scan from 1 hour ago"
else
echo "Adding branch $branch to global dependencies scan list - no successful scan from 1 hour ago"
branches_to_scan="$branches_to_scan $branch"
fi
fi
done
# Clean up and convert to JSON array
branches_to_scan=$(echo $branches_to_scan | xargs)
if [ -n "$branches_to_scan" ]; then
json_branches=$(echo "$branches_to_scan" | tr ' ' '\n' | jq -R . | jq -s -c .)
echo "branches=$json_branches" >> $GITHUB_OUTPUT
echo "Global dependencies branches to scan: $json_branches"
else
echo "branches=[]" >> $GITHUB_OUTPUT
echo "No global dependencies branches to scan - all have successful scans from 1 hour ago"
fi
security-scan:
runs-on: ubuntu-latest
needs: [get-branches-to-scan]
if: needs.get-branches-to-scan.outputs.security-scan-branches != '[]'
environment: security-scanning-workflow-env
permissions:
id-token: write # Required for OIDC
strategy:
fail-fast: false
matrix:
target: [code-editor-sagemaker-server]
branch: ${{ fromJson(needs.get-branches-to-scan.outputs.security-scan-branches) }}
steps:
- name: Checkout branch
uses: actions/checkout@v4
with:
ref: ${{ matrix.branch }}
submodules: recursive
- name: Create Success Indicator File
run: |
# For PR events, use base_ref as output branch name, otherwise use actual branch
if [ "${{ github.event_name }}" = "pull_request" ]; then
output_branch="${{ needs.get-branches-to-scan.outputs.output-branch-name }}"
else
output_branch="${{ matrix.branch }}"
fi
echo "PASS" > scan-success-${{ matrix.target }}-${output_branch}.txt
- name: Upload Success Indicator File
uses: actions/upload-artifact@v4
with:
name: scan-success-${{ matrix.target }}-${{ github.event_name == 'pull_request' && needs.get-branches-to-scan.outputs.output-branch-name || matrix.branch }}
path: scan-success-${{ matrix.target }}-${{ github.event_name == 'pull_request' && needs.get-branches-to-scan.outputs.output-branch-name || matrix.branch }}.txt
retention-days: 90
generate-security-scan-output:
runs-on: ubuntu-latest
needs: [get-branches-to-scan, security-scan]
if: always() && needs.get-branches-to-scan.outputs.security-scan-branches != '[]'
strategy:
fail-fast: false
matrix:
branch: ${{ fromJson(needs.get-branches-to-scan.outputs.security-scan-branches) }}
steps:
- name: Download all scan success files
uses: actions/download-artifact@v4
with:
pattern: scan-success-*
merge-multiple: true
- name: Check if branch was successful for all targets
run: |
# Parse targets from environment variables
targets_json='${{ env.CODE_EDITOR_TARGETS }}'
targets=($(echo "$targets_json" | jq -r '.[]'))
branch="${{ matrix.branch }}"
# For PR events, use base_ref as output branch name, otherwise use actual branch
if [ "${{ github.event_name }}" = "pull_request" ]; then
output_branch="${{ needs.get-branches-to-scan.outputs.output-branch-name }}"
else
output_branch="$branch"
fi
echo "Targets to check: ${targets[@]}"
echo "Checking success for branch: $branch (output branch: $output_branch)"
all_success=true
# Check if all target success files exist for this branch
for target in "${targets[@]}"; do
success_file="scan-success-${target}-${output_branch}.txt"
echo "Checking for file: $success_file"
if [ -f "$success_file" ]; then
echo "✓ Found success file for target $target on branch $output_branch"
else
echo "✗ Missing success file for target $target on branch $output_branch"
all_success=false
break
fi
done
# Create branch success file only if all targets succeeded
if [ "$all_success" = true ]; then
echo "✓ All scans successful for branch $output_branch - creating branch success file"
echo "PASS" > scan-success-branch-${output_branch}.txt
else
echo "✗ Some scans failed for branch $output_branch - not creating branch success file"
fi
- name: Upload Branch Success File
uses: actions/upload-artifact@v4
with:
name: scan-success-branch-${{ github.event_name == 'pull_request' && needs.get-branches-to-scan.outputs.output-branch-name || matrix.branch }}
path: scan-success-branch-${{ github.event_name == 'pull_request' && needs.get-branches-to-scan.outputs.output-branch-name || matrix.branch }}.txt
retention-days: 90
if-no-files-found: ignore
security-scan-global-dependencies:
runs-on: ubuntu-latest
needs: [get-branches-to-scan]
if: needs.get-branches-to-scan.outputs.global-dependencies-branches != '[]'
environment: security-scanning-workflow-env
permissions:
id-token: write # Required for OIDC
strategy:
fail-fast: false
matrix:
branch: ${{ fromJson(needs.get-branches-to-scan.outputs.global-dependencies-branches) }}
steps:
- name: Checkout branch
uses: actions/checkout@v4
with:
ref: ${{ matrix.branch }}
submodules: recursive
- name: Create Global Success Indicator File
run: |
# For PR events, use base_ref as output branch name, otherwise use actual branch
if [ "${{ github.event_name }}" = "pull_request" ]; then
output_branch="${{ needs.get-branches-to-scan.outputs.output-branch-name }}"
else
output_branch="${{ matrix.branch }}"
fi
echo "PASS" > global-scan-success-${output_branch}.txt
- name: Upload Global Success Indicator File
uses: actions/upload-artifact@v4
with:
name: global-scan-success-${{ github.event_name == 'pull_request' && needs.get-branches-to-scan.outputs.output-branch-name || matrix.branch }}
path: global-scan-success-${{ github.event_name == 'pull_request' && needs.get-branches-to-scan.outputs.output-branch-name || matrix.branch }}.txt
retention-days: 90