From 3c1cb5d05a81758a5567b6bec714e9922b3e4f99 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Fri, 11 Apr 2025 13:59:25 +1000 Subject: [PATCH] chore: add generic DNS record for checking if Coder Connect is running (#17298) Closes https://github.com/coder/internal/issues/466 ``` $ dig -6 @fd60:627a:a42b::53 is.coder--connect--enabled--right--now.coder AAAA ; <<>> DiG 9.10.6 <<>> -6 @fd60:627a:a42b::53 is.coder--connect--enabled--right--now.coder AAAA ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62390 ;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;is.coder--connect--enabled--right--now.coder. IN AAAA ;; ANSWER SECTION: is.coder--connect--enabled--right--now.coder. 2 IN AAAA fd60:627a:a42b::53 ;; Query time: 3 msec ;; SERVER: fd60:627a:a42b::53#53(fd60:627a:a42b::53) ;; WHEN: Wed Apr 09 16:59:18 AEST 2025 ;; MSG SIZE rcvd: 134 ``` Hostname considerations: - Workspace names, usernames, and agent names can't have double hyphens, so this name can't conflict with a real Coder Connect hostname. - Components can't start or end with hyphens according to [RFC 952](https://www.rfc-editor.org/rfc/rfc952.html) - DNS records can't have hyphens in the 3rd and 4th positions, as to not conflict with IDNs https://datatracker.ietf.org/doc/html/rfc5891 --- tailnet/conn.go | 7 +++++++ tailnet/controllers.go | 2 ++ tailnet/controllers_test.go | 37 +++++++++++++++++++++---------------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/tailnet/conn.go b/tailnet/conn.go index 59ddefc636..89b3b7d483 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -354,6 +354,13 @@ func NewConn(options *Options) (conn *Conn, err error) { return server, nil } +// A FQDN to be mapped to `tsaddr.CoderServiceIPv6`. This address can be used +// when you want to know if Coder Connect is running, but are not trying to +// connect to a specific known workspace. +const IsCoderConnectEnabledFQDNString = "is.coder--connect--enabled--right--now.coder." + +var IsCoderConnectEnabledFQDN, _ = dnsname.ToFQDN(IsCoderConnectEnabledFQDNString) + type ServicePrefix [6]byte var ( diff --git a/tailnet/controllers.go b/tailnet/controllers.go index bf2ec1d964..7a077ffabf 100644 --- a/tailnet/controllers.go +++ b/tailnet/controllers.go @@ -16,6 +16,7 @@ import ( "golang.org/x/xerrors" "storj.io/drpc" "storj.io/drpc/drpcerr" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/util/dnsname" @@ -1265,6 +1266,7 @@ func (t *tunnelUpdater) updateDNSNamesLocked() map[dnsname.FQDN][]netip.Addr { } } } + names[IsCoderConnectEnabledFQDN] = []netip.Addr{tsaddr.CoderServiceIPv6()} return names } diff --git a/tailnet/controllers_test.go b/tailnet/controllers_test.go index 16f254e324..3cfa47e3ad 100644 --- a/tailnet/controllers_test.go +++ b/tailnet/controllers_test.go @@ -22,6 +22,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "storj.io/drpc" "storj.io/drpc/drpcerr" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/util/dnsname" @@ -1563,13 +1564,14 @@ func TestTunnelAllWorkspaceUpdatesController_Initial(t *testing.T) { // Also triggers setting DNS hosts expectedDNS := map[dnsname.FQDN][]netip.Addr{ - "w1a1.w1.me.coder.": {ws1a1IP}, - "w2a1.w2.me.coder.": {w2a1IP}, - "w2a2.w2.me.coder.": {w2a2IP}, - "w1a1.w1.testy.coder.": {ws1a1IP}, - "w2a1.w2.testy.coder.": {w2a1IP}, - "w2a2.w2.testy.coder.": {w2a2IP}, - "w1.coder.": {ws1a1IP}, + "w1a1.w1.me.coder.": {ws1a1IP}, + "w2a1.w2.me.coder.": {w2a1IP}, + "w2a2.w2.me.coder.": {w2a2IP}, + "w1a1.w1.testy.coder.": {ws1a1IP}, + "w2a1.w2.testy.coder.": {w2a1IP}, + "w2a2.w2.testy.coder.": {w2a2IP}, + "w1.coder.": {ws1a1IP}, + tailnet.IsCoderConnectEnabledFQDNString: {tsaddr.CoderServiceIPv6()}, } dnsCall := testutil.RequireRecvCtx(ctx, t, fDNS.calls) require.Equal(t, expectedDNS, dnsCall.hosts) @@ -1661,9 +1663,10 @@ func TestTunnelAllWorkspaceUpdatesController_DeleteAgent(t *testing.T) { // DNS for w1a1 expectedDNS := map[dnsname.FQDN][]netip.Addr{ - "w1a1.w1.testy.coder.": {ws1a1IP}, - "w1a1.w1.me.coder.": {ws1a1IP}, - "w1.coder.": {ws1a1IP}, + "w1a1.w1.testy.coder.": {ws1a1IP}, + "w1a1.w1.me.coder.": {ws1a1IP}, + "w1.coder.": {ws1a1IP}, + tailnet.IsCoderConnectEnabledFQDNString: {tsaddr.CoderServiceIPv6()}, } dnsCall := testutil.RequireRecvCtx(ctx, t, fDNS.calls) require.Equal(t, expectedDNS, dnsCall.hosts) @@ -1716,9 +1719,10 @@ func TestTunnelAllWorkspaceUpdatesController_DeleteAgent(t *testing.T) { // DNS contains only w1a2 expectedDNS = map[dnsname.FQDN][]netip.Addr{ - "w1a2.w1.testy.coder.": {ws1a2IP}, - "w1a2.w1.me.coder.": {ws1a2IP}, - "w1.coder.": {ws1a2IP}, + "w1a2.w1.testy.coder.": {ws1a2IP}, + "w1a2.w1.me.coder.": {ws1a2IP}, + "w1.coder.": {ws1a2IP}, + tailnet.IsCoderConnectEnabledFQDNString: {tsaddr.CoderServiceIPv6()}, } dnsCall = testutil.RequireRecvCtx(ctx, t, fDNS.calls) require.Equal(t, expectedDNS, dnsCall.hosts) @@ -1798,9 +1802,10 @@ func TestTunnelAllWorkspaceUpdatesController_DNSError(t *testing.T) { // DNS for w1a1 expectedDNS := map[dnsname.FQDN][]netip.Addr{ - "w1a1.w1.me.coder.": {ws1a1IP}, - "w1a1.w1.testy.coder.": {ws1a1IP}, - "w1.coder.": {ws1a1IP}, + "w1a1.w1.me.coder.": {ws1a1IP}, + "w1a1.w1.testy.coder.": {ws1a1IP}, + "w1.coder.": {ws1a1IP}, + tailnet.IsCoderConnectEnabledFQDNString: {tsaddr.CoderServiceIPv6()}, } dnsCall := testutil.RequireRecvCtx(ctx, t, fDNS.calls) require.Equal(t, expectedDNS, dnsCall.hosts)