flowchart LR
client["VPN client"] --> vpn["openvpn udp/5443"]
vpn --> mitm["mitmproxy<br/>splice | bump per URL category"]
mitm --> clam["clamd :3310"]
mitm --> log[("decisions.jsonl")]
mitm --> net((Internet))
OpenSASE
Self-hosted SASE-style TLS inspection, declared end-to-end with Nix
OpenSASE is an open, self-hosted SASE-style edge, deployed from one source of truth: a Nix flake. Clients connect over OpenVPN; an mitmproxy addon decrypts and re-encrypts traffic per a URL-category policy; every payload is scanned by ClamAV; every verdict — splice, bump, clean, INFECTED — is written to a JSONL decision log.
It is growing two halves: the SASE edge for teams and servers, and a home appliance — install it like a Pi-hole and every family device routes through it, with per-device policy, traffic visibility, and verdicts. The stack stays modular: the bare-minimum decrypt edge stands alone; scanner, DNS-policy, agent-inspection, and chat modules layer on as opt-ins. The planned shapes live in Future; the working tracker is the Roadmap.
Two deployment paths:
Containers — no Nix required
Pre-built OCI images are published to GHCR by CI. Pull and docker compose up on Linux, macOS, or Windows, any OCI runtime.
NixOS appliance
The same stack as stock NixOS modules: build a QEMU VM, nixos-rebuild a physical machine, or deploy remotely with --target-host.
Kubernetes
Prebuilt manifests are not shipped yet (a kustomize follow-up is tracked). The four images are stateless OCI and stage on Kubernetes with the standard contract — NET_ADMIN on openvpn, RWO volumes per service, PKI injected before the VPN starts. See the README Kubernetes section for the staging table and constraints.
Purely declarative
Every deployable artifact is derived, not hand-maintained. There is no docker build anywhere in the repo’s release path — the same flake that boots the appliance emits the containers, so what you run as a container is exactly what NixOS would run, byte-for-byte.
flowchart LR
flake["flake.nix<br/>single source of truth"] --> imgs["nix/images.nix<br/>dockerTools.buildLayeredImage"]
flake --> appl["nix/appliance.nix<br/>NixOS module"]
flake --> cue["k8s/manifests.cue"]
imgs --> ghcr["ghcr.io/shsingh/opensase-*<br/>(:latest + version)"]
appl --> ghcr
cue --> manifests["k8s/manifests.yaml<br/>(cue export, committed)"]
imgs --> compose["docker-compose.yml"]
One flake, four surfaces. The flake is the only place versions, image references, ports, capabilities, and volume paths are declared; everything else consumes it:
- OCI containers without Dockerfiles. Each service is a
pkgs.dockerTools.buildLayeredImagederivation (nix/images.nix): the store path for the binary, a minimal closure, the few files a bare image lacks (/etc/passwd, CA certs,/tmp). Because images are hashes of inputs rather than layers of imperativeRUNstatements, CI and a localnix run .#load-imagesproduce byte-identical images — nothing floats, no base image is ever pulled at release time. - The appliance is modules, not scripts.
nix/appliance.nixcomposes stock NixOS services —services.openvpn,services.dnsmasq,services.clamav, plusservices.opensase(the mitmproxy addon + decision log) — so the appliance is defined, not assembled, and every knob is a typed NixOS option checkable bynix flake check. - Kubernetes manifests as CUE. The same service model (images, ports, capabilities, volumes) is expressed once in
k8s/manifests.cuewith a typed schema;nix run .#k8s-manifests(cue export ./k8s -e list) renders the committedk8s/manifests.yaml. CUE was chosen over hand-written YAML or Helm because the constraints live next to the data: a service that needsNET_ADMINmust declare it in type, so compose, the appliance, and Kubernetes cannot drift apart. - PKI in the flake too. CA bootstrap and per-device client profiles come from flake apps (
vpn-init,vpn-getclient) that vendor easyRSA via Nix — nocurl | bash, no drifting helper scripts.
The payoff is uniformity: one lockfile determines the appliance boot, the GHCR images, the compose wiring, and the Kubernetes base manifests. Consumers who never touch Nix still benefit — they pull dockerTools-produced images whose every layer is hash-pinned to the same nixpkgs revision CI used.
Plan of record
Scope for the next two releases, refreshed 2026-10-22. The full listing, including later targets and parked directions, lives on the OpenSASE Roadmap project board.
| Target | # | Item | Area |
|---|---|---|---|
| v0.1.x | #8 | Images: publish multi-arch (amd64 + arm64) GHCR images | images |
| v0.1.x | #11 | VPN: certificate renewal / rotation workflow (vpn-renew, expiry checks, client re-export) | vpn & pki |
| v0.1.x | #15 | Policy: fail-mode verdict test suite (splice / bump / INFECTED decision-log samples) | policy & inspection |
| v0.1.x | #25 | DNS-layer policy: dnsmasq RPZ + query logging into the decision log | home-appliance |
| v0.2.x | #9 | Images: cosign signing + provenance attestations in the release pipeline | release & supply-chain |
| v0.2.x | #10 | Kubernetes: ship prebuilt kustomize manifests + deployment docs | kubernetes |
| v0.2.x | #14 | Policy: remote category-list feeds with signed updates + hot reload | policy & inspection |
| v0.2.x | #16 | Observability: decision-log export + metrics endpoint | observability |
| v0.2.x | #19 | Home appliance: per-device policy + family visibility (Pi-hole-plus) | home-appliance |
| v0.2.x | #20 | Agent traffic: MCP tool calls as a first-class policy category | agent & ai traffic |
| v0.2.x | #21 | LLM provider egress: per-device authorization + counters (lightweight) | agent & ai traffic |
| v0.2.x | #22 | QUIC / HTTP-3: fail-closed first, then interception parity | policy & inspection |
| v0.2.x | #23 | Identity: step-ca integration — short-lived device certs, passive revocation | vpn & pki |
| v0.2.x | #26 | DLP verdicts: LEAK_BLOCK joins INFECTED (secret/credential egress) | policy & inspection |
| v0.2.x | #27 | Chat: Prosody XMPP server + inspection component (family chat on the edge) | chat (xmpp) |
| v0.2.x | #29 | Packaging: compose profiles — bare-minimum decrypt vs full appliance | home-appliance |
Items that have already shipped are listed on the Roadmap page, which reads through the project direction area by area. Ideas that go beyond the current roadmap are collected under Future.
The rest of this site covers how the edge works and how to operate it: read the architecture overview first, then deployment to stand one up. When something misbehaves, Troubleshooting walks the debug path in stages. Contributing, the security policy, and the code of conduct are linked in the footer.




