mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-30 00:19:30 +00:00
Merge feature/498-nm-keyfile : NetworkManager keyfile export for WG R3 (ref #498)
This commit is contained in:
commit
6812dfb0f6
|
|
@ -1000,6 +1000,49 @@ async def wg_profile_new(request: Request) -> Response:
|
|||
)
|
||||
|
||||
|
||||
@router.get("/wg/profile/new.nmconnection")
|
||||
async def wg_profile_new_nmconnection(request: Request) -> Response:
|
||||
"""Phase 7 (#498) — same fresh WG profile but in NetworkManager
|
||||
keyfile format. Cosmic / GNOME / KDE's nmcli GUI fail to import
|
||||
a plain wg-quick .conf because they store private-key as an
|
||||
"agent-owned" secret (`private-key-flags=1`) and refuse to bring
|
||||
up the tunnel without --ask.
|
||||
|
||||
This endpoint emits a native .nmconnection : drop it into
|
||||
/etc/NetworkManager/system-connections/, chmod 0600, then
|
||||
`nmcli connection reload` — the profile is ready to click in the
|
||||
Network panel.
|
||||
|
||||
sudo install -m 0600 village3b-toolbox.nmconnection \
|
||||
/etc/NetworkManager/system-connections/
|
||||
sudo nmcli connection reload
|
||||
nmcli connection up village3b-toolbox
|
||||
"""
|
||||
try:
|
||||
from . import wg as _wg
|
||||
except ImportError:
|
||||
raise HTTPException(503, "WG module not available (Phase 6 not provisioned)")
|
||||
profile = _wg.generate_client_profile(client_label=request.headers.get("user-agent", "")[:60])
|
||||
|
||||
import hashlib as _h
|
||||
wg_hash = _h.sha256(profile["client_pubkey"].encode()).hexdigest()[:16]
|
||||
try:
|
||||
store.upsert_client(wg_hash, profile["client_ip"], level="r3")
|
||||
except Exception as e:
|
||||
log.warning("wg peer upsert failed for %s: %s", wg_hash, e)
|
||||
|
||||
return Response(
|
||||
content=profile["nm_text"],
|
||||
media_type="text/plain; charset=utf-8",
|
||||
headers={
|
||||
"Content-Disposition": "attachment; filename=village3b-toolbox.nmconnection",
|
||||
"X-Client-PubKey": profile["client_pubkey"],
|
||||
"X-Client-IP": profile["client_ip"],
|
||||
"X-Client-Hash": wg_hash,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/wg/qr.png")
|
||||
async def wg_qr(request: Request) -> Response:
|
||||
"""QR code encoding the WG profile .conf — scannable by the iOS WG app."""
|
||||
|
|
|
|||
|
|
@ -128,6 +128,20 @@ def generate_client_profile(client_label: str | None = None) -> dict:
|
|||
f"PersistentKeepalive = 25\n"
|
||||
)
|
||||
|
||||
# Phase 7 (#498) — NetworkManager keyfile alongside the standard
|
||||
# wg-quick .conf. The Cosmic / GNOME Network UI imports .conf files
|
||||
# via nmcli but stores the private key as an "agent-owned" secret by
|
||||
# default (private-key-flags=1) — which means nmcli refuses to bring
|
||||
# the tunnel up without --ask. A native .nmconnection keyfile drops
|
||||
# straight into /etc/NetworkManager/system-connections/ with the
|
||||
# right system-owned flag and lets the operator click "Connect".
|
||||
nm_text = _nm_keyfile(
|
||||
conn_id=(client_label or f"village3b-{client_ip}"),
|
||||
priv=priv,
|
||||
client_ip=client_ip,
|
||||
server_pub=server_pub,
|
||||
)
|
||||
|
||||
return {
|
||||
"client_privkey": priv,
|
||||
"client_pubkey": pub,
|
||||
|
|
@ -136,10 +150,61 @@ def generate_client_profile(client_label: str | None = None) -> dict:
|
|||
"endpoint": f"{WG_ENDPOINT}:{WG_PORT}",
|
||||
"dns": "10.99.1.1",
|
||||
"conf_text": conf_text,
|
||||
"nm_text": nm_text,
|
||||
"ca_pem": _ca_pem(),
|
||||
}
|
||||
|
||||
|
||||
def _nm_keyfile(*, conn_id: str, priv: str, client_ip: str, server_pub: str) -> str:
|
||||
"""Build a NetworkManager keyfile (.nmconnection) for the same peer.
|
||||
|
||||
Dropped into /etc/NetworkManager/system-connections/<conn_id>.nmconnection
|
||||
(chmod 0600, owner root:root). After `nmcli connection reload` the
|
||||
profile appears in the Network panel and connects in one click.
|
||||
|
||||
private-key-flags=0 means "system-owned" — the secret lives in the
|
||||
file itself, NM doesn't ask the user at connect time. That's the
|
||||
setting nmcli's default import is missing.
|
||||
"""
|
||||
import re as _re
|
||||
import uuid as _uuid
|
||||
# netlink iface name : [a-z0-9-_], max 15 chars. Slugify aggressively
|
||||
# so any User-Agent-derived label still produces a valid name.
|
||||
safe = _re.sub(r"[^a-z0-9_-]+", "-", conn_id.lower()).strip("-")[:13] or "village3b"
|
||||
iface = f"wg-{safe}"[:15]
|
||||
uuid = str(_uuid.uuid4())
|
||||
return (
|
||||
"[connection]\n"
|
||||
f"id={conn_id}\n"
|
||||
f"uuid={uuid}\n"
|
||||
"type=wireguard\n"
|
||||
f"interface-name={iface}\n"
|
||||
"autoconnect=false\n"
|
||||
"\n"
|
||||
"[wireguard]\n"
|
||||
f"private-key={priv}\n"
|
||||
"private-key-flags=0\n"
|
||||
"listen-port=0\n"
|
||||
"fwmark=0\n"
|
||||
"\n"
|
||||
f"[wireguard-peer.{server_pub}]\n"
|
||||
f"endpoint={WG_ENDPOINT}:{WG_PORT}\n"
|
||||
"persistent-keepalive=25\n"
|
||||
"allowed-ips=0.0.0.0/0;::/0;\n"
|
||||
"\n"
|
||||
"[ipv4]\n"
|
||||
f"address1={client_ip}/32\n"
|
||||
"dns=10.99.1.1;\n"
|
||||
"method=manual\n"
|
||||
"\n"
|
||||
"[ipv6]\n"
|
||||
"addr-gen-mode=default\n"
|
||||
"method=disabled\n"
|
||||
"\n"
|
||||
"[proxy]\n"
|
||||
)
|
||||
|
||||
|
||||
def revoke_client(client_pubkey: str) -> bool:
|
||||
"""Remove a client peer from the WG interface + peer DB."""
|
||||
peers = _load_peers()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user