Security Scanning #37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |