amnezia-fellow
Amnezia VPN client manager written in Rust on top of the public cot 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
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_USERNAMEDOCKERHUB_TOKEN
Publishing runs for version tags matching v*.*.*. A tag like v0.1.0 pushes:
ultradesu/amnezia-fellow:v0.1.0ultradesu/amnezia-fellow:0.1.0ultradesu/amnezia-fellow:latest
Local image build:
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_GROUPSbecomeadmin; - users in
AMNEZIA_FELLOW_OIDC_CLIENT_GROUPSbecomeclient; - 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, keyserver-public-key; - node endpoints from
AMNEZIA_FELLOW_K8S_ENDPOINTS_SECRET; Secret data keys are used as Amnezia server names in generatedvpn://links; - AmneziaWG pods selected by
app=amneziawgfor 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/meGET /api/vpn-clientsGET /api/vpn-statusPOST /api/vpn-clientsPOST /api/vpn-clients/{id}/enabledDELETE /api/vpn-clients/{id}GET /api/vpn-clients/{id}/configreturnsservers[]with one raw AWG config and one Amneziavpn://import link per registered endpointPOST /api/vpn-clients/sync
Development
cargo fmt
cargo check
The project depends on public crates only; cot = "0.6.0" is pulled from crates.io.