# amnezia-fellow Amnezia VPN client manager written in Rust on top of the public [`cot`](https://cot.rs) framework. The app uses SQLite as the source of truth, authenticates users through OIDC/SSO, renders AmneziaWG client peers into a Kubernetes Secret, and avoids updating that Secret when the rendered content is byte-for-byte identical. ## Quick Start ```bash export AMNEZIA_FELLOW_DATABASE_URL=sqlite://amnezia-fellow.sqlite3?mode=rwc cargo run -- --listen 127.0.0.1:8000 ``` Open `http://localhost:8000/admin/setup` to create the first local admin account. ## Docker The image is built by GitHub Actions and published to Docker Hub as `ultradesu/amnezia-fellow`. Required repository secrets: - `DOCKERHUB_USERNAME` - `DOCKERHUB_TOKEN` Publishing runs for version tags matching `v*.*.*`. A tag like `v0.1.0` pushes: - `ultradesu/amnezia-fellow:v0.1.0` - `ultradesu/amnezia-fellow:0.1.0` - `ultradesu/amnezia-fellow:latest` Local image build: ```bash docker build -t amnezia-fellow . docker run --rm -p 8000:8000 -v "$PWD/data:/data" amnezia-fellow ``` ## Roles There are two roles: - `admin`: full access, sees all client configs. - `client`: sees and manages only their own configs. OIDC provisioning is deny-by-default: - users in `AMNEZIA_FELLOW_OIDC_ADMIN_GROUPS` become `admin`; - users in `AMNEZIA_FELLOW_OIDC_CLIENT_GROUPS` become `client`; - users outside both group lists cannot sign in. The OIDC groups claim is expected to be `groups`. ## VPN Data Model SQLite stores all client data needed to restore configs: - owner user id - display name - assigned IPv4 address - public key - private key - enabled flag - created/updated timestamps The Kubernetes Secret is derived from the database. Active clients are rendered into one configured Secret key, `peers.conf` by default. ## Kubernetes Sync The app is intended to run inside Kubernetes with a ServiceAccount and RBAC that can read and update Secrets and list Pods in the Amnezia namespace. It reads: - server public key from `AMNEZIA_FELLOW_K8S_SERVER_SECRET`, key `server-public-key`; - node endpoints from `AMNEZIA_FELLOW_K8S_ENDPOINTS_SECRET`; Secret data keys are used as Amnezia server names in generated `vpn://` links; - AmneziaWG pods selected by `app=amneziawg` for config rollout status; - writes rendered client peers to `AMNEZIA_FELLOW_K8S_CLIENTS_SECRET`. It does not modify the server Secret. Argo/ExternalSecrets can keep managing `amneziawg-server`, while amnezia-fellow owns only the client Secret content. ## UI The main user interface is `/configs`. It is an Alpine.js reactive page backed by JSON API endpoints: - clients and admins use the same page; - the backend filters data by role; - admins get an extra manual Secret sync action. - clients and admins see AmneziaWG pod rollout status and uptime. ## Environment Variables All settings use `AMNEZIA_FELLOW_` prefix. Priority is: `environment variable > database override > compiled default` | Variable | Description | Default | | --- | --- | --- | | `AMNEZIA_FELLOW_DATABASE_URL` | SQLite connection URL | `sqlite://amnezia-fellow.sqlite3?mode=rwc` | | `AMNEZIA_FELLOW_LOG_LEVEL` | Tracing filter | `info` | | `AMNEZIA_FELLOW_AUTH_PASSWORD_ENABLED` | Enable password login | `true` | | `AMNEZIA_FELLOW_AUTH_SSO_ENABLED` | Enable OIDC login | `false` | | `AMNEZIA_FELLOW_OIDC_ISSUER` | OIDC issuer URL | empty | | `AMNEZIA_FELLOW_OIDC_CLIENT_ID` | OIDC client ID | empty | | `AMNEZIA_FELLOW_OIDC_CLIENT_SECRET` | OIDC client secret | empty | | `AMNEZIA_FELLOW_OIDC_BUTTON_TEXT` | SSO button label | `Sign in with SSO` | | `AMNEZIA_FELLOW_OIDC_ADMIN_GROUPS` | Comma-separated admin groups | empty | | `AMNEZIA_FELLOW_OIDC_CLIENT_GROUPS` | Comma-separated client groups | empty | | `AMNEZIA_FELLOW_K8S_NAMESPACE` | Amnezia namespace | `amnezia` | | `AMNEZIA_FELLOW_K8S_CLIENTS_SECRET` | Client peers Secret | `amneziawg-clients` | | `AMNEZIA_FELLOW_K8S_CLIENTS_SECRET_KEY` | Secret data key for rendered peers | `peers.conf` | | `AMNEZIA_FELLOW_K8S_SERVER_SECRET` | Server config Secret | `amneziawg-server` | | `AMNEZIA_FELLOW_K8S_ENDPOINTS_SECRET` | Node endpoints Secret | `amneziawg-endpoints` | | `AMNEZIA_FELLOW_VPN_CLIENT_CIDR` | Client address pool | `10.8.0.0/16` | | `AMNEZIA_FELLOW_VPN_DNS` | DNS servers in generated configs | `1.1.1.1, 8.8.8.8` | | `AMNEZIA_FELLOW_VPN_MTU` | MTU in generated configs | `1376` | | `AMNEZIA_FELLOW_SWAGGER_ENABLED` | Serve Swagger UI at `/swagger/` | `false` | ## API The JSON API is session-authenticated: - `GET /api/me` - `GET /api/vpn-clients` - `GET /api/vpn-status` - `POST /api/vpn-clients` - `POST /api/vpn-clients/{id}/enabled` - `DELETE /api/vpn-clients/{id}` - `GET /api/vpn-clients/{id}/config` returns `servers[]` with one raw AWG config and one Amnezia `vpn://` import link per registered endpoint - `POST /api/vpn-clients/sync` ## Development ```bash cargo fmt cargo check ``` The project depends on public crates only; `cot = "0.6.0"` is pulled from crates.io.