- JavaScript 54.1%
- Python 43.2%
- CSS 1.6%
- HTML 0.5%
- Shell 0.4%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| backend | ||
| deploy | ||
| desktop | ||
| frontend | ||
| reference | ||
| .gitignore | ||
| deploy.sh | ||
| README.md | ||
OVH Firewall Manager
Self-hosted app to manage OVH Network/Edge Firewall + view GAME anti-DDoS mitigation across multiple OVH accounts — instead of logging into the OVH dashboard. Built for a 2-owner setup (you + co-owner), 2 OVH accounts (FR + PL).
Raspberry Pi 5 (NVMe, 24/7)
├─ FastAPI backend (127.0.0.1:8080) ── python-ovh ──> OVH API (ovh-eu)
│ └─ SQLite: users, OVH accounts (Fernet-encrypted creds), grants, audit
├─ React webapp (served by FastAPI)
├─ nginx (127.0.0.1:80, reverse proxy)
└─ cloudflared (tunnel ── public host fw.yourdomain.com)
Clients (all same UI, same backend):
• Browser → https://fw.yourdomain.com (co-owner, phone)
• Linux desktop app → Tauri shell (you)
• macOS desktop app → Tauri shell (co-owner)
- Auth: own login (argon2) + TOTP 2FA + rate-limiting. No passwords leave the Pi.
- RBAC: per-user grants → which OVH account, read or write. Admin manages users/accounts.
- Audit: every firewall change logged (who/what/when).
- Secrets: OVH API keys encrypted at rest (Fernet); only the Pi holds them.
1. OVH credentials (per account)
For each OVH account (yours, co-owner's), create API creds scoped tight:
Easiest: use the createToken page — it returns AK + AS + CK in one shot and lets you enter the exact method+path rows below: https://api.ovh.com/createToken/
Grant exactly these least-privilege rights (nothing broader — do not use /*):
| Method | Path | Used for |
|---|---|---|
| GET | /me |
"Test" button |
| GET | /v2/networkDefense/vac/event |
DDoS alerts (VAC attack events) |
| GET | /dedicated/server |
server list |
| GET | /dedicated/server/* |
server detail |
| GET | /ip |
IP block list |
| GET | /ip/* |
all reads: firewall state, rules, mitigation, GAME |
| POST | /ip/*/firewall |
enable firewall on an IP |
| POST | /ip/*/firewall/* |
create rule |
| PUT | /ip/*/firewall/* |
toggle firewall enabled |
| DELETE | /ip/*/firewall/* |
remove firewall / delete rule / edit (delete half) |
| POST | /ip/*/mitigation |
put an IP under mitigation |
| DELETE | /ip/*/mitigation/* |
remove an IP from mitigation |
| POST | /ip/*/game/* |
add GAME rule (/game/{ipOnGame}/rule) |
| PUT | /ip/*/game/* |
toggle GAME firewall mode |
| DELETE | /ip/*/game/* |
delete GAME rule |
| POST | /ip/*/reverse |
set reverse DNS (PTR) |
| DELETE | /ip/*/reverse/* |
remove reverse DNS |
| POST | /ip/*/mitigationProfiles |
create auto-mitigation profile |
| PUT | /ip/*/mitigationProfiles/* |
tune auto-mitigation timeout |
| DELETE | /ip/*/mitigationProfiles/* |
remove auto-mitigation profile |
Notes:
- Set Validity: Unlimited — otherwise the key expires and the app stops working.
- Rule edit needs no extra right: OVH rules are immutable, so the app does
delete-then-recreate via the existing
POST+DELETE /ip/*/firewall/*. - The
*wildcard spans slashes, so it matches CIDR blocks like1.2.3.0/24. - This key cannot reach reverse-DNS, IP move, or billing — firewall/anti-DDoS only.
- Endpoint =
ovh-eufor both FR and PL.
Co-owner generates their own creds for their own account and you add them in the app (Accounts tab). You never see each other's secrets in plaintext.
Option B (recommended): "Connect with OVH" SSO flow
Instead of pasting keys, register one OVH Application and let each user self-connect by logging into their own OVH account:
- Create the app once: https://api.ovh.com/createApp/ → put the
AppKey + AppSecret in backend
.envasOVH_APP_KEY/OVH_APP_SECRET. - Set
PUBLIC_BASE_URLto how the app is reached (the tunnel host in prod). - In the app: Accounts → Connect with OVH → enter a label → you're redirected to OVH's login, you approve the scoped rules, and the key is created + stored automatically. Co-owner does the same on their OVH account.
The scoped rules requested are defined in app/ovh_access.py (same least-privilege
set as the table above). Co-owner's OVH password never touches this app.
API reference (verify exact firewall field names here): https://api.eu.ovhcloud.com/console/?section=%2Fip&branch=v1
2. Backend (on the Pi)
cd backend
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
cp .env.example .env
# generate the two secrets and paste into .env:
.venv/bin/python -c "from cryptography.fernet import Fernet; print('VAULT_KEY=',Fernet.generate_key().decode())"
.venv/bin/python -c "import secrets; print('SESSION_SECRET=',secrets.token_urlsafe(48))"
# set ADMIN_EMAIL in .env, then first run:
.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8080 --proxy-headers --forwarded-allow-ips 127.0.0.1
On first start it prints a one-time admin password to the log. Log in, change
it, enable 2FA. Then install the systemd unit (deploy/ovhfw.service) for 24/7.
3. Frontend build
cd frontend
npm install
npm run build # outputs frontend/dist/ — FastAPI serves it automatically
Dev mode (hot reload, proxies API to :8080): npm run dev → http://localhost:5173
4. nginx + cloudflared
sudo cp deploy/nginx.conf /etc/nginx/sites-available/ovhfw
sudo ln -s /etc/nginx/sites-available/ovhfw /etc/nginx/sites-enabled/ovhfw
sudo nginx -t && sudo systemctl reload nginx
Cloudflare tunnel: see deploy/cloudflared-config.example.yml. After
cloudflared tunnel route dns ovhfw fw.yourdomain.com, the app is reachable at
https://fw.yourdomain.com — gated by the app login + 2FA.
5. Desktop apps
See desktop/README.md. Set the tunnel host in desktop/src-tauri/tauri.conf.json,
then cargo tauri build on Linux (→ AppImage/deb) and on a Mac (→ dmg).
Updating (push new changes live)
Code changes are not auto-applied. After pulling/pushing changes to the Pi:
./deploy.sh # git pull → build frontend → install deps → restart service
- Frontend-only changes: FastAPI serves the rebuilt
frontend/dist/live — no restart needed; Vite hashes filenames so browsers fetch the new bundle on reload. - Backend changes: the restart in
deploy.shapplies them. - DB schema changes: on every start the app auto-migrates — adds any new
columns to existing tables (
app/db.py:_auto_migrate). No data loss, no manual SQL. (This is why you can keep iterating locally without resettingdata/app.db.)
Optional push-to-deploy: a GitHub Action can SSH to the Pi (via the tunnel or Tailscale)
and run ./deploy.sh on every push — ask if you want it wired.
Scope (v1)
| Feature | State |
|---|---|
| Network/Edge Firewall: create on IP, rules create/edit/delete, enable/disable | ✅ read+write |
| Anti-DDoS: mitigation add/remove, GAME rules | ✅ read+write |
| Multi-account + RBAC grants | ✅ |
| Login + TOTP 2FA + audit log | ✅ |
Per-server IP grouping (auto-list a server's IPs) is an easy follow-up.
Network/Edge Firewall rule fields
Verified against the OVH schema (reference/ovh_ip_schema.json). Up to 20 rules per IP,
lower sequence evaluated first. Stateless — remember to allow return traffic.
| Field | Values | Notes |
|---|---|---|
sequence |
0–19 | order; lower first |
action |
permit | deny |
|
protocol |
tcp udp icmp ipv4 gre esp ah |
|
source |
CIDR (e.g. 1.2.3.0/24) |
blank = any |
| source port | 80 or 1000-2000 |
single → sourcePort; range → sourcePortRange {from,to} |
| dest port | 80 or 1000-2000 |
single → destinationPort; range → destinationPortRange {from,to} |
| TCP option | established | syn + fragments |
tcp only; sent as tcpOption {option,fragments} |
| L3 packet length | from / to |
sent as l3PacketLength {from,to} (Range) |
Port ranges are supported on both source and destination — type 1000-2000 in the
port field and the app sends the …PortRange {from,to} variant; a bare number sends the
single-port variant. Rules are immutable in OVH, so editing = delete + recreate.
GAME firewall rules use a fixed protocol enum (incl. counterStrike2, rust, …) plus a
ports {from,to} range, under /ip/{ip}/game/{ipOnGame}/rule.
First-time firewall on a new IP
New OVH IPs (dedicated/VPS/GAME) ship with no firewall object. Flow in the app:
- Firewall tab → pick account + IP block → Enable firewall on an IP (auto-selects
the IP for
/32blocks; dropdown for small blocks) → creates the firewall object. - Add the first rule (works while the firewall is disabled).
- Enable the firewall — the toggle is gated until ≥1 rule exists.
Anti-DDoS note
OVH removed permanent mitigation mode — the permanent field and its PUT are
deprecated. The app only adds/removes IPs under (auto-)mitigation; no permanent toggle.
Security checklist
.envnever committed;VAULT_KEYbacked up separately from the DB.- Backend bound to
127.0.0.1only; tunnel is the sole ingress. - Every user has 2FA enabled (banner nags until they do).
- OVH consumer keys use the least-privilege rights table above (firewall/anti-DDoS only), not
/*, Validity = Unlimited. - DB +
VAULT_KEYon the NVMe SSD; encrypted off-box backup of the DB. - Pi kept patched; ideally on its own VLAN.