Skip to content
Merged
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
27 changes: 12 additions & 15 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,22 @@ copied to any other operation.

---

## syva-core CP Mode vs Legacy Mode
## syva-core ingests zones via syva-cp

`syva-core` supports two zone ingestion paths that share the same in-process
`ZoneRegistry` and `EnforceEbpf`:
After session 4b, `syva-core` has one and only one ingestion path: the
`NodeAssignmentUpdate` stream from `syva-cp`. The local gRPC surface that
existed in v0.2 is deleted. `--cp-endpoint` is mandatory.

1. Local gRPC surface. Adapters push zones to `syva-core` directly. This is
the v0.2 architecture and remains the default.
2. CP mode (`--cp-endpoint`). `syva-core` connects to a remote `syva-cp`,
registers as a node, and consumes assignments via server-streaming. The
reconcile loop lives in `syva-core/src/cp_reconcile/`.
Adapters (`syva-file`, `syva-k8s`, `syva-api`) push zones to `syva-cp`
directly via `syva-cp-client`. They do not connect to `syva-core`.

Both paths call the same in-process mutation helpers. CP mode is additive, not
a replacement yet. Session 4b will migrate adapters to push to `syva-cp`
instead of `syva-core`.
Single-node operation (laptop, demo, CI) is achieved by running both
`syva-cp` and `syva-core` on the same machine, with `syva-cp` using a local
Postgres. There is no separate "local mode" code path.

The reconciler keeps in-memory state only (`AppliedState`). On restart, a fresh
subscription receives a `FULL_SNAPSHOT` from `syva-cp` and reconstructs desired
state. The only local persistence in CP mode is the `node-id` file used for
re-registration.
If easier single-node operation becomes important, the future plan is to ship a
`syva-cp --embedded` mode that bundles Postgres or SQLite into the control
plane binary. That work is not in scope here.

---

Expand Down
24 changes: 6 additions & 18 deletions Cargo.lock

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

30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,30 +96,42 @@ Without Syva, these containers can interact through the shared kernel — read e

## How It Works

Syva has two layers: a **core engine** that manages eBPF enforcement, and **adapters** that tell it what to enforce.
Syva now has three layers: adapters write desired state to the **control plane**, the control plane computes per-node assignments, and `syva-core` reconciles those assignments into kernel-enforced BPF map state.

```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ syva-file │ │ syva-k8s │ │ syva-api │
│ TOML/ConfigMap│ │ CRD + Pods │ │ REST API
│ TOML watcher │ │ CRD watcher │ │ REST proxy
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
└─────────────────┼─────────────────┘
gRPC / Unix socket
gRPC client
┌──────▼──────┐
│ syva-cp │
│ teams/zones │
│ nodes/audit │
└──────┬──────┘
NodeAssignmentUpdate stream
┌─────────────────▼─────────────────┐
│ syva-core │
│ BPF maps + 7 LSM hooks │
│ Health :9091 + Prometheus
│ Health :9091 + Prometheus │
└───────────────────────────────────┘
```

**The core** loads eBPF programs into the kernel and exposes a gRPC API over a Unix socket. It handles zone registration, container membership, cross-zone communication policy, and inode-level file enforcement. It never knows where commands came from.
**`syva-cp`** is the source of truth. It stores teams, zones, policies, nodes, assignments, and audit history. Adapters write zones to it through `ZoneService`. Nodes register with it through `NodeService` and receive desired state through `AssignmentService`.

**`syva-core`** is now CP-only. It no longer exposes a local adapter-facing gRPC server. It registers as a node with `syva-cp`, subscribes to assignment updates, and reconciles those assignments into the kernel.

**Adapters** translate external truth into zone CRUD on `syva-cp`:
- **syva-file** — reconciles a directory of TOML policies into zones in one team
- **syva-k8s** — reconciles `SyvaZonePolicy` CRDs into zones in one team
- **syva-api** — exposes a REST API and proxies zone CRUD to `syva-cp`

**Adapters** connect to the core and translate their domain into enforcement commands:
- **syva-file** — reads TOML policy files, watches containerd for container events, hot-reloads on ConfigMap changes
- **syva-k8s** — watches `SyvaZonePolicy` CRDs and Pod annotations via kube-rs
- **syva-api** — exposes a REST API for programmatic zone management
Container and pod membership ingestion is temporarily deferred while `ContainerService` is being wired end to end. Session 4b moves zone management to `syva-cp`; membership will follow.

**Step 1: Label your containers.**

Expand Down
6 changes: 3 additions & 3 deletions deploy/v0.2/daemonset-file.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syva v0.2 — Standalone / ConfigMap deployment
# Two containers: syva-core (enforcement) + syva-file (policy adapter)
# Use this when managing policies via TOML files or Kubernetes ConfigMaps.
# syva v0.2 — Legacy local-socket deployment
# Historical reference only. Session 4b removed syva-core's local gRPC surface.
# For current deployments, use deploy/v0.3/daemonset-cp-mode.yaml instead.
apiVersion: apps/v1
kind: DaemonSet
metadata:
Expand Down
6 changes: 3 additions & 3 deletions deploy/v0.2/daemonset-k8s.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syva v0.2 — Kubernetes CRD deployment
# Two containers: syva-core (enforcement) + syva-k8s (CRD adapter)
# Use this when managing policies via SyvaZonePolicy CRDs.
# syva v0.2 — Legacy local-socket deployment
# Historical reference only. Session 4b removed syva-core's local gRPC surface.
# For current deployments, use deploy/v0.3/daemonset-cp-mode.yaml instead.
apiVersion: apps/v1
kind: DaemonSet
metadata:
Expand Down
197 changes: 197 additions & 0 deletions deploy/v0.3/daemonset-cp-mode.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
apiVersion: v1
kind: Namespace
metadata:
name: syva-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: syva
namespace: syva-system
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: syva-core
namespace: syva-system
spec:
selector:
matchLabels:
app: syva-core
template:
metadata:
labels:
app: syva-core
spec:
hostPID: true
serviceAccountName: syva
tolerations:
- effect: NoSchedule
operator: Exists
containers:
- name: syva-core
image: ghcr.io/false-systems/syva-core:0.3.0
args:
- --cp-endpoint=http://syva-cp.syva-system.svc:50051
- --node-labels=$(NODE_LABELS)
env:
- name: SYVA_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: SYVA_CLUSTER_ID
value: production
- name: NODE_LABELS
value: "tier=prod"
securityContext:
privileged: true
volumeMounts:
- name: machine-id
mountPath: /etc/machine-id
readOnly: true
- name: node-state
mountPath: /var/lib/syva
- name: bpf
mountPath: /sys/fs/bpf
- name: cgroup
mountPath: /sys/fs/cgroup
readOnly: true
- name: btf
mountPath: /sys/kernel/btf
readOnly: true
volumes:
- name: machine-id
hostPath:
path: /etc/machine-id
type: File
- name: node-state
hostPath:
path: /var/lib/syva
type: DirectoryOrCreate
- name: bpf
hostPath:
path: /sys/fs/bpf
type: DirectoryOrCreate
- name: cgroup
hostPath:
path: /sys/fs/cgroup
type: Directory
- name: btf
hostPath:
path: /sys/kernel/btf
type: Directory
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: syva-file-adapter
namespace: syva-system
spec:
replicas: 1
selector:
matchLabels:
app: syva-file-adapter
template:
metadata:
labels:
app: syva-file-adapter
spec:
serviceAccountName: syva
containers:
- name: syva-file
image: ghcr.io/false-systems/syva-adapter-file:0.3.0
args:
- --cp-endpoint=http://syva-cp.syva-system.svc:50051
- --team-id=$(TEAM_ID)
- --policy-dir=/etc/syva/policies
env:
- name: TEAM_ID
value: "00000000-0000-0000-0000-000000000000"
volumeMounts:
- name: policies
mountPath: /etc/syva/policies
readOnly: true
volumes:
- name: policies
configMap:
name: syva-policies
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: syva-k8s-adapter
namespace: syva-system
spec:
replicas: 1
selector:
matchLabels:
app: syva-k8s-adapter
template:
metadata:
labels:
app: syva-k8s-adapter
spec:
serviceAccountName: syva
containers:
- name: syva-k8s
image: ghcr.io/false-systems/syva-adapter-k8s:0.3.0
args:
- --cp-endpoint=http://syva-cp.syva-system.svc:50051
- --team-id=$(TEAM_ID)
- --namespace=$(WATCH_NAMESPACE)
env:
- name: TEAM_ID
value: "00000000-0000-0000-0000-000000000000"
- name: WATCH_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: syva-api-adapter
namespace: syva-system
spec:
replicas: 1
selector:
matchLabels:
app: syva-api-adapter
template:
metadata:
labels:
app: syva-api-adapter
spec:
serviceAccountName: syva
containers:
- name: syva-api
image: ghcr.io/false-systems/syva-adapter-api:0.3.0
args:
- --cp-endpoint=http://syva-cp.syva-system.svc:50051
- --team-id=$(TEAM_ID)
- --listen=0.0.0.0:8080
env:
- name: TEAM_ID
value: "00000000-0000-0000-0000-000000000000"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: syva-k8s
rules:
- apiGroups: ["syva.dev"]
resources: ["syvazonepolicies"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: syva-k8s
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: syva-k8s
subjects:
- kind: ServiceAccount
name: syva
namespace: syva-system
Loading
Loading