From 41440c8678583337d471cbb80326969ca6a7ad08 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Fri, 9 Jan 2026 15:27:33 +0100 Subject: [PATCH 1/5] Add helm chart --- .github/workflows/style.yml | 30 ++ Dockerfile | 8 +- charts/stackrox-mcp/.helmignore | 7 + charts/stackrox-mcp/Chart.yaml | 14 + charts/stackrox-mcp/README.md | 460 ++++++++++++++++++ charts/stackrox-mcp/templates/NOTES.txt | 49 ++ charts/stackrox-mcp/templates/_helpers.tpl | 75 +++ charts/stackrox-mcp/templates/configmap.yaml | 39 ++ charts/stackrox-mcp/templates/deployment.yaml | 116 +++++ charts/stackrox-mcp/templates/route.yaml | 23 + charts/stackrox-mcp/templates/secret.yaml | 12 + .../templates/service-account.yaml | 12 + charts/stackrox-mcp/templates/service.yaml | 20 + charts/stackrox-mcp/values.yaml | 193 ++++++++ 14 files changed, 1055 insertions(+), 3 deletions(-) create mode 100644 charts/stackrox-mcp/.helmignore create mode 100644 charts/stackrox-mcp/Chart.yaml create mode 100644 charts/stackrox-mcp/README.md create mode 100644 charts/stackrox-mcp/templates/NOTES.txt create mode 100644 charts/stackrox-mcp/templates/_helpers.tpl create mode 100644 charts/stackrox-mcp/templates/configmap.yaml create mode 100644 charts/stackrox-mcp/templates/deployment.yaml create mode 100644 charts/stackrox-mcp/templates/route.yaml create mode 100644 charts/stackrox-mcp/templates/secret.yaml create mode 100644 charts/stackrox-mcp/templates/service-account.yaml create mode 100644 charts/stackrox-mcp/templates/service.yaml create mode 100644 charts/stackrox-mcp/values.yaml diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index dd6674d..51f9fed 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -36,3 +36,33 @@ jobs: uses: hadolint/hadolint-action@v3.3.0 with: dockerfile: Dockerfile + + - name: Create ../results directory for SARIF report files + shell: bash + run: mkdir -p ../results + + - name: Scan Helm with kube-linter + uses: stackrox/kube-linter-action@v1.0.7 + id: kube-linter-helm-scan + with: + directory: charts/stackrox-mcp + format: sarif + output-file: ../results/kube-linter.sarif + # This allows the following upload-sarif action to still upload the results to your GitHub repo. + continue-on-error: true + + - name: Upload SARIF report files to GitHub + uses: github/codeql-action/upload-sarif@v4 + + # Ensure the workflow eventually fails if files did not pass kube-linter checks. + - name: Verify kube-linter-action succeeded + shell: bash + run: | + echo "If this step fails, kube-linter found issues. Check the output of the scan step above." + [[ "${{ steps.kube-linter-helm-scan.outcome }}" == "success" ]] + + - name: Set up chart-testing + uses: helm/chart-testing-action@v2.8.0 + + - name: Run chart-testing (lint) + run: ct lint charts/stackrox-mcp --validate-maintainers=false --all diff --git a/Dockerfile b/Dockerfile index bd1fc80..20661f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,10 +50,12 @@ WORKDIR /app # Copy binary from builder COPY --from=builder /tmp/stackrox-mcp /app/stackrox-mcp -# Set ownership to non-root user -RUN chown -R 4000:4000 /app +# Set ownership for OpenShift arbitrary UID support +# Files owned by 4000, group 0 (root), with group permissions matching user +RUN chown -R 4000:0 /app && \ + chmod -R g=u /app -# Switch to non-root user +# Switch to non-root user (can be overridden by OpenShift SCC) USER 4000 # Expose port for MCP server diff --git a/charts/stackrox-mcp/.helmignore b/charts/stackrox-mcp/.helmignore new file mode 100644 index 0000000..2d5e960 --- /dev/null +++ b/charts/stackrox-mcp/.helmignore @@ -0,0 +1,7 @@ +# Patterns to ignore when packaging +.git/ +.gitignore +*.swp +*.bak +*.tmp +.DS_Store diff --git a/charts/stackrox-mcp/Chart.yaml b/charts/stackrox-mcp/Chart.yaml new file mode 100644 index 0000000..693e8c9 --- /dev/null +++ b/charts/stackrox-mcp/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: stackrox-mcp +description: A Helm chart for StackRox Model Context Protocol (MCP) Server +type: application +version: 0.1.0 +appVersion: "dev" +home: https://github.com/stackrox/stackrox-mcp +sources: + - https://github.com/stackrox/stackrox-mcp +keywords: + - stackrox + - mcp + - security + - vulnerability diff --git a/charts/stackrox-mcp/README.md b/charts/stackrox-mcp/README.md new file mode 100644 index 0000000..badc074 --- /dev/null +++ b/charts/stackrox-mcp/README.md @@ -0,0 +1,460 @@ +# StackRox MCP Helm Chart + +A Helm chart for deploying the StackRox Model Context Protocol (MCP) Server to Kubernetes and OpenShift clusters. + +## Prerequisites + +- Kubernetes 1.19+ or OpenShift 4.x+ +- Helm 3.0+ +- Access to a StackRox Central instance +- Valid StackRox API token (for static authentication mode) + +## Installing the Chart + +To install the chart with the release name `stackrox-mcp`: + +```bash +helm install stackrox-mcp charts/stackrox-mcp \ + --namespace stackrox-mcp \ + --create-namespace \ + --set config.tools.vulnerability.enabled=true \ + --set config.central.url= +``` + +## Upgrading the Chart + +To upgrade an existing release: + +```bash +helm upgrade stackrox-mcp charts/stackrox-mcp \ + --namespace stackrox-mcp \ + --reuse-values +``` + +## Uninstalling the Chart + +To uninstall/delete the `stackrox-mcp` release: + +```bash +helm uninstall stackrox-mcp --namespace stackrox-mcp +``` + +## Configuration + +The following table lists the configurable parameters of the StackRox MCP chart and their default values. + +### Image Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `image.registry` | Container image registry | `quay.io` | +| `image.repository` | Container image repository | `stackrox-io/mcp` | +| `image.tag` | Container image tag (overrides appVersion) | `""` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `imagePullSecrets` | Image pull secrets for private registries | `[]` | + +### Deployment Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `replicaCount` | Number of replicas | `1` | +| `annotations` | Annotations for Deployment and Pod metadata | `{}` | + +### Service Account + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `serviceAccount.annotations` | Annotations for the service account | `{}` | +| `serviceAccount.automountServiceAccountToken` | Automount service account token | `false` | + +### Security Contexts + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `podSecurityContext.runAsNonRoot` | Run as non-root user | `true` | +| `podSecurityContext.runAsUser` | User ID to run as (auto-omitted on OpenShift) | `4000` | +| `podSecurityContext.runAsGroup` | Group ID to run as (auto-omitted on OpenShift) | `4000` | +| `podSecurityContext.fsGroup` | Filesystem group ID (auto-omitted on OpenShift) | `4000` | +| `podSecurityContext.seccompProfile.type` | Seccomp profile type | `RuntimeDefault` | +| `securityContext.allowPrivilegeEscalation` | Allow privilege escalation | `false` | +| `securityContext.readOnlyRootFilesystem` | Read-only root filesystem | `true` | +| `securityContext.runAsNonRoot` | Run as non-root user | `true` | +| `securityContext.runAsUser` | User ID to run as (auto-omitted on OpenShift) | `4000` | +| `securityContext.capabilities.drop` | List of capabilities to drop | `["ALL"]` | + +**Note:** On OpenShift, `runAsUser`, `runAsGroup`, and `fsGroup` are automatically omitted to allow the SecurityContextConstraints (SCC) to assign UIDs from the namespace's valid range (e.g., 1000760000-1000769999). + +### Service Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `service.type` | Service type | `ClusterIP` | +| `service.port` | Service port | `8080` | +| `service.targetPort` | Target port | `8080` | +| `service.annotations` | Service annotations | `{}` | + +### Resource Limits + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `resources.requests.cpu` | CPU request | `200m` | +| `resources.requests.memory` | Memory request | `500Mi` | +| `resources.limits.cpu` | CPU limit | | +| `resources.limits.memory` | Memory limit | `500Mi` | + +### Probes + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `livenessProbe.enabled` | Enable liveness probe | `true` | +| `livenessProbe.httpGet.path` | Path for liveness probe | `/health` | +| `livenessProbe.httpGet.port` | Port for liveness probe | `http` | +| `livenessProbe.initialDelaySeconds` | Initial delay for liveness probe | `10` | +| `livenessProbe.periodSeconds` | Period for liveness probe | `10` | +| `livenessProbe.timeoutSeconds` | Timeout for liveness probe | `5` | +| `livenessProbe.successThreshold` | Success threshold for liveness probe | `1` | +| `livenessProbe.failureThreshold` | Failure threshold for liveness probe | `3` | +| `readinessProbe.enabled` | Enable readiness probe | `true` | +| `readinessProbe.httpGet.path` | Path for readiness probe | `/health` | +| `readinessProbe.httpGet.port` | Port for readiness probe | `http` | +| `readinessProbe.initialDelaySeconds` | Initial delay for readiness probe | `5` | +| `readinessProbe.periodSeconds` | Period for readiness probe | `5` | +| `readinessProbe.timeoutSeconds` | Timeout for readiness probe | `3` | +| `readinessProbe.successThreshold` | Success threshold for readiness probe | `1` | +| `readinessProbe.failureThreshold` | Failure threshold for readiness probe | `3` | + +### OpenShift Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `openshift.route.host` | Route hostname | `""` | +| `openshift.route.tls.enabled` | Enable TLS for route | `true` | +| `openshift.route.tls.termination` | TLS termination type | `edge` | +| `openshift.route.tls.insecureEdgeTerminationPolicy` | Policy for insecure edge traffic | `Redirect` | + +### Scheduling + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | +| `affinity` | Affinity rules for pod assignment | `{}` | +| `priorityClassName` | Priority class name for pod scheduling | `""` | + +### Advanced Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `extraEnv` | Additional environment variables | `[]` | + +### StackRox MCP Configuration + +#### Central Connection + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `config.central.url` | StackRox Central URL | `central.stackrox:8443` | +| `config.central.authType` | Authentication type (`passthrough` or `static`) | `passthrough` | +| `config.central.apiToken` | API token for static auth (base64 encoded) | `""` | +| `config.central.existingSecret.name` | Name of existing secret with API token | `""` | +| `config.central.existingSecret.key` | Key in existing secret | `api-token` | +| `config.central.insecureSkipTLSVerify` | Skip TLS verification (testing only) | `false` | +| `config.central.forceHTTP1` | Force HTTP/1 bridge | `false` | +| `config.central.requestTimeout` | Request timeout | `30s` | +| `config.central.maxRetries` | Maximum retry attempts | `3` | +| `config.central.initialBackoff` | Initial retry backoff | `1s` | +| `config.central.maxBackoff` | Maximum retry backoff | `10s` | + +#### Global Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `config.global.readOnlyTools` | Restrict to read-only operations | `true` | + +#### Server Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `config.server.type` | Server type (`streamable-http` or `stdio`) | `streamable-http` | +| `config.server.address` | Server listen address | `0.0.0.0` | +| `config.server.port` | Server listen port | `8080` | + +#### Tools Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `config.tools.vulnerability.enabled` | Enable vulnerability management tools | `false` | +| `config.tools.configManager.enabled` | Enable configuration management tools | `false` | + +**Note**: At least one tool must be enabled. + +## Common Configurations + +### Passthrough Authentication (Default) + +This mode passes API tokens from MCP client request headers to StackRox Central: + +```yaml +config: + central: + url: "central.stackrox:8443" + authType: "passthrough" + tools: + vulnerability: + enabled: true +``` + +### Static Authentication with Inline Token + +For static authentication with an inline token (not recommended for production): + +```yaml +config: + central: + url: "central.stackrox:8443" + authType: "static" + apiToken: "" + tools: + vulnerability: + enabled: true +``` + +### Static Authentication with Existing Secret + +For production use, reference an existing Kubernetes secret: + +1. Create the secret: + +```bash +kubectl create secret generic stackrox-api-token \ + --from-literal=api-token= \ + --namespace stackrox-mcp +``` + +2. Reference it in values: + +```yaml +config: + central: + url: "central.stackrox:8443" + authType: "static" + existingSecret: + name: "stackrox-api-token" + key: "api-token" + tools: + vulnerability: + enabled: true +``` + +### OpenShift Deployment + +For OpenShift with external Route: + +```yaml +# OpenShift automatically assigns UIDs via SCC +# No need to override security context values + +openshift: + route: + host: "stackrox-mcp.apps.example.com" + tls: + enabled: true + termination: edge + +config: + central: + url: "central.stackrox:8443" + tools: + vulnerability: + enabled: true +``` + +**OpenShift Security Context Constraints (SCC):** + +The chart automatically detects OpenShift and omits `runAsUser`, `runAsGroup`, and `fsGroup` settings to allow the cluster's SCC to assign UIDs from valid namespace ranges (e.g., 1000760000-1000769999). + +The chart maintains security hardening on OpenShift: +- `runAsNonRoot: true` - Always enforced +- `readOnlyRootFilesystem: true` - Always enforced +- `allowPrivilegeEscalation: false` - Always enforced +- `capabilities: drop: ["ALL"]` - Always enforced + +The chart is compatible with the `restricted-v2` SCC on OpenShift. + +### High Availability Setup + +For high availability with multiple replicas: + +```yaml +replicaCount: 3 + +affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - stackrox-mcp + topologyKey: kubernetes.io/hostname +``` + +## Configuration Loading + +The StackRox MCP Helm chart uses a YAML configuration file approach for cleaner and more maintainable configuration management. + +### How It Works + +1. **ConfigMap with YAML File**: The chart creates a ConfigMap containing a complete `config.yaml` file with all non-sensitive configuration +2. **File Mounting**: The ConfigMap is mounted as a file at `/config/config.yaml` in the pod +3. **Command Args**: The application is started with `--config /config/config.yaml` to load the configuration +4. **Environment Variable Override**: The API token (for static auth) is injected via environment variable `STACKROX_MCP__CENTRAL__API_TOKEN`, which takes precedence over the YAML file + +### Viewing Configuration + +To view the current configuration: + +```bash +# View the YAML config file in ConfigMap +kubectl get configmap -n stackrox-mcp -config -o jsonpath='{.data.config\.yaml}' + +# Verify config file is mounted in pod +kubectl exec -n stackrox-mcp deployment/ -- cat /config/config.yaml +``` + +### Configuration Precedence + +The application loads configuration in this order (highest to lowest precedence): +1. Environment variables (e.g., `STACKROX_MCP__CENTRAL__API_TOKEN`) +2. YAML configuration file (`/config/config.yaml`) +3. Application defaults + +This means you can override any YAML configuration value using environment variables via `extraEnv` in values.yaml. + +## Security Considerations + +### Secret Management + +1. **Never commit secrets to version control**: Use external secret management solutions like: + - Kubernetes External Secrets Operator + - HashiCorp Vault + - Sealed Secrets + +2. **Use RBAC**: Limit access to the secret containing the API token: + +```bash +kubectl create role secret-reader \ + --verb=get,list \ + --resource=secrets \ + --resource-name=stackrox-api-token \ + --namespace stackrox-mcp +``` + +3. **Rotate tokens regularly**: Update the API token periodically and restart the deployment. + +### Network Policies + +Consider implementing NetworkPolicies to restrict traffic: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: stackrox-mcp + namespace: stackrox-mcp +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: stackrox-mcp + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: {} + ports: + - protocol: TCP + port: 8080 + egress: + - to: + - namespaceSelector: {} + ports: + - protocol: TCP + port: 8443 # StackRox Central + - to: + - namespaceSelector: + matchLabels: + name: kube-system + ports: + - protocol: UDP + port: 53 # DNS +``` + +### Pod Security Standards + +This chart is compatible with the Kubernetes Pod Security Standards (restricted level). + +## Troubleshooting + +### Deployment fails to start + +Check the pod logs: + +```bash +kubectl logs -n stackrox-mcp deployment/stackrox-mcp +``` + +Common issues: +- **Missing API token**: Ensure token is set when using static authentication +- **Invalid Central URL**: Verify the StackRox Central URL is correct +- **Network connectivity**: Ensure the pod can reach StackRox Central on port 8443 + +### Health check failures + +Test the health endpoint: + +```bash +kubectl run -i --tty --rm debug --image=curlimages/curl --restart=Never -- \ + curl http://stackrox-mcp.stackrox-mcp.svc.cluster.local:8080/health +``` + +Expected response: `{"status":"ok"}` + +### Configuration not updating + +Configuration changes trigger automatic pod restarts via checksum annotations. If changes don't apply: + +```bash +kubectl rollout restart deployment/stackrox-mcp -n stackrox-mcp +``` + +### No tools enabled error + +At least one tool must be enabled. Set either: +- `config.tools.vulnerability.enabled: true` +- `config.tools.configManager.enabled: true` + +### OpenShift SCC Errors + +If you see errors like: +``` +.containers[0].runAsUser: Invalid value: 4000: must be in the ranges: [1000760000, 1000769999] +``` + +**Solution:** The chart automatically detects OpenShift and omits hardcoded UIDs. Ensure you're using Helm 3.1+ which properly passes API capabilities to templates. + +To verify OpenShift detection: +```bash +helm template test charts/stackrox-mcp --api-versions route.openshift.io/v1 | grep -A 5 "securityContext:" +``` + +Expected: No `runAsUser`, `runAsGroup`, or `fsGroup` should appear in the pod security context. + +## Support + +For issues and questions: +- GitHub Issues: https://github.com/stackrox/stackrox-mcp/issues +- Documentation: https://github.com/stackrox/stackrox-mcp diff --git a/charts/stackrox-mcp/templates/NOTES.txt b/charts/stackrox-mcp/templates/NOTES.txt new file mode 100644 index 0000000..20a1e23 --- /dev/null +++ b/charts/stackrox-mcp/templates/NOTES.txt @@ -0,0 +1,49 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} + +StackRox MCP Server Configuration: +----------------------------------- + +1. The server is configured to connect to: {{ .Values.config.central.url }} + +2. Authentication mode: {{ .Values.config.central.authType }} +{{- if eq .Values.config.central.authType "static" }} + - Using static API token from Secret: {{ include "stackrox-mcp.secretName" . }} +{{- else }} + - Using passthrough authentication (tokens from MCP client headers) +{{- end }} + +3. Enabled tools: + - Vulnerability Management: {{ .Values.config.tools.vulnerability.enabled | ternary "ENABLED" "DISABLED" }} + - Configuration Manager: {{ .Values.config.tools.configManager.enabled | ternary "ENABLED" "DISABLED" }} + +4. The service is available at: +{{- if eq (include "stackrox-mcp.isOpenshift" .) "true" }} + Openshift route: kubectl get route --namespace {{ .Release.Namespace }} {{ include "stackrox-mcp.fullname" . }} +{{- else }} + Kubernetes service: {{ include "stackrox-mcp.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }} +{{- end }} + +To test connectivity: + $ kubectl run -i --tty --rm debug --image=curlimages/curl --restart=Never -- \ + curl http://{{ include "stackrox-mcp.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}/health + +{{- if and (eq .Values.config.central.authType "static") (not .Values.config.central.apiToken) (not .Values.config.central.existingSecret.name) }} + +WARNING: Static authentication is enabled but no API token was provided! +Please update the secret with your StackRox API token: + + $ kubectl create secret generic {{ include "stackrox-mcp.fullname" . }}-api-token \ + --from-literal=api-token= \ + --namespace {{ .Release.Namespace }} \ + --dry-run=client -o yaml | kubectl apply -f - + +Then restart the deployment: + $ kubectl rollout restart deployment/{{ include "stackrox-mcp.fullname" . }} -n {{ .Release.Namespace }} +{{- end }} diff --git a/charts/stackrox-mcp/templates/_helpers.tpl b/charts/stackrox-mcp/templates/_helpers.tpl new file mode 100644 index 0000000..addf0c6 --- /dev/null +++ b/charts/stackrox-mcp/templates/_helpers.tpl @@ -0,0 +1,75 @@ +{{/* +Create a default fully qualified app name. +*/}} +{{- define "stackrox-mcp.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "stackrox-mcp.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "stackrox-mcp.labels" -}} +helm.sh/chart: {{ include "stackrox-mcp.chart" . }} +{{ include "stackrox-mcp.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "stackrox-mcp.selectorLabels" -}} +app.kubernetes.io/name: stackrox-mcp +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Image reference +*/}} +{{- define "stackrox-mcp.image" -}} +{{- $tag := .Values.image.tag | default .Chart.AppVersion }} +{{- printf "%s/%s:%s" .Values.image.registry .Values.image.repository $tag }} +{{- end }} + +{{/* +Secret name for API token +*/}} +{{- define "stackrox-mcp.secretName" -}} +{{- if .Values.config.central.existingSecret.name }} +{{- .Values.config.central.existingSecret.name }} +{{- else }} +{{- include "stackrox-mcp.fullname" . }}-api-token +{{- end }} +{{- end }} + +{{/* +ConfigMap name +*/}} +{{- define "stackrox-mcp.configMapName" -}} +{{- include "stackrox-mcp.fullname" . }}-config +{{- end }} + +{{/* +Detect if running on OpenShift +*/}} +{{- define "stackrox-mcp.isOpenshift" -}} +{{- .Capabilities.APIVersions.Has "route.openshift.io/v1" -}} +{{- end }} diff --git a/charts/stackrox-mcp/templates/configmap.yaml b/charts/stackrox-mcp/templates/configmap.yaml new file mode 100644 index 0000000..b88e72c --- /dev/null +++ b/charts/stackrox-mcp/templates/configmap.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "stackrox-mcp.configMapName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "stackrox-mcp.labels" . | nindent 4 }} +data: + config.yaml: | + # Central connection configuration + central: + url: {{ .Values.config.central.url | quote }} + auth_type: {{ .Values.config.central.authType | quote }} + {{- if eq .Values.config.central.authType "static" }} + # API token will be injected via environment variable for security + {{- end }} + insecure_skip_tls_verify: {{ .Values.config.central.insecureSkipTLSVerify }} + force_http1: {{ .Values.config.central.forceHTTP1 }} + request_timeout: {{ .Values.config.central.requestTimeout | quote }} + max_retries: {{ .Values.config.central.maxRetries }} + initial_backoff: {{ .Values.config.central.initialBackoff | quote }} + max_backoff: {{ .Values.config.central.maxBackoff | quote }} + + # Global MCP server configuration + global: + read_only_tools: {{ .Values.config.global.readOnlyTools }} + + # HTTP server configuration + server: + type: {{ .Values.config.server.type | quote }} + address: {{ .Values.config.server.address | quote }} + port: {{ .Values.config.server.port }} + + # MCP tools configuration + tools: + vulnerability: + enabled: {{ .Values.config.tools.vulnerability.enabled }} + config_manager: + enabled: {{ .Values.config.tools.configManager.enabled }} diff --git a/charts/stackrox-mcp/templates/deployment.yaml b/charts/stackrox-mcp/templates/deployment.yaml new file mode 100644 index 0000000..4995c34 --- /dev/null +++ b/charts/stackrox-mcp/templates/deployment.yaml @@ -0,0 +1,116 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + {{- with .Values.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ include "stackrox-mcp.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "stackrox-mcp.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "stackrox-mcp.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + # Force pod restart on config/secret changes + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- with .Values.annotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "stackrox-mcp.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "stackrox-mcp.fullname" . }} + automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + runAsNonRoot: {{ .Values.podSecurityContext.runAsNonRoot }} + {{- if .Values.podSecurityContext.seccompProfile }} + seccompProfile: + {{- toYaml .Values.podSecurityContext.seccompProfile | nindent 10 }} + {{- end }} + {{- if and .Values.podSecurityContext.runAsUser (ne (include "stackrox-mcp.isOpenshift" .) "true") }} + runAsUser: {{ .Values.podSecurityContext.runAsUser }} + {{- end }} + {{- if and .Values.podSecurityContext.runAsGroup (ne (include "stackrox-mcp.isOpenshift" .) "true") }} + runAsGroup: {{ .Values.podSecurityContext.runAsGroup }} + {{- end }} + {{- if and .Values.podSecurityContext.fsGroup (ne (include "stackrox-mcp.isOpenshift" .) "true") }} + fsGroup: {{ .Values.podSecurityContext.fsGroup }} + {{- end }} + containers: + - name: stackrox-mcp + securityContext: + allowPrivilegeEscalation: {{ .Values.securityContext.allowPrivilegeEscalation }} + readOnlyRootFilesystem: {{ .Values.securityContext.readOnlyRootFilesystem }} + runAsNonRoot: {{ .Values.securityContext.runAsNonRoot }} + {{- if and .Values.securityContext.runAsUser (ne (include "stackrox-mcp.isOpenshift" .) "true") }} + runAsUser: {{ .Values.securityContext.runAsUser }} + {{- end }} + {{- if .Values.securityContext.capabilities }} + capabilities: + {{- toYaml .Values.securityContext.capabilities | nindent 12 }} + {{- end }} + image: {{ include "stackrox-mcp.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: + - --config + - /config/config.yaml + ports: + - name: http + containerPort: {{ .Values.config.server.port }} + protocol: TCP + env: + {{- if eq .Values.config.central.authType "static" }} + # API token injected via environment variable (overrides YAML config) + - name: STACKROX_MCP__CENTRAL__API_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "stackrox-mcp.secretName" . }} + key: {{ .Values.config.central.existingSecret.key }} + {{- end }} + {{- with .Values.extraEnv }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + {{- omit .Values.livenessProbe "enabled" | toYaml | nindent 10 }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + {{- omit .Values.readinessProbe "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 10 }} + volumeMounts: + - name: config + mountPath: /config + readOnly: true + volumes: + - name: config + configMap: + name: {{ include "stackrox-mcp.configMapName" . }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} diff --git a/charts/stackrox-mcp/templates/route.yaml b/charts/stackrox-mcp/templates/route.yaml new file mode 100644 index 0000000..2c1a008 --- /dev/null +++ b/charts/stackrox-mcp/templates/route.yaml @@ -0,0 +1,23 @@ +{{- if eq (include "stackrox-mcp.isOpenshift" .) "true" }} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ include "stackrox-mcp.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "stackrox-mcp.labels" . | nindent 4 }} +spec: + {{- with .Values.openshift.route.host }} + host: {{ . | quote }} + {{- end }} + to: + kind: Service + name: {{ include "stackrox-mcp.fullname" . }} + port: + targetPort: http + {{- if .Values.openshift.route.tls.enabled }} + tls: + termination: {{ .Values.openshift.route.tls.termination }} + insecureEdgeTerminationPolicy: {{ .Values.openshift.route.tls.insecureEdgeTerminationPolicy }} + {{- end }} +{{- end }} diff --git a/charts/stackrox-mcp/templates/secret.yaml b/charts/stackrox-mcp/templates/secret.yaml new file mode 100644 index 0000000..301d6ae --- /dev/null +++ b/charts/stackrox-mcp/templates/secret.yaml @@ -0,0 +1,12 @@ +{{- if and (eq .Values.config.central.authType "static") (not .Values.config.central.existingSecret.name) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "stackrox-mcp.fullname" . }}-api-token + namespace: {{ .Release.Namespace }} + labels: + {{- include "stackrox-mcp.labels" . | nindent 4 }} +type: Opaque +data: + api-token: {{ required "config.central.apiToken is required when authType is 'static'. Please provide the API token value." .Values.config.central.apiToken | b64enc | quote }} +{{- end }} diff --git a/charts/stackrox-mcp/templates/service-account.yaml b/charts/stackrox-mcp/templates/service-account.yaml new file mode 100644 index 0000000..9f15082 --- /dev/null +++ b/charts/stackrox-mcp/templates/service-account.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "stackrox-mcp.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "stackrox-mcp.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} diff --git a/charts/stackrox-mcp/templates/service.yaml b/charts/stackrox-mcp/templates/service.yaml new file mode 100644 index 0000000..ea92434 --- /dev/null +++ b/charts/stackrox-mcp/templates/service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "stackrox-mcp.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "stackrox-mcp.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "stackrox-mcp.selectorLabels" . | nindent 4 }} diff --git a/charts/stackrox-mcp/values.yaml b/charts/stackrox-mcp/values.yaml new file mode 100644 index 0000000..95d28a1 --- /dev/null +++ b/charts/stackrox-mcp/values.yaml @@ -0,0 +1,193 @@ +# Image configuration +image: + registry: quay.io + repository: stackrox-io/mcp + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion + tag: "" + +# Image pull secrets for private registries +imagePullSecrets: [] + +# Name overrides +nameOverride: "" +fullnameOverride: "" + +# Service account configuration +serviceAccount: + # Annotations to add to the service account + annotations: {} + # Whether to automount the service account token + # Set to false for security hardening since this app doesn't need K8s API access + automountServiceAccountToken: false + +# Number of replicas +replicaCount: 1 + +# Annotations +annotations: {} + +# Pod security context +podSecurityContext: + runAsNonRoot: true + # runAsUser: User ID to run as + # On OpenShift, this is automatically omitted to let SCC assign UID from valid range + runAsUser: 4000 + # runAsGroup: Group ID to run as + # On OpenShift, this is automatically omitted to let SCC assign GID + runAsGroup: 4000 + # fsGroup: Filesystem group ID + # On OpenShift, this is automatically omitted to let SCC assign fsGroup + fsGroup: 4000 + seccompProfile: + type: RuntimeDefault + +# Container security context +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + # runAsUser: User ID to run as + # On OpenShift, this is automatically omitted to let SCC assign UID from valid range + runAsUser: 4000 + capabilities: + drop: + - ALL + +# Service configuration +service: + type: ClusterIP + port: 8080 + targetPort: 8080 + annotations: {} + +# Resource limits and requests +resources: + limits: + memory: 500Mi + requests: + cpu: 200m + memory: 500Mi + +# Liveness probe configuration +livenessProbe: + enabled: true + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + +# Readiness probe configuration +readinessProbe: + enabled: true + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + successThreshold: 1 + failureThreshold: 3 + +# Node selector +nodeSelector: {} + +# Tolerations +tolerations: [] + +# Affinity rules +affinity: {} + +# Priority class name +priorityClassName: "" + +# OpenShift specific configuration +openshift: + route: + host: "" + tls: + enabled: true + termination: edge + insecureEdgeTerminationPolicy: Redirect + +# StackRox MCP Configuration +# Maps to environment variables with STACKROX_MCP__ prefix +config: + # Central connection configuration + central: + # URL of StackRox Central instance (required) + url: "central.stackrox:8443" + + # Authentication type: "passthrough" or "static" + # - passthrough: Use API token from MCP client request headers + # - static: Use statically configured API token + authType: "passthrough" + + # API token for static authentication + # Required only when authType is "static", must not be set when "passthrough" + # When using static auth, populate this via values override or Helm secrets + apiToken: "" + + # Alternatively, reference an existing secret for the API token + # If set, this takes precedence over apiToken + existingSecret: + name: "" + key: "api-token" + + # Skip TLS certificate verification (default: false) + # WARNING: Only use for testing in non-production environments + insecureSkipTLSVerify: false + + # Force HTTP/1 bridge via gRPC-Web/WebSockets (default: false) + # Enable only when Central is behind HTTP/1-only proxy/load balancer + forceHTTP1: false + + # Request timeout (default: 30s) + requestTimeout: "30s" + + # Maximum retry attempts (0-10, default: 3) + maxRetries: 3 + + # Initial backoff for retries (default: 1s) + initialBackoff: "1s" + + # Maximum backoff for retries (default: 10s) + maxBackoff: "10s" + + # Global MCP server configuration + global: + # Allow only read-only MCP tools (default: true) + readOnlyTools: true + + # HTTP server configuration + server: + # Server type: "streamable-http" or "stdio" + # Note: Helm deployment typically uses streamable-http + type: "streamable-http" + + # Server listen address (default: 0.0.0.0) + address: "0.0.0.0" + + # Server listen port (1-65535, default: 8080) + port: 8080 + + # MCP tools configuration + # At least one tool must be enabled + tools: + # Vulnerability management tools + vulnerability: + enabled: false + + # Configuration management tools + configManager: + enabled: false + +# Additional environment variables (advanced use) +# These will be added directly to the container +extraEnv: [] +# - name: CUSTOM_VAR +# value: "custom-value" From f803a61a3806e252a8e10d4aeb38a35d47b18a09 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Wed, 14 Jan 2026 12:20:39 +0100 Subject: [PATCH 2/5] Set tools enabled by default --- charts/stackrox-mcp/README.md | 17 ++--------------- charts/stackrox-mcp/templates/configmap.yaml | 3 +++ charts/stackrox-mcp/values.yaml | 4 ++-- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/charts/stackrox-mcp/README.md b/charts/stackrox-mcp/README.md index badc074..4189088 100644 --- a/charts/stackrox-mcp/README.md +++ b/charts/stackrox-mcp/README.md @@ -17,7 +17,6 @@ To install the chart with the release name `stackrox-mcp`: helm install stackrox-mcp charts/stackrox-mcp \ --namespace stackrox-mcp \ --create-namespace \ - --set config.tools.vulnerability.enabled=true \ --set config.central.url= ``` @@ -183,8 +182,8 @@ The following table lists the configurable parameters of the StackRox MCP chart | Parameter | Description | Default | |-----------|-------------|---------| -| `config.tools.vulnerability.enabled` | Enable vulnerability management tools | `false` | -| `config.tools.configManager.enabled` | Enable configuration management tools | `false` | +| `config.tools.vulnerability.enabled` | Enable vulnerability management tools | `true` | +| `config.tools.configManager.enabled` | Enable configuration management tools | `true` | **Note**: At least one tool must be enabled. @@ -199,9 +198,6 @@ config: central: url: "central.stackrox:8443" authType: "passthrough" - tools: - vulnerability: - enabled: true ``` ### Static Authentication with Inline Token @@ -214,9 +210,6 @@ config: url: "central.stackrox:8443" authType: "static" apiToken: "" - tools: - vulnerability: - enabled: true ``` ### Static Authentication with Existing Secret @@ -241,9 +234,6 @@ config: existingSecret: name: "stackrox-api-token" key: "api-token" - tools: - vulnerability: - enabled: true ``` ### OpenShift Deployment @@ -264,9 +254,6 @@ openshift: config: central: url: "central.stackrox:8443" - tools: - vulnerability: - enabled: true ``` **OpenShift Security Context Constraints (SCC):** diff --git a/charts/stackrox-mcp/templates/configmap.yaml b/charts/stackrox-mcp/templates/configmap.yaml index b88e72c..cb46fea 100644 --- a/charts/stackrox-mcp/templates/configmap.yaml +++ b/charts/stackrox-mcp/templates/configmap.yaml @@ -32,6 +32,9 @@ data: port: {{ .Values.config.server.port }} # MCP tools configuration + {{- if not (or .Values.config.tools.vulnerability.enabled .Values.config.tools.configManager.enabled) }} + {{ fail "At least one tool has to be enabled. Enable check 'config.tools' in helm values." }} + {{- end }} tools: vulnerability: enabled: {{ .Values.config.tools.vulnerability.enabled }} diff --git a/charts/stackrox-mcp/values.yaml b/charts/stackrox-mcp/values.yaml index 95d28a1..3bad924 100644 --- a/charts/stackrox-mcp/values.yaml +++ b/charts/stackrox-mcp/values.yaml @@ -180,11 +180,11 @@ config: tools: # Vulnerability management tools vulnerability: - enabled: false + enabled: true # Configuration management tools configManager: - enabled: false + enabled: true # Additional environment variables (advanced use) # These will be added directly to the container From 237324ad55a579df0540737f1a80962d06f65907 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Wed, 14 Jan 2026 16:09:01 +0100 Subject: [PATCH 3/5] Remove support for static auth type --- charts/stackrox-mcp/README.md | 67 +------------------ charts/stackrox-mcp/templates/NOTES.txt | 20 +----- charts/stackrox-mcp/templates/_helpers.tpl | 11 --- charts/stackrox-mcp/templates/configmap.yaml | 5 +- charts/stackrox-mcp/templates/deployment.yaml | 11 +-- charts/stackrox-mcp/templates/secret.yaml | 12 ---- charts/stackrox-mcp/values.yaml | 16 ----- 7 files changed, 5 insertions(+), 137 deletions(-) delete mode 100644 charts/stackrox-mcp/templates/secret.yaml diff --git a/charts/stackrox-mcp/README.md b/charts/stackrox-mcp/README.md index 4189088..d5edaf6 100644 --- a/charts/stackrox-mcp/README.md +++ b/charts/stackrox-mcp/README.md @@ -7,7 +7,6 @@ A Helm chart for deploying the StackRox Model Context Protocol (MCP) Server to K - Kubernetes 1.19+ or OpenShift 4.x+ - Helm 3.0+ - Access to a StackRox Central instance -- Valid StackRox API token (for static authentication mode) ## Installing the Chart @@ -153,10 +152,6 @@ The following table lists the configurable parameters of the StackRox MCP chart | Parameter | Description | Default | |-----------|-------------|---------| | `config.central.url` | StackRox Central URL | `central.stackrox:8443` | -| `config.central.authType` | Authentication type (`passthrough` or `static`) | `passthrough` | -| `config.central.apiToken` | API token for static auth (base64 encoded) | `""` | -| `config.central.existingSecret.name` | Name of existing secret with API token | `""` | -| `config.central.existingSecret.key` | Key in existing secret | `api-token` | | `config.central.insecureSkipTLSVerify` | Skip TLS verification (testing only) | `false` | | `config.central.forceHTTP1` | Force HTTP/1 bridge | `false` | | `config.central.requestTimeout` | Request timeout | `30s` | @@ -189,7 +184,7 @@ The following table lists the configurable parameters of the StackRox MCP chart ## Common Configurations -### Passthrough Authentication (Default) +### Passthrough Authentication This mode passes API tokens from MCP client request headers to StackRox Central: @@ -197,43 +192,6 @@ This mode passes API tokens from MCP client request headers to StackRox Central: config: central: url: "central.stackrox:8443" - authType: "passthrough" -``` - -### Static Authentication with Inline Token - -For static authentication with an inline token (not recommended for production): - -```yaml -config: - central: - url: "central.stackrox:8443" - authType: "static" - apiToken: "" -``` - -### Static Authentication with Existing Secret - -For production use, reference an existing Kubernetes secret: - -1. Create the secret: - -```bash -kubectl create secret generic stackrox-api-token \ - --from-literal=api-token= \ - --namespace stackrox-mcp -``` - -2. Reference it in values: - -```yaml -config: - central: - url: "central.stackrox:8443" - authType: "static" - existingSecret: - name: "stackrox-api-token" - key: "api-token" ``` ### OpenShift Deployment @@ -295,10 +253,9 @@ The StackRox MCP Helm chart uses a YAML configuration file approach for cleaner ### How It Works -1. **ConfigMap with YAML File**: The chart creates a ConfigMap containing a complete `config.yaml` file with all non-sensitive configuration +1. **ConfigMap with YAML File**: The chart creates a ConfigMap containing a complete `config.yaml` file with all configuration 2. **File Mounting**: The ConfigMap is mounted as a file at `/config/config.yaml` in the pod 3. **Command Args**: The application is started with `--config /config/config.yaml` to load the configuration -4. **Environment Variable Override**: The API token (for static auth) is injected via environment variable `STACKROX_MCP__CENTRAL__API_TOKEN`, which takes precedence over the YAML file ### Viewing Configuration @@ -323,25 +280,6 @@ This means you can override any YAML configuration value using environment varia ## Security Considerations -### Secret Management - -1. **Never commit secrets to version control**: Use external secret management solutions like: - - Kubernetes External Secrets Operator - - HashiCorp Vault - - Sealed Secrets - -2. **Use RBAC**: Limit access to the secret containing the API token: - -```bash -kubectl create role secret-reader \ - --verb=get,list \ - --resource=secrets \ - --resource-name=stackrox-api-token \ - --namespace stackrox-mcp -``` - -3. **Rotate tokens regularly**: Update the API token periodically and restart the deployment. - ### Network Policies Consider implementing NetworkPolicies to restrict traffic: @@ -395,7 +333,6 @@ kubectl logs -n stackrox-mcp deployment/stackrox-mcp ``` Common issues: -- **Missing API token**: Ensure token is set when using static authentication - **Invalid Central URL**: Verify the StackRox Central URL is correct - **Network connectivity**: Ensure the pod can reach StackRox Central on port 8443 diff --git a/charts/stackrox-mcp/templates/NOTES.txt b/charts/stackrox-mcp/templates/NOTES.txt index 20a1e23..e1efe81 100644 --- a/charts/stackrox-mcp/templates/NOTES.txt +++ b/charts/stackrox-mcp/templates/NOTES.txt @@ -12,12 +12,8 @@ StackRox MCP Server Configuration: 1. The server is configured to connect to: {{ .Values.config.central.url }} -2. Authentication mode: {{ .Values.config.central.authType }} -{{- if eq .Values.config.central.authType "static" }} - - Using static API token from Secret: {{ include "stackrox-mcp.secretName" . }} -{{- else }} +2. Authentication mode: passthrough - Using passthrough authentication (tokens from MCP client headers) -{{- end }} 3. Enabled tools: - Vulnerability Management: {{ .Values.config.tools.vulnerability.enabled | ternary "ENABLED" "DISABLED" }} @@ -33,17 +29,3 @@ StackRox MCP Server Configuration: To test connectivity: $ kubectl run -i --tty --rm debug --image=curlimages/curl --restart=Never -- \ curl http://{{ include "stackrox-mcp.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}/health - -{{- if and (eq .Values.config.central.authType "static") (not .Values.config.central.apiToken) (not .Values.config.central.existingSecret.name) }} - -WARNING: Static authentication is enabled but no API token was provided! -Please update the secret with your StackRox API token: - - $ kubectl create secret generic {{ include "stackrox-mcp.fullname" . }}-api-token \ - --from-literal=api-token= \ - --namespace {{ .Release.Namespace }} \ - --dry-run=client -o yaml | kubectl apply -f - - -Then restart the deployment: - $ kubectl rollout restart deployment/{{ include "stackrox-mcp.fullname" . }} -n {{ .Release.Namespace }} -{{- end }} diff --git a/charts/stackrox-mcp/templates/_helpers.tpl b/charts/stackrox-mcp/templates/_helpers.tpl index addf0c6..2350ee4 100644 --- a/charts/stackrox-mcp/templates/_helpers.tpl +++ b/charts/stackrox-mcp/templates/_helpers.tpl @@ -49,17 +49,6 @@ Image reference {{- printf "%s/%s:%s" .Values.image.registry .Values.image.repository $tag }} {{- end }} -{{/* -Secret name for API token -*/}} -{{- define "stackrox-mcp.secretName" -}} -{{- if .Values.config.central.existingSecret.name }} -{{- .Values.config.central.existingSecret.name }} -{{- else }} -{{- include "stackrox-mcp.fullname" . }}-api-token -{{- end }} -{{- end }} - {{/* ConfigMap name */}} diff --git a/charts/stackrox-mcp/templates/configmap.yaml b/charts/stackrox-mcp/templates/configmap.yaml index cb46fea..02927a1 100644 --- a/charts/stackrox-mcp/templates/configmap.yaml +++ b/charts/stackrox-mcp/templates/configmap.yaml @@ -10,10 +10,7 @@ data: # Central connection configuration central: url: {{ .Values.config.central.url | quote }} - auth_type: {{ .Values.config.central.authType | quote }} - {{- if eq .Values.config.central.authType "static" }} - # API token will be injected via environment variable for security - {{- end }} + auth_type: "passthrough" insecure_skip_tls_verify: {{ .Values.config.central.insecureSkipTLSVerify }} force_http1: {{ .Values.config.central.forceHTTP1 }} request_timeout: {{ .Values.config.central.requestTimeout | quote }} diff --git a/charts/stackrox-mcp/templates/deployment.yaml b/charts/stackrox-mcp/templates/deployment.yaml index 4995c34..0f85ae7 100644 --- a/charts/stackrox-mcp/templates/deployment.yaml +++ b/charts/stackrox-mcp/templates/deployment.yaml @@ -17,9 +17,8 @@ spec: template: metadata: annotations: - # Force pod restart on config/secret changes + # Force pod restart on config changes checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} - checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} {{- with .Values.annotations }} {{- toYaml . | nindent 8 }} {{- end }} @@ -70,14 +69,6 @@ spec: containerPort: {{ .Values.config.server.port }} protocol: TCP env: - {{- if eq .Values.config.central.authType "static" }} - # API token injected via environment variable (overrides YAML config) - - name: STACKROX_MCP__CENTRAL__API_TOKEN - valueFrom: - secretKeyRef: - name: {{ include "stackrox-mcp.secretName" . }} - key: {{ .Values.config.central.existingSecret.key }} - {{- end }} {{- with .Values.extraEnv }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/charts/stackrox-mcp/templates/secret.yaml b/charts/stackrox-mcp/templates/secret.yaml deleted file mode 100644 index 301d6ae..0000000 --- a/charts/stackrox-mcp/templates/secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if and (eq .Values.config.central.authType "static") (not .Values.config.central.existingSecret.name) }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "stackrox-mcp.fullname" . }}-api-token - namespace: {{ .Release.Namespace }} - labels: - {{- include "stackrox-mcp.labels" . | nindent 4 }} -type: Opaque -data: - api-token: {{ required "config.central.apiToken is required when authType is 'static'. Please provide the API token value." .Values.config.central.apiToken | b64enc | quote }} -{{- end }} diff --git a/charts/stackrox-mcp/values.yaml b/charts/stackrox-mcp/values.yaml index 3bad924..c9735f5 100644 --- a/charts/stackrox-mcp/values.yaml +++ b/charts/stackrox-mcp/values.yaml @@ -122,22 +122,6 @@ config: # URL of StackRox Central instance (required) url: "central.stackrox:8443" - # Authentication type: "passthrough" or "static" - # - passthrough: Use API token from MCP client request headers - # - static: Use statically configured API token - authType: "passthrough" - - # API token for static authentication - # Required only when authType is "static", must not be set when "passthrough" - # When using static auth, populate this via values override or Helm secrets - apiToken: "" - - # Alternatively, reference an existing secret for the API token - # If set, this takes precedence over apiToken - existingSecret: - name: "" - key: "api-token" - # Skip TLS certificate verification (default: false) # WARNING: Only use for testing in non-production environments insecureSkipTLSVerify: false From 8065831013755d015233def07c58a1efaaacf948 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Wed, 14 Jan 2026 18:11:38 +0100 Subject: [PATCH 4/5] Additional modifications --- Makefile | 4 + README.md | 76 +++++++++++++++++++ charts/stackrox-mcp/README.md | 67 +++------------- charts/stackrox-mcp/templates/NOTES.txt | 11 +-- charts/stackrox-mcp/templates/configmap.yaml | 2 +- charts/stackrox-mcp/templates/deployment.yaml | 2 +- charts/stackrox-mcp/values.yaml | 8 +- 7 files changed, 97 insertions(+), 73 deletions(-) diff --git a/Makefile b/Makefile index 816cbee..fee4c1a 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,10 @@ image: ## Build the docker image dockerfile-lint: ## Run hadolint for Dockerfile $(DOCKER_CMD) run --rm -i --env HADOLINT_FAILURE_THRESHOLD=info ghcr.io/hadolint/hadolint < Dockerfile +.PHONY: helm-lint +helm-lint: ## Run helm lint for Helm chart + helm lint charts/stackrox-mcp + .PHONY: test test: ## Run unit tests $(GOTEST) -v ./... diff --git a/README.md b/README.md index d15e26d..68a3bdb 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,82 @@ Images are automatically built and pushed on: See [.github/workflows/build.yml](.github/workflows/build.yml) for build pipeline details. +## Kubernetes Deployment + +Deploy the StackRox MCP server to Kubernetes or OpenShift clusters using Helm. + +### Prerequisites + +- Kubernetes 1.19+ or OpenShift 4.x+ +- Helm 3.0+ +- Access to a StackRox Central instance + +### Installing with Helm + +**Basic installation:** + +```bash +helm install stackrox-mcp charts/stackrox-mcp \ + --namespace stackrox-mcp \ + --create-namespace \ + --set config.central.url=central.stackrox:8443 +``` + +**With custom values file:** + +Create a `values.yaml` file: + +```yaml +config: + central: + url: "central.example.com:443" +``` + +Install with custom values: + +```bash +helm install stackrox-mcp charts/stackrox-mcp \ + --namespace stackrox-mcp \ + --create-namespace \ + --values values.yaml +``` + +**OpenShift deployment with Route:** + +```bash +helm install stackrox-mcp charts/stackrox-mcp \ + --namespace stackrox-mcp \ + --create-namespace \ + --set config.central.url=central.stackrox:8443 \ + --set openshift.route.host=stackrox-mcp.apps.example.com +``` + +### Managing the Deployment + +**Upgrade an existing release:** + +```bash +helm upgrade stackrox-mcp charts/stackrox-mcp \ + --namespace stackrox-mcp \ + --reuse-values +``` + +**Uninstall:** + +```bash +helm uninstall stackrox-mcp --namespace stackrox-mcp +``` + +### Configuration + +For complete configuration options including: +- Security contexts and pod security standards +- Resource limits and requests +- High availability setup +- OpenShift-specific configuration + +See the [Helm Chart README](charts/stackrox-mcp/README.md). + ## Development For detailed development guidelines, testing standards, and contribution workflows, see [CONTRIBUTING.md](.github/CONTRIBUTING.md). diff --git a/charts/stackrox-mcp/README.md b/charts/stackrox-mcp/README.md index d5edaf6..0c8d1e0 100644 --- a/charts/stackrox-mcp/README.md +++ b/charts/stackrox-mcp/README.md @@ -55,7 +55,7 @@ The following table lists the configurable parameters of the StackRox MCP chart | Parameter | Description | Default | |-----------|-------------|---------| -| `replicaCount` | Number of replicas | `1` | +| `replicaCount` | Number of replicas | `2` | | `annotations` | Annotations for Deployment and Pod metadata | `{}` | ### Service Account @@ -97,7 +97,7 @@ The following table lists the configurable parameters of the StackRox MCP chart |-----------|-------------|---------| | `resources.requests.cpu` | CPU request | `200m` | | `resources.requests.memory` | Memory request | `500Mi` | -| `resources.limits.cpu` | CPU limit | | +| `resources.limits.cpu` | CPU limit | *not defined* | | `resources.limits.memory` | Memory limit | `500Mi` | ### Probes @@ -121,7 +121,7 @@ The following table lists the configurable parameters of the StackRox MCP chart | `readinessProbe.successThreshold` | Success threshold for readiness probe | `1` | | `readinessProbe.failureThreshold` | Failure threshold for readiness probe | `3` | -### OpenShift Configuration +### OpenShift Route Configuration | Parameter | Description | Default | |-----------|-------------|---------| @@ -169,7 +169,6 @@ The following table lists the configurable parameters of the StackRox MCP chart | Parameter | Description | Default | |-----------|-------------|---------| -| `config.server.type` | Server type (`streamable-http` or `stdio`) | `streamable-http` | | `config.server.address` | Server listen address | `0.0.0.0` | | `config.server.port` | Server listen port | `8080` | @@ -184,9 +183,7 @@ The following table lists the configurable parameters of the StackRox MCP chart ## Common Configurations -### Passthrough Authentication - -This mode passes API tokens from MCP client request headers to StackRox Central: +### Basic configuration ```yaml config: @@ -245,6 +242,10 @@ affinity: values: - stackrox-mcp topologyKey: kubernetes.io/hostname + +config: + central: + url: "central.stackrox:8443" ``` ## Configuration Loading @@ -278,50 +279,6 @@ The application loads configuration in this order (highest to lowest precedence) This means you can override any YAML configuration value using environment variables via `extraEnv` in values.yaml. -## Security Considerations - -### Network Policies - -Consider implementing NetworkPolicies to restrict traffic: - -```yaml -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: stackrox-mcp - namespace: stackrox-mcp -spec: - podSelector: - matchLabels: - app.kubernetes.io/name: stackrox-mcp - policyTypes: - - Ingress - - Egress - ingress: - - from: - - namespaceSelector: {} - ports: - - protocol: TCP - port: 8080 - egress: - - to: - - namespaceSelector: {} - ports: - - protocol: TCP - port: 8443 # StackRox Central - - to: - - namespaceSelector: - matchLabels: - name: kube-system - ports: - - protocol: UDP - port: 53 # DNS -``` - -### Pod Security Standards - -This chart is compatible with the Kubernetes Pod Security Standards (restricted level). - ## Troubleshooting ### Deployment fails to start @@ -334,7 +291,7 @@ kubectl logs -n stackrox-mcp deployment/stackrox-mcp Common issues: - **Invalid Central URL**: Verify the StackRox Central URL is correct -- **Network connectivity**: Ensure the pod can reach StackRox Central on port 8443 +- **Network connectivity**: Ensure the pod can reach StackRox Central on defined URL and port ### Health check failures @@ -376,9 +333,3 @@ helm template test charts/stackrox-mcp --api-versions route.openshift.io/v1 | gr ``` Expected: No `runAsUser`, `runAsGroup`, or `fsGroup` should appear in the pod security context. - -## Support - -For issues and questions: -- GitHub Issues: https://github.com/stackrox/stackrox-mcp/issues -- Documentation: https://github.com/stackrox/stackrox-mcp diff --git a/charts/stackrox-mcp/templates/NOTES.txt b/charts/stackrox-mcp/templates/NOTES.txt index e1efe81..60b498a 100644 --- a/charts/stackrox-mcp/templates/NOTES.txt +++ b/charts/stackrox-mcp/templates/NOTES.txt @@ -4,22 +4,19 @@ Your release is named {{ .Release.Name }}. To learn more about the release, try: - $ helm status {{ .Release.Name }} - $ helm get all {{ .Release.Name }} + $ helm status --namespace {{ .Release.Namespace }} {{ .Release.Name }} + $ helm get all --namespace {{ .Release.Namespace }} {{ .Release.Name }} StackRox MCP Server Configuration: ----------------------------------- 1. The server is configured to connect to: {{ .Values.config.central.url }} -2. Authentication mode: passthrough - - Using passthrough authentication (tokens from MCP client headers) - -3. Enabled tools: +2. Enabled tools: - Vulnerability Management: {{ .Values.config.tools.vulnerability.enabled | ternary "ENABLED" "DISABLED" }} - Configuration Manager: {{ .Values.config.tools.configManager.enabled | ternary "ENABLED" "DISABLED" }} -4. The service is available at: +3. The service is available at: {{- if eq (include "stackrox-mcp.isOpenshift" .) "true" }} Openshift route: kubectl get route --namespace {{ .Release.Namespace }} {{ include "stackrox-mcp.fullname" . }} {{- else }} diff --git a/charts/stackrox-mcp/templates/configmap.yaml b/charts/stackrox-mcp/templates/configmap.yaml index 02927a1..e28b90b 100644 --- a/charts/stackrox-mcp/templates/configmap.yaml +++ b/charts/stackrox-mcp/templates/configmap.yaml @@ -24,7 +24,7 @@ data: # HTTP server configuration server: - type: {{ .Values.config.server.type | quote }} + type: "streamable-http" address: {{ .Values.config.server.address | quote }} port: {{ .Values.config.server.port }} diff --git a/charts/stackrox-mcp/templates/deployment.yaml b/charts/stackrox-mcp/templates/deployment.yaml index 0f85ae7..e64fea5 100644 --- a/charts/stackrox-mcp/templates/deployment.yaml +++ b/charts/stackrox-mcp/templates/deployment.yaml @@ -68,8 +68,8 @@ spec: - name: http containerPort: {{ .Values.config.server.port }} protocol: TCP - env: {{- with .Values.extraEnv }} + env: {{- toYaml . | nindent 8 }} {{- end }} {{- if .Values.livenessProbe.enabled }} diff --git a/charts/stackrox-mcp/values.yaml b/charts/stackrox-mcp/values.yaml index c9735f5..629c8b2 100644 --- a/charts/stackrox-mcp/values.yaml +++ b/charts/stackrox-mcp/values.yaml @@ -22,7 +22,7 @@ serviceAccount: automountServiceAccountToken: false # Number of replicas -replicaCount: 1 +replicaCount: 2 # Annotations annotations: {} @@ -64,6 +64,7 @@ service: # Resource limits and requests resources: limits: + # CPU limit is not defined. That follows best practices memory: 500Mi requests: cpu: 200m @@ -115,7 +116,6 @@ openshift: insecureEdgeTerminationPolicy: Redirect # StackRox MCP Configuration -# Maps to environment variables with STACKROX_MCP__ prefix config: # Central connection configuration central: @@ -149,10 +149,6 @@ config: # HTTP server configuration server: - # Server type: "streamable-http" or "stdio" - # Note: Helm deployment typically uses streamable-http - type: "streamable-http" - # Server listen address (default: 0.0.0.0) address: "0.0.0.0" From 7f29fdaad8f7922c3feadaa543904c7d20ad6778 Mon Sep 17 00:00:00 2001 From: Mladen Todorovic Date: Wed, 14 Jan 2026 19:05:56 +0100 Subject: [PATCH 5/5] Fix kube-linter warning --- charts/stackrox-mcp/templates/deployment.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/charts/stackrox-mcp/templates/deployment.yaml b/charts/stackrox-mcp/templates/deployment.yaml index e64fea5..0a6176a 100644 --- a/charts/stackrox-mcp/templates/deployment.yaml +++ b/charts/stackrox-mcp/templates/deployment.yaml @@ -94,9 +94,19 @@ spec: nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.affinity }} + {{- if .Values.affinity }} affinity: - {{- toYaml . | nindent 8 }} + {{- toYaml .Values.affinity | nindent 8 }} + {{- else }} + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchLabels: + {{- include "stackrox-mcp.selectorLabels" . | nindent 20 }} + topologyKey: kubernetes.io/hostname {{- end }} {{- with .Values.tolerations }} tolerations: