Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5fbca6c
ROX-31495: Add WireMock standalone mock service for StackRox Central
janisz Jan 29, 2026
cef9fbf
Simplify smoke test to focus on functionality
janisz Jan 29, 2026
07bf2c0
chore: cleanup WireMock code and documentation
janisz Jan 29, 2026
a157c6b
feat: Use go mod cache for proto files instead of manual repo clone
janisz Jan 29, 2026
cf2af0c
feat: Add E2E test infrastructure with mock/real service support
janisz Feb 5, 2026
15497ef
fix: Configure E2E tests to work with WireMock mock service
janisz Feb 6, 2026
01f58ab
fix: Fix WireMock JSONPath patterns for gRPC protobuf field names
janisz Feb 6, 2026
ad34e28
fix: Fix WireMock smoke test failures and add grpcurl dependency
janisz Feb 9, 2026
a878c6f
Delete IMPLEMENTATION_SUMMARY.md
janisz Feb 10, 2026
56d0a4a
Change contains prompt
janisz Feb 11, 2026
924e044
Apply suggestion from @mtodor
janisz Feb 12, 2026
2e28dd4
Apply suggestion from @mtodor
janisz Feb 12, 2026
c7559c8
fixes
janisz Feb 12, 2026
488768d
Address PR review comments from @mtodor
janisz Feb 13, 2026
5cf7602
Clean up unused WireMock files and compact README
janisz Feb 13, 2026
74fc864
fix: Fix WireMock JSONPath patterns for deployment CVE queries
janisz Feb 13, 2026
4e23fe9
feat: Add automatic protoc installation to Makefile
janisz Feb 13, 2026
7763733
refactor: Simplify e2e tests to only support WireMock mode
janisz Feb 13, 2026
84bb4ab
Merge branch 'main' into ROX-31495-wiremock-mock-service
janisz Feb 13, 2026
3ba6aaf
Update .github/workflows/wiremock-test.yml
janisz Feb 18, 2026
483ee9e
fix: Fix WireMock smoke test in GitHub Actions
janisz Feb 19, 2026
f5bb3b0
fix: Correct JSON payload in smoke test to match protobuf structure
janisz Feb 19, 2026
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
51 changes: 51 additions & 0 deletions .github/workflows/wiremock-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: WireMock Smoke Test

on:
push:
branches:
- main
pull_request:
types:
- opened
- reopened
- synchronize

jobs:
wiremock-smoke-test:
name: WireMock Smoke Test
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'

- name: Download Go dependencies
run: go mod download

- name: Install protoc
run: make proto-install

- name: Setup proto files from go mod cache
run: ./scripts/setup-proto-files.sh

- name: Run smoke test
run: make mock-test

- name: Upload logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: wiremock-logs
path: wiremock/wiremock.log
if-no-files-found: ignore
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,19 @@
# Lint output
/report.xml

# Proto tools
/.proto/

# E2E tests
/e2e-tests/.env
/e2e-tests/mcp-reports/
/e2e-tests/bin/
/e2e-tests/**/*-out.json

# WireMock
/wiremock/lib/*.jar
/wiremock/*.pid
/wiremock/*.log
/wiremock/__files
/wiremock/proto/
/wiremock/grpc/
101 changes: 99 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ test: ## Run unit tests
e2e-smoke-test: ## Run E2E smoke test (build and verify mcpchecker)
@cd e2e-tests && ./scripts/smoke-test.sh

.PHONY: e2e-test
e2e-test: ## Run E2E tests
.PHONY: e2e-test mock-start proto-generate
e2e-test: ## Run E2E tests (uses WireMock)
@cd e2e-tests && ./scripts/run-tests.sh

.PHONY: test-coverage-and-junit
Expand Down Expand Up @@ -91,9 +91,106 @@ lint: ## Run golangci-lint
go install -v "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6"
golangci-lint run

##############
## Protobuf ##
##############

# Protoc version and paths
PROTOC_VERSION := 32.1
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
PROTOC_OS = linux
endif
ifeq ($(UNAME_S),Darwin)
PROTOC_OS = osx
endif
PROTOC_ARCH=$(shell case $$(uname -m) in (arm64|aarch64) echo aarch_64 ;; (s390x) echo s390_64 ;; (*) uname -m ;; esac)

PROTO_PRIVATE_DIR := .proto
PROTOC_DIR := $(PROTO_PRIVATE_DIR)/protoc-$(PROTOC_OS)-$(PROTOC_ARCH)-$(PROTOC_VERSION)
PROTOC := $(PROTOC_DIR)/bin/protoc
PROTOC_ZIP := protoc-$(PROTOC_VERSION)-$(PROTOC_OS)-$(PROTOC_ARCH).zip
PROTOC_DOWNLOADS_DIR := $(PROTO_PRIVATE_DIR)/.downloads
PROTOC_FILE := $(PROTOC_DOWNLOADS_DIR)/$(PROTOC_ZIP)

$(PROTOC_DOWNLOADS_DIR):
@mkdir -p "$@"

$(PROTOC_FILE): $(PROTOC_DOWNLOADS_DIR)
@echo "Downloading protoc $(PROTOC_VERSION)..."
@curl -fSL -o "$@" "https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/$(PROTOC_ZIP)"

$(PROTOC): $(PROTOC_FILE)
@echo "Installing protoc to $(PROTOC_DIR)..."
@mkdir -p "$(PROTOC_DIR)"
@unzip -q -o -d "$(PROTOC_DIR)" "$(PROTOC_FILE)"
@test -x "$@"
@echo "✓ protoc $(PROTOC_VERSION) installed"

.PHONY: proto-install
proto-install: $(PROTOC) ## Install protoc locally

.PHONY: proto-setup
proto-setup: ## Setup proto files from go mod cache
@./scripts/setup-proto-files.sh

.PHONY: proto-generate
proto-generate: $(PROTOC) ## Generate proto descriptors for WireMock
@PROTOC_BIN=$(PROTOC) ./scripts/generate-proto-descriptors.sh

.PHONY: proto-clean
proto-clean: ## Clean generated proto files
@rm -rf wiremock/proto/ wiremock/grpc/

.PHONY: proto-check
proto-check: ## Verify proto setup is correct
@if [ ! -f wiremock/proto/descriptors/stackrox.dsc ]; then \
echo "❌ Proto descriptors not found"; \
echo "Run: make proto-generate"; \
exit 1; \
fi
@echo "✓ Proto descriptors present"

.PHONY: mock-download
mock-download: ## Download WireMock JARs
@./scripts/download-wiremock.sh

.PHONY: mock-start
mock-start: proto-check ## Start WireMock mock Central locally
@./scripts/start-mock-central.sh

.PHONY: mock-stop
mock-stop: ## Stop WireMock mock Central
@./scripts/stop-mock-central.sh

.PHONY: mock-logs
mock-logs: ## View WireMock logs
@tail -f wiremock/wiremock.log

.PHONY: mock-restart
mock-restart: mock-stop mock-start ## Restart WireMock

.PHONY: mock-status
mock-status: ## Check WireMock status
@if [ -f wiremock/wiremock.pid ]; then \
PID=$$(cat wiremock/wiremock.pid); \
if ps -p $$PID > /dev/null 2>&1; then \
echo "WireMock is running (PID: $$PID)"; \
else \
echo "WireMock PID file exists but process not running"; \
fi \
else \
echo "WireMock is not running"; \
fi

.PHONY: mock-test
mock-test: proto-generate mock-download ## Run WireMock smoke tests
@./scripts/smoke-test-wiremock.sh

.PHONY: clean
clean: ## Clean build artifacts and coverage files
$(GOCLEAN)
rm -f $(BINARY_NAME)
rm -f $(COVERAGE_OUT)
rm -f $(LINT_OUT)
rm -rf $(PROTO_PRIVATE_DIR)
45 changes: 27 additions & 18 deletions e2e-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ This is useful for CI and quickly checking that everything compiles.
- Go 1.25+
- Google Cloud Project with Vertex AI enabled (for Claude agent)
- OpenAI API Key (for LLM judge)
- StackRox API Token

## Setup

Expand All @@ -39,9 +38,6 @@ Create `.env` file:
# Required: GCP Project for Vertex AI (Claude agent)
ANTHROPIC_VERTEX_PROJECT_ID=<GCP Project ID>

# Required: StackRox Central API Token
STACKROX_MCP__CENTRAL__API_TOKEN=<StackRox API Token>

# Required: OpenAI API Key (for LLM judge)
OPENAI_API_KEY=<OpenAI API Key>

Expand All @@ -52,12 +48,22 @@ CLOUD_ML_REGION=us-east5
JUDGE_MODEL_NAME=gpt-5-nano
```

Note: No StackRox API token required - tests use WireMock mock service.

## Running Tests

Run tests against the WireMock mock service:

```bash
./scripts/run-tests.sh
```

The test suite:
- Starts WireMock automatically on localhost:8081
- Uses deterministic test fixtures
- Requires no StackRox API tokens
- Fast and reliable for development and CI

Results are saved to `mcpchecker/mcpchecker-stackrox-mcp-e2e-out.json`.

### View Results
Expand All @@ -72,21 +78,24 @@ jq '[.[] | .callHistory.ToolCalls[]? | {name: .request.Params.name, arguments: .

## Test Cases

| Test | Description | Tool |
|------|-------------|------|
| `list-clusters` | List all clusters | `list_clusters` |
| `cve-detected-workloads` | CVE detected in deployments | `get_deployments_for_cve` |
| `cve-detected-clusters` | CVE detected in clusters | `get_clusters_with_orchestrator_cve` |
| `cve-nonexistent` | Handle non-existent CVE | `get_clusters_with_orchestrator_cve` |
| `cve-cluster-does-exist` | CVE with cluster filter | `get_clusters_with_orchestrator_cve` |
| `cve-cluster-does-not-exist` | CVE with cluster filter | `get_clusters_with_orchestrator_cve` |
| `cve-clusters-general` | General CVE query | `get_clusters_with_orchestrator_cve` |
| `cve-cluster-list` | CVE across clusters | `get_clusters_with_orchestrator_cve` |
| Test | Description | Tool | Eval Coverage |
|------|-------------|------|---------------|
| `list-clusters` | List all clusters | `list_clusters` | - |
| `cve-detected-workloads` | CVE detected in deployments | `get_deployments_for_cve` | Eval 1 |
| `cve-detected-clusters` | CVE detected in clusters | `get_clusters_with_orchestrator_cve` | Eval 1 |
| `cve-nonexistent` | Handle non-existent CVE | `get_clusters_with_orchestrator_cve` | Eval 2 |
| `cve-cluster-does-exist` | CVE with cluster filter | `get_clusters_with_orchestrator_cve` | Eval 4 |
| `cve-cluster-does-not-exist` | CVE with non-existent cluster | `list_clusters` | - |
| `cve-clusters-general` | General CVE query | `get_clusters_with_orchestrator_cve` | Eval 1 |
| `cve-cluster-list` | CVE across clusters | `get_clusters_with_orchestrator_cve` | - |
| `cve-log4shell` | Well-known CVE (log4shell) | `get_deployments_for_cve` | Eval 3 |
| `cve-multiple` | Multiple CVEs in one prompt | `get_deployments_for_cve` | Eval 5 |
| `rhsa-not-supported` | RHSA detection (should fail) | None | Eval 7 |

## Configuration

- **`mcpchecker/eval.yaml`**: Main test configuration, agent settings, assertions
- **`mcpchecker/mcp-config.yaml`**: MCP server configuration
- **`mcpchecker/eval.yaml`**: Test configuration, agent settings, assertions
- **`mcpchecker/mcp-config-mock.yaml`**: MCP server configuration for WireMock
- **`mcpchecker/tasks/*.yaml`**: Individual test task definitions

## How It Works
Expand All @@ -103,8 +112,8 @@ mcpchecker uses a proxy architecture to intercept MCP tool calls:
## Troubleshooting

**Tests fail - no tools called**
- Verify StackRox Central is accessible
- Check API token permissions
- Verify WireMock is running: `make mock-status`
- Check WireMock logs: `make mock-logs`

**Build errors**
```bash
Expand Down
38 changes: 36 additions & 2 deletions e2e-tests/mcpchecker/eval.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ config:
model: "claude-sonnet-4-5"
llmJudge:
env:
typeKey: JUDGE_TYPE
baseUrlKey: JUDGE_BASE_URL
apiKeyKey: JUDGE_API_KEY
modelNameKey: JUDGE_MODEL_NAME
mcpConfigFile: mcp-config.yaml
mcpConfigFile: mcp-config-mock.yaml
taskSets:
# Assertion Fields Explained:
# - toolsUsed: List of tools that MUST be called at least once
Expand Down Expand Up @@ -77,13 +78,14 @@ config:
maxToolCalls: 4

# Test 6: CVE with specific cluster filter (does not exist)
# Claude does comprehensive checking even when cluster doesn't exist
- path: tasks/cve-cluster-does-not-exist.yaml
assertions:
toolsUsed:
- server: stackrox-mcp
toolPattern: "list_clusters"
minToolCalls: 1
maxToolCalls: 4
maxToolCalls: 5

# Test 7: CVE detected in clusters - general
- path: tasks/cve-clusters-general.yaml
Expand All @@ -106,3 +108,35 @@ config:
cveName: "CVE-2024-52577"
minToolCalls: 1
maxToolCalls: 5

# Test 9: Log4shell (well-known CVE)
- path: tasks/cve-log4shell.yaml
assertions:
toolsUsed:
- server: stackrox-mcp
toolPattern: "get_deployments_for_cve"
argumentsMatch:
cveName: "CVE-2021-44228"
minToolCalls: 1
maxToolCalls: 3

# Test 10: Multiple CVEs in one prompt
- path: tasks/cve-multiple.yaml
assertions:
toolsUsed:
- server: stackrox-mcp
toolPattern: "get_deployments_for_cve"
argumentsMatch:
cveName: "CVE-2021-31805"
- server: stackrox-mcp
toolPattern: "get_deployments_for_cve"
argumentsMatch:
cveName: "CVE-2016-1000031"
minToolCalls: 2
maxToolCalls: 6

# Test 11: RHSA detection (should fail gracefully)
- path: tasks/rhsa-not-supported.yaml
assertions:
minToolCalls: 0
maxToolCalls: 1
21 changes: 21 additions & 0 deletions e2e-tests/mcpchecker/mcp-config-mock.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mcpServers:
stackrox-mcp:
command: go
args:
- run
- ../../cmd/stackrox-mcp/...
- --config
- ../stackrox-mcp-e2e-config.yaml
# Explicit environment overrides for mock mode
# mcpchecker doesn't support proper environment inheritance,
# so we need to explicitly set all required variables here
env:
STACKROX_MCP__CENTRAL__URL: "localhost:8081"
STACKROX_MCP__CENTRAL__API_TOKEN: "test-token-admin"
STACKROX_MCP__CENTRAL__INSECURE_SKIP_TLS_VERIFY: "true"
STACKROX_MCP__CENTRAL__AUTH_TYPE: "static"
STACKROX_MCP__SERVER__TYPE: "stdio"
STACKROX_MCP__TOOLS__VULNERABILITY__ENABLED: "true"
STACKROX_MCP__TOOLS__CONFIG_MANAGER__ENABLED: "true"
# Auto-approve all tools
enableAllTools: true
2 changes: 1 addition & 1 deletion e2e-tests/mcpchecker/tasks/cve-detected-workloads.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ steps:
prompt:
inline: "Is this CVE-2021-31805 detected in my workloads?"
verify:
contains: "A response indicating whether CVE-2021-31805 is detected in workloads/deployments, with specific deployment names if detected, or a statement that no deployments have this CVE."
contains: "CVE-2021-31805 is detected in deployments: struts-app (staging-cluster/applications) and legacy-portal (development-cluster/production)."
9 changes: 9 additions & 0 deletions e2e-tests/mcpchecker/tasks/cve-log4shell.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
kind: Task
metadata:
name: "cve-log4shell"
difficulty: easy
steps:
prompt:
inline: "Is log4shell CVE detected in my cluster fleet?"
verify:
contains: The commonly used name "log4shell" is resolved into the exact CVE number (CVE-2021-44228). And it's detected in a few deployments.
9 changes: 9 additions & 0 deletions e2e-tests/mcpchecker/tasks/cve-multiple.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
kind: Task
metadata:
name: "cve-multiple"
difficulty: medium
steps:
prompt:
inline: "Are CVE-2021-31805 and CVE-2016-1000031 detected in my cluster fleet?"
verify:
contains: "Both CVEs (CVE-2021-31805 and CVE-2016-1000031), along with the cluster names, deployments, and namespaces where they are detected."
Loading
Loading