Deployment guide¶
Kysira ships in two deployment models. Choose the one that fits your infrastructure.
Model 1 — Standalone proxy¶
The Go proxy sits in front of your application and reverse-proxies all traffic. No changes to your app or infrastructure are required.
The proxy adds X-Kysira-Score, X-Kysira-Reason, and X-Kysira-Mode headers to every forwarded request so your app can log or react to threat scores if it wants. In shadow mode, X-Kysira-Would-Have-Killed: true is added when a request exceeds the score threshold.
Helm install¶
helm install kysira oci://ghcr.io/kysira/charts/kysira-platform \
--namespace kysira --create-namespace \
--set "kysira-proxy.config.targetURL=http://your-app:3000"
Or install the proxy chart alone (requires a separately running inference service):
helm install kysira-proxy oci://ghcr.io/kysira/charts/kysira-proxy \
--namespace kysira \
--set config.targetURL=http://your-app:3000 \
--set config.inferenceURL=http://kysira-inference:8081
kysira-proxy Helm values¶
| Value | Default | Description |
|---|---|---|
config.targetURL | http://localhost:3000 | Upstream application URL |
config.inferenceURL | (auto-derived) | Inference sidecar URL — defaults to http://<release>-kysira-inference:8081. Override only when inference is deployed separately. |
config.mode | shadow | shadow or active |
config.scoreThreshold | 0.95 | Kill threshold [0–1] |
config.sinks | file | Comma-separated sinks: file, stdout, http |
config.httpSinkURL | "" | Endpoint for the http sink (Datadog, Splunk, etc.) |
config.proxyPort | 8080 | Port to listen on |
persistence.enabled | true | Mount a PVC for events.jsonl and registrations.jsonl |
persistence.size | 1Gi | PVC size |
metrics.enabled | true | Add Prometheus scrape annotations to the pod |
replicaCount | 1 | Number of proxy replicas |
kysira-inference Helm values¶
| Value | Default | Description |
|---|---|---|
env.KYSIRA_DEVICE | cpu | cpu, cuda, or mps — models run offline, baked into the image |
resources.requests.memory | 1Gi | Minimum memory (models are ~1.5 GB resident) |
persistence.enabled | false | Models are baked into the image; no PVC needed unless caching external models |
Model 2 — Istio ext_proc¶
kysira-ext-proc implements the Envoy External Processing gRPC protocol. The Istio sidecar calls it per request over gRPC — no additional proxy hop, lower latency.
Client → Envoy sidecar ──ext_proc gRPC──▶ kysira-ext-proc :50051
↕
kysira-inference :8081
↓
Your App
Helm install¶
helm install kysira-ext-proc oci://ghcr.io/kysira/charts/kysira-ext-proc \
--namespace kysira \
--set "envoyFilter.workloadSelector.app=your-app" \
--set config.inferenceURL=http://kysira-inference:8081
The chart creates an EnvoyFilter resource in the istio-system namespace (configurable via envoyFilter.namespace). The filter inserts the ext_proc HTTP filter into every sidecar matching workloadSelector, just before the router filter.
kysira-ext-proc Helm values¶
| Value | Default | Description |
|---|---|---|
config.inferenceURL | http://kysira-inference:8081 | Inference sidecar URL |
config.mode | shadow | shadow or active |
config.scoreThreshold | 0.95 | Kill threshold [0–1] |
service.grpcPort | 50051 | gRPC listen port |
service.metricsPort | 9090 | HTTP port for health + metrics + mode API |
envoyFilter.enabled | true | Create the EnvoyFilter resource |
envoyFilter.context | SIDECAR_INBOUND | Envoy listener context |
envoyFilter.workloadSelector | {} | Label selector for target workloads |
envoyFilter.namespace | istio-system | Namespace where the EnvoyFilter is created |
envoyFilter.messageTimeout | 500ms | Per-message gRPC timeout |
envoyFilter.failureModeAllow | true | Pass requests through if ext_proc is unreachable |
Fail-open behaviour¶
Both deployment models fail open: if the inference service is unreachable, the request is passed through unmodified (proxy logs the error; ext_proc behaviour is controlled by failureModeAllow). This prevents Kysira from becoming a single point of failure.
Port exclusion for ext_proc¶
The ext_proc deployment has traffic.sidecar.istio.io/excludeInboundPorts: "50051" on its pod so Istio does not intercept the gRPC connections from Envoy sidecars — otherwise Envoy would route the ext_proc call through itself, creating a loop.
Model 3 — Standalone Envoy (without Istio)¶
kysira-ext-proc works with any Envoy deployment, not just Istio. If the customer runs Envoy directly (as an API gateway, via Contour, or as a standalone edge proxy), they configure the ext_proc filter in their envoy.yaml instead of using an EnvoyFilter CRD.
Deploy only the ext-proc service and inference — skip the EnvoyFilter resource by setting envoyFilter.enabled: false:
helm install kysira-inference oci://ghcr.io/kysira/charts/kysira-inference \
--namespace kysira \
--set config.device=cpu
helm install kysira-ext-proc oci://ghcr.io/kysira/charts/kysira-ext-proc \
--namespace kysira \
--set config.inferenceURL=http://kysira-inference:8081 \
--set envoyFilter.enabled=false
Then add the ext_proc filter to the customer's Envoy config. The filter goes in the HTTP filter chain, before the router:
static_resources:
clusters:
- name: kysira_ext_proc
type: STRICT_DNS
http2_protocol_options: {} # gRPC requires HTTP/2
load_assignment:
cluster_name: kysira_ext_proc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: kysira-ext-proc.kysira.svc.cluster.local
port_value: 50051
listeners:
- name: ingress
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
http_filters:
- name: envoy.filters.http.ext_proc
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor
grpc_service:
envoy_grpc:
cluster_name: kysira_ext_proc
timeout: 0.5s
failure_mode_allow: true # fail open if ext_proc is unreachable
processing_mode:
request_header_mode: SEND
request_body_mode: BUFFERED
response_header_mode: SKIP
response_body_mode: SKIP
message_timeout: 0.5s
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
# ... rest of HCM config (route_config, stat_prefix, etc.)
The gRPC cluster must have http2_protocol_options: {} — ext_proc uses gRPC which requires HTTP/2. All other behaviour (shadow/active mode, scoring, fail-open) is identical to the Istio integration.
Contour / Envoy Gateway¶
Contour and Envoy Gateway both support custom Envoy configuration via ExtensionService or EnvoyPatchPolicy resources. The kysira-ext-proc gRPC endpoint is the same — only the mechanism for injecting the filter differs per project. Refer to the Contour ExtensionService docs or Envoy Gateway EnvoyPatchPolicy docs for the specific API.
Model 4 — nginx via auth_request¶
Standard nginx cannot make inline HTTP calls, but it can delegate auth decisions to an external service using the ngx_http_auth_request_module (included in all nginx builds). Kysira ships a thin adapter — kysira-nginx-auth-adapter — that acts as the auth_request target.
nginx ──auth_request──▶ /_kysira/check (internal location)
│ proxy_pass
kysira-nginx-auth-adapter:8090/check
│ POST /score/all
kysira-inference:8081
│
200 (pass) or 403 (block)
│
nginx ──proxy_pass──▶ your-app (if 200)
nginx ──403──▶ client (if 403, via error_page)
Deploy¶
helm install kysira-inference oci://ghcr.io/kysira/charts/kysira-inference \
--namespace kysira \
--set config.device=cpu
helm install kysira-nginx-auth-adapter oci://ghcr.io/kysira/charts/kysira-nginx-auth-adapter \
--namespace kysira \
--set config.inferenceURL=http://kysira-inference:8081 \
--set config.mode=shadow
nginx configuration¶
http {
# Buffer request bodies so auth_request can forward them to the adapter.
# Without buffering, POST body scoring is skipped (the adapter still scores
# URI + method + headers, which catches most GET-based attacks).
client_body_buffer_size 64k;
client_max_body_size 10m;
server {
listen 80;
location / {
# Delegate every request to the Kysira adapter before proxying.
auth_request /_kysira/check;
# Capture scoring headers from the adapter response so they can be
# forwarded to the upstream app as request headers.
auth_request_set $kysira_score $upstream_http_x_kysira_score;
auth_request_set $kysira_reason $upstream_http_x_kysira_reason;
auth_request_set $kysira_mode $upstream_http_x_kysira_mode;
proxy_set_header X-Kysira-Score $kysira_score;
proxy_set_header X-Kysira-Reason $kysira_reason;
proxy_set_header X-Kysira-Mode $kysira_mode;
proxy_pass http://your-app;
# Custom error page when the adapter returns 403 (active mode).
error_page 403 = @kysira_blocked;
}
# Internal-only location — nginx does not expose this externally.
location = /_kysira/check {
internal;
proxy_pass http://kysira-nginx-auth-adapter:8090/check;
proxy_pass_request_body on;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Method $request_method;
proxy_set_header Content-Type $content_type;
# Never cache auth decisions.
proxy_no_cache 1;
proxy_cache_bypass 1;
}
location @kysira_blocked {
default_type application/json;
return 403 '{"error":"forbidden","detail":"request blocked by Kysira WAF"}';
}
}
}
kysira-nginx-auth-adapter Helm values¶
| Value | Default | Description |
|---|---|---|
config.inferenceURL | http://kysira-inference:8081 | Inference service URL |
config.mode | shadow | shadow (score + log, never block) or active (block above threshold) |
config.scoreThreshold | 0.95 | Kill threshold [0–1] |
service.port | 8090 | Port the adapter listens on |
replicaCount | 2 | Replicas — keep ≥ 2 so rolling updates don't cause auth failures |
Behaviour notes¶
Fail-open: If the adapter is unreachable, nginx's auth_request returns a 500 error. To prevent this from blocking traffic, add error_page 500 = @kysira_failopen; alongside the 403 handler and have it proxy through. Alternatively set proxy_next_upstream error on the check location.
Body scoring: proxy_pass_request_body on forwards the buffered body to the adapter. nginx buffers bodies up to client_body_buffer_size in memory; larger bodies spill to disk then forward. For APIs that receive large uploads, tune client_body_buffer_size or set the adapter to score URI-only for those paths.
Shadow vs active: In shadow mode the adapter always returns 200 — flagged requests are logged but not blocked. Switch to active with --set config.mode=active or update the ConfigMap directly.
Kustomize overlays¶
The kustomize/ directory provides a base plus three overlays:
kustomize/
├── base/ # shared Deployment + Service manifests
├── overlays/
│ ├── dev/ # single replica, debug logging, emptyDir storage
│ ├── prod/ # HPA, PDB, resource limits, PVC storage
│ └── demo/ # Juice Shop + NodePort services (:30080, :30090)
Apply an overlay¶
# Development (uses emptyDir — no PVC needed)
kubectl apply -k kustomize/overlays/dev
# Production
kubectl apply -k kustomize/overlays/prod
# Full demo with Juice Shop (minikube)
kubectl apply -k kustomize/overlays/demo
Namespace¶
All overlays deploy into the kysira namespace. Create it first if it does not exist:
Umbrella chart — kysira-platform¶
kysira-platform is a Helm umbrella chart that installs all four components (inference, proxy, dashboard, ext-proc) with one command and exposes a unified values.yaml.
helm install kysira oci://ghcr.io/kysira/charts/kysira-platform \
--namespace kysira --create-namespace \
-f my-values.yaml
Sub-chart values are namespaced by component:
# my-values.yaml
kysira-proxy:
config:
targetURL: http://my-app:3000
mode: active
kysira-inference:
resources:
requests:
memory: 2Gi
kysira-dashboard:
replicaCount: 2
Prometheus metrics¶
All three Go services expose metrics at /metrics (Prometheus text format). The proxy and ext-proc Helm charts add scrape annotations to pods when metrics.enabled: true:
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: "/metrics"
prometheus.io/port: "8080" # proxy
# or :9090 for ext-proc
Proxy metric names¶
| Metric | Type | Labels |
|---|---|---|
kysira_requests_total | Counter | action (passed/active_kill/shadow_kill/error/skipped) |
kysira_request_duration_seconds | Histogram | action |
kysira_flagged_total | Counter | — |
kysira_killed_total | Counter | — |
kysira_inference_errors_total | Counter | — |
ext-proc metric names¶
| Metric | Type | Labels |
|---|---|---|
kysira_extproc_requests_total | Counter | action |
kysira_extproc_request_duration_seconds | Histogram | action |
kysira_extproc_flagged_total | Counter | — |
kysira_extproc_killed_total | Counter | — |
kysira_extproc_inference_errors_total | Counter | — |
kysira_extproc_active_streams | Gauge | — |
Pulling registration data¶
Interested contacts who click "Register" in the dashboard are saved to registrations.jsonl in the proxy data volume.