mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 06:21:23 +00:00
feat(helm): add Pipelock ConfigMap, scanning config, and consolidate compose (#1064)
* feat(helm): add Pipelock ConfigMap, scanning config, and consolidate compose - Add ConfigMap template rendering DLP, response scanning, MCP input/tool scanning, and forward proxy settings from values - Mount ConfigMap as /etc/pipelock/pipelock.yaml volume in deployment - Add checksum/config annotation for automatic pod restart on config change - Gate HTTPS_PROXY/HTTP_PROXY env injection on forwardProxy.enabled (skip in MCP-only mode) - Use hasKey for all boolean values to prevent Helm default swallowing false - Single source of truth for ports (forwardProxy.port/mcpProxy.port) - Pipelock-specific imagePullSecrets with fallback to app secrets - Merge standalone compose.example.pipelock.yml into compose.example.ai.yml - Add pipelock.example.yaml for Docker Compose users - Add exclude-paths to CI workflow for locale file false positives * Add CHANGELOG entry for Pipelock security proxy integration * Missed v0.6.8 release --------- Co-authored-by: Juan José Mata <jjmata@jjmata.com>
This commit is contained in:
4
.github/workflows/pipelock.yml
vendored
4
.github/workflows/pipelock.yml
vendored
@@ -20,5 +20,7 @@ jobs:
|
||||
uses: luckyPipewrench/pipelock@v1
|
||||
with:
|
||||
scan-diff: 'true'
|
||||
fail-on-findings: 'false'
|
||||
fail-on-findings: 'true'
|
||||
test-vectors: 'false'
|
||||
exclude-paths: |
|
||||
config/locales/views/reports/
|
||||
|
||||
@@ -5,22 +5,25 @@ All notable changes to the Sure Helm chart will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
### [0.0.0], [0.6.5]
|
||||
## [0.6.9-alpha] - 2026-03-01
|
||||
|
||||
### Added
|
||||
- **Pipelock security proxy** (`pipelock.enabled=true`): Separate Deployment + Service that provides two scanning layers
|
||||
- **Forward proxy** (port 8888): Scans outbound HTTPS from Faraday-based clients (e.g. ruby-openai). Auto-injects `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` env vars into app pods
|
||||
- **MCP reverse proxy** (port 8889): Scans inbound MCP traffic for DLP, prompt injection, and tool poisoning. Auto-computes upstream URL via `sure.pipelockUpstream` helper
|
||||
- **WebSocket proxy** configuration support (disabled by default, requires Pipelock >= 0.2.9)
|
||||
- ConfigMap with scanning config (DLP, prompt injection detection, MCP input/tool scanning, response scanning)
|
||||
- ConfigMap checksum annotation for automatic pod restart on config changes
|
||||
- Helm helpers: `sure.pipelockImage`, `sure.pipelockUpstream`
|
||||
- Health and readiness probes on the Pipelock deployment
|
||||
- `imagePullSecrets` with fallback to app-level secrets
|
||||
- Boolean safety: uses `hasKey` to prevent Helm's `default` from swallowing explicit `false`
|
||||
- Configurable ports via `forwardProxy.port` and `mcpProxy.port` (single source of truth across Service, Deployment, and env vars)
|
||||
- `pipelock.example.yaml` reference config for Docker Compose deployments
|
||||
|
||||
- First (nightly/test) releases via <https://we-promise.github.io/sure/index.yaml>
|
||||
|
||||
### [0.6.6] - 2025-12-31
|
||||
|
||||
### Added
|
||||
|
||||
- First version/release that aligns versions with monorepo
|
||||
- CNPG: render `Cluster.spec.backup` from `cnpg.cluster.backup`.
|
||||
- If `backup.method` is omitted and `backup.volumeSnapshot` is present, the chart will infer `method: volumeSnapshot`.
|
||||
- For snapshot backups, `backup.volumeSnapshot.className` is required (template fails early if missing).
|
||||
- Example-only keys like `backup.ttl` and `backup.volumeSnapshot.enabled` are stripped to avoid CRD warnings.
|
||||
- CNPG: render `Cluster.spec.plugins` from `cnpg.cluster.plugins` (enables barman-cloud plugin / WAL archiver configuration).
|
||||
### Changed
|
||||
- Consolidated `compose.example.pipelock.yml` into `compose.example.ai.yml` — Pipelock now runs alongside Ollama in one compose file with health checks, config volume mount, and MCP env vars (`MCP_API_TOKEN`, `MCP_USER_EMAIL`)
|
||||
- CI: Pipelock scan `fail-on-findings` changed from `false` to `true`; added `exclude-paths` for locale help text false positives
|
||||
|
||||
## [0.6.7-alpha] - 2026-01-10
|
||||
|
||||
@@ -33,6 +36,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Production-ready HA timeouts: 200ms connect, 1s read/write, 3 reconnection attempts
|
||||
- Backward compatible with existing `REDIS_URL` deployments
|
||||
|
||||
### [0.6.6] - 2025-12-31
|
||||
|
||||
### Added
|
||||
|
||||
- First version/release that aligns versions with monorepo
|
||||
- CNPG: render `Cluster.spec.backup` from `cnpg.cluster.backup`.
|
||||
- If `backup.method` is omitted and `backup.volumeSnapshot` is present, the chart will infer `method: volumeSnapshot`.
|
||||
- For snapshot backups, `backup.volumeSnapshot.className` is required (template fails early if missing).
|
||||
- Example-only keys like `backup.ttl` and `backup.volumeSnapshot.enabled` are stripped to avoid CRD warnings.
|
||||
- CNPG: render `Cluster.spec.plugins` from `cnpg.cluster.plugins` (enables barman-cloud plugin / WAL archiver configuration).
|
||||
|
||||
### [0.0.0], [0.6.5]
|
||||
|
||||
### Added
|
||||
|
||||
- First (nightly/test) releases via <https://we-promise.github.io/sure/index.yaml>
|
||||
|
||||
## Notes
|
||||
- Chart version and application version are kept in sync
|
||||
- Requires Kubernetes >= 1.25.0
|
||||
|
||||
@@ -11,6 +11,7 @@ The helper always injects:
|
||||
- optional Active Record Encryption keys (controlled by rails.encryptionEnv.enabled)
|
||||
- optional DATABASE_URL + DB_PASSWORD (includeDatabase=true and helper can compute a DB URL)
|
||||
- optional REDIS_URL + REDIS_PASSWORD (includeRedis=true and helper can compute a Redis URL)
|
||||
- optional HTTPS_PROXY / HTTP_PROXY / NO_PROXY (pipelock.enabled=true)
|
||||
- rails.settings / rails.extraEnv / rails.extraEnvVars
|
||||
- optional additional per-workload env / envFrom blocks via extraEnv / extraEnvFrom.
|
||||
*/}}
|
||||
@@ -77,6 +78,18 @@ The helper always injects:
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and $ctx.Values.pipelock.enabled (ne (toString (dig "forwardProxy" "enabled" true $ctx.Values.pipelock)) "false") }}
|
||||
{{- $proxyPort := 8888 -}}
|
||||
{{- if $ctx.Values.pipelock.forwardProxy -}}
|
||||
{{- $proxyPort = int ($ctx.Values.pipelock.forwardProxy.port | default 8888) -}}
|
||||
{{- end }}
|
||||
- name: HTTPS_PROXY
|
||||
value: {{ printf "http://%s-pipelock.%s.svc.cluster.local:%d" (include "sure.fullname" $ctx) $ctx.Release.Namespace $proxyPort | quote }}
|
||||
- name: HTTP_PROXY
|
||||
value: {{ printf "http://%s-pipelock.%s.svc.cluster.local:%d" (include "sure.fullname" $ctx) $ctx.Release.Namespace $proxyPort | quote }}
|
||||
- name: NO_PROXY
|
||||
value: "localhost,127.0.0.1,.svc.cluster.local,.cluster.local"
|
||||
{{- end }}
|
||||
{{- range $k, $v := $ctx.Values.rails.settings }}
|
||||
- name: {{ $k }}
|
||||
value: {{ $v | quote }}
|
||||
|
||||
@@ -157,3 +157,27 @@ true
|
||||
{{- default "redis-password" .Values.redis.passwordKey -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Pipelock image string */}}
|
||||
{{- define "sure.pipelockImage" -}}
|
||||
{{- $repo := "ghcr.io/luckypipewrench/pipelock" -}}
|
||||
{{- $tag := "latest" -}}
|
||||
{{- if .Values.pipelock.image -}}
|
||||
{{- $repo = .Values.pipelock.image.repository | default $repo -}}
|
||||
{{- $tag = .Values.pipelock.image.tag | default $tag -}}
|
||||
{{- end -}}
|
||||
{{- printf "%s:%s" $repo $tag -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Pipelock MCP upstream URL (auto-compute or explicit override) */}}
|
||||
{{- define "sure.pipelockUpstream" -}}
|
||||
{{- $upstream := "" -}}
|
||||
{{- if .Values.pipelock.mcpProxy -}}
|
||||
{{- $upstream = .Values.pipelock.mcpProxy.upstream | default "" -}}
|
||||
{{- end -}}
|
||||
{{- if $upstream -}}
|
||||
{{- $upstream -}}
|
||||
{{- else -}}
|
||||
{{- printf "http://%s:%d/mcp" (include "sure.fullname" .) (int (.Values.service.port | default 80)) -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
76
charts/sure/templates/pipelock-configmap.yaml
Normal file
76
charts/sure/templates/pipelock-configmap.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
{{- if .Values.pipelock.enabled }}
|
||||
{{- $fwdEnabled := true -}}
|
||||
{{- $fwdMaxTunnel := 300 -}}
|
||||
{{- $fwdIdleTimeout := 60 -}}
|
||||
{{- if .Values.pipelock.forwardProxy -}}
|
||||
{{- if hasKey .Values.pipelock.forwardProxy "enabled" -}}
|
||||
{{- $fwdEnabled = .Values.pipelock.forwardProxy.enabled -}}
|
||||
{{- end -}}
|
||||
{{- $fwdMaxTunnel = int (.Values.pipelock.forwardProxy.maxTunnelSeconds | default 300) -}}
|
||||
{{- $fwdIdleTimeout = int (.Values.pipelock.forwardProxy.idleTimeoutSeconds | default 60) -}}
|
||||
{{- end -}}
|
||||
{{- $wsEnabled := false -}}
|
||||
{{- $wsMaxMsg := 1048576 -}}
|
||||
{{- $wsMaxConns := 128 -}}
|
||||
{{- $wsScanText := true -}}
|
||||
{{- $wsAllowBinary := false -}}
|
||||
{{- $wsForwardCookies := false -}}
|
||||
{{- $wsMaxConnSec := 3600 -}}
|
||||
{{- $wsIdleTimeout := 300 -}}
|
||||
{{- $wsOriginPolicy := "rewrite" -}}
|
||||
{{- if .Values.pipelock.websocketProxy -}}
|
||||
{{- if hasKey .Values.pipelock.websocketProxy "enabled" -}}
|
||||
{{- $wsEnabled = .Values.pipelock.websocketProxy.enabled -}}
|
||||
{{- end -}}
|
||||
{{- $wsMaxMsg = int (.Values.pipelock.websocketProxy.maxMessageBytes | default 1048576) -}}
|
||||
{{- $wsMaxConns = int (.Values.pipelock.websocketProxy.maxConcurrentConnections | default 128) -}}
|
||||
{{- if hasKey .Values.pipelock.websocketProxy "scanTextFrames" -}}
|
||||
{{- $wsScanText = .Values.pipelock.websocketProxy.scanTextFrames -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey .Values.pipelock.websocketProxy "allowBinaryFrames" -}}
|
||||
{{- $wsAllowBinary = .Values.pipelock.websocketProxy.allowBinaryFrames -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey .Values.pipelock.websocketProxy "forwardCookies" -}}
|
||||
{{- $wsForwardCookies = .Values.pipelock.websocketProxy.forwardCookies -}}
|
||||
{{- end -}}
|
||||
{{- $wsMaxConnSec = int (.Values.pipelock.websocketProxy.maxConnectionSeconds | default 3600) -}}
|
||||
{{- $wsIdleTimeout = int (.Values.pipelock.websocketProxy.idleTimeoutSeconds | default 300) -}}
|
||||
{{- $wsOriginPolicy = .Values.pipelock.websocketProxy.originPolicy | default "rewrite" -}}
|
||||
{{- end }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "sure.fullname" . }}-pipelock
|
||||
labels:
|
||||
{{- include "sure.labels" . | nindent 4 }}
|
||||
data:
|
||||
pipelock.yaml: |
|
||||
forward_proxy:
|
||||
enabled: {{ $fwdEnabled }}
|
||||
max_tunnel_seconds: {{ $fwdMaxTunnel }}
|
||||
idle_timeout_seconds: {{ $fwdIdleTimeout }}
|
||||
websocket_proxy:
|
||||
enabled: {{ $wsEnabled }}
|
||||
max_message_bytes: {{ $wsMaxMsg }}
|
||||
max_concurrent_connections: {{ $wsMaxConns }}
|
||||
scan_text_frames: {{ $wsScanText }}
|
||||
allow_binary_frames: {{ $wsAllowBinary }}
|
||||
forward_cookies: {{ $wsForwardCookies }}
|
||||
strip_compression: true
|
||||
max_connection_seconds: {{ $wsMaxConnSec }}
|
||||
idle_timeout_seconds: {{ $wsIdleTimeout }}
|
||||
origin_policy: {{ $wsOriginPolicy }}
|
||||
dlp:
|
||||
scan_env: true
|
||||
response_scanning:
|
||||
enabled: true
|
||||
action: warn
|
||||
mcp_input_scanning:
|
||||
enabled: true
|
||||
action: block
|
||||
on_parse_error: block
|
||||
mcp_tool_scanning:
|
||||
enabled: true
|
||||
action: warn
|
||||
detect_drift: true
|
||||
{{- end }}
|
||||
101
charts/sure/templates/pipelock-deployment.yaml
Normal file
101
charts/sure/templates/pipelock-deployment.yaml
Normal file
@@ -0,0 +1,101 @@
|
||||
{{- if .Values.pipelock.enabled }}
|
||||
{{- $fwdPort := 8888 -}}
|
||||
{{- $mcpPort := 8889 -}}
|
||||
{{- $pullPolicy := "IfNotPresent" -}}
|
||||
{{- if .Values.pipelock.forwardProxy -}}
|
||||
{{- $fwdPort = int (.Values.pipelock.forwardProxy.port | default 8888) -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.pipelock.mcpProxy -}}
|
||||
{{- $mcpPort = int (.Values.pipelock.mcpProxy.port | default 8889) -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.pipelock.image -}}
|
||||
{{- $pullPolicy = .Values.pipelock.image.pullPolicy | default "IfNotPresent" -}}
|
||||
{{- end }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "sure.fullname" . }}-pipelock
|
||||
labels:
|
||||
{{- include "sure.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.pipelock.replicas | default 1 }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: pipelock
|
||||
{{- include "sure.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: pipelock
|
||||
{{- include "sure.selectorLabels" . | nindent 8 }}
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/pipelock-configmap.yaml") . | sha256sum }}
|
||||
{{- with .Values.pipelock.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
{{- $plSecrets := coalesce .Values.pipelock.image.imagePullSecrets .Values.image.imagePullSecrets }}
|
||||
{{- if $plSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml $plSecrets | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: {{ include "sure.fullname" . }}-pipelock
|
||||
containers:
|
||||
- name: pipelock
|
||||
image: {{ include "sure.pipelockImage" . }}
|
||||
imagePullPolicy: {{ $pullPolicy }}
|
||||
args:
|
||||
- "run"
|
||||
- "--config"
|
||||
- "/etc/pipelock/pipelock.yaml"
|
||||
- "--listen"
|
||||
- "0.0.0.0:{{ $fwdPort }}"
|
||||
- "--mode"
|
||||
- {{ .Values.pipelock.mode | default "balanced" | quote }}
|
||||
- "--mcp-listen"
|
||||
- "0.0.0.0:{{ $mcpPort }}"
|
||||
- "--mcp-upstream"
|
||||
- {{ include "sure.pipelockUpstream" . | quote }}
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/pipelock
|
||||
readOnly: true
|
||||
ports:
|
||||
- name: proxy
|
||||
containerPort: {{ $fwdPort }}
|
||||
protocol: TCP
|
||||
- name: mcp
|
||||
containerPort: {{ $mcpPort }}
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: proxy
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: proxy
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 3
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
resources:
|
||||
{{- toYaml (.Values.pipelock.resources | default dict) | nindent 12 }}
|
||||
nodeSelector:
|
||||
{{- toYaml (.Values.pipelock.nodeSelector | default dict) | nindent 8 }}
|
||||
affinity:
|
||||
{{- toYaml (.Values.pipelock.affinity | default dict) | nindent 8 }}
|
||||
tolerations:
|
||||
{{- toYaml (.Values.pipelock.tolerations | default list) | nindent 8 }}
|
||||
{{- end }}
|
||||
30
charts/sure/templates/pipelock-service.yaml
Normal file
30
charts/sure/templates/pipelock-service.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
{{- if .Values.pipelock.enabled }}
|
||||
{{- $fwdPort := 8888 -}}
|
||||
{{- $mcpPort := 8889 -}}
|
||||
{{- if .Values.pipelock.forwardProxy -}}
|
||||
{{- $fwdPort = int (.Values.pipelock.forwardProxy.port | default 8888) -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.pipelock.mcpProxy -}}
|
||||
{{- $mcpPort = int (.Values.pipelock.mcpProxy.port | default 8889) -}}
|
||||
{{- end }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "sure.fullname" . }}-pipelock
|
||||
labels:
|
||||
{{- include "sure.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ (.Values.pipelock.service).type | default "ClusterIP" }}
|
||||
selector:
|
||||
app.kubernetes.io/component: pipelock
|
||||
{{- include "sure.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- name: proxy
|
||||
port: {{ $fwdPort }}
|
||||
targetPort: proxy
|
||||
protocol: TCP
|
||||
- name: mcp
|
||||
port: {{ $mcpPort }}
|
||||
targetPort: mcp
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
@@ -465,3 +465,53 @@ hpa:
|
||||
minReplicas: 2
|
||||
maxReplicas: 10
|
||||
targetCPUUtilizationPercentage: 70
|
||||
|
||||
# Pipelock: AI agent security proxy (optional)
|
||||
# Provides forward proxy (outbound HTTPS scanning) and MCP reverse proxy
|
||||
# (inbound MCP traffic scanning for prompt injection, DLP, tool poisoning).
|
||||
# More info: https://github.com/luckyPipewrench/pipelock
|
||||
pipelock:
|
||||
enabled: false
|
||||
image:
|
||||
repository: ghcr.io/luckypipewrench/pipelock
|
||||
tag: "0.2.7"
|
||||
pullPolicy: IfNotPresent
|
||||
imagePullSecrets: []
|
||||
replicas: 1
|
||||
# Pipelock run mode: strict, balanced, audit
|
||||
mode: balanced
|
||||
forwardProxy:
|
||||
enabled: true
|
||||
port: 8888
|
||||
maxTunnelSeconds: 300
|
||||
idleTimeoutSeconds: 60
|
||||
mcpProxy:
|
||||
port: 8889
|
||||
# Auto-computed when empty: http://<fullname>:<service.port>/mcp
|
||||
upstream: ""
|
||||
# WebSocket proxy: bidirectional frame scanning for ws/wss connections.
|
||||
# Runs on the same listener as the forward proxy at /ws?url=<ws-url>.
|
||||
# Requires Pipelock >= 0.2.9 (or current dev build).
|
||||
websocketProxy:
|
||||
# Requires image.tag >= 0.2.9. Update pipelock.image.tag before enabling.
|
||||
enabled: false
|
||||
maxMessageBytes: 1048576 # 1MB per message
|
||||
maxConcurrentConnections: 128
|
||||
scanTextFrames: true # DLP + injection scanning on text frames
|
||||
allowBinaryFrames: false # block binary frames by default
|
||||
forwardCookies: false
|
||||
maxConnectionSeconds: 3600 # 1 hour max connection lifetime
|
||||
idleTimeoutSeconds: 300 # 5 min idle timeout
|
||||
originPolicy: rewrite # rewrite, forward, or strip
|
||||
service:
|
||||
type: ClusterIP
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 128Mi
|
||||
podAnnotations: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
# ===========================================================================
|
||||
# Example Docker Compose file with additional Ollama service for LLM tools
|
||||
# Example Docker Compose file with Ollama (local LLM) and Pipelock (agent
|
||||
# security proxy)
|
||||
# ===========================================================================
|
||||
#
|
||||
# Purpose:
|
||||
# --------
|
||||
#
|
||||
# This file is an example Docker Compose configuration for self hosting
|
||||
# Sure with Ollama on your local machine or on a cloud VPS.
|
||||
# This file extends the standard Sure setup with two optional capabilities:
|
||||
#
|
||||
# The configuration below is a "standard" setup that works out of the box,
|
||||
# but if you're running this outside of a local network, it is recommended
|
||||
# to set the environment variables for extra security.
|
||||
# Pipelock — agent security proxy (always runs)
|
||||
# - Forward proxy (port 8888): scans outbound HTTPS from Faraday-based
|
||||
# clients (e.g. ruby-openai). NOT covered: SimpleFin, Coinbase, or
|
||||
# anything using Net::HTTP/HTTParty directly. HTTPS_PROXY is
|
||||
# cooperative; Docker Compose has no egress network policy.
|
||||
# - MCP reverse proxy (port 8889): scans inbound AI traffic (DLP,
|
||||
# prompt injection, tool poisoning, tool call policy). External AI
|
||||
# clients should connect to Pipelock on port 8889 rather than
|
||||
# directly to Sure's /mcp endpoint. Note: /mcp is still reachable
|
||||
# on web port 3000 (auth token required); Pipelock adds scanning
|
||||
# but Docker Compose cannot enforce network-level routing.
|
||||
#
|
||||
# Ollama + Open WebUI — local LLM inference (optional, --profile ai)
|
||||
# - Only starts when you run: docker compose --profile ai up
|
||||
#
|
||||
# Setup:
|
||||
# ------
|
||||
#
|
||||
# To run this, you should read the setup guide:
|
||||
# 1. Copy pipelock.example.yaml alongside this file (or customize it).
|
||||
# 2. Read the full setup guide:
|
||||
#
|
||||
# https://github.com/we-promise/sure/blob/main/docs/hosting/docker.md
|
||||
#
|
||||
@@ -41,6 +53,17 @@ x-rails-env: &rails_env
|
||||
DB_HOST: db
|
||||
DB_PORT: 5432
|
||||
REDIS_URL: redis://redis:6379/1
|
||||
# MCP server endpoint — enables /mcp for external AI assistants (e.g. Claude, GPT).
|
||||
# Set both values to activate. MCP_USER_EMAIL must match an existing user's email.
|
||||
# External AI clients should connect via Pipelock (port 8889) for scanning.
|
||||
MCP_API_TOKEN: ${MCP_API_TOKEN:-}
|
||||
MCP_USER_EMAIL: ${MCP_USER_EMAIL:-}
|
||||
# Route outbound HTTPS through Pipelock for clients that respect HTTPS_PROXY.
|
||||
# Covered: OpenAI API (ruby-openai/Faraday). NOT covered: SimpleFin, Coinbase (Net::HTTP).
|
||||
HTTPS_PROXY: "http://pipelock:8888"
|
||||
HTTP_PROXY: "http://pipelock:8888"
|
||||
# Skip proxy for internal Docker network services (including ollama for local LLM calls)
|
||||
NO_PROXY: "db,redis,pipelock,ollama,localhost,127.0.0.1"
|
||||
AI_DEBUG_MODE: "true" # Useful for debugging, set to "false" in production
|
||||
# Ollama using OpenAI API compatible endpoints
|
||||
OPENAI_ACCESS_TOKEN: token-can-be-any-value-for-ollama
|
||||
@@ -50,6 +73,39 @@ x-rails-env: &rails_env
|
||||
# OPENAI_ACCESS_TOKEN: ${OPENAI_ACCESS_TOKEN}
|
||||
|
||||
services:
|
||||
pipelock:
|
||||
image: ghcr.io/luckypipewrench/pipelock:latest # pin to a specific version (e.g., :0.2.7) for production
|
||||
container_name: pipelock
|
||||
hostname: pipelock
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./pipelock.example.yaml:/etc/pipelock/pipelock.yaml:ro
|
||||
command:
|
||||
- "run"
|
||||
- "--config"
|
||||
- "/etc/pipelock/pipelock.yaml"
|
||||
- "--listen"
|
||||
- "0.0.0.0:8888"
|
||||
- "--mode"
|
||||
- "balanced"
|
||||
- "--mcp-listen"
|
||||
- "0.0.0.0:8889"
|
||||
- "--mcp-upstream"
|
||||
- "http://web:3000/mcp"
|
||||
ports:
|
||||
# MCP reverse proxy — external AI assistants connect here
|
||||
- "${MCP_PROXY_PORT:-8889}:8889"
|
||||
# Uncomment to expose forward proxy endpoints (/health, /metrics, /stats):
|
||||
# - "8888:8888"
|
||||
healthcheck:
|
||||
test: ["CMD", "/pipelock", "healthcheck", "--addr", "127.0.0.1:8888"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
networks:
|
||||
- sure_net
|
||||
|
||||
# Note: You still have to download models manually using the ollama CLI or via Open WebUI
|
||||
ollama:
|
||||
profiles:
|
||||
@@ -106,6 +162,10 @@ services:
|
||||
volumes:
|
||||
- app-storage:/rails/storage
|
||||
ports:
|
||||
# Web UI for browser access. Note: /mcp is also reachable on this port,
|
||||
# bypassing Pipelock's MCP scanning (auth token is still required).
|
||||
# For hardened deployments, use `expose: [3000]` instead and front
|
||||
# the web UI with a separate reverse proxy.
|
||||
- ${PORT:-3000}:3000
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@@ -115,6 +175,8 @@ services:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
pipelock: # Remove this block and unset HTTPS_PROXY/HTTP_PROXY to run without Pipelock
|
||||
condition: service_healthy
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
@@ -132,6 +194,8 @@ services:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
pipelock: # Remove this block and unset HTTPS_PROXY/HTTP_PROXY to run without Pipelock
|
||||
condition: service_healthy
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
# ===========================================================================
|
||||
# Example Docker Compose file with Pipelock agent security proxy
|
||||
# ===========================================================================
|
||||
#
|
||||
# Purpose:
|
||||
# --------
|
||||
#
|
||||
# This file adds Pipelock (https://github.com/luckyPipewrench/pipelock)
|
||||
# as a security proxy for Sure, providing two layers of protection:
|
||||
#
|
||||
# 1. Forward proxy (port 8888) — routes outbound HTTPS through Pipelock
|
||||
# for clients that respect the HTTPS_PROXY environment variable.
|
||||
#
|
||||
# 2. MCP reverse proxy (port 8889) — scans inbound MCP traffic from
|
||||
# external AI assistants bidirectionally (DLP, prompt injection,
|
||||
# tool poisoning, tool call policy).
|
||||
#
|
||||
# Forward proxy coverage:
|
||||
# -----------------------
|
||||
#
|
||||
# Covered (Faraday-based clients respect HTTPS_PROXY automatically):
|
||||
# - OpenAI API calls (ruby-openai gem)
|
||||
# - Market data providers using Faraday
|
||||
#
|
||||
# NOT covered (these clients ignore HTTPS_PROXY):
|
||||
# - SimpleFin (HTTParty / Net::HTTP)
|
||||
# - Coinbase (HTTParty / Net::HTTP)
|
||||
# - Any code using Net::HTTP or HTTParty directly
|
||||
#
|
||||
# For covered traffic, Pipelock provides:
|
||||
# - Domain allowlisting (only known-good external APIs can be reached)
|
||||
# - SSRF protection (blocks connections to private/internal IPs)
|
||||
# - DLP scanning on connection targets (detects exfiltration patterns)
|
||||
# - Rate limiting per domain
|
||||
# - Structured JSON audit logging of all outbound connections
|
||||
#
|
||||
# MCP reverse proxy coverage:
|
||||
# ---------------------------
|
||||
#
|
||||
# External AI assistants connect to Pipelock on port 8889 instead of
|
||||
# directly to Sure's /mcp endpoint. Pipelock scans all traffic:
|
||||
#
|
||||
# Request scanning (client → Sure):
|
||||
# - DLP detection (blocks credential/secret leakage in tool arguments)
|
||||
# - Prompt injection detection in tool call parameters
|
||||
# - Tool call policy enforcement (blocks dangerous operations)
|
||||
#
|
||||
# Response scanning (Sure → client):
|
||||
# - Prompt injection detection in tool response content
|
||||
# - Tool poisoning / drift detection (tool definitions changing)
|
||||
#
|
||||
# The MCP endpoint on Sure (port 3000/mcp) should NOT be exposed directly
|
||||
# to the internet. Route all external MCP traffic through Pipelock.
|
||||
#
|
||||
# Limitations:
|
||||
# ------------
|
||||
#
|
||||
# HTTPS_PROXY is cooperative. Docker Compose has no egress network policy,
|
||||
# so any code path that doesn't check the env var can connect directly.
|
||||
# For hard enforcement, deploy with network-level controls that deny all
|
||||
# egress except through the proxy. Example for Kubernetes:
|
||||
#
|
||||
# # NetworkPolicy: deny all egress, allow only proxy + DNS
|
||||
# egress:
|
||||
# - to:
|
||||
# - podSelector:
|
||||
# matchLabels:
|
||||
# app: pipelock
|
||||
# ports:
|
||||
# - port: 8888
|
||||
# - ports:
|
||||
# - port: 53
|
||||
# protocol: UDP
|
||||
#
|
||||
# Monitoring:
|
||||
# -----------
|
||||
#
|
||||
# Pipelock logs every connection and MCP request as structured JSON to stdout.
|
||||
# View logs with: docker compose logs pipelock
|
||||
#
|
||||
# Forward proxy endpoints (port 8888):
|
||||
# http://localhost:8888/health - liveness check
|
||||
# http://localhost:8888/metrics - Prometheus metrics
|
||||
# http://localhost:8888/stats - JSON summary
|
||||
#
|
||||
# More info: https://github.com/luckyPipewrench/pipelock
|
||||
#
|
||||
# Setup:
|
||||
# ------
|
||||
#
|
||||
# 1. Copy this file to compose.yml (or use -f flag)
|
||||
# 2. Set your environment variables (OPENAI_ACCESS_TOKEN, MCP_API_TOKEN, etc.)
|
||||
# 3. docker compose up
|
||||
#
|
||||
# Pipelock runs both proxies in a single container:
|
||||
# - Port 8888: forward proxy for outbound HTTPS (internal only)
|
||||
# - Port 8889: MCP reverse proxy for external AI assistants
|
||||
#
|
||||
# External AI clients connect to http://<host>:8889 as their MCP endpoint.
|
||||
# Pipelock scans the traffic and forwards clean requests to Sure's /mcp.
|
||||
#
|
||||
# Customization:
|
||||
# --------------
|
||||
#
|
||||
# Requires Pipelock with MCP HTTP listener support (--mcp-listen flag).
|
||||
# See: https://github.com/luckyPipewrench/pipelock/releases
|
||||
#
|
||||
# Edit the pipelock command to change the mode:
|
||||
# --mode strict Block unknown domains (recommended for production)
|
||||
# --mode balanced Warn on unknown domains, block known-bad (default)
|
||||
# --mode audit Log everything, block nothing (for evaluation)
|
||||
#
|
||||
# For a custom config, mount a file and use --config instead of --mode:
|
||||
# volumes:
|
||||
# - ./config/pipelock.yml:/etc/pipelock/config.yml:ro
|
||||
# command: ["run", "--config", "/etc/pipelock/config.yml",
|
||||
# "--mcp-listen", "0.0.0.0:8889", "--mcp-upstream", "http://web:3000/mcp"]
|
||||
#
|
||||
|
||||
x-db-env: &db_env
|
||||
POSTGRES_USER: ${POSTGRES_USER:-sure_user}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-sure_password}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-sure_production}
|
||||
|
||||
x-rails-env: &rails_env
|
||||
<<: *db_env
|
||||
SECRET_KEY_BASE: ${SECRET_KEY_BASE:-a7523c3d0ae56415046ad8abae168d71074a79534a7062258f8d1d51ac2f76d3c3bc86d86b6b0b307df30d9a6a90a2066a3fa9e67c5e6f374dbd7dd4e0778e13}
|
||||
SELF_HOSTED: "true"
|
||||
RAILS_FORCE_SSL: "false"
|
||||
RAILS_ASSUME_SSL: "false"
|
||||
DB_HOST: db
|
||||
DB_PORT: 5432
|
||||
REDIS_URL: redis://redis:6379/1
|
||||
# NOTE: enabling OpenAI will incur costs when you use AI-related features in the app (chat, rules). Make sure you have set appropriate spend limits on your account before adding this.
|
||||
OPENAI_ACCESS_TOKEN: ${OPENAI_ACCESS_TOKEN}
|
||||
# MCP server endpoint — enables /mcp for external AI assistants (e.g. Claude, GPT).
|
||||
# Set both values to activate. MCP_USER_EMAIL must match an existing user's email.
|
||||
# External AI clients connect via Pipelock (port 8889), not directly to /mcp.
|
||||
MCP_API_TOKEN: ${MCP_API_TOKEN:-}
|
||||
MCP_USER_EMAIL: ${MCP_USER_EMAIL:-}
|
||||
# Route outbound HTTPS through Pipelock for clients that respect HTTPS_PROXY.
|
||||
# See "Forward proxy coverage" section above for which clients are covered.
|
||||
HTTPS_PROXY: "http://pipelock:8888"
|
||||
HTTP_PROXY: "http://pipelock:8888"
|
||||
# Skip proxy for internal Docker network services
|
||||
NO_PROXY: "db,redis,pipelock,localhost,127.0.0.1"
|
||||
|
||||
services:
|
||||
pipelock:
|
||||
image: ghcr.io/luckypipewrench/pipelock:latest
|
||||
container_name: pipelock
|
||||
hostname: pipelock
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- "run"
|
||||
- "--listen"
|
||||
- "0.0.0.0:8888"
|
||||
- "--mode"
|
||||
- "balanced"
|
||||
- "--mcp-listen"
|
||||
- "0.0.0.0:8889"
|
||||
- "--mcp-upstream"
|
||||
- "http://web:3000/mcp"
|
||||
ports:
|
||||
# MCP reverse proxy — external AI assistants connect here
|
||||
- "${MCP_PROXY_PORT:-8889}:8889"
|
||||
# Uncomment to expose forward proxy endpoints (/health, /metrics, /stats):
|
||||
# - "8888:8888"
|
||||
healthcheck:
|
||||
test: ["CMD", "/pipelock", "healthcheck", "--addr", "127.0.0.1:8888"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
networks:
|
||||
- sure_net
|
||||
|
||||
web:
|
||||
image: ghcr.io/we-promise/sure:stable
|
||||
volumes:
|
||||
- app-storage:/rails/storage
|
||||
ports:
|
||||
# Web UI for browser access. Note: /mcp is also reachable on this port,
|
||||
# bypassing Pipelock's MCP scanning (auth token is still required).
|
||||
# For hardened deployments, use `expose: [3000]` instead and front
|
||||
# the web UI with a separate reverse proxy.
|
||||
- ${PORT:-3000}:3000
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
<<: *rails_env
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
pipelock:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- sure_net
|
||||
|
||||
worker:
|
||||
image: ghcr.io/we-promise/sure:stable
|
||||
command: bundle exec sidekiq
|
||||
volumes:
|
||||
- app-storage:/rails/storage
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
pipelock:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
<<: *rails_env
|
||||
networks:
|
||||
- sure_net
|
||||
|
||||
db:
|
||||
image: postgres:16
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
<<: *db_env
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" ]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- sure_net
|
||||
|
||||
backup:
|
||||
profiles:
|
||||
- backup
|
||||
image: prodrigestivill/postgres-backup-local
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /opt/sure-data/backups:/backups # Change this path to your desired backup location on the host machine
|
||||
environment:
|
||||
- POSTGRES_HOST=db
|
||||
- POSTGRES_DB=${POSTGRES_DB:-sure_production}
|
||||
- POSTGRES_USER=${POSTGRES_USER:-sure_user}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-sure_password}
|
||||
- SCHEDULE=@daily # Runs once a day at midnight
|
||||
- BACKUP_KEEP_DAYS=7 # Keeps the last 7 days of backups
|
||||
- BACKUP_KEEP_WEEKS=4 # Keeps 4 weekly backups
|
||||
- BACKUP_KEEP_MONTHS=6 # Keeps 6 monthly backups
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- sure_net
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: [ "CMD", "redis-cli", "ping" ]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- sure_net
|
||||
|
||||
volumes:
|
||||
app-storage:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
|
||||
networks:
|
||||
sure_net:
|
||||
driver: bridge
|
||||
36
pipelock.example.yaml
Normal file
36
pipelock.example.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
# Pipelock configuration for Docker Compose
|
||||
# See https://github.com/luckyPipewrench/pipelock for full options.
|
||||
|
||||
forward_proxy:
|
||||
enabled: true
|
||||
max_tunnel_seconds: 300
|
||||
idle_timeout_seconds: 60
|
||||
|
||||
websocket_proxy:
|
||||
enabled: false
|
||||
max_message_bytes: 1048576
|
||||
max_concurrent_connections: 128
|
||||
scan_text_frames: true
|
||||
allow_binary_frames: false
|
||||
forward_cookies: false
|
||||
strip_compression: true
|
||||
max_connection_seconds: 3600
|
||||
idle_timeout_seconds: 300
|
||||
origin_policy: rewrite
|
||||
|
||||
dlp:
|
||||
scan_env: true
|
||||
|
||||
response_scanning:
|
||||
enabled: true
|
||||
action: warn
|
||||
|
||||
mcp_input_scanning:
|
||||
enabled: true
|
||||
action: block
|
||||
on_parse_error: block
|
||||
|
||||
mcp_tool_scanning:
|
||||
enabled: true
|
||||
action: warn
|
||||
detect_drift: true
|
||||
Reference in New Issue
Block a user