mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-05 20:40:16 -08:00
test: add end-to-end testing framework with image scan and proxy tests (#9231)
Co-authored-by: knqyf263 <knqyf263@users.noreply.github.com>
This commit is contained in:
19
.github/workflows/test.yaml
vendored
19
.github/workflows/test.yaml
vendored
@@ -187,6 +187,25 @@ jobs:
|
||||
run: |
|
||||
mage test:vm
|
||||
|
||||
e2e-test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
- name: Install Go tools
|
||||
run: go install tool # GOBIN is added to the PATH by the setup-go action
|
||||
|
||||
- name: Run E2E tests
|
||||
run: mage test:e2e
|
||||
|
||||
build-test:
|
||||
name: Build Test
|
||||
runs-on: ${{ matrix.operating-system }}
|
||||
|
||||
70
e2e/README.md
Normal file
70
e2e/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# End-to-End (E2E) Tests
|
||||
|
||||
## Testing Philosophy
|
||||
|
||||
The E2E tests in this directory are designed to test Trivy's functionality in realistic environments with **external dependencies and network connectivity**. These tests complement unit tests and integration tests by focusing on scenarios that require real external resources.
|
||||
|
||||
### What E2E Tests Should Cover
|
||||
|
||||
E2E tests should focus on functionality that involves:
|
||||
- **External network connections** (downloading container images, vulnerability databases)
|
||||
- **External service dependencies** (Docker daemon, registry access, proxy servers)
|
||||
- **Real-world scenarios** that cannot be easily mocked or simulated
|
||||
- **Cross-component integration** involving external systems
|
||||
|
||||
### What E2E Tests Should NOT Cover
|
||||
|
||||
E2E tests should **avoid** detailed assertions and comprehensive validation:
|
||||
- **Detailed JSON output validation** - this should be covered by integration tests
|
||||
- **Comprehensive vulnerability detection** - this should be covered by unit tests
|
||||
- **Complex result comparison** - basic functionality verification is sufficient
|
||||
- **Edge cases and error conditions** - these should be covered by unit tests
|
||||
|
||||
### Testing Approach
|
||||
|
||||
- **Minimal assertions**: Focus on basic functionality rather than detailed output validation
|
||||
- **External dependencies**: Use real registries, databases, and services where practical
|
||||
- **Environment isolation**: Each test should use isolated cache and working directories
|
||||
- **Golden files**: Use -update flag for maintainable output comparison
|
||||
- **Conditional execution**: Tests should validate required dependencies during setup
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **Docker**: Required for local image scanning tests
|
||||
- **Internet access**: Required for downloading images and databases
|
||||
|
||||
### Test Execution
|
||||
|
||||
The E2E tests build and execute trivy in isolated temporary directories. When you run `mage test:e2e`, it automatically:
|
||||
1. Builds trivy in a test-specific temporary directory (via `t.TempDir()`)
|
||||
2. Adds the temporary directory to the PATH for test execution
|
||||
3. Runs the E2E tests using the isolated binary
|
||||
|
||||
This approach ensures:
|
||||
- No pollution of the global environment
|
||||
- Each test run uses a freshly built binary
|
||||
- Test isolation between different test runs
|
||||
- Clean test environment without side effects
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all E2E tests
|
||||
mage test:e2e
|
||||
|
||||
# Run specific test
|
||||
go test -v -tags=e2e ./e2e/ -run TestE2E/image_scan
|
||||
|
||||
# Update golden files when output changes
|
||||
go test -v -tags=e2e ./e2e/ -update
|
||||
```
|
||||
|
||||
### Adding New Tests
|
||||
|
||||
When adding new E2E tests:
|
||||
1. Focus on external dependencies and real-world scenarios
|
||||
2. Use minimal assertions - verify functionality, not detailed output
|
||||
3. Use golden files with -update flag for output comparison
|
||||
4. Validate required dependencies in test setup
|
||||
5. Use fixed/pinned versions for reproducible results
|
||||
6. Include clear test documentation explaining the scenario being tested
|
||||
94
e2e/e2e_test.go
Normal file
94
e2e/e2e_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
//go:build e2e
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/rogpeppe/go-internal/testscript"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update golden files")
|
||||
|
||||
func TestE2E(t *testing.T) {
|
||||
testscript.Run(t, testscript.Params{
|
||||
Dir: "testdata",
|
||||
Setup: func(env *testscript.Env) error {
|
||||
return setupTestEnvironment(t, env)
|
||||
},
|
||||
UpdateScripts: *update,
|
||||
})
|
||||
}
|
||||
|
||||
func buildTrivy(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
tmp := t.TempDir() // Test-specific directory
|
||||
exe := filepath.Join(tmp, "trivy")
|
||||
if runtime.GOOS == "windows" {
|
||||
exe += ".exe"
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "build",
|
||||
"-o", exe,
|
||||
"../cmd/trivy",
|
||||
)
|
||||
// Prevent environment pollution
|
||||
cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("Trivy build failed: %v\n%s", err, out)
|
||||
}
|
||||
return exe
|
||||
}
|
||||
|
||||
func setupTestEnvironment(t *testing.T, env *testscript.Env) error {
|
||||
// Validate Docker availability - fail if not available
|
||||
if err := validateDockerAvailability(); err != nil {
|
||||
return fmt.Errorf("Docker validation failed: %v", err)
|
||||
}
|
||||
|
||||
// Build Trivy once and cache it
|
||||
trivyExe := buildTrivy(t)
|
||||
|
||||
// Add directory containing trivy to PATH
|
||||
env.Setenv("PATH", filepath.Dir(trivyExe)+string(os.PathListSeparator)+env.Getenv("PATH"))
|
||||
|
||||
// Set environment variables for test scripts
|
||||
env.Setenv("TRIVY_DB_DIGEST", "sha256:b4d3718a89a78d4a6b02250953e92fcd87776de4774e64e818c1d0e01c928025")
|
||||
// Disable VEX notice in test environment
|
||||
env.Setenv("TRIVY_DISABLE_VEX_NOTICE", "true")
|
||||
|
||||
// Define test image
|
||||
testImage := "alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b"
|
||||
env.Setenv("TEST_IMAGE", testImage)
|
||||
|
||||
// Pre-pull the test image to Docker daemon
|
||||
t.Logf("Pre-pulling test image: %s", testImage)
|
||||
cmd := exec.Command("docker", "pull", testImage)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to pull test image: %v\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
// Pass through DOCKER_HOST if set
|
||||
if dockerHost := os.Getenv("DOCKER_HOST"); dockerHost != "" {
|
||||
env.Setenv("DOCKER_HOST", dockerHost)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDockerAvailability() error {
|
||||
cmd := exec.Command("docker", "version")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("Docker is not available or not running: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
58
e2e/testdata/image_scan.txtar
vendored
Normal file
58
e2e/testdata/image_scan.txtar
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
# Image scan test
|
||||
# This test verifies that Trivy can scan images from both local Docker daemon and remote registry
|
||||
# and that both methods produce equivalent results
|
||||
#
|
||||
# Image is already pre-pulled in setupTestEnvironment
|
||||
|
||||
# Test 1: Remote image scanning
|
||||
exec trivy image --image-src remote --cache-dir $WORK/.cache --format table --output remote_result.txt --db-repository mirror.gcr.io/aquasec/trivy-db@sha256:b4d3718a89a78d4a6b02250953e92fcd87776de4774e64e818c1d0e01c928025 --severity HIGH,CRITICAL --no-progress $TEST_IMAGE
|
||||
|
||||
# Verify DB download message appears for remote scan
|
||||
stderr 'Downloading vulnerability DB...'
|
||||
|
||||
# Test 2: Local Docker daemon scanning
|
||||
exec trivy image --image-src docker --cache-dir $WORK/.cache --format table --output local_result.txt --db-repository mirror.gcr.io/aquasec/trivy-db@sha256:b4d3718a89a78d4a6b02250953e92fcd87776de4774e64e818c1d0e01c928025 --severity HIGH,CRITICAL --no-progress $TEST_IMAGE
|
||||
|
||||
# Test 3: Exit code testing - scan with exit code for vulnerabilities
|
||||
! exec trivy image --exit-code 1 --cache-dir $WORK/.cache --format table --db-repository mirror.gcr.io/aquasec/trivy-db@sha256:b4d3718a89a78d4a6b02250953e92fcd87776de4774e64e818c1d0e01c928025 --severity HIGH,CRITICAL --no-progress $TEST_IMAGE
|
||||
|
||||
# Verify all scans completed successfully
|
||||
! stderr 'FATAL'
|
||||
exists remote_result.txt
|
||||
exists local_result.txt
|
||||
|
||||
|
||||
# Verify both scans produce equivalent results
|
||||
cmp remote_result.txt local_result.txt
|
||||
|
||||
# Compare with golden file to ensure expected output format
|
||||
cmp remote_result.txt image_scan_golden.txt
|
||||
|
||||
-- image_scan_golden.txt --
|
||||
|
||||
Report Summary
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────┬────────┬─────────────────┬─────────┐
|
||||
│ Target │ Type │ Vulnerabilities │ Secrets │
|
||||
├────────────────────────────────────────────────────────────────────────────────┼────────┼─────────────────┼─────────┤
|
||||
│ alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b │ alpine │ 2 │ - │
|
||||
│ (alpine 3.19.1) │ │ │ │
|
||||
└────────────────────────────────────────────────────────────────────────────────┴────────┴─────────────────┴─────────┘
|
||||
Legend:
|
||||
- '-': Not scanned
|
||||
- '0': Clean (no security findings detected)
|
||||
|
||||
|
||||
alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b (alpine 3.19.1)
|
||||
==============================================================================================
|
||||
Total: 2 (HIGH: 2, CRITICAL: 0)
|
||||
|
||||
┌────────────┬───────────────┬──────────┬────────┬───────────────────┬───────────────┬──────────────────────────────────────────────────────────┐
|
||||
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
|
||||
├────────────┼───────────────┼──────────┼────────┼───────────────────┼───────────────┼──────────────────────────────────────────────────────────┤
|
||||
│ libcrypto3 │ CVE-2024-6119 │ HIGH │ fixed │ 3.1.4-r5 │ 3.1.7-r0 │ openssl: Possible denial of service in X.509 name checks │
|
||||
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-6119 │
|
||||
├────────────┤ │ │ │ │ │ │
|
||||
│ libssl3 │ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
└────────────┴───────────────┴──────────┴────────┴───────────────────┴───────────────┴──────────────────────────────────────────────────────────┘
|
||||
27
e2e/testdata/proxy_scan.txtar
vendored
Normal file
27
e2e/testdata/proxy_scan.txtar
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Proxy scan test
|
||||
# This test verifies that Trivy can work through HTTP/HTTPS proxy with self-signed certificates
|
||||
# Tests both --insecure flag behavior: failure without it, success with it
|
||||
|
||||
# Start mitmdump proxy container on port 18080
|
||||
exec docker rm -f mitmproxy-e2e-test
|
||||
exec docker run -d --name mitmproxy-e2e-test -p 18080:8080 mitmproxy/mitmproxy:latest mitmdump
|
||||
exec sleep 3
|
||||
|
||||
# Set proxy environment variables
|
||||
env HTTPS_PROXY=http://localhost:18080
|
||||
env HTTP_PROXY=http://localhost:18080
|
||||
|
||||
# Test 1: Scan without --insecure flag (should fail due to certificate issues)
|
||||
! exec trivy image --cache-dir $WORK/.cache --format table --db-repository mirror.gcr.io/aquasec/trivy-db@sha256:b4d3718a89a78d4a6b02250953e92fcd87776de4774e64e818c1d0e01c928025 --severity HIGH,CRITICAL --no-progress alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b
|
||||
|
||||
# Verify certificate error occurred
|
||||
stderr 'certificate'
|
||||
|
||||
# Test 2: Scan with --insecure flag (should succeed despite certificate issues)
|
||||
exec trivy image --insecure --cache-dir $WORK/.cache --format table --db-repository mirror.gcr.io/aquasec/trivy-db@sha256:b4d3718a89a78d4a6b02250953e92fcd87776de4774e64e818c1d0e01c928025 --severity HIGH,CRITICAL --no-progress alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b
|
||||
|
||||
# Verify scan completed successfully with --insecure
|
||||
! stderr 'FATAL'
|
||||
|
||||
# Stop proxy container
|
||||
exec docker rm -f mitmproxy-e2e-test
|
||||
2
go.mod
2
go.mod
@@ -132,6 +132,8 @@ require (
|
||||
modernc.org/sqlite v1.38.0
|
||||
)
|
||||
|
||||
require github.com/rogpeppe/go-internal v1.14.1
|
||||
|
||||
require (
|
||||
buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.6-20250121211742-6d880cc6cc8d.1 // indirect
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250613105001-9f2d3c737feb.1 // indirect
|
||||
|
||||
@@ -331,6 +331,11 @@ func (t Test) UpdateVMGolden() error {
|
||||
return sh.RunWithV(ENV, "go", "test", "-v", "-tags=vm_integration", "./integration/...", "-update")
|
||||
}
|
||||
|
||||
// E2e runs E2E tests using testscript framework
|
||||
func (t Test) E2e() error {
|
||||
return sh.RunWithV(ENV, "go", "test", "-v", "-tags=e2e", "./e2e/...")
|
||||
}
|
||||
|
||||
type Lint mg.Namespace
|
||||
|
||||
// Run runs linters
|
||||
|
||||
Reference in New Issue
Block a user