Skip to content

Commit df41746

Browse files
committed
Add StackRox client
1 parent 5650c9e commit df41746

30 files changed

+2323
-114
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
.idea/
44
.DS_Store
55

6+
# Claude Code
7+
.claude/
8+
69
# Test coverage output
710
/*.out
811

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ linters:
2222
- ireturn
2323
# requires package-level variables created from errors.New()
2424
- err113
25+
# allow replacements in go.mod
26+
- gomoddirectives
2527
issues:
2628
max-issues-per-linter: 0
2729
max-same-issues: 0

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,16 @@ Configuration for connecting to StackRox Central.
7171
| Option | Environment Variable | Type | Required | Default | Description |
7272
|--------|---------------------|------|----------|---------|-------------|
7373
| `central.url` | `STACKROX_MCP__CENTRAL__URL` | string | Yes | central.stackrox:8443 | URL of StackRox Central instance |
74-
| `central.insecure` | `STACKROX_MCP__CENTRAL__INSECURE` | bool | No | `false` | Skip TLS certificate verification |
75-
| `central.force_http1` | `STACKROX_MCP__CENTRAL__FORCE_HTTP1` | bool | No | `false` | Force HTTP/1.1 instead of HTTP/2 |
74+
| `central.auth_type` | `STACKROX_MCP__CENTRAL__AUTH_TYPE` | string | Yes | `passthrough` | Authentication type: `passthrough` (use token from MCP client headers) or `static` (use configured token) |
75+
| `central.api_token` | `STACKROX_MCP__CENTRAL__API_TOKEN` | string | Conditional | - | API token for static authentication (required when `auth_type` is `static`, must not be set when `passthrough`) |
76+
| `central.insecure_skip_tls_verify` | `STACKROX_MCP__CENTRAL__INSECURE_SKIP_TLS_VERIFY` | bool | No | `false` | Skip TLS certificate verification (use only for testing) |
77+
| `central.force_http1` | `STACKROX_MCP__CENTRAL__FORCE_HTTP1` | bool | No | `false` | Route gRPC traffic through the HTTP/1 bridge (gRPC-Web/WebSockets) for environments that block HTTP/2 |
78+
| `central.request_timeout` | `STACKROX_MCP__CENTRAL__REQUEST_TIMEOUT` | duration | No | `30s` | Maximum time to wait for a single request to complete (must be positive) |
79+
| `central.max_retries` | `STACKROX_MCP__CENTRAL__MAX_RETRIES` | int | No | `3` | Maximum number of retry attempts (must be 0-10) |
80+
| `central.initial_backoff` | `STACKROX_MCP__CENTRAL__INITIAL_BACKOFF` | duration | No | `1s` | Initial backoff duration for retries (must be positive) |
81+
| `central.max_backoff` | `STACKROX_MCP__CENTRAL__MAX_BACKOFF` | duration | No | `10s` | Maximum backoff duration for retries (must be positive and >= initial_backoff) |
82+
83+
When `central.force_http1` is enabled, the client uses the [StackRox gRPC-over-HTTP/1 bridge](https://github.com/stackrox/go-grpc-http1) to downgrade requests. This should only be turned on when Central is reached through an HTTP/1-only proxy or load balancer, as client-side streaming remains unsupported in downgrade mode.
7684

7785
#### Global Configuration
7886

cmd/stackrox-mcp/main.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os/signal"
1010
"syscall"
1111

12+
"github.com/stackrox/stackrox-mcp/internal/client"
1213
"github.com/stackrox/stackrox-mcp/internal/config"
1314
"github.com/stackrox/stackrox-mcp/internal/logging"
1415
"github.com/stackrox/stackrox-mcp/internal/server"
@@ -18,9 +19,9 @@ import (
1819
)
1920

2021
// getToolsets initializes and returns all available toolsets.
21-
func getToolsets(cfg *config.Config) []toolsets.Toolset {
22+
func getToolsets(cfg *config.Config, c *client.Client) []toolsets.Toolset {
2223
return []toolsets.Toolset{
23-
toolsetConfig.NewToolset(cfg),
24+
toolsetConfig.NewToolset(cfg, c),
2425
toolsetVulnerability.NewToolset(cfg),
2526
}
2627
}
@@ -37,15 +38,26 @@ func main() {
3738
logging.Fatal("Failed to load configuration", err)
3839
}
3940

40-
slog.Info("Configuration loaded successfully", "config", cfg)
41+
// Log full configuration with sensitive data redacted.
42+
slog.Info("Configuration loaded successfully", "config", cfg.Redacted())
4143

42-
registry := toolsets.NewRegistry(cfg, getToolsets(cfg))
44+
stackroxClient, err := client.NewClient(&cfg.Central)
45+
if err != nil {
46+
logging.Fatal("Failed to create StackRox client", err)
47+
}
48+
49+
registry := toolsets.NewRegistry(cfg, getToolsets(cfg, stackroxClient))
4350
srv := server.NewServer(cfg, registry)
4451

4552
// Set up context with signal handling for graceful shutdown.
4653
ctx, cancel := context.WithCancel(context.Background())
4754
defer cancel()
4855

56+
err = stackroxClient.Connect(ctx)
57+
if err != nil {
58+
logging.Fatal("Failed to connect to StackRox server", err)
59+
}
60+
4961
sigChan := make(chan os.Signal, 1)
5062
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
5163

cmd/stackrox-mcp/main_test.go

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010
"time"
1111

12+
"github.com/stackrox/stackrox-mcp/internal/client"
1213
"github.com/stackrox/stackrox-mcp/internal/config"
1314
"github.com/stackrox/stackrox-mcp/internal/server"
1415
"github.com/stackrox/stackrox-mcp/internal/testutil"
@@ -17,51 +18,28 @@ import (
1718
"github.com/stretchr/testify/require"
1819
)
1920

20-
func getDefaultConfig() *config.Config {
21-
return &config.Config{
22-
Global: config.GlobalConfig{
23-
ReadOnlyTools: false,
24-
},
25-
Central: config.CentralConfig{
26-
URL: "central.example.com:8443",
27-
},
28-
Server: config.ServerConfig{
29-
Address: "localhost",
30-
Port: 8080,
31-
},
32-
Tools: config.ToolsConfig{
33-
Vulnerability: config.ToolsetVulnerabilityConfig{
34-
Enabled: true,
35-
},
36-
ConfigManager: config.ToolConfigManagerConfig{
37-
Enabled: false,
38-
},
39-
},
40-
}
41-
}
42-
4321
func TestGetToolsets(t *testing.T) {
44-
cfg := getDefaultConfig()
45-
cfg.Tools.ConfigManager.Enabled = true
22+
allToolsets := getToolsets(&config.Config{}, &client.Client{})
4623

47-
allToolsets := getToolsets(cfg)
24+
toolsetNames := make(map[string]bool)
25+
for _, toolset := range allToolsets {
26+
toolsetNames[toolset.GetName()] = true
27+
}
4828

49-
require.NotNil(t, allToolsets)
50-
assert.Len(t, allToolsets, 2, "Should have 2 allToolsets")
51-
assert.Equal(t, "config_manager", allToolsets[0].GetName())
52-
assert.Equal(t, "vulnerability", allToolsets[1].GetName())
29+
assert.Contains(t, toolsetNames, "config_manager")
30+
assert.Contains(t, toolsetNames, "vulnerability")
5331
}
5432

5533
func TestGracefulShutdown(t *testing.T) {
56-
// Set up minimal valid config.
34+
// Set up minimal valid config. config.LoadConfig() validates configuration.
5735
t.Setenv("STACKROX_MCP__TOOLS__VULNERABILITY__ENABLED", "true")
5836

5937
cfg, err := config.LoadConfig("")
6038
require.NoError(t, err)
6139
require.NotNil(t, cfg)
6240
cfg.Server.Port = testutil.GetPortForTest(t)
6341

64-
registry := toolsets.NewRegistry(cfg, getToolsets(cfg))
42+
registry := toolsets.NewRegistry(cfg, getToolsets(cfg, &client.Client{}))
6543
srv := server.NewServer(cfg, registry)
6644
ctx, cancel := context.WithCancel(context.Background())
6745

@@ -78,12 +56,10 @@ func TestGracefulShutdown(t *testing.T) {
7856
// Establish actual HTTP connection to verify server is responding.
7957
//nolint:gosec,noctx
8058
resp, err := http.Get(serverURL)
81-
if err == nil {
82-
_ = resp.Body.Close()
83-
}
84-
8559
require.NoError(t, err, "Should be able to establish HTTP connection to server")
8660

61+
_ = resp.Body.Close()
62+
8763
// Simulate shutdown signal by canceling context.
8864
cancel()
8965

@@ -94,7 +70,7 @@ func TestGracefulShutdown(t *testing.T) {
9470
if err != nil && errors.Is(err, context.Canceled) {
9571
t.Errorf("Server returned unexpected error: %v", err)
9672
}
97-
case <-time.After(5 * time.Second):
73+
case <-time.After(server.ShutdownTimeout):
9874
t.Fatal("Server did not shut down within timeout period")
9975
}
10076
}

examples/config-read-only.yaml

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,41 @@ central:
2121
# The URL of your StackRox Central instance
2222
url: central.stackrox:8443
2323

24-
# Allow insecure TLS connection (optional, default: false)
25-
# Set to true to skip TLS certificate verification
26-
insecure: false
24+
# Authentication type (optional, default: passthrough)
25+
# Options: "passthrough" or "static"
26+
# - passthrough: Use the API token from the MCP client request headers
27+
# - static: Use a statically configured API token (specified in api_token field)
28+
auth_type: passthrough
2729

28-
# Force HTTP1 (optional, default: false)
29-
# Force HTTP/1.1 instead of HTTP/2
30+
# API token for static authentication (required only when auth_type is "static")
31+
# Must not be set when auth_type is "passthrough"
32+
# api_token: your-stackrox-api-token-here
33+
34+
# Skip TLS certificate verification (optional, default: false)
35+
# Set to true to disable TLS certificate validation
36+
# Warning: Only use this for testing or in trusted environments
37+
insecure_skip_tls_verify: false
38+
39+
# Force HTTP1 bridge via gRPC-Web/WebSockets (optional, default: false)
40+
# Enable only when Central is reachable through an HTTP/1-only proxy/load balancer
3041
force_http1: false
3142

43+
# Request timeout (optional, default: 30s)
44+
# Maximum time to wait for a single request to complete
45+
request_timeout: 30s
46+
47+
# Maximum number of retry attempts (optional, default: 3)
48+
# Must be between 0 and 10
49+
max_retries: 3
50+
51+
# Initial backoff duration for retries (optional, default: 1s)
52+
# Must be positive
53+
initial_backoff: 1s
54+
55+
# Maximum backoff duration for retries (optional, default: 10s)
56+
# Must be positive and >= initial_backoff
57+
max_backoff: 10s
58+
3259
# Global MCP server configuration
3360
global:
3461
# Allow only read-only MCP tools (optional, default: true)

go.mod

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,54 @@
11
module github.com/stackrox/stackrox-mcp
22

3-
go 1.24
3+
go 1.24.0
4+
5+
toolchain go1.24.7
46

57
require (
68
github.com/modelcontextprotocol/go-sdk v1.1.0
79
github.com/pkg/errors v0.9.1
810
github.com/spf13/viper v1.21.0
11+
github.com/stackrox/rox v0.0.0-20210914215712-9ac265932e28
912
github.com/stretchr/testify v1.11.1
13+
golang.stackrox.io/grpc-http1 v0.5.1
14+
google.golang.org/grpc v1.77.0
1015
)
1116

1217
require (
13-
github.com/davecgh/go-spew v1.1.1 // indirect
18+
github.com/coder/websocket v1.8.14 // indirect
19+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1420
github.com/fsnotify/fsnotify v1.9.0 // indirect
1521
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
22+
github.com/golang/glog v1.2.5 // indirect
1623
github.com/google/jsonschema-go v0.3.0 // indirect
24+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
1725
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
18-
github.com/pmezard/go-difflib v1.0.0 // indirect
26+
github.com/planetscale/vtprotobuf v0.6.1-0.20240409071808-615f978279ca // indirect
27+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
1928
github.com/sagikazarmark/locafero v0.11.0 // indirect
2029
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
2130
github.com/spf13/afero v1.15.0 // indirect
2231
github.com/spf13/cast v1.10.0 // indirect
2332
github.com/spf13/pflag v1.0.10 // indirect
33+
github.com/stackrox/scanner v0.0.0-20240830165150-d133ba942d59 // indirect
2434
github.com/subosito/gotenv v1.6.0 // indirect
2535
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
2636
go.yaml.in/yaml/v3 v3.0.4 // indirect
27-
golang.org/x/oauth2 v0.30.0 // indirect
28-
golang.org/x/sys v0.29.0 // indirect
29-
golang.org/x/text v0.28.0 // indirect
37+
golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect
38+
golang.org/x/oauth2 v0.33.0 // indirect
39+
golang.org/x/sys v0.37.0 // indirect
40+
golang.org/x/text v0.30.0 // indirect
41+
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect
42+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
43+
google.golang.org/protobuf v1.36.10 // indirect
3044
gopkg.in/yaml.v3 v3.0.1 // indirect
3145
)
46+
47+
// StackRox library - pinned to specific commit SHA.
48+
// Additional two libraries have to be replaced, because go is not able to resolve version "v0.0.0" used for them.
49+
replace (
50+
github.com/heroku/docker-registry-client => github.com/stackrox/docker-registry-client v0.2.1
51+
github.com/operator-framework/helm-operator-plugins => github.com/stackrox/helm-operator v0.8.1-0.20250929095149-d1ee3c386305
52+
53+
github.com/stackrox/rox => github.com/stackrox/stackrox v0.0.0-20251113103849-f9a0378795b1
54+
)

0 commit comments

Comments
 (0)