diff --git a/content/post/2021-01-30-quick-tip-sni-for-internal-domains-with-turris-omnia.md b/content/post/2021-01-30-quick-tip-sni-for-internal-domains-with-turris-omnia.md new file mode 100644 index 0000000..193b13e --- /dev/null +++ b/content/post/2021-01-30-quick-tip-sni-for-internal-domains-with-turris-omnia.md @@ -0,0 +1,80 @@ +--- +categories: +- General +- Linux +comments: true +date: 2021-01-30 01:05:17+01:00 +disable_share: true +draft: true +featured_image: /images/banner.jpg +omit_header_text: true +tags: +- general +- openwrt +- turris-omnia +title: 'Quick tips: Server Name Indication for internal domains with Turris Omnia' +--- + +## Omnia? + +The [Turris Omnia](https://www.turris.com/en/omnia/overview/) is quite a nice (although a little pricey) OpenWRT-based router from [CZ.NIC](https://www.nic.cz/). It provides a fairly powerful CPU, relatively unconstrained [eMMC](https://en.wikipedia.org/wiki/MultiMediaCard#eMMC) space, and quite a lot of hackability (some revisions even have GPIO ports to play with). It runs a modified version of OpenWRT, named [TurrisOS](https://docs.turris.cz/basics/tos-versions/). + +## The problem + +A few years ago, I built a custom NAS for my storage needs, using a cheap Intel SoC (J1900 chipset) and a (much pricier) [mini-ITX small form factor server tower](https://www.ipc.in-win.com/soho-smb-iw-ms04). While I want it to be primarily a NAS, I wanted to run a few services on it, for example [Nextcloud](https://nextcloud.org) or [Pymedusa](https://github.com/pymedusa/Medusa). Of course, in these days it's better to use HTTPS, and the process is both cheap and fast using [Let's Encrypt](https://letsencrypt.org). I also wanted to use [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) with multiple subdomains (`files.example.com`, `medusa.example.com`, etc.) pointing to the same internal LAN IP (because some of the software I used is much harder to set up with subpaths of a single domain). + +Actually setting up the certificates (with the dns-01 challenge) was rather easy to do (I might do a separate blog post on the topic), but then I got hit by a problem: the default domain used by DHCP in Omnia is `lan`, which of course is not the same as the one used with the Let's Encrypt certificates. Also, subdomains meant that I had to insert static host mapping for each one of them, which made testing of new software (on a new subdomain) and maintenance tedious. + +Hence, the question: + +**How could I have an arbitrary number of subdomains (`foo.bar`, `baz.foo.bar`) all resolve to the same IP without manual mapping? + +## The solution + +Unlike upstream OpenWRT, TurrisOS does not use [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) for name resolution (it is only used as DHCP server), opting instead for [Knot Resolver](https://www.knot-resolver.cz/), also developed by CZ.NIC. kresd (the Knot Resolver daemon) is sufficiently nimble and can be scripted and configured with a bit of Lua. Unlike others that absolutely require features that are dnsmasq specific, I had absolutely no problems with it. + +Thanks to the [extensive documentation](https://knot-resolver.readthedocs.io/en/stable/) and [some helpful developers](https://forum.turris.cz/u/vcunat) I was able to configure it to do exactly like I wanted. + +One first needs to check, on the Omnia, the configuration file which is located at `/etc/config/resolver`, and navigate to the options related to kresd: + +``` +config resolver 'kresd' +option rundir '/tmp/kresd' +option log_stderr '0' +option log_stdout '0' +option forks '1' +option include_config '/etc/kresd/custom.conf' +option keep_cache '0' +list hostname_config '/etc/hosts' +``` + +This is my configuration, which might be different from yours. What's important is `option include_config`, which you can point to a custom file where you can set additional kresd configuration. + +What I did was to create this file and add: + +```lua + +local genRR = policy.ANSWER({ +[kres.type.A] = { rdata=kres.str2ip('192.168.30.55'), ttl=900 }, +}, true) + +policy.add(policy.suffix(genRR, { todname('internal.example.com.') })) +``` + +What does it do? It sets a [query policy](https://knot-resolver.readthedocs.io/en/stable/modules-policy.html#mod-policy), which tells kresd what to do exactly with some requests. + +Notice that these statements will *only* work with kresd >= 5.1, but even the legacy TurrisOS 3.x has the latest version, if you are up-to-date with updates. + +In particular, when the request is `kres.type.A`, so an A record, it gives back `192.168.30.55` with a [time-to-live](https://en.wikipedia.org/wiki/Time_to_live) of 900 seconds. This means that every request that follows this policy will answer the same IP address. The secont line adds [a new policy rule](https://knot-resolver.readthedocs.io/en/stable/modules-policy.html#policy.add) to the resolver, which means that every subdomain of `internal.example.com` will resolve to 192.168.30.55. + +Since kresd assumes data in the DNS wire format, as defined in [RFC 1035](https://tools.ietf.org/html/rfc1035), we use a couple of convenience functions (`kres.str2ip` and `todname`) so we can just type our IP addresses or domain names without any trouble. We can also, potentially, specify multiple subdomains to check with the `policy.todnames` function: + +```lua +policy.add(policy.suffix(genRR, policy.todnames({'internal.example.com', 'external.example.com'}))) +``` + +Once that is a done deal, you can just restart kresd on the Omnia (`/etc/init.d/resolver restart`) to have kresd pick up your configuration. If it doesn't start... well, you have a problem. In this case you can set `log_stderr` and `log_stdout` to 1 in the configuration and then check `/var/log/messages` for potential configuration errors. + +## Conclusion + +Like this, I was able to set Let's Encrypt for all the subdomains I needed and I could also use HTTPS internally, without resorting to ideas like adding my own CA (which would have complicated things quite a bit. It took a while to figure out, including [troubles due to API changes](https://forum.turris.cz/t/kresd-crashes-with-policy-configuration-and-fqdn-as-router-name/14722), but now it performs exactly as I want to.