https://github.com/sqdzy/awg-rest
Internal all-in-one REST control plane for AmneziaWG V2 peer provisioning with Go, embedded Postgres, Docker Compose, OpenAPI, and GHCR.
https://github.com/sqdzy/awg-rest
amneziawg control-plane docker ghcr go openapi postgres rest-api vpn wireguard
Last synced: 16 days ago
JSON representation
Internal all-in-one REST control plane for AmneziaWG V2 peer provisioning with Go, embedded Postgres, Docker Compose, OpenAPI, and GHCR.
- Host: GitHub
- URL: https://github.com/sqdzy/awg-rest
- Owner: sqdzy
- License: mit
- Created: 2026-05-01T18:10:03.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-26T18:21:01.000Z (17 days ago)
- Last Synced: 2026-05-26T20:04:57.637Z (17 days ago)
- Topics: amneziawg, control-plane, docker, ghcr, go, openapi, postgres, rest-api, vpn, wireguard
- Language: Go
- Size: 191 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# awg-rest
Internal REST API for provisioning AmneziaWG V2 VPN peers from another backend
container.
`awg-rest` is designed for a single private control-plane path:
```text
your backend container -> awg-rest REST API -> embedded Postgres -> embedded worker -> awg/amneziawg-go
```
The default distribution is an all-in-one Docker image. It contains the API,
embedded worker, PostgreSQL, `amneziawg-tools`, and `amneziawg-go` userspace
fallback, so the host does not need an AmneziaWG kernel module.
Do not expose the REST API to the public internet. Publish only the VPN UDP
port.
## Requirements
- Linux VPS with Docker Engine and Docker Compose.
- `/dev/net/tun` available on the host.
- UDP port `38823` open in the VPS firewall, or the port you configure.
- Public IP or DNS name for generated client configs.
The all-in-one image still needs `NET_ADMIN` and `/dev/net/tun` because the VPN
interface is created inside the container.
## Deploy
Clone the repository or place `compose.yaml` and `.env` in an empty directory:
```bash
cp .env.example .env
```
Edit `.env`:
```dotenv
BOOTSTRAP_NODE_ENDPOINT=203.0.113.10
JWT_SECRET=
CLIENT_DNS=1.1.1.1,1.0.0.1
AWG_API_BIND=127.0.0.1:18080
AWG_UDP_BIND=38823
AWG_UDP_PORT=38823
```
Generate `JWT_SECRET` with:
```bash
openssl rand -base64 32
```
Start the stack:
```bash
docker compose up -d
```
What starts:
- `awg-rest` on the internal Docker network `awg-backend-internal`
- REST API on `http://awg-rest:18080` inside Docker
- optional host-local API access on `127.0.0.1:18080`
- VPN UDP listener on `38823/udp`
- persistent volumes `awg-postgres` and `awg-state`
On first start, the container creates:
- tenant `default`
- AmneziaWG V2 profile `default-v2`
- node `awg-node-1`
- address pool `10.200.0.0/24`
- server private key and bootstrap interface config in `awg-state`
The server uses the first usable IPv4 address in the pool, so the default
server address is `10.200.0.1/24` and generated peers start from
`10.200.0.2/32`.
The default `default-v2` profile uses AmneziaWG V2 parameters compatible with
current AmneziaVPN clients, including `Jmin=10`, `Jmax=50`, `S1-S4`, and
non-overlapping `H1-H4` ranges below `2147483647`. It also renders the same
default `I1` special junk packet used by the Amnezia client for new AWG
profiles.
Back up both Docker volumes. `awg-state` contains the server private key.
## Connect Your Backend
Attach your backend container to the same Docker network:
```yaml
services:
backend:
image: your-backend-image
networks:
- awg-backend-internal
networks:
awg-backend-internal:
external: true
```
Use this base URL from the backend container:
```text
http://awg-rest:18080
```
If the backend runs in the same compose project, it can also use the alias:
```text
http://awg-api:18080
```
## Authentication
Every protected API call needs:
```http
Authorization: Bearer
```
Default all-in-one auth uses `HS256` with `JWT_SECRET`. Your backend signs JWTs
with the same secret and these claims:
- `iss`: value of `JWT_ISSUER`
- `aud`: value of `JWT_AUDIENCE`
- `exp`, `nbf`, `iat`
- `roles`: include `platform_admin`, `tenant_admin`, or `automation_client`
For tenant-scoped roles, include `tenant_id`. `platform_admin` can access all
tenants.
For a local smoke test, generate a temporary admin token inside the container:
```bash
JWT="$(docker compose exec -T awg-rest /awg-api -dev-token)"
```
## API Usage
Create a peer:
```bash
curl -sS -X POST "http://127.0.0.1:18080/v1/tenants/default/peers" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: user-123-create-v1" \
-d '{
"external_id": "user-123",
"display_name": "User 123",
"profile_name": "default-v2"
}'
```
The first create response includes one-time secret material:
- `private_key`
- `client_config`
- `preshared_key`
Store `client_config` in your backend and deliver it to the user once. It is not
returned again on idempotency replay.
Poll the operation:
```bash
curl -sS "http://127.0.0.1:18080/v1/operations/$OPERATION_ID" \
-H "Authorization: Bearer $JWT"
```
Read public peer metadata:
```bash
curl -sS "http://127.0.0.1:18080/v1/tenants/default/peers/$PEER_ID" \
-H "Authorization: Bearer $JWT"
```
Render the non-secret client config skeleton:
```bash
curl -sS "http://127.0.0.1:18080/v1/tenants/default/peers/$PEER_ID/configuration" \
-H "Authorization: Bearer $JWT"
```
Revoke a peer:
```bash
curl -sS -X POST "http://127.0.0.1:18080/v1/tenants/default/peers/$PEER_ID:revoke" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: user-123-revoke-v1" \
-d '{"reason":"user disabled"}'
```
OpenAPI contract: [`api/openapi.yaml`](api/openapi.yaml).
## Configuration
Important `.env` values:
| Variable | Meaning |
| --- | --- |
| `AWG_REST_IMAGE` | all-in-one image tag |
| `BOOTSTRAP_NODE_ENDPOINT` | public IP or DNS name placed into client configs |
| `CLIENT_DNS` | comma-separated DNS servers rendered into generated client configs |
| `JWT_SECRET` | HMAC signing secret shared only with your backend |
| `AWG_API_BIND` | host binding for REST API, keep loopback-only |
| `AWG_UDP_BIND` | host UDP binding for VPN traffic |
| `AWG_UDP_PORT` | UDP listen port inside client configs |
| `BOOTSTRAP_POOL_CIDR` | VPN client address pool |
| `AWG_INTERNAL_NETWORK` | Docker network for backend-to-API traffic |
## Security Notes
- Keep `AWG_API_BIND=127.0.0.1:18080` unless a private reverse proxy or private
Docker network protects it.
- Publish only the configured UDP VPN port to the internet.
- Rotate `JWT_SECRET` if it was exposed.
- Back up `awg-state`; losing it loses the server private key.
- Do not delete volumes unless you want to recreate the VPN node and reissue
client configs.
## License
Repository code is MIT licensed. The all-in-one image bundles
`amneziawg-tools`, which is GPL-2.0-only upstream software.