From 902a00bf31e7e07e156969564c46ced6da04d7c2 Mon Sep 17 00:00:00 2001 From: Matthew Mattox Date: Thu, 9 Jul 2026 20:49:25 -0500 Subject: [PATCH 1/2] chore(ci): migrate disabled Trivy scan to reusable Grype workflow Replace the per-repo (if: false) Trivy security-scan job with a caller of SupportTools/ci-runners' reusable grype-scan.yml, keeping it opt-in/non-blocking (if: false + blocking: false) since the KubeTTY image is still too large to scan practically in CI. Also swaps the local best-effort validate-pipeline-local.sh trivy checks for their direct grype equivalents, and adds an empty .grype.yaml (no .trivyignore existed previously). --- .github/workflows/pipeline.yml | 110 +++++---------------------------- .grype.yaml | 5 ++ validate-pipeline-local.sh | 28 +++++---- 3 files changed, 37 insertions(+), 106 deletions(-) create mode 100644 .grype.yaml diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 4f3fea5..838c583 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -208,99 +208,23 @@ jobs: NODE_MAJOR=20 # Stage 4: Security Scanning (disabled - image too large for CI timeout) - security-scan: - name: Security Scan - runs-on: ubuntu-latest - needs: build - if: false # Disabled - KubeTTY image too large for Trivy to scan in CI + # Migrated from a per-repo Trivy job to the org-wide reusable Grype workflow + # for consistency across repos. Still opt-in/non-blocking: `if: false` mirrors + # the previous Trivy job's disabled state, and `blocking: false` ensures the + # scan (if ever enabled) never gates the pipeline. + grype-scan: + name: Security Scan (Grype) + needs: [build] + if: false # Disabled - KubeTTY image too large for Grype to scan in CI (same reasoning as the prior Trivy job) + uses: SupportTools/ci-runners/.github/workflows/grype-scan.yml@main + with: + image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.image-digest }} + fail-on: high + blocking: false permissions: - actions: read contents: read + id-token: write security-events: write - id-token: write # GitHub OIDC -> Vault (keyless Harbor login) - steps: - - name: Checkout code - uses: actions/checkout@v4 - - # Keyless: exchange the GitHub OIDC token at Vault for the repo-scoped - # Harbor robot (replaces org admin HARBOR_USERNAME/PASSWORD). - - name: Vault login (GitHub OIDC) + Harbor robot - uses: hashicorp/vault-action@v3 - with: - url: https://vault.support.tools - method: jwt - path: github-actions - role: gha-kubetty - jwtGithubAudience: https://github.com/SupportTools - exportEnv: true - secrets: | - secret/data/harbor/kubetty-ci-robot username | HARBOR_ROBOT_USER ; - secret/data/harbor/kubetty-ci-robot password | HARBOR_ROBOT_TOKEN - - - name: Log in to Harbor - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ env.HARBOR_ROBOT_USER }} - password: ${{ env.HARBOR_ROBOT_TOKEN }} - - - name: Extract image tag - id: extract-tag - run: | - # Use proper quoting to prevent shell injection - # For semver tags (v0.6.0), docker/metadata-action creates tag without 'v' prefix (0.6.0) - if [[ "${{ github.ref_type }}" == "tag" ]]; then - TAG="${{ github.ref_name }}" - # Strip 'v' prefix to match docker/metadata-action semver pattern - TAG="${TAG#v}" - elif [[ "${{ github.ref_name }}" == "main" ]]; then - TAG="latest" - else - TAG="${{ github.ref_name }}-${{ github.sha }}" - fi - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - echo "Image tag: ${TAG}" - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.extract-tag.outputs.tag }} - format: 'sarif' - output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH' - scanners: 'vuln' - timeout: '30m' - - - name: Upload Trivy results to GitHub Security - uses: github/codeql-action/upload-sarif@v3 - continue-on-error: true # Optional - requires Code Security to be enabled on repo - with: - sarif_file: 'trivy-results.sarif' - - - name: Run Trivy for blocking on CRITICAL - uses: aquasecurity/trivy-action@master - with: - image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.extract-tag.outputs.tag }} - format: 'table' - exit-code: '1' - severity: 'CRITICAL' - scanners: 'vuln' - timeout: '30m' - - - name: Generate SBOM - uses: aquasecurity/trivy-action@master - with: - image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.extract-tag.outputs.tag }} - format: 'cyclonedx' - output: 'sbom.json' - scanners: 'vuln' - timeout: '30m' - - - name: Upload SBOM as artifact - uses: actions/upload-artifact@v4 - with: - name: sbom - path: sbom.json # Stage 5: Helm Chart Validation helm-validate: @@ -378,11 +302,11 @@ jobs: echo "✓ Valid semantic version: $TAG" - name: Verify security scan passed - if: false # Disabled - security scan is skipped (image too large for CI) + if: false # Disabled - Grype scan is opt-in/skipped (image too large for CI) run: | - if [ "${{ needs.security-scan.result }}" != "success" ]; then + if [ "${{ needs.grype-scan.result }}" != "success" ]; then echo "ERROR: Security scan did not pass" - echo "Security scan result: ${{ needs.security-scan.result }}" + echo "Security scan result: ${{ needs.grype-scan.result }}" echo "Cannot deploy to production with security issues" exit 1 fi diff --git a/.grype.yaml b/.grype.yaml new file mode 100644 index 0000000..ddb3f88 --- /dev/null +++ b/.grype.yaml @@ -0,0 +1,5 @@ +# Grype configuration for KubeTTY. +# No .trivyignore existed prior to this migration, so this starts empty. +# Add CVE IDs here to suppress specific findings once the grype-scan job +# (currently opt-in/disabled in .github/workflows/pipeline.yml) is enabled. +ignore: [] diff --git a/validate-pipeline-local.sh b/validate-pipeline-local.sh index 70d4094..d03de46 100755 --- a/validate-pipeline-local.sh +++ b/validate-pipeline-local.sh @@ -314,20 +314,22 @@ if [ "$FULL_MODE" = true ]; then exit 1 fi - # Security scan with Trivy (if available) - if command -v trivy &> /dev/null; then - print_stage "Running Trivy security scan" - - echo "Scanning for HIGH and CRITICAL vulnerabilities..." - if trivy image --severity HIGH,CRITICAL --exit-code 0 "$IMAGE_TAG"; then - print_success "Trivy scan completed (informational)" + # Security scan with Grype (if available) + # NOTE: CI's grype-scan job is opt-in/disabled (if: false) - this local scan + # is best-effort and informational only; it does not block CI either way. + if command -v grype &> /dev/null; then + print_stage "Running Grype security scan" + + echo "Scanning for vulnerabilities (informational)..." + if grype "$IMAGE_TAG"; then + print_success "Grype scan completed (informational)" else - print_warning "Trivy scan found vulnerabilities (non-blocking in local mode)" + print_warning "Grype scan found vulnerabilities (non-blocking in local mode)" fi echo "" echo "Checking for CRITICAL vulnerabilities (blocking)..." - if trivy image --severity CRITICAL --exit-code 1 "$IMAGE_TAG"; then + if grype "$IMAGE_TAG" --fail-on critical; then print_success "No CRITICAL vulnerabilities found" else print_error "CRITICAL vulnerabilities found - this will block CI/CD pipeline" @@ -337,8 +339,8 @@ if [ "$FULL_MODE" = true ]; then exit 1 fi else - print_warning "Trivy not installed, skipping security scan" - echo "Install with: brew install trivy # or appropriate package manager" + print_warning "Grype not installed, skipping security scan" + echo "Install with: brew install grype # or appropriate package manager" fi # Clean up Docker image @@ -378,8 +380,8 @@ fi if [ "$FULL_MODE" = true ]; then echo " ✓ Docker image build" - if command -v trivy &> /dev/null; then - echo " ✓ Trivy security scan" + if command -v grype &> /dev/null; then + echo " ✓ Grype security scan" fi fi From dcfb2fb0105490c2f11b339d3dfef3947b9052bc Mon Sep 17 00:00:00 2001 From: Matthew Mattox Date: Thu, 9 Jul 2026 21:18:23 -0500 Subject: [PATCH 2/2] ci(grype): use anchore/scan-action in-line instead of reusable workflow pipeline.yml is pull_request-triggered, and the private-repo reusable grype-scan.yml fails to resolve at workflow-load time on pull_request, failing the entire run at startup (0 jobs) even with if: false (resolution precedes if). Replace the reusable uses: with an in-line anchore/scan-action@v6 step, keeping the job disabled (if: false) as before (image too large to scan in CI). --- .github/workflows/pipeline.yml | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 838c583..3126395 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -208,23 +208,30 @@ jobs: NODE_MAJOR=20 # Stage 4: Security Scanning (disabled - image too large for CI timeout) - # Migrated from a per-repo Trivy job to the org-wide reusable Grype workflow - # for consistency across repos. Still opt-in/non-blocking: `if: false` mirrors - # the previous Trivy job's disabled state, and `blocking: false` ensures the - # scan (if ever enabled) never gates the pipeline. + # Migrated from a per-repo Trivy job to Grype for consistency across repos, but kept + # opt-in/disabled: `if: false` mirrors the previous Trivy job's disabled state (KubeTTY's + # image is too large for Grype to scan in CI). Uses anchore/scan-action (grype engine) + # IN-LINE rather than the org reusable grype-scan.yml: pipeline.yml is pull_request- + # triggered, and a private-repo reusable `uses:` fails to resolve at workflow-LOAD time + # on pull_request, failing the ENTIRE run at startup (0 jobs) even with `if: false`, since + # resolution happens before `if` is evaluated. An in-line action has no such problem. grype-scan: name: Security Scan (Grype) + runs-on: ubuntu-latest needs: [build] if: false # Disabled - KubeTTY image too large for Grype to scan in CI (same reasoning as the prior Trivy job) - uses: SupportTools/ci-runners/.github/workflows/grype-scan.yml@main - with: - image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.image-digest }} - fail-on: high - blocking: false permissions: contents: read - id-token: write security-events: write + steps: + # NOTE: if ever enabled, this needs a Harbor login step (image is a private + # harbor.support.tools ref); left out while the job is disabled. + - name: Grype scan (anchore/scan-action, informational) + uses: anchore/scan-action@v6 + with: + image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.image-digest }} + fail-build: false + severity-cutoff: high # Stage 5: Helm Chart Validation helm-validate: