Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
111 changes: 21 additions & 90 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,99 +208,30 @@ jobs:
NODE_MAJOR=20

# Stage 4: Security Scanning (disabled - image too large for CI timeout)
security-scan:
name: Security Scan
# 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 Trivy to scan in CI
needs: [build]
if: false # Disabled - KubeTTY image too large for Grype to scan in CI (same reasoning as the prior Trivy job)
permissions:
actions: read
contents: read
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
# 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:
name: sbom
path: sbom.json
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.image-digest }}
fail-build: false
severity-cutoff: high

# Stage 5: Helm Chart Validation
helm-validate:
Expand Down Expand Up @@ -378,11 +309,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
Expand Down
5 changes: 5 additions & 0 deletions .grype.yaml
Original file line number Diff line number Diff line change
@@ -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: []
28 changes: 15 additions & 13 deletions validate-pipeline-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
Loading