diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 7fc59b61f..d8d65030f 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -621,16 +621,23 @@ jobs: - name: Security scan Dockerfile.server image with Trivy run: | - # Install Trivy - apk add --no-cache curl - curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin - + # Install Trivy at a pinned version with checksum verification + apk add --no-cache curl tar + TRIVY_VERSION="0.57.1" + ARCH="amd64" + TRIVY_TARBALL="trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" + curl -fsSL "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/${TRIVY_TARBALL}" -o "/tmp/${TRIVY_TARBALL}" + curl -fsSL "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_checksums.txt" -o /tmp/trivy_checksums.txt + cd /tmp && grep "${TRIVY_TARBALL}" trivy_checksums.txt | sha256sum -c + tar -xzf "/tmp/${TRIVY_TARBALL}" -C /usr/local/bin trivy + trivy --version + # Scan the Dockerfile.server image for vulnerabilities # Exit with non-zero status if HIGH or CRITICAL vulnerabilities are found trivy image --severity HIGH,CRITICAL --exit-code 1 cleveragents-server:test - + # Also generate a detailed report for visibility - echo "=== Detailed Trivy Scan Report ===" + echo "=== Detailed Trivy Scan Report ===" trivy image --format table cleveragents-server:test || true helm: diff --git a/CHANGELOG.md b/CHANGELOG.md index feee12ae1..905586333 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -553,6 +553,15 @@ ensuring data is stored with proper parameter values. relative globs also match absolute paths. Added BDD regression tests in `execute_phase_context_assembler_coverage.feature` and `project_context_phase_analysis.feature`. +### Added + +- **CI Dockerfile.server security scan with Trivy** (#1927): Added Trivy vulnerability + scan step to the CI docker job. The scan runs against the built `Dockerfile.server` + image and fails the build on HIGH or CRITICAL severity findings. Trivy is installed + at a pinned version with checksum verification to prevent supply chain attacks. Scan + results are surfaced in CI output. Robot Framework integration tests verify the + workflow configuration. + ### Changed - Restored `benchmark-regression` CI job to `master.yml` with `pull_request` trigger guard diff --git a/features/steps/ci_dockerfile_server_security_scan_steps.py b/features/steps/ci_dockerfile_server_security_scan_steps.py index bc6329a94..8799b1680 100644 --- a/features/steps/ci_dockerfile_server_security_scan_steps.py +++ b/features/steps/ci_dockerfile_server_security_scan_steps.py @@ -16,15 +16,23 @@ def step_dockerfile_server_exists(context): @given("Trivy is available in the CI environment") def step_trivy_available(context): """Verify that Trivy is available (or will be in CI).""" - # In CI, Trivy will be installed. For local testing, we just note this requirement. context.trivy_required = True + workflow_path = Path(".forgejo/workflows/ci.yml") + if workflow_path.exists(): + with open(workflow_path) as f: + context.workflow_content = f.read() + else: + context.workflow_content = "" + context.workflow_path = workflow_path @given("the CI workflow file exists at .forgejo/workflows/ci.yml") def step_ci_workflow_exists(context): """Verify that the CI workflow file exists.""" workflow_path = Path(".forgejo/workflows/ci.yml") - assert workflow_path.exists(), "CI workflow file not found at .forgejo/workflows/ci.yml" + assert workflow_path.exists(), ( + "CI workflow file not found at .forgejo/workflows/ci.yml" + ) context.workflow_path = workflow_path @@ -40,31 +48,38 @@ def step_examine_docker_job(context): def step_docker_job_includes_scan_step(context): """Verify that the docker job includes a security scan step.""" # Look for a step that scans the Dockerfile.server image - assert "Dockerfile.server" in context.workflow_content, \ + assert "Dockerfile.server" in context.workflow_content, ( "Dockerfile.server not referenced in docker job" + ) # Look for Trivy or security scanning references - assert "trivy" in context.workflow_content.lower() or "scan" in context.workflow_content.lower(), \ - "No security scanning step found in docker job" + assert ( + "trivy" in context.workflow_content.lower() + or "scan" in context.workflow_content.lower() + ), "No security scanning step found in docker job" @then("the scan step should use Trivy or equivalent tool") def step_scan_uses_trivy(context): """Verify that the scan step uses Trivy.""" - assert "trivy" in context.workflow_content.lower(), \ - "Trivy not found in CI workflow" + assert "trivy" in context.workflow_content.lower(), "Trivy not found in CI workflow" -@then("the scan step should be configured to fail on HIGH or CRITICAL severity findings") +@then( + "the scan step should be configured to fail on HIGH or CRITICAL severity findings" +) def step_scan_fails_on_high_severity(context): """Verify that the scan is configured to fail on HIGH or CRITICAL findings.""" # Look for severity configuration - assert "HIGH" in context.workflow_content or "CRITICAL" in context.workflow_content, \ - "Severity configuration not found in scan step" + assert ( + "HIGH" in context.workflow_content or "CRITICAL" in context.workflow_content + ), "Severity configuration not found in scan step" # Look for exit code handling - assert "exit" in context.workflow_content.lower() or "fail" in context.workflow_content.lower(), \ - "Exit code handling not configured for scan failures" + assert ( + "exit" in context.workflow_content.lower() + or "fail" in context.workflow_content.lower() + ), "Exit code handling not configured for scan failures" @given("a Dockerfile.server image has been built") @@ -84,23 +99,26 @@ def step_execute_security_scan(context): def step_scan_results_displayed(context): """Verify that scan results are displayed in output.""" # In CI, this is handled by the workflow output - assert "scan" in context.workflow_content.lower(), \ + assert "scan" in context.workflow_content.lower(), ( "Scan output not configured in workflow" + ) @then("the output should include a summary of vulnerabilities found") def step_output_includes_summary(context): """Verify that output includes vulnerability summary.""" # Trivy provides summary by default - assert "trivy" in context.workflow_content.lower(), \ + assert "trivy" in context.workflow_content.lower(), ( "Trivy not configured to provide output" + ) @then("the output should indicate the severity levels of findings") def step_output_includes_severity(context): """Verify that output includes severity levels.""" - assert "HIGH" in context.workflow_content or "CRITICAL" in context.workflow_content, \ - "Severity levels not indicated in output configuration" + assert ( + "HIGH" in context.workflow_content or "CRITICAL" in context.workflow_content + ), "Severity levels not indicated in output configuration" @given("a Dockerfile.server image with known HIGH severity vulnerabilities") @@ -113,24 +131,25 @@ def step_image_with_vulnerabilities(context): def step_scan_exits_nonzero(context): """Verify that scan exits with non-zero status on vulnerabilities.""" # This is verified by the workflow configuration - assert "trivy" in context.workflow_content.lower(), \ - "Trivy not configured" + assert "trivy" in context.workflow_content.lower(), "Trivy not configured" @then("the CI job should fail") def step_ci_job_fails(context): """Verify that the CI job fails on vulnerabilities.""" # The workflow should be configured to fail on scan failure - assert "docker" in context.workflow_content.lower(), \ + assert "docker" in context.workflow_content.lower(), ( "Docker job not found in workflow" + ) @then("the failure should block merge to master") def step_failure_blocks_merge(context): """Verify that job failure blocks merge.""" # This is enforced by branch protection rules - assert "docker" in context.workflow_content.lower(), \ + assert "docker" in context.workflow_content.lower(), ( "Docker job not configured as required check" + ) @given("a Dockerfile.server image with no HIGH or CRITICAL severity vulnerabilities") @@ -143,21 +162,20 @@ def step_image_without_vulnerabilities(context): def step_scan_exits_zero(context): """Verify that scan exits with zero status on clean image.""" # Trivy exits 0 when no HIGH/CRITICAL vulnerabilities found - assert "trivy" in context.workflow_content.lower(), \ - "Trivy not configured" + assert "trivy" in context.workflow_content.lower(), "Trivy not configured" @then("the CI job should pass") def step_ci_job_passes(context): """Verify that the CI job passes on clean image.""" # The workflow should allow the job to pass - assert "docker" in context.workflow_content.lower(), \ + assert "docker" in context.workflow_content.lower(), ( "Docker job not found in workflow" + ) @then("the build can proceed to the next stage") def step_build_proceeds(context): """Verify that the build can proceed after passing scan.""" # The workflow should have subsequent jobs that depend on docker job - assert "needs:" in context.workflow_content, \ - "Job dependencies not configured" + assert "needs:" in context.workflow_content, "Job dependencies not configured" diff --git a/robot/ci_dockerfile_server_security_scan.robot b/robot/ci_dockerfile_server_security_scan.robot new file mode 100644 index 000000000..4d7d04f04 --- /dev/null +++ b/robot/ci_dockerfile_server_security_scan.robot @@ -0,0 +1,46 @@ +*** Settings *** +Documentation Integration tests for CI Dockerfile.server security scanning +... Validates that the CI workflow includes the Trivy security scan step +... configured to fail on HIGH/CRITICAL vulnerabilities (issue #1927). +Resource ${CURDIR}/common.resource +Suite Setup Setup Test Environment +Suite Teardown Cleanup Test Environment + +*** Test Cases *** +CI Workflow Contains Trivy Security Scan Step + [Documentation] Verify the docker CI job includes the Trivy security scan step + [Tags] security ci dockerfile + ${content}= Get File ${WORKSPACE}/.forgejo/workflows/ci.yml + Should Contain ${content} Security scan Dockerfile.server image with Trivy + +CI Workflow Targets Dockerfile Server Image + [Documentation] Verify the CI scan targets the Dockerfile.server image specifically + [Tags] security ci dockerfile + ${content}= Get File ${WORKSPACE}/.forgejo/workflows/ci.yml + Should Contain ${content} Dockerfile.server + Should Contain ${content} cleveragents-server:test + +CI Trivy Configured To Fail On High Severity + [Documentation] Verify Trivy exits non-zero on HIGH or CRITICAL findings + [Tags] security ci dockerfile + ${content}= Get File ${WORKSPACE}/.forgejo/workflows/ci.yml + Should Contain ${content} --severity HIGH,CRITICAL + Should Contain ${content} --exit-code 1 + +CI Trivy Uses Pinned Version + [Documentation] Verify Trivy installation is pinned to a specific version + [Tags] security ci dockerfile + ${content}= Get File ${WORKSPACE}/.forgejo/workflows/ci.yml + Should Contain ${content} TRIVY_VERSION= + +CI Trivy Install Verifies Checksum + [Documentation] Verify Trivy install downloads and validates the checksum file + [Tags] security ci dockerfile + ${content}= Get File ${WORKSPACE}/.forgejo/workflows/ci.yml + Should Contain ${content} checksums.txt + Should Contain ${content} sha256sum -c + +Dockerfile Server Exists In Repository + [Documentation] Verify Dockerfile.server exists at the expected repository location + [Tags] security dockerfile + File Should Exist ${WORKSPACE}/Dockerfile.server