Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ tracing-subscriber = { version = "0.3", features = ["json", "env-filter"] }
clap = { version = "4", features = ["derive"] }
anyhow = "1"
axum = { version = "0.8", optional = true }
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] }
# `system-proxy` re-enables HTTPS_PROXY / HTTP_PROXY / NO_PROXY env-var auto-detection,
# which is a default reqwest feature disabled by `default-features = false`. Lets operators
# route reqwest-based HTTP calls (LLM APIs, Slack Web API, remote config, hooks, STT, media)
# through an HTTP CONNECT proxy by setting the env vars.
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json", "system-proxy"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
async-trait = "0.1"
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ kubectl create secret generic openab-secret \
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/pvc.yaml
kubectl apply -f k8s/deployment.yaml

# Optional: opt-in pod-level network isolation (deny-all ingress,
# DNS + HTTPS egress with cloud-metadata exclusion). See the file
# header for tunable CIDRs / IPv6 exclusions per cloud.
kubectl apply -f k8s/networkpolicy.yaml
```

| Manifest | Purpose |
Expand All @@ -280,6 +285,7 @@ kubectl apply -f k8s/deployment.yaml
| `k8s/configmap.yaml` | `config.toml` mounted at `/etc/openab/` |
| `k8s/secret.yaml` | `DISCORD_BOT_TOKEN` injected as env var |
| `k8s/pvc.yaml` | Persistent storage for auth + settings |
| `k8s/networkpolicy.yaml` | **Optional** pod-level network isolation — deny-all ingress + DNS/HTTPS egress with cloud-metadata (`169.254.169.254`, AWS `fd00:ec2::254`) excluded. Requires a CNI that enforces NetworkPolicy (Calico, Cilium, etc.). |

## AWS ECS Deployment

Expand Down
6 changes: 6 additions & 0 deletions README.zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ kubectl create secret generic openab-secret \
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/pvc.yaml
kubectl apply -f k8s/deployment.yaml

# 選用:pod 層網路隔離(deny-all ingress、DNS + HTTPS egress,並排除
# cloud metadata service)。若需要調整 CIDR 或不同雲的 IPv6 metadata
# 位址,請直接修改該 manifest 檔案開頭的註解。
kubectl apply -f k8s/networkpolicy.yaml
```

| Manifest | 用途 |
Expand All @@ -280,6 +285,7 @@ kubectl apply -f k8s/deployment.yaml
| `k8s/configmap.yaml` | `config.toml` 掛載至 `/etc/openab/` |
| `k8s/secret.yaml` | 透過 env 注入 `DISCORD_BOT_TOKEN` |
| `k8s/pvc.yaml` | 持久保存驗證資訊與設定 |
| `k8s/networkpolicy.yaml` | **選用**:pod 層網路隔離 — deny-all ingress + DNS/HTTPS egress,並排除 cloud metadata(`169.254.169.254`、AWS `fd00:ec2::254`)。需要有 CNI 執行 NetworkPolicy(Calico、Cilium 等)。 |

## AWS ECS 部署

Expand Down
14 changes: 13 additions & 1 deletion charts/openab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ This page highlights commonly used values and deployment patterns. For the compl
| `fullnameOverride` | Override the full generated release name for chart resources. Useful when deploying multiple instances with predictable names. | `""` |
| `serviceAccountName` | Chart-global ServiceAccount name attached to every agent pod that doesn't define its own. Empty = cluster `default` SA. Per-agent `agents.<name>.serviceAccountName` fully overrides this. Chart references an existing SA only — does not create one. Required for workload identity and pod-level RBAC. | `""` |
| `imagePullSecrets` | Chart-global image pull secrets attached to every agent pod that doesn't define its own. Per-agent `agents.<name>.imagePullSecrets` fully overrides this. | `[]` |
| `networkPolicy.enabled` | Master switch for the chart-managed NetworkPolicy. Off by default — no `NetworkPolicy` resource is rendered until this flips to `true`, keeping existing releases unaffected. When on: one policy per agent (deny-all ingress + DNS/HTTPS egress + TCP 8080 to the chart-managed gateway pod when `gateway.enabled=true` and `gateway.deploy` is not `false`), plus one policy per deployed gateway (port 8080 ingress + same egress). ⚠️ **Prerequisite: your cluster's CNI must enforce NetworkPolicy** (Calico, Cilium, Antrea, EKS with NetworkPolicy add-on, GKE with Dataplane V2 / Calico, AKS with Azure NPM / Calico, etc.). Stock KIND, Docker Desktop, or bare-EKS clusters without a NetworkPolicy-capable CNI silently ignore the resource — verify enforcement with a scratch namespace + deny-all test before relying on this. ⚠️ **Direct egress only for now** — proxy-mode egress (routing through an operator-supplied HTTP CONNECT proxy) was explored in early rounds of [RFC #1394](https://github.com/openabdev/openab/issues/1394) but requires a proxy-aware networking refactor across `openab-core` / `openab-gateway` (see `docs/openshell.md`). Follow-up RFC to come. | `false` |
| `networkPolicy.ingress.extraRules` | Raw `NetworkPolicyIngressRule` entries appended verbatim to every rendered policy. Use for cluster-specific allow-lists (Prometheus scrape, mesh sidecars, etc.). | `[]` |
| `networkPolicy.egress.allowDns` | Allow DNS egress on UDP+TCP 53 to pods labelled `k8s-app=kube-dns`. | `true` |
| `networkPolicy.egress.dnsNamespace` | Trusted namespace hosting the DNS pods. Default `"kube-system"` matches kubeadm / CoreDNS / kube-dns defaults and prevents a co-tenant labelling their own pod `k8s-app=kube-dns` from becoming an approved port-53 destination. Empty string reverts to the legacy any-namespace selector — use only if your DNS runs outside kube-system. | `"kube-system"` |
| `networkPolicy.egress.allowHttps` | Allow HTTPS (TCP 443) to `0.0.0.0/0` (and `::/0` when `allowIpv6=true`), except CIDRs listed in `metadataExclusions`. | `true` |
| `networkPolicy.egress.allowHttp` | Allow HTTP (TCP 80) with the same metadata exclusion. Off by default. ⚠️ Enable if any of these fetch over HTTP: `agents.<name>.configUrl`, lifecycle hooks that download over HTTP (see `docs/hooks.md`), or STT base URLs on HTTP. Prefer HTTPS wherever possible. | `false` |
| `networkPolicy.egress.allowIpv6` | Add `::/0` to the HTTPS/HTTP rules alongside `0.0.0.0/0`, excluding CIDRs from `metadataExclusions.ipv6`. Turn off on IPv4-only clusters. | `true` |
| `networkPolicy.egress.metadataExclusions.ipv4` | CIDRs excluded from the IPv4 HTTPS/HTTP allow rule. Defaults to `169.254.169.254/32` (AWS/GCP/Azure IMDS). Add IBM Cloud's `161.26.0.0/16` here if applicable. **NetworkPolicy allow rules combine additively — you cannot subtract via `extraRules`; edit this list directly.** ⚠️ **Defense-in-depth only, not a portable IMDS guarantee**: pods can always reach services on their resident node, and CNI implementations differ in whether `ipBlock` matches pre- or post-NAT addresses. Use cloud-provider hardening (IMDSv2 mandatory + hop-limit 1, GCP metadata concealment, Azure metadata endpoint policy) as the primary control. | `["169.254.169.254/32"]` |
| `networkPolicy.egress.metadataExclusions.ipv6` | CIDRs excluded from the IPv6 HTTPS/HTTP allow rule. Defaults to `fd00:ec2::254/128` (AWS IPv6 IMDS). Non-AWS IPv6 metadata addresses differ — override this list per cloud. **Same additive-only semantics as `ipv4` above, and same defense-in-depth caveat.** | `["fd00:ec2::254/128"]` |
| `networkPolicy.egress.extraRules` | Raw `NetworkPolicyEgressRule` entries appended verbatim. Use to open extra ports or add cluster-specific carve-outs. **Cannot subtract from existing allows — for tighter metadata exclusions use `metadataExclusions` above.** ⚠️ **Non-80/443 destinations require an explicit entry here** — enabling `networkPolicy.enabled=true` silently breaks: (a) external config-only gateway (`gateway.deploy=false` + `gateway.url` on TCP 8080), (b) self-hosted STT on non-standard ports (e.g. `http://192.168.1.100:8080/v1`), (c) internal LLM proxies (LiteLLM on 4000, etc.). See `values.yaml` for a concrete `extraRules` recipe for the external-gateway case. | `[]` |

### Agent values

Expand Down Expand Up @@ -47,7 +57,9 @@ Each agent lives under `agents.<name>`.
| `stt.model` | STT model name. | `"whisper-large-v3-turbo"` |
| `stt.baseUrl` | STT API base URL. | `"https://api.groq.com/openai/v1"` |
| `gateway.enabled` | Enable the gateway config block for webhook-based platforms. | `false` |
| `gateway.deploy` | Deploy the gateway Deployment and Service. | `true` |
| `gateway.deploy` | Deploy the gateway Deployment and Service. ⚠️ When `false` (config-only / external gateway), the chart-managed agent→gateway TCP 8080 egress rule is NOT rendered — if `networkPolicy.enabled=true` you must add a matching entry to `networkPolicy.egress.extraRules` targeting your external gateway pod/service, or agent WSS to the external gateway will be blocked. See values.yaml for a concrete recipe. | `true` |
| `gateway.env` | Additional environment variables for the gateway container (map, `{name: value}`). Same shape as agent-level `env`. Useful for feature flags, `RUST_LOG` overrides, or HTTP proxy env vars if the gateway runs behind a transparent proxy. | `{}` |
| `gateway.envFrom` | Load env vars for the gateway from existing Secrets or ConfigMaps (list of `secretRef`/`configMapRef` entries). Same shape as agent-level `envFrom`. | `[]` |
| `cron.usercronEnabled` | Enable user-provided cron configuration. | `false` |
| `cronjobs` | Config-driven scheduled messages for an agent. | `[]` |
| `persistence.enabled` | Enable persistent storage for auth and settings. | `true` |
Expand Down
8 changes: 8 additions & 0 deletions charts/openab/templates/gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ spec:
{{- end }}
- name: RUST_LOG
value: {{ ($cfg.gateway).rustLog | default "info" | quote }}
{{- range $k, $v := ($cfg.gateway).env }}
- name: {{ $k }}
value: {{ $v | quote }}
{{- end }}
{{- with ($cfg.gateway).envFrom }}
envFrom:
{{- toYaml . | nindent 12 }}
{{- end }}
livenessProbe:
httpGet:
path: /health
Expand Down
132 changes: 132 additions & 0 deletions charts/openab/templates/networkpolicy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
{{- if .Values.networkPolicy.enabled }}
{{- $np := .Values.networkPolicy }}
{{- range $name, $cfg := .Values.agents }}
{{- if ne (include "openab.agentEnabled" $cfg) "false" }}
{{- $d := dict "ctx" $ "agent" $name "cfg" $cfg }}
{{- $gwDeployed := and (($cfg.gateway).enabled) (ne (($cfg.gateway).deploy | toString) "false") }}
{{- $gwD := dict "ctx" $ "agent" (printf "%s-gateway" $name) "cfg" (omit $cfg "nameOverride") }}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "openab.agentFullname" $d }}
labels:
{{- include "openab.labels" $d | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "openab.selectorLabels" $d | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
{{- with $np.ingress.extraRules }}
{{- toYaml . | nindent 4 }}
{{- else }}
[]
{{- end }}
egress:
{{ include "openab.networkPolicyEgress" (dict "np" $np) | indent 4 }}
{{- if $gwDeployed }}
- to:
- podSelector:
matchLabels:
{{- include "openab.selectorLabels" $gwD | nindent 14 }}
ports:
- protocol: TCP
port: 8080
{{- end }}
{{- if $gwDeployed }}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "openab.agentFullname" $gwD }}
labels:
{{- include "openab.labels" $gwD | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "openab.selectorLabels" $gwD | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
- ports:
- protocol: TCP
port: 8080
{{- with $np.ingress.extraRules }}
{{- toYaml . | nindent 4 }}
{{- end }}
egress:
{{ include "openab.networkPolicyEgress" (dict "np" $np) | indent 4 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

{{/* Egress rule set shared by agent and gateway NetworkPolicies.
Call with: dict "np" .Values.networkPolicy */}}
{{- define "openab.networkPolicyEgress" -}}
{{- $np := .np -}}
{{- if $np.egress.allowDns -}}
{{/* Default DNS destination scoped to a trusted namespace (usually kube-system).
Empty dnsNamespace preserves the legacy any-namespace behaviour for exotic DNS setups. */}}
- to:
{{- if $np.egress.dnsNamespace }}
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ $np.egress.dnsNamespace | quote }}
podSelector:
matchLabels:
k8s-app: kube-dns
{{- else }}
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
{{- end }}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
{{- end }}
{{- $mdxV4 := $np.egress.metadataExclusions.ipv4 | default (list "169.254.169.254/32") }}
{{- $mdxV6 := $np.egress.metadataExclusions.ipv6 | default (list "fd00:ec2::254/128") }}
{{- if $np.egress.allowHttps }}
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
{{- toYaml $mdxV4 | nindent 10 }}
{{- if $np.egress.allowIpv6 }}
- ipBlock:
cidr: ::/0
except:
{{- toYaml $mdxV6 | nindent 10 }}
{{- end }}
ports:
- protocol: TCP
port: 443
{{- end }}
{{- if $np.egress.allowHttp }}
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
{{- toYaml $mdxV4 | nindent 10 }}
{{- if $np.egress.allowIpv6 }}
- ipBlock:
cidr: ::/0
except:
{{- toYaml $mdxV6 | nindent 10 }}
{{- end }}
ports:
- protocol: TCP
port: 80
{{- end }}
{{- with $np.egress.extraRules }}
{{ toYaml . }}
{{- end }}
{{- end }}
Loading
Loading