Pretty cool. Have you seen considerations regarding port knocking by Moxie Marlinspike? I think he raises some valid points: https://github.com/moxie0/knockknock
Instead of using UDP he reads the firewall log. Also he prevents replay attacks (even though his implementation is apparently not secure in this regard). Unfortunately his code is ancient and in Python 2, so a rust implementation would be awesome.
One of the reason why I wrote ruroco is, that I can run this from probably anywhere in the world, if I put the service on port 53, because thats DNS and that does not get blocked by any wifi whatsoever.
I used to use port knocking, but at some point found myself in a hotel where they blocked ALL ports, except TCP 80 and 443 (did not check UDP at the time).
My ssh port is on 80, so I can use all of my tools, even if the network I'm in blocks everything else.
I mean you can still use 53/udp, but the point is he doesn't start a service or sniff the interface with libpcap, because both of these increase the attack surface.
You are right, but if you are in a network that blocks every packet that is sent to any port which is not 80 or 443 your port knocking capabilities are very limited.
Ultimately reading firewall logs to do port knocking is most secure way, because - as you said - there is virtually no attack surface.
I would argue that port knocking is extremely inconvenient and does not work in every scenario. So for me it's a tradeoff between "ultimate" security and convenience.
Port knocking appeals to me because of how few bytes you have to send. But a system I’ve been thinking of (and surely a bunch of people before me) goes like this:
Instead of knocking on ports, send actual HTTP requests to different paths. Over TLS or just plain HTTP.
So where you’d port knock a sequence of ports here instead you send GET requests to some different, publicly known paths
GET /index.htm
GET /about_us.htm
GET /about_us.htm
GET /index.htm
GET /about_us.htm
GET /products.htm
You get the idea.
And now then the challenge is that if you’re on a network that does HTTP caching, it would interfere with this.
But we already have the well known cache-busting technique for that right, so
GET /css/main.css?ver=64729929
GET /js/bundle.js?ver=947367292
GET /js/bundle.js?ver=7483939
And so on. And version is for example current Unix time and is actually ignored in terms of “knocking”. Only the path matters.
Hah! I'd never seen this before, but in a fit of pique driven by ssh scanning one day I did a similar pattern on my OpenBSD router: I added a block in log to a high port in pf.conf, wired up a little shell script that did tcpdump on pflog0 and watched for a packet to come in, then added the IP to the allow port 22 table.
I knocked on the door three times, two of which were testing, and ended up throwing it all out in favor of wireguard & making SSH no longer listen on em0, which seems infinitely less silly.
I know its very minor, but are there ergonomic improvements possible to this setup besides shell aliases/functions around pairing `wg-up host && ssh host && wg-down host`?
I agree that ultimately, with wg in the kernel, this is a much simpler setup.
You don't really have to do that. Wireguard is very silent protocol. Even when you bring up the interface, unless you are sending anything to that interface, it will not redo the handshake. So you can keep it up all the time.
This is why you sometimes have to enable PersistentKeepalive on peers that are behind NAT and are calling in to the server. Without them keeping up the connection NAT would simply close it down and you wouldn't be able to connect.
From the OpenBSD perspective, I just populate /etc/hostname.wg0 on my laptop with my wg configuration ... and I can immediately `ssh router` at home or on the road :-)
IOW, why ever down the connection? Why not start your tunnel immediately when the network comes up and leave it running until the network goes down?
I was thinking about doing this to multiple different servers and thought they could all share the same vpn network address for simpler configuration but now that I think about it doing that might run into constant server-key-changed warnings from SSH.
Wireguard interfaces are _cheap and easy_ - there's no reason not to set up an interface for normal client traffic that sshd doesn't listen on, and an interface for just sshd with different ACLs and routing logic if you want.
> Well, tcpdump is explicitely not what you want to do in this case as Moxie points out
Why's that?
E: oh, if you aren't familiar with OpenBSD I might get the confusion – pflogd/the kernel (not me!) watches the actual network device and dumps to a file. So the actual "knocking" daemon I bodged together is one part running as a privsep user watching a log file and giving IPs to the other part which just adds to the pf table allowing access.
My threat model didn't include people who can fuck with pflog(4) to attack tcpdump(8) - I'm sure they're out there, and if they wanted to be they'd already be in my network (or already are).
ruroco DOES prevent replay attacks, by saving the deadline (which is in ns) in a blocklist. It does not matter if the deadline has "passed", the deadline is added to the blocklist as soon as the packet reaches the server and is deemed "valid". So each packet is only valid exacly ONCE
Instead of using UDP he reads the firewall log. Also he prevents replay attacks (even though his implementation is apparently not secure in this regard). Unfortunately his code is ancient and in Python 2, so a rust implementation would be awesome.