From e981659621d70cccc9810ca5be87e6a21ca03a71 Mon Sep 17 00:00:00 2001 From: Vladimir Buzalka Date: Fri, 19 Jun 2026 11:30:52 +0200 Subject: [PATCH] notebookvb --- WireGuard/NOTES.md | 54 +++++++++++++++++++++++++++ WireGuard/gen_clients.py | 55 ++++++++++++++++++++++++++++ WireGuard/rollback_unraid_wg.rsc | 8 ++++ WireGuard/rosrun.py | 30 +++++++++++++++ WireGuard/wg-clients/_peers_add.rsc | 3 ++ WireGuard/wg-clients/client2.conf | 11 ++++++ WireGuard/wg-clients/client2.png | Bin 0 -> 2630 bytes WireGuard/wg-clients/client3.conf | 11 ++++++ WireGuard/wg-clients/client3.png | Bin 0 -> 2704 bytes WireGuard/wg-clients/client4.conf | 11 ++++++ WireGuard/wg-clients/client4.png | Bin 0 -> 2761 bytes 11 files changed, 183 insertions(+) create mode 100644 WireGuard/NOTES.md create mode 100644 WireGuard/gen_clients.py create mode 100644 WireGuard/rollback_unraid_wg.rsc create mode 100644 WireGuard/rosrun.py create mode 100644 WireGuard/wg-clients/_peers_add.rsc create mode 100644 WireGuard/wg-clients/client2.conf create mode 100644 WireGuard/wg-clients/client2.png create mode 100644 WireGuard/wg-clients/client3.conf create mode 100644 WireGuard/wg-clients/client3.png create mode 100644 WireGuard/wg-clients/client4.conf create mode 100644 WireGuard/wg-clients/client4.png diff --git a/WireGuard/NOTES.md b/WireGuard/NOTES.md new file mode 100644 index 0000000..d21d924 --- /dev/null +++ b/WireGuard/NOTES.md @@ -0,0 +1,54 @@ +# WireGuard road-warrior na MikroTiku (router-hosted) + +Nastaveno 2026-06-18 podle runbooku `wireguard-mikrotik-runbook.md`. + +## Router +- **MikrotikFirewall** (hEX, RouterOS 7.19.6), LAN IP `192.168.1.2`, SSH port **22**. +- WAN = `pppoe-out1`, veřejná IP `78.80.38.51` (PPPoE, bere se jako statická). + +## DŮLEŽITÉ — proč port 51821, ne 51820 +Na routeru **už běží jiná WireGuard VPN na Unraidu** (`192.168.1.76`): NAT rule +„WireGuard to Unraid" DST-NATuje příchozí UDP **51820** na Unraid. Proto tahle +nová, **na routeru hostovaná** VPN běží na **UDP 51821** (51820 by se nikdy +nedostalo k routeru). Existující Unraid VPN ani tunel `10.253.0.0/24` nejsou dotčené. + +## Parametry této VPN +| | | +|---|---| +| WG rozhraní | `wg-vpn`, listen-port **51821** | +| Server public key | `CGGFHYR83W8IuTB46cJ49IuL/tL3w4yu3o0hQh0Cxwo=` | +| Tunelová síť | `10.10.10.0/24`, router `10.10.10.1` | +| Klienti | `10.10.10.2` (client2), `.3` (client3), `.4` (client4) | +| Endpoint | `78.80.38.51:51821` | +| Split tunel | AllowedIPs = `192.168.1.0/24` (jen LAN přes VPN) | +| DNS klientů | `192.168.1.2` (router) | + +## Přidané firewall pravidla (jen accept, nic nemazáno/nepřeřazeno) +- input: accept udp dst-port 51821 in-interface=pppoe-out1 „WireGuard in (router)" +- input: accept in-interface=wg-vpn „WG -> router (DNS/ping)" (DNS a ping na router z tunelu) +- forward: accept in-interface=wg-vpn „WG -> LAN" +Všechna vložena PŘED příslušné `drop` v daném chainu. +NAT hairpin **nepřidán** — LAN hosti mají router jako default gw, návratová cesta funguje. + +## Skripty +- `rosrun.py` — spouští RouterOS příkazy přes SSH. Creds z env: `ROS_HOST/ROS_PORT/ROS_USER/ROS_PASS`. + Pozn.: v Git Bash nutné `MSYS_NO_PATHCONV=1` a příkazy přes stdin (ne `--cmd`, mangluje `/...`). +- `gen_clients.py` — generuje klíče (wg.exe) + `.conf` + QR PNG do `wg-clients/`, a `_peers_add.rsc`. + +## Klientské konfigurace +`wg-clients/clientN.conf` (import na notebook) + `wg-clients/clientN.png` (QR pro mobilní app). +**Obsahují privátní klíče** — po rozdání na zařízení smaž, ať neleží zbytečně. + +## Test (jen zvenku, ne z LAN!) +Telefon na mobilních datech → naskenuj QR → ověř `ping 192.168.1.2`. Z LAN to +handshake neudělá (accept je vázán na in-interface=pppoe-out1, hairpin pro 51821 není). + +## Rollback +``` +/interface wireguard peers remove [find interface=wg-vpn] +/ip firewall filter remove [find comment="WG -> LAN"] +/ip firewall filter remove [find comment="WG -> router (DNS/ping)"] +/ip firewall filter remove [find comment="WireGuard in (router)"] +/ip address remove [find interface=wg-vpn] +/interface wireguard remove [find name=wg-vpn] +``` diff --git a/WireGuard/gen_clients.py b/WireGuard/gen_clients.py new file mode 100644 index 0000000..e517344 --- /dev/null +++ b/WireGuard/gen_clients.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Generate WireGuard road-warrior client configs + QR PNGs, and emit RouterOS peer-add commands.""" +import subprocess, pathlib, qrcode + +WG = r"C:\Program Files\WireGuard\wg" +SERVER_PUB = "CGGFHYR83W8IuTB46cJ49IuL/tL3w4yu3o0hQh0Cxwo=" +ENDPOINT = "78.80.38.51:51821" +LAN = "192.168.1.0/24" # split tunnel -> only LAN goes through VPN +DNS = "192.168.1.2" # router LAN IP + +CLIENTS = [2, 3, 4] +outdir = pathlib.Path(__file__).resolve().parent / "wg-clients" +outdir.mkdir(exist_ok=True) + + +def wg(*args, inp=None): + return subprocess.run([WG, *args], input=inp, capture_output=True, + text=True, check=True).stdout.strip() + + +peer_cmds = [] +for i in CLIENTS: + name = f"client{i}" + priv = wg("genkey") + pub = wg("pubkey", inp=priv) + psk = wg("genpsk") + + conf = f"""[Interface] +PrivateKey = {priv} +Address = 10.10.10.{i}/32 +DNS = {DNS} + +[Peer] +PublicKey = {SERVER_PUB} +PresharedKey = {psk} +AllowedIPs = {LAN} +Endpoint = {ENDPOINT} +PersistentKeepalive = 25 +""" + (outdir / f"{name}.conf").write_text(conf, encoding="utf-8") + + img = qrcode.make(conf) + img.save(outdir / f"{name}.png") + + peer_cmds.append( + f'/interface wireguard peers add interface=wg-vpn ' + f'public-key="{pub}" preshared-key="{psk}" ' + f'allowed-address=10.10.10.{i}/32 comment="{name}"' + ) + print(f"[ok] {name}: pub={pub} -> {name}.conf, {name}.png") + +(outdir / "_peers_add.rsc").write_text("\n".join(peer_cmds) + "\n", encoding="utf-8") +print("\n--- RouterOS peer-add commands written to wg-clients/_peers_add.rsc ---") +for c in peer_cmds: + print(c) diff --git a/WireGuard/rollback_unraid_wg.rsc b/WireGuard/rollback_unraid_wg.rsc new file mode 100644 index 0000000..216b704 --- /dev/null +++ b/WireGuard/rollback_unraid_wg.rsc @@ -0,0 +1,8 @@ +# ROLLBACK — obnova Unraid WireGuard objektů na routeru MikrotikFirewall +# Odstraněno 2026-06-18 na žádost uživatele. Spusť tyto příkazy pro obnovu. +/ip firewall nat add chain=dstnat action=dst-nat to-addresses=192.168.1.76 to-ports=51820 protocol=udp in-interface=pppoe-out1 dst-port=51820 comment="WireGuard to Unraid" +/ip firewall filter add chain=input action=accept protocol=udp in-interface=pppoe-out1 dst-port=51820 comment="Allow WireGuard" +/ip firewall filter add chain=forward action=accept src-address=10.253.0.0/24 comment="Allow VPN to LAN" +/ip firewall filter add chain=forward action=accept dst-address=10.253.0.0/24 comment="Allow LAN to VPN" +/ip route add dst-address=10.253.0.0/24 gateway=192.168.1.76 comment="Route to WireGuard VPN via Unraid" +# Pozn.: po obnově zkontroluj pořadí filter pravidel (accept musí být PŘED drop). diff --git a/WireGuard/rosrun.py b/WireGuard/rosrun.py new file mode 100644 index 0000000..a90d71d --- /dev/null +++ b/WireGuard/rosrun.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +"""Run RouterOS commands over SSH. Creds from env: ROS_HOST, ROS_PORT, ROS_USER, ROS_PASS. +Commands: one per line on stdin, or via --cmd. Prints output per command.""" +import os, sys, paramiko + +host = os.environ["ROS_HOST"] +port = int(os.environ.get("ROS_PORT", "22")) +user = os.environ["ROS_USER"] +pw = os.environ["ROS_PASS"] + +cmds = [] +if "--cmd" in sys.argv: + cmds = [sys.argv[sys.argv.index("--cmd") + 1]] +else: + cmds = [l.rstrip("\n") for l in sys.stdin if l.strip()] + +cli = paramiko.SSHClient() +cli.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +cli.connect(host, port=port, username=user, password=pw, + look_for_keys=False, allow_agent=False, timeout=20) + +for c in cmds: + print(f"\n===== CMD: {c}") + stdin, stdout, stderr = cli.exec_command(c, timeout=30) + out = stdout.read().decode("utf-8", "replace") + err = stderr.read().decode("utf-8", "replace") + sys.stdout.write(out) + if err.strip(): + sys.stdout.write("--- stderr ---\n" + err) +cli.close() diff --git a/WireGuard/wg-clients/_peers_add.rsc b/WireGuard/wg-clients/_peers_add.rsc new file mode 100644 index 0000000..5b2c21b --- /dev/null +++ b/WireGuard/wg-clients/_peers_add.rsc @@ -0,0 +1,3 @@ +/interface wireguard peers add interface=wg-vpn public-key="nToZ1GzONgfW1ve3O1WeEpGbgzUMhDVKE7qrD/Jc23c=" preshared-key="Y6eHm6MbLa+tyleSgwbPc8oJqLZkXZkMEUJZDU7f5kg=" allowed-address=10.10.10.2/32 comment="client2" +/interface wireguard peers add interface=wg-vpn public-key="tqA98HvVupGGYpR1PUe7/j9DO8MtaNP3Fh5tkpqgqD0=" preshared-key="94TmjBE+mTZi3KDy/tWefq/wXPpvmBtjPlX/LZnAKbE=" allowed-address=10.10.10.3/32 comment="client3" +/interface wireguard peers add interface=wg-vpn public-key="j/3kzNQ6vmUL4xFmqq5PL6Qf1xVWPzVWEXoOkBIDxFk=" preshared-key="pHR1441168wSrjlLZ2E44J4WrHpLRuWdjfsNHk23CQ8=" allowed-address=10.10.10.4/32 comment="client4" diff --git a/WireGuard/wg-clients/client2.conf b/WireGuard/wg-clients/client2.conf new file mode 100644 index 0000000..a8f67e6 --- /dev/null +++ b/WireGuard/wg-clients/client2.conf @@ -0,0 +1,11 @@ +[Interface] +PrivateKey = YPvh0rKU+xi82eQftBucCnuQzZNqk9jOHLwfEH0wsGk= +Address = 10.10.10.2/32 +DNS = 192.168.1.2 + +[Peer] +PublicKey = CGGFHYR83W8IuTB46cJ49IuL/tL3w4yu3o0hQh0Cxwo= +PresharedKey = Y6eHm6MbLa+tyleSgwbPc8oJqLZkXZkMEUJZDU7f5kg= +AllowedIPs = 192.168.1.0/24 +Endpoint = 78.80.38.51:51821 +PersistentKeepalive = 25 diff --git a/WireGuard/wg-clients/client2.png b/WireGuard/wg-clients/client2.png new file mode 100644 index 0000000000000000000000000000000000000000..4ec397971be5c45c8d7433d716c9e74f372f6d43 GIT binary patch literal 2630 zcmZuzc|6o<7@r(D7Ndw&l9Z!mMwA?Dgbpz>XkFu4k(47xHG?!=eBHK-~Pw zMOy%XpKo{V+c^L%w^$wm07S*jFPi)op8MI=`SuBO$&OTk@9B*~i#9{L%oF0MBGQ9R zHKPVo&NW3F=)j3HgqD{!-t}2Q0?;JfynW>}OFKKG0$WU1GxE!%(BnlB;nstE6_27c zjw?d{XN%Uv*fyDal?pXaKyaBfbIm%{6sSdo5o@h`A-pGK!;Pd%aan~xz6_p!jO%(k zcI~`tqv}0bjMW;JZI_gZ|IUq|XR5Yw3XAHg@|CUnQ24nv^ONQ>BwHY2&blxqWMaQN3R?qW?q}+aHp0Cv`<7)qRb^KyvNfV>q+^!5#XU zu#`d;A#5?{vN1+783+7YWe^0hiJ2eI?Z2RlU?OX_`bS85|(}sFJEz=qqH#$rZzSg^VvN5I!rmd zIHi07)W)7sp}`a*nTAq63l2R8#ByU_-u);ANXC`Pu44@F^KQf5W28&(hLmscu{_6+J}aT< zGRr|43Y74Uq$|3?&sQPP7~QYXH}4TBcVf79>h*iX1km`W)TFN%JBe@DkbQ^zd{#B1s3kHs}91 zC}}C^2)WQR)=>|ASg`ca&c|+-uBk!Log139EQUt_eBsC$M>pqzNI_Sy+P}g#z_(+s zHjwgTjD0T|V-&-^Nl9<-2l%`@y((7KU%VQXU52NmL>nbO7>JM3F!3QyPuxZis|@R3 zCU05?xH?l11{cZ06c-rm$A!Wd-*0gy^L{r6E5N&Fn_?Wpze+E3R(;Z+W=TD*R#JpY z-jn27hd$R|;?$E0#ajm6+eAtaHOPaKv02AGytn5?|k}o7=%6 z4Em3FIFp71&-Ur--IQ<}7V=FyT3Pw|dYOYqf}iLvMiEos4KqXm<+9H_LQQL0X9`cL ztNNK8-=}y zk6A$(?{o7;U(67E4Wb{K zv_Z0+lKu*rhwPQ*GacP5PwyVwD1d&@D9LgqxB7Nm*AfkJ`T@wWlYM)C`P5`RL2l*I z-4GPa0Z8&6K>f(*qL}@$IFwPlYEKypGZt%-99UPRiE~;=jOJBD*h5pD@h6KiNJ}oQ z2q@VCJArc=)>fr`N`+f{{qDbdV@PUvb!q~2q4uRmIgiD zL*sNTQT`ShCvh^1hP#0WAFbPVvM>i$=n>L7$eY8nK`qWiyecn&ftY1Nd~hvdSqGO1 zwUt}&H7rR&-XIl;M&glXM;b;IkRA1I%f{#hxjnggG?cy!{%j*P$>_~#H9cde&u*sbh*mga&bj-ZSV_&uRwY?Oj=&1KzyX6~oQ!(6^SU ztfwhC8(|+*y8bY|kP&vIhO(y65q;?L|_9{2-=se*8?j6mb%F-2nT3LD~fh#S!s(SpM_75A$?&YIm zgJ<492S-GlO>|GGH;U&5$}5A0)4Y1N2Yw?$m1qPi5#I2(+Be2|J8<2D(H-O5X@ghS z;Ybr^ck6MR=GL2I6I6^zczX4-J-$?GX%o%{eakLQRb%-(yGta>qZcANDLhZb&16eS z0mR8PY;0#@9b;859Z05CvQRR-EY(aEHIrQ0TXoulnQOhuG}HS-78aYaw$ole^s2_; zg676wqQy48mF5&eSAbh|8^zo$dgWf7wJN|>$8>9@hfQ0&T0(U`+Kit>8vg4B;b1~% z7GpYx+DAF139BvIw4bU8VcVs_&0fq9**CkQZrz4*ffpimLfMyq%!)&FKURUA@=*XwpEs^EnIil zml3^15kPBim@7>7Z8$Sxj*y;}9iYxRZB~0lpFtn(!bseE zU~1CAH@{{C-ly-)V818xe-kVDrm$nYyryW%^Wgv43N*Y%c^xIl+$z%99T3egSzRQW H{{HYEvE&xa literal 0 HcmV?d00001 diff --git a/WireGuard/wg-clients/client3.conf b/WireGuard/wg-clients/client3.conf new file mode 100644 index 0000000..dda89ef --- /dev/null +++ b/WireGuard/wg-clients/client3.conf @@ -0,0 +1,11 @@ +[Interface] +PrivateKey = 8JFWJp/zvoRYl7w2Jon0Xv+9YidiguiC26qGbr4ozlg= +Address = 10.10.10.3/32 +DNS = 192.168.1.2 + +[Peer] +PublicKey = CGGFHYR83W8IuTB46cJ49IuL/tL3w4yu3o0hQh0Cxwo= +PresharedKey = 94TmjBE+mTZi3KDy/tWefq/wXPpvmBtjPlX/LZnAKbE= +AllowedIPs = 192.168.1.0/24 +Endpoint = 78.80.38.51:51821 +PersistentKeepalive = 25 diff --git a/WireGuard/wg-clients/client3.png b/WireGuard/wg-clients/client3.png new file mode 100644 index 0000000000000000000000000000000000000000..62dac88869473635caaba48cef9aa8887dcdfe3e GIT binary patch literal 2704 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 literal 0 HcmV?d00001