Controlling Your Fate with OIDC and Tailscale
I think the urge to self host services in a way that makes it difficult, if not impossible, for a third party to take away your ability to use the service is an itch many of us in tech have encountered and tried to fulfill in some way or another. In my experience there are three approaches. First, one can opt for a third party provider to host the underlying server, with the freedom to install and operate whatever you want inside. This exchanges a great deal of this autonomy from third parties in return for convenience, which is a bit of a theme among the approaches. Next is utilising hardware within your own home, such as a Raspberry Pi or a spare computer, to do the same. And finally you have the approach of outright rejecting servers and going for services that run entirely on-device where possible.
For myself, I went with the second approach of hosting things using hardware in my home where possible, with a small amount of "cloud" mixed in mostly for redundancy or backups, and I've blogged about it before. Connecting it all together is Tailscale, and despite being a major piece, it still relied on a third party provider - Google. Rather unfortunate given they're a large company I'm trying to interact with less, but I didn't really have another option until Tailscale rolled out support for custom OIDC providers! The one stumbling block I had was in regards to getting a webfinger up and running, as I assumed the OIDC provider had to be hosted on the same domain. Thankfully, this isn't actually the case, and I need to give a huge thank you to Nora for pointing me in the right direction. I quickly signed up for a basic managed Keycloak instance through Cloud-IAM and set about putting up a
.well-known/webfinger file on my gmem.ca domain. It's important to note that although I opted for a managed service to handle Identity and Access Management, it's theoretically trivial for me to migrate elsewhere by updating the webfinger and checking with Tailscale support.
.well-known/webfinger endpoint for my domain started as a static file in an S3 bucket, and that worked well enough for myself. However, I wanted to grant my partner access to my tailnet, and realised that the static file wouldn't cut it. So, after an evening of hacking while recovering from COVID-19, I got a basic Rust-based AWS Lambda function written. Functionally, very simple - it pulls a webfinger JSON file from that same AWS bucket, finds the
subject matching the query parameter, and returns the spec-conforming file. It's very straightforward, and I picked Rust to both learn Rust in the context of Lambdas and to keep any sort of resource usage as low as possible. The source is available on sourcehut, although the documentation is a little lacking since I have yet to fully import the resources into my infrastructure Terraform. While the static JSON file would have still worked (maybe) when adding my partner to the tailnet, since the OIDC provider would still be the correct one, it doesn't hurt to set myself up for the future.
And with that, I have my Tailscale tailnet entirely under my domain, using my own OIDC provider, with the ability to add people as needed! Suprisingly straightforward and is entirely free. It's a few steps removed from running my own headscale instance, but I don't have any desire to set that up at the moment since my primary use for Tailscale is to not worry about the networking between my devices.