Compare commits

..

4 Commits

7 changed files with 240 additions and 56 deletions

View File

@ -2,35 +2,18 @@
import { onMount } from 'svelte';
import { formatDate } from './datefmt.js';
import { makeSlug } from '$lib/slug.js';
import Link from './Link.svelte';
export { Link as a };
</script>
<script>
export let title, date;
// deal with sidenote collisions
onMount(async () => {
const minNoteGap = 15;
let sidenotes = Array.from(document.getElementsByClassName('sidenote'));
var prev = 0;
for (var i=0; i < sidenotes.length; i++) {
let note = sidenotes[i];
let {y, height} = note.getBoundingClientRect();
y += window.scrollY; // getBoundingClientRect is relative to viewport
if (y < prev + minNoteGap) {
note.style.top = `${prev + minNoteGap}px`;
prev = prev + minNoteGap + height;
}
else {
prev = y + height;
}
}
})
</script>
<svelte:head>
<title>{title}</title>
<link rel="stylesheet" href="/prism-dracula.css" />
</svelte:head>
<div id="post">

View File

@ -40,6 +40,7 @@
position: absolute;
left: calc(50vw + var(--content-width) / 2 + 1rem);
max-width: 12rem;
hyphens: auto;
}
}
@ -86,12 +87,63 @@
}
</style>
<script>
let id = Math.random().toString().slice(2);
<script context="module">
import { browser } from '$app/env';
var sidenotes = {};
function tileNotes() {
// find and fix collisions between sidenotes
const minNoteGap = 15;
var prevBottom = 0;
Object.values(sidenotes).forEach(s => {
if (window.getComputedStyle(s.note).position === 'fixed') {
// fixed position means we are in mobile territory,
// so get rid of the overflow stuff
s.note.style.top = '';
return;
}
let labelTop = s.label.getBoundingClientRect().y + window.scrollY;
let noteHeight = s.note.getBoundingClientRect().height;
if (labelTop < prevBottom + minNoteGap) {
// there is a collision
s.note.style.top = `${prevBottom + minNoteGap}px`;
prevBottom = prevBottom + minNoteGap + noteHeight;
}
else {
// no collision, but these don't quite match otherwise
s.note.style.top = `${labelTop}px`;
prevBottom = labelTop + noteHeight;
}
})
}
if (browser) {
window.addEventListener('resize', tileNotes);
}
</script>
<label for={id} class="counter"></label>
<script>
import { onMount } from 'svelte';
const id = Math.random().toString().slice(2);
sidenotes[id] = {mounted: false};
let label;
let note;
onMount(async () => {
sidenotes[id] = {mounted: true, label, note};
if (Object.values(sidenotes).every(n => n.mounted)) {
// all sidenotes have been mounted, now we can fix the collisions
tileNotes();
}
return () => sidenotes[id].mounted = false;
});
</script>
<label bind:this={label} for={id} class="counter"></label>
<input type="checkbox" class="sidenote-toggle" {id}/>
<span class="sidenote">
<span bind:this={note} class="sidenote">
<slot></slot>
</span>

View File

@ -0,0 +1,43 @@
---
title: Exposing Docker Containers to your LAN
description: If, for some strange reason, you should want to do such a thing.
date: 2022-03-21
---
<script>
import Sidenote from '$lib/Sidenote.svelte';
</script>
A while back I had occasion to make a number of docker containers directly accessible on the LAN, i.e. without all the usual ceremony of port-forwardism that Docker requires. In retrospect I made it a lot more complicated than it had to be, but I wanted to document the process anyway because you never know when that sort of thing might come in handy.
## Aside: You Probably Don't Want This
In my case, the reason for doing this was so that I could expose multiple difference services that all wanted to bind the same port. In other words, given that I was going to be hosting more than one HTTP-based application, I didn't want to have to remember (and type out all the time) a bunch of different ports to distinguish between the services I wanted to talk to. DNS is great, but it only points to IP addresses<Sidenote>Well, SRV records can include ports, but browsers don't pay attention to those.</Sidenote>, after all.
That said, had I only realized it at the time, there's a much better way to accomplish this than exposing entire containers to the LAN, and much less... questionable from a security standpoint: Just bind multiple IPs on the host. Docker allows you to specify what IP address to bind when forwarding a port to a container, so you can forward e.g. 192.168.50.21:80 to App 1, and 192.168.50.22:80 to App 2, and neither the apps nor the users need ever worry their pretty little heads about a thing. This is better than exposing the container directly - containerized applications generally expect to be pretty isolated from a networking point of view, with external traffic only hitting the one or two ports that they specify as their window to the outside world. So if some packaged application has to run its own Redis server<Sidenote>Because some people just can't help jamming Redis into every app they write, it's like a spinal reflex or something.</Sidenote>, it might not take the extra step of only binding to localhost, and congratulations now anyone on the LAN can read your session cookies or whatever.<Sidenote>Alternatively you can do what I did: Set up a _shared_ Redis server for a _bunch_ of different applications, in Docker of course, and then _knowingly_ expose that to the entire LAN, and damn the torpedoes. I cannot legally recommend this course of action.</Sidenote>
The caveat here is of course that you need to be sure the IP addresses you use aren't going to be stolen out from under you by somebody's iPad or something next time it connects to the network. This is easy if you control the DHCP server, and either easy or impossible if you don't. For reasons that I've never fully understood, but _probably_ boil down to leaving room for people to do exactly this sort of thing, many standard DHCP configurations assign IPs from just a portion of the available range. .100 is a common start point in a /24 network, so you can usually expect that .2-.99 will be available for you to work your will upon.
The worse solution (exposing containers directly to the LAN) has this same caveat, so it's just worse in every way, there's really no advantage except that _maybe_ it's lower-overhead, since not as much forwarding of packets needs to take place. So yeah, probably just don't unless your containerized application _really needs_ Layer 2 access to the network, like it's an intrusion detection system and needs keep an eye on broadcast traffic or something.
## Anyway
With that all out of the way, having hopefully convinced you that this is almost never a good idea, here's how to do it:
```
docker network create \\
-d ipvlan \\
--subnet 192.168.50.0/24 \\
--gateway 192.168.50.1 \\
-o parent=eth0 \\
lan
docker run --network lan --ip 192.168.50.24 some/image:version
```
That's it! You're done, congratulations. (Obviously `--subnet`, `--gateway`, and `--parent` should be fed values appropriate to your network.)
This isn't actually what the first draft of this post said. Initially I was going to suggest using the `macvlan` driver, and then go into a whole spiel about how if you do this and you also want the host to be able to talk to its containers, then you have to create _another_ (non-Docker-managed) `macvlan` interface in `bridge` mode, then route an IP range or two via that interface, as described [here](https://blog.oddbit.com/post/2018-03-12-using-docker-macvlan-networks/).
`ipvlan` is a lot easier, though, and gives you almost exactly the same result. The only difference is that with `macvlan` Docker will actually make up a MAC address for the virtual interface and respond to ARP queries and so on with that. With `ipvlan` it just uses the host MAC. My suspicion is that this is probably another argument _for_ `ipvlan`, as I think I remember reading that multiple MAC addresses on one physical interface is considered a Bad Sign by some network watchdog types of things.
So there you have it. You can dump containers on your LAN, and they will (from a networking standpoint) behave as if they were their own machines. But you probably don't want to.

View File

@ -33,7 +33,7 @@ So for us, the boxes that a VPN needed to tick were:
## Interlude: Wireguard
If you've been following the state of the art in VPNery for the last few years, then you've heard of [Wireguard](https://wireguard.com). It first started making serious waves (to my knowledge) in 2018, when Linus Torvalds referred to it as a "work of art" (as compared to OpenVPN and IPSec) on the Linux kernel mailing list. Given Torvalds' reputation for acerbic comments regarding code quality, the fact that he was referring to _someone else's code_ as a "work of art" raised a few eyebrows. And so when the fullness of time had come to pass, Wireguard was adopted into the mainline Linux kernel, and Jason A. Donenfeld became the herald of the new Golden Age of Networking.
If you've been following the state of the art in VPNery for the last few years, then you've heard of [Wireguard](https://wireguard.com). It first started making serious waves (to my knowledge) in 2018, when Linus Torvalds referred to it as a "work of art" (as compared to OpenVPN and IPSec) on the Linux kernel mailing list. Given Torvalds' reputation for acerbic comments regarding code quality, the fact that he was referring to _someone else's code_ as a "work of art" raised a few eyebrows. One thing led to another, eventually Wireguard was adopted into the mainline Linux kernel, and Jason A. Donenfeld became the herald of the new Golden Age of Networking.
Wireguard is relevant to our discussion for being an encrypted tunnel protocol that Works Really Well, which is why at least three of the options I've looked at are based on it. I say "based on", however, because Wireguard is _not_ a mesh VPN on its own. By itself, Wireguard gives you nothing more than an encrypted tunnel between two points. It's fast and low-latency and (can be) in-kernel so it's very low-overhead, and the connections are all secured with public/private keypairs like SSH. Also like SSH, however, it gives you exactly zero help when it comes to distributing those keys, and if you're looking for some form of automatic peer discovery you're barking up the wrong tree.
@ -81,13 +81,13 @@ In theory you could solve this by allowing a single device to have multiple IPs
In conclusion, I'm conflicted. There's a lot to like about Innernet, and I'm interested to see where they take it as time goes on, but I find myself disagreeing just a little too much with some of the fundamental design choices. I may still end up trying it out some day, since setting up a new VPN for my personal fleet of network-connected thingies is my idea of a fun weekend, but I doubt I'll ever use it seriously unless there's some signficant change in how access control works.
Oh yeah, and there's no Windows client as yet. Probably won't be using it until it gets one, unless the Year of Linux on the Desktop arrives sooner than anticipated.
Oh yeah, and there's no Windows client as yet. Hard to sell switching your whole workforce to Linux just so you can use a cool VPN thingy.
### Cloudflare One
Ok, I'm cheating a little bit. [Cloudflare One](https://www.cloudflare.com/cloudflare-one/) technically isn't a mesh VPN, because it always routes your traffic through a Cloudflare gateway, rather than establishing direct links between devices and letting them do the communicating. I'm including it here anyway, because the _result_ is pretty comparable to what you get from these mesh VPNs: A logically "flat" network in which any node can communicate with any other node, subject to centrally-administered access control rules. It even gets you _most_ of the latency and throughput advantages you'd get from a true mesh VPN, because Cloudflare's edge is basically everywhere and its capacity is effectively infinite, as far as the lowly user is concerned.
It's surprisingly inexpensive, as well, with a free tier for up to 50 users, a $7/user/month tier for intermediate cases, and a "call us for pricing" option if you tend to use scientific notation when you talk about your company's market cap. We ended up deciding against it anyway, largely because of some anecdotal claims about its user-friendliness being not-so-great, and the fact that... well, Cloudflare already gets their greasy paws on something like 15% of internet traffic as it stands, and do we really want to contribute to that?<Sidenote>Not that I have anything against Cloudflare, mind. They seem great so far. They just give me the same feeling as 2010-era Google, and look how that turned out.</Sidenote>
It's surprisingly inexpensive, as well, with a free tier for up to 50 users, a $7/user/month tier for intermediate cases, and a "call us for pricing" option if you tend to use scientific notation when you talk about your company's market cap. We ended up deciding against it anyway, largely because of some anecdotal claims about its user-friendliness being not-so-great, and the fact that... well, Cloudflare already gets their greasy paws<Sidenote>He said, on the blog site hosted behind Cloudflare's CDN.</Sidenote> on something like 15% of internet traffic as it stands, and do we really want to contribute to that?<Sidenote>Not that I have anything against Cloudflare, mind. They seem great so far. They just give me the same feeling as 2010-era Google, and look how that turned out.</Sidenote>
Also, the one place where you'd feel the lack of true mesh-ness would be LAN communication, which was actually a concern for us. Proper mesh VPNs can detect when two clients are on the same LAN and route their traffic accordingly, so lower latency, higher throughput, yadda yadda. As far as I can tell, Cloudflare's needs every packet to pass through the Cloudflare edge (aka "the internet"), meaning it turns LAN hops into WAN hops. Probably not a big deal for their customers, since this product is pretty clearly targeting Proper Enterprise types, and they undoubtedly have built-up layers of LAN cruft that you couldn't dig your way out of with a backhoe and so wouldn't be using it within their LAN anyway. A slightly bigger deal for us, since "route even LAN traffic through the VPN so we can enforce ACLs" was one of our stated goals.
@ -123,7 +123,7 @@ Of course, it's not _perfect_. What ever is? I have a few (minor) nitpicks:
*Usermode Wireguard:* Obviously this currently only applies to Linux (and maybe BSD?) as far as I'm aware. Still, it would be nice if Tailscale could make use of kernel-mode Wireguard where available, since otherwise you're leaving throughput on the table. For example, between two fairly beefy machines I get about 680 Mb/s throughput when testing with iPerf. Between one beefy machine and one Synology NAS with a wimpy CPU, I get about 300. Obviously the extent to which this matters depends on what you're trying to do, and it's more than fast enough for most use cases. It just bugs me that it could be better.
*Data Sovereignty:* (Network sovereignty?) Different people will weight this one differently, but at the end of the day it's true that Tailscale runs a coordination server that is responsible for telling your network who's in it and what kind of access they get. If they decide to add an invisible node that can talk to any of your devices on any port, there's not really anything you can do about it.<Sidenote>Note that this still doesn't mean they can eavsedrop on network traffic between two nodes you _do_ control. Even if you can't make NAT traversal work and end up using a realy, the actual network flows are encrypted with Wireguard. Effectively, each packet is encrypted with its destination's public key. And since private keys are generated on the client, the control server has no ability to decrypt them.</Sidenote> It's not quite as much control over your infrastructure as a third-party SSO service gets, but it's up there. Oh, and I don't think it's officially mentioned on their site, but I've seen comments from Tailscale employees that they can do an on-premise control server for big enough enterprise installs.
*Data Sovereignty:* (Network sovereignty?) Different people will weight this one differently, but at the end of the day it's true that Tailscale runs a coordination server that is responsible for telling your network who's in it and what kind of access they get. If they decide to add an invisible node that can talk to any of your devices on any port, there's not really anything you can do about it.<Sidenote>Note that this still doesn't mean they can eavsedrop on network traffic between two nodes you _do_ control. Even if you can't make NAT traversal work and end up using a relay, the actual network flows are encrypted with Wireguard. Effectively, each packet is encrypted with its destination's public key. And since private keys are generated on the client, the control server has no ability to decrypt them.</Sidenote> It's not quite as much control over your infrastructure as a third-party SSO service gets, but it's up there. Oh, and I don't think it's officially mentioned on their site, but I've seen comments from Tailscale employees that they can do an on-premise control server for big enough enterprise installs.
### Headscale
@ -155,27 +155,16 @@ Much like the above, just something that showed up while I was looking around. I
### VPNCloud
VPNCloud is a little more fully-featured, like the bigger players I've mentioned. It doesn't seem to do access control, so it's not a true contender for our use-case, but it does look like it works fairly well for what it does do. Their site claims that they've gotten multiple gigabits of throughput between m5.large AWS instances (so, not terribly beefy) which is better than pretty much anything else I've seen other than vanilla Wireguard.
VPNCloud is a little more fully-featured, like the bigger players I've mentioned. It doesn't seem to do access control, so it's not a true contender for our use-case, but it does look like it works fairly well for what it does do. Their site claims that they've gotten multiple gigabits of throughput between m5.large AWS instances (so, not terribly beefy) which is better than pretty much anything else I've seen other than vanilla Wireguard.
### Netbird
The first time I ran across [this one](https://netbird.io), it was called "Wiretrustee". A change for the better, I think. It looks to be pretty much exactly "open-source Tailscale", so my guess is it will entirely live or die by how well it executes on that. Obviously Tailscale is great, and Headscale proves that there are people who would like to run the control plane themselves, so there's a market for them. Unfortunately it looks like their monetization scheme is "be Tailscale" (i.e. run a hosted version and charge for anything over a single user), at which point why wouldn't you just use Tailscale?
Tailscale (/headscale)
ZeroTier
- Can self-host, but you lose the web interface
Nebula
- not sure I like the way you specify security groups
Netmaker
- does it even have acl's?
Innernet
- CIDR-based, which I don't like
Tinc
- Why hasn't it taken off before now
Cloudflare Zero Trust Services
- no idea
### And More
There's a handy [list](https://github.com/HarvsG/WireGuardMeshes) on Github of Wireguard mesh things, some of which I've already mentioned. And I'm sure even more will continue to pop up like weeds, since everybody seems to want one and a surprisingly large number of people are happy to just sit down and write their own. I guess that's proof that Wireguard made good choices about what problems to address and what to ignore - not an easy task, especially the latter.
tinc
peervpn
freelan
vpncloud
https://github.com/HarvsG/WireGuardMeshes
## Where Do We Go From Here
It's an exciting time in the world of networking. The Tailscale people talk a lot about this on their blog, because of course they do, but the advent of high-performance, low-overhead VPNery has opened up some pretty interesting possibilities in the world of how we interact with computers. Most excitingly it promises something of a return to the Good Old LAN Days, where every device on the network was trusted by default and no one ever worried about things like authentication and encryption, because why would anyone want to do anything unpleasant to your computer? The Internet made that position untenable, but Tailscale and its ilk hope to bring it back again, With some added benefits from modern cryptography. I can't say whether they'll succeed, but if nothing else it's looking like a fun ride.

121
static/prism-dracula.css Normal file
View File

@ -0,0 +1,121 @@
/**
* Dracula Theme originally by Zeno Rocha [@zenorocha]
* https://draculatheme.com/
*
* Ported for PrismJS by Albert Vallverdu [@byverdu]
*/
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
background: none;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 1em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #282a36;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #6272a4;
}
.token.punctuation {
color: #f8f8f2;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: #ff79c6;
}
.token.boolean,
.token.number {
color: #bd93f9;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #50fa7b;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #f8f8f2;
}
.token.atrule,
.token.attr-value,
.token.function,
.token.class-name {
color: #f1fa8c;
}
.token.keyword {
color: #8be9fd;
}
.token.regex,
.token.important {
color: #ffb86c;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@ -12,6 +12,7 @@ html {
font-size: 20px;
line-height: 1.3;
letter-spacing: -0.005em;
color: #1e1e1e;
}
body {
@ -63,20 +64,12 @@ code {
background: #eee;
border-radius: 0.2rem;
font-family: Consolas, monospace;
font-size: 0.85rem;
padding: 0 0.15rem;
}
pre {
padding: 0.5rem;
line-height: 1.1;
border-radius: 0.15rem;
font-size: 0.8rem;
padding: 0.05rem 0.2rem 0.1rem;
}
pre > code {
padding: 0;
font-size: 0.8rem;
background-color: transparent;
}
/* TESTING */

View File

@ -16,6 +16,9 @@ const config = {
kit: {
// hydrate the <div id="svelte"> element in src/app.html
adapter: staticAdapter(),
prerender: {
default: true,
},
}
};