From e236cc57c21e88205687cadcca79330c5290e8c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Sep 2025 06:01:30 +0000 Subject: [PATCH 1/3] Initial plan From 350bfb72c64d7a91edce749a3e648d97b2ddae1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Sep 2025 06:13:39 +0000 Subject: [PATCH 2/3] [docker] Add Trivy security scanning and build optimizations Co-authored-by: mfittko <326798+mfittko@users.noreply.github.com> --- .github/workflows/docker.yml | 15 +++++++++++++++ Dockerfile | 4 ++-- Makefile | 10 +++++++++- README.md | 1 + docs/issues/phase-6-docker-optimization.md | 8 ++++++-- 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f14d04b7..d44c03f2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -92,6 +92,21 @@ jobs: docker pull ghcr.io/${{ github.repository }}:${TAG} docker tag ghcr.io/${{ github.repository }}:${TAG} llm-proxy:${TAG} + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: 'llm-proxy:${{ steps.set_tag.outputs.name }}' + format: 'sarif' + output: 'trivy-results.sarif' + continue-on-error: true + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + if: always() + with: + sarif_file: 'trivy-results.sarif' + continue-on-error: true + - name: Smoke test using Makefile (run + health) run: | make docker-smoke IMAGE=llm-proxy:${{ steps.set_tag.outputs.name }} diff --git a/Dockerfile b/Dockerfile index 73de87cf..8fa528e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,10 +21,10 @@ ENV CGO_ENABLED=1 RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ GOMODCACHE=/go/pkg/mod \ - go build -ldflags "-w" -trimpath -o /llm-proxy ./cmd/proxy + go build -ldflags "-w -s -extldflags '-static'" -a -trimpath -o /llm-proxy ./cmd/proxy # Use a small alpine image for the final container -FROM alpine:3.18 +FROM alpine:3.20 # Security: Add only required runtime libraries RUN --mount=type=cache,target=/var/cache/apk apk add ca-certificates tzdata sqlite-libs wget diff --git a/Makefile b/Makefile index 035904b8..81106c40 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build test test-coverage test-coverage-ci test-watch test-coverage-watch test-dev lint clean tools dev-setup db-setup run docker docker-build docker-run docker-smoke docker-stop swag test-benchmark coverage +.PHONY: all build test test-coverage test-coverage-ci test-watch test-coverage-watch test-dev lint clean tools dev-setup db-setup run docker docker-build docker-run docker-smoke docker-scan docker-stop swag test-benchmark coverage # Go parameters GOCMD=go @@ -112,6 +112,14 @@ docker-smoke: docker logs llm-proxy || true; \ exit 1 +docker-scan: + @echo "Running Trivy security scan on llm-proxy:latest..." + @if command -v trivy >/dev/null 2>&1; then \ + trivy image --severity HIGH,CRITICAL llm-proxy:latest; \ + else \ + echo "Trivy not installed. Install with: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin"; \ + fi + # API documentation swag: diff --git a/README.md b/README.md index 278d8fb0..c2662108 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,7 @@ This ensures both the proxy and dispatcher share events via Redis, enabling full make docker-build make docker-run make docker-smoke +make docker-scan # Run Trivy security scan ``` ### Publishing diff --git a/docs/issues/phase-6-docker-optimization.md b/docs/issues/phase-6-docker-optimization.md index dd556aca..adb8bf97 100644 --- a/docs/issues/phase-6-docker-optimization.md +++ b/docs/issues/phase-6-docker-optimization.md @@ -17,16 +17,20 @@ Optimize the Dockerfile and containerization setup for the LLM proxy. This inclu - [x] Document Docker build and deployment process - [x] Add tests for Docker builds and health checks (CI smoke test in `.github/workflows/docker.yml`) - [x] Build and publish Docker image to GitHub Container Registry (GHCR) +- [x] Add Trivy security scanning to CI pipeline and Makefile ## Acceptance Criteria - Dockerfile is multi-stage and optimized - Volumes and health checks are properly configured - Security best practices are followed +- Security scanning (Trivy) integrated in CI - Documentation and tests are updated accordingly - CI builds and publishes multi-arch images to `ghcr.io/sofatutor/llm-proxy` ## Notes - Removed unnecessary `redis` package from runtime image; Redis runs as a separate service. - Added `.dockerignore` to reduce build context. -- Makefile targets: `docker-build`, `docker-run`, `docker-smoke` for local validation. -- Added GitHub Actions workflow `.github/workflows/docker.yml` to build multi-arch image and push to GHCR on `main` and tags. \ No newline at end of file +- Makefile targets: `docker-build`, `docker-run`, `docker-smoke`, `docker-scan` for local validation. +- Added GitHub Actions workflow `.github/workflows/docker.yml` to build multi-arch image and push to GHCR on `main` and tags. +- Integrated Trivy security scanning in CI pipeline with results uploaded to GitHub Security tab. +- Enhanced Dockerfile with additional security build flags. \ No newline at end of file From 4a67d418d75f8897085646795721ef10fbf89185 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Sep 2025 06:14:33 +0000 Subject: [PATCH 3/3] Add Docker security tests to validate enhancements Co-authored-by: mfittko <326798+mfittko@users.noreply.github.com> --- test/docker_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 test/docker_test.go diff --git a/test/docker_test.go b/test/docker_test.go new file mode 100644 index 00000000..90a16017 --- /dev/null +++ b/test/docker_test.go @@ -0,0 +1,64 @@ +package test + +import ( + "os/exec" + "testing" +) + +// TestDockerScanTarget verifies the docker-scan Makefile target exists and can be executed +func TestDockerScanTarget(t *testing.T) { + // Test that make docker-scan target exists and has proper syntax + cmd := exec.Command("make", "-n", "docker-scan") + cmd.Dir = ".." + + output, err := cmd.Output() + if err != nil { + t.Fatalf("Failed to run 'make -n docker-scan': %v", err) + } + + expectedCommands := []string{ + "echo \"Running Trivy security scan on llm-proxy:latest...\"", + "trivy image --severity HIGH,CRITICAL llm-proxy:latest", + } + + outputStr := string(output) + for _, expected := range expectedCommands { + if !contains(outputStr, expected) { + t.Errorf("Expected command not found in make output: %s", expected) + } + } +} + +// TestDockerfileEnhancements verifies the Dockerfile contains security enhancements +func TestDockerfileEnhancements(t *testing.T) { + dockerfile := "../Dockerfile" + + // Read and check Dockerfile content + cmd := exec.Command("grep", "-E", "(alpine:3\\.20|\\-s \\-extldflags)", dockerfile) + output, err := cmd.Output() + if err != nil { + t.Fatalf("Failed to verify Dockerfile enhancements: %v", err) + } + + outputStr := string(output) + if !contains(outputStr, "alpine:3.20") && !contains(outputStr, "-s -extldflags") { + t.Error("Expected Dockerfile security enhancements not found") + } +} + +// contains checks if a string contains a substring +func contains(str, substr string) bool { + return len(str) >= len(substr) && + str[len(str)-len(substr):] == substr || + str[:len(substr)] == substr || + findInString(str, substr) +} + +func findInString(str, substr string) bool { + for i := 0; i <= len(str)-len(substr); i++ { + if str[i:i+len(substr)] == substr { + return true + } + } + return false +} \ No newline at end of file