chore: add Coder service prefix to tailnet (#14943)

re: #14715

This PR introduces the Coder service prefix: `fd60:627a:a42b::/48` and refactors our existing code as calling the Tailscale service prefix explicitly (rather than implicitly).

Removes the unused `Addresses` agent option. All clients today assume they can compute the Agent's IP address based on its UUID, so an agent started with a custom address would break things.
This commit is contained in:
Spike Curtis
2024-10-04 10:04:10 +04:00
committed by GitHub
parent 68ec532ca7
commit 7d9f5ab81d
16 changed files with 120 additions and 75 deletions
+37 -17
View File
@@ -327,28 +327,48 @@ func NewConn(options *Options) (conn *Conn, err error) {
return server, nil
}
func maskUUID(uid uuid.UUID) uuid.UUID {
// This is Tailscale's ephemeral service prefix. This can be changed easily
// later-on, because all of our nodes are ephemeral.
// fd7a:115c:a1e0
uid[0] = 0xfd
uid[1] = 0x7a
uid[2] = 0x11
uid[3] = 0x5c
uid[4] = 0xa1
uid[5] = 0xe0
type ServicePrefix [6]byte
var (
// TailscaleServicePrefix is the IPv6 prefix for all tailnet nodes since it was first added to
// Coder. It is identical to the service prefix Tailscale.com uses. With the introduction of
// CoderVPN, we would like to stop using the Tailscale prefix so that we don't conflict with
// Tailscale if both are installed at the same time. However, there are a large number of agents
// and clients using this prefix, so we need to carefully manage deprecation and eventual
// removal.
// fd7a:115c:a1e0:://48
TailscaleServicePrefix ServicePrefix = [6]byte{0xfd, 0x7a, 0x11, 0x5c, 0xa1, 0xe0}
// CoderServicePrefix is the Coder-specific IPv6 prefix for tailnet nodes, which we are in the
// process of migrating to. It allows Coder to run alongside Tailscale without conflicts even
// if both are set up as TUN interfaces into the OS (e.g. CoderVPN).
// fd60:627a:a42b::/48
CoderServicePrefix ServicePrefix = [6]byte{0xfd, 0x60, 0x62, 0x7a, 0xa4, 0x2b}
)
// maskUUID returns a new UUID with the first 6 bytes changed to the ServicePrefix
func (p ServicePrefix) maskUUID(uid uuid.UUID) uuid.UUID {
copy(uid[:], p[:])
return uid
}
// IP generates a random IP with a static service prefix.
func IP() netip.Addr {
uid := maskUUID(uuid.New())
return netip.AddrFrom16(uid)
// RandomAddr returns a random IP address in the service prefix.
func (p ServicePrefix) RandomAddr() netip.Addr {
return netip.AddrFrom16(p.maskUUID(uuid.New()))
}
// IP generates a new IP from a UUID.
func IPFromUUID(uid uuid.UUID) netip.Addr {
return netip.AddrFrom16(maskUUID(uid))
// AddrFromUUID returns an IPv6 address corresponding to the given UUID in the service prefix.
func (p ServicePrefix) AddrFromUUID(uid uuid.UUID) netip.Addr {
return netip.AddrFrom16(p.maskUUID(uid))
}
// PrefixFromUUID returns a single IPv6 /128 prefix corresponding to the given UUID.
func (p ServicePrefix) PrefixFromUUID(uid uuid.UUID) netip.Prefix {
return netip.PrefixFrom(p.AddrFromUUID(uid), 128)
}
// RandomPrefix returns a single IPv6 /128 prefix within the service prefix.
func (p ServicePrefix) RandomPrefix() netip.Prefix {
return netip.PrefixFrom(p.RandomAddr(), 128)
}
// Conn is an actively listening Wireguard connection.
+44 -15
View File
@@ -3,6 +3,7 @@ package tailnet_test
import (
"context"
"net/netip"
"strings"
"testing"
"time"
@@ -30,7 +31,7 @@ func TestTailnet(t *testing.T) {
t.Parallel()
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
conn, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.RandomPrefix()},
Logger: logger.Named("w1"),
DERPMap: derpMap,
})
@@ -42,7 +43,7 @@ func TestTailnet(t *testing.T) {
t.Parallel()
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
ctx := testutil.Context(t, testutil.WaitLong)
w1IP := tailnet.IP()
w1IP := tailnet.TailscaleServicePrefix.RandomAddr()
w1, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(w1IP, 128)},
Logger: logger.Named("w1"),
@@ -51,7 +52,7 @@ func TestTailnet(t *testing.T) {
require.NoError(t, err)
w2, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.RandomPrefix()},
Logger: logger.Named("w2"),
DERPMap: derpMap,
})
@@ -106,7 +107,7 @@ func TestTailnet(t *testing.T) {
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
ctx := testutil.Context(t, testutil.WaitMedium)
w1IP := tailnet.IP()
w1IP := tailnet.TailscaleServicePrefix.RandomAddr()
derpMap := tailnettest.RunDERPOnlyWebSockets(t)
w1, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(w1IP, 128)},
@@ -117,7 +118,7 @@ func TestTailnet(t *testing.T) {
require.NoError(t, err)
w2, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.RandomPrefix()},
Logger: logger.Named("w2"),
DERPMap: derpMap,
BlockEndpoints: true,
@@ -168,7 +169,7 @@ func TestTailnet(t *testing.T) {
t.Parallel()
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
ctx := testutil.Context(t, testutil.WaitLong)
w1IP := tailnet.IP()
w1IP := tailnet.TailscaleServicePrefix.RandomAddr()
w1, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(w1IP, 128)},
Logger: logger.Named("w1"),
@@ -177,7 +178,7 @@ func TestTailnet(t *testing.T) {
require.NoError(t, err)
w2, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.RandomPrefix()},
Logger: logger.Named("w2"),
DERPMap: derpMap,
})
@@ -211,7 +212,7 @@ func TestTailnet(t *testing.T) {
t.Parallel()
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
ctx := testutil.Context(t, testutil.WaitLong)
w1IP := tailnet.IP()
w1IP := tailnet.TailscaleServicePrefix.RandomAddr()
w1, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(w1IP, 128)},
Logger: logger.Named("w1"),
@@ -221,7 +222,7 @@ func TestTailnet(t *testing.T) {
require.NoError(t, err)
w2, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.RandomPrefix()},
Logger: logger.Named("w2"),
DERPMap: derpMap,
BlockEndpoints: true,
@@ -261,7 +262,7 @@ func TestConn_PreferredDERP(t *testing.T) {
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
derpMap, _ := tailnettest.RunDERPAndSTUN(t)
conn, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.RandomPrefix()},
Logger: logger.Named("w1"),
DERPMap: derpMap,
})
@@ -290,7 +291,7 @@ func TestConn_UpdateDERP(t *testing.T) {
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
derpMap1, _ := tailnettest.RunDERPAndSTUN(t)
ip := tailnet.IP()
ip := tailnet.TailscaleServicePrefix.RandomAddr()
conn, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(ip, 128)},
Logger: logger.Named("w1"),
@@ -320,7 +321,7 @@ func TestConn_UpdateDERP(t *testing.T) {
// Connect from a different client.
client1, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.RandomPrefix()},
Logger: logger.Named("client1"),
DERPMap: derpMap1,
BlockEndpoints: true,
@@ -394,7 +395,7 @@ parentLoop:
// Connect from a different different client with up-to-date derp map and
// nodes.
client2, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.RandomPrefix()},
Logger: logger.Named("client2"),
DERPMap: derpMap2,
BlockEndpoints: true,
@@ -425,7 +426,7 @@ func TestConn_BlockEndpoints(t *testing.T) {
derpMap, _ := tailnettest.RunDERPAndSTUN(t)
// Setup conn 1.
ip1 := tailnet.IP()
ip1 := tailnet.TailscaleServicePrefix.RandomAddr()
conn1, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(ip1, 128)},
Logger: logger.Named("w1"),
@@ -439,7 +440,7 @@ func TestConn_BlockEndpoints(t *testing.T) {
}()
// Setup conn 2.
ip2 := tailnet.IP()
ip2 := tailnet.TailscaleServicePrefix.RandomAddr()
conn2, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(ip2, 128)},
Logger: logger.Named("w2"),
@@ -492,3 +493,31 @@ func stitch(t *testing.T, dst, src *tailnet.Conn) {
assert.NoError(t, err)
})
}
func TestTailscaleServicePrefix(t *testing.T) {
t.Parallel()
a := tailnet.TailscaleServicePrefix.RandomAddr()
require.True(t, strings.HasPrefix(a.String(), "fd7a:115c:a1e0"))
p := tailnet.TailscaleServicePrefix.RandomPrefix()
require.True(t, strings.HasPrefix(p.String(), "fd7a:115c:a1e0"))
require.True(t, strings.HasSuffix(p.String(), "/128"))
u := uuid.MustParse("aaaaaaaa-aaaa-aaaa-aaaa-123456789abc")
a = tailnet.TailscaleServicePrefix.AddrFromUUID(u)
require.Equal(t, "fd7a:115c:a1e0:aaaa:aaaa:1234:5678:9abc", a.String())
p = tailnet.TailscaleServicePrefix.PrefixFromUUID(u)
require.Equal(t, "fd7a:115c:a1e0:aaaa:aaaa:1234:5678:9abc/128", p.String())
}
func TestCoderServicePrefix(t *testing.T) {
t.Parallel()
a := tailnet.CoderServicePrefix.RandomAddr()
require.True(t, strings.HasPrefix(a.String(), "fd60:627a:a42b"))
p := tailnet.CoderServicePrefix.RandomPrefix()
require.True(t, strings.HasPrefix(p.String(), "fd60:627a:a42b"))
require.True(t, strings.HasSuffix(p.String(), "/128"))
u := uuid.MustParse("aaaaaaaa-aaaa-aaaa-aaaa-123456789abc")
a = tailnet.CoderServicePrefix.AddrFromUUID(u)
require.Equal(t, "fd60:627a:a42b:aaaa:aaaa:1234:5678:9abc", a.String())
p = tailnet.CoderServicePrefix.PrefixFromUUID(u)
require.Equal(t, "fd60:627a:a42b:aaaa:aaaa:1234:5678:9abc/128", p.String())
}
+6 -5
View File
@@ -40,7 +40,7 @@ func TestCoordinator(t *testing.T) {
client := test.NewClient(ctx, t, coordinator, "client", uuid.New())
defer client.Close(ctx)
client.UpdateNode(&proto.Node{
Addresses: []string{netip.PrefixFrom(tailnet.IP(), 128).String()},
Addresses: []string{tailnet.TailscaleServicePrefix.RandomPrefix().String()},
PreferredDerp: 10,
})
require.Eventually(t, func() bool {
@@ -63,7 +63,7 @@ func TestCoordinator(t *testing.T) {
client.UpdateNode(&proto.Node{
Addresses: []string{
netip.PrefixFrom(tailnet.IP(), 64).String(),
netip.PrefixFrom(tailnet.TailscaleServicePrefix.RandomAddr(), 64).String(),
},
PreferredDerp: 10,
})
@@ -84,7 +84,7 @@ func TestCoordinator(t *testing.T) {
defer agent.Close(ctx)
agent.UpdateNode(&proto.Node{
Addresses: []string{
netip.PrefixFrom(tailnet.IPFromUUID(agent.ID), 128).String(),
tailnet.TailscaleServicePrefix.PrefixFromUUID(agent.ID).String(),
},
PreferredDerp: 10,
})
@@ -106,7 +106,7 @@ func TestCoordinator(t *testing.T) {
defer agent.Close(ctx)
agent.UpdateNode(&proto.Node{
Addresses: []string{
netip.PrefixFrom(tailnet.IP(), 128).String(),
tailnet.TailscaleServicePrefix.RandomPrefix().String(),
},
PreferredDerp: 10,
})
@@ -126,7 +126,8 @@ func TestCoordinator(t *testing.T) {
defer agent.Close(ctx)
agent.UpdateNode(&proto.Node{
Addresses: []string{
netip.PrefixFrom(tailnet.IPFromUUID(agent.ID), 64).String(),
netip.PrefixFrom(
tailnet.TailscaleServicePrefix.AddrFromUUID(agent.ID), 64).String(),
},
PreferredDerp: 10,
})
+5 -5
View File
@@ -18,7 +18,7 @@ func TestTelemetryStore(t *testing.T) {
t.Run("CreateEvent", func(t *testing.T) {
t.Parallel()
remotePrefix := netip.PrefixFrom(IP(), 128)
remotePrefix := TailscaleServicePrefix.RandomPrefix()
remoteIP := remotePrefix.Addr()
application := "test"
@@ -31,16 +31,16 @@ func TestTelemetryStore(t *testing.T) {
{
ID: 1,
Addresses: []netip.Prefix{
netip.PrefixFrom(IP(), 128),
netip.PrefixFrom(IP(), 128),
TailscaleServicePrefix.RandomPrefix(),
TailscaleServicePrefix.RandomPrefix(),
},
},
{
ID: 2,
Addresses: []netip.Prefix{
remotePrefix,
netip.PrefixFrom(IP(), 128),
netip.PrefixFrom(IP(), 128),
TailscaleServicePrefix.RandomPrefix(),
TailscaleServicePrefix.RandomPrefix(),
},
},
},
+4 -4
View File
@@ -373,7 +373,7 @@ http {
// and creates a tailnet.Conn which will only use DERP to connect to the peer.
func StartClientDERP(t *testing.T, logger slog.Logger, serverURL *url.URL, derpMap *tailcfg.DERPMap, me, peer Client) *tailnet.Conn {
return startClientOptions(t, logger, serverURL, me, peer, &tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IPFromUUID(me.ID), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.PrefixFromUUID(me.ID)},
DERPMap: derpMap,
BlockEndpoints: true,
Logger: logger,
@@ -389,7 +389,7 @@ func StartClientDERP(t *testing.T, logger slog.Logger, serverURL *url.URL, derpM
// only use DERP WebSocket fallback.
func StartClientDERPWebSockets(t *testing.T, logger slog.Logger, serverURL *url.URL, derpMap *tailcfg.DERPMap, me, peer Client) *tailnet.Conn {
return startClientOptions(t, logger, serverURL, me, peer, &tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IPFromUUID(me.ID), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.PrefixFromUUID(me.ID)},
DERPMap: derpMap,
BlockEndpoints: true,
Logger: logger,
@@ -406,7 +406,7 @@ func StartClientDERPWebSockets(t *testing.T, logger slog.Logger, serverURL *url.
// connection to be established between the two peers.
func StartClientDirect(t *testing.T, logger slog.Logger, serverURL *url.URL, derpMap *tailcfg.DERPMap, me, peer Client) *tailnet.Conn {
conn := startClientOptions(t, logger, serverURL, me, peer, &tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IPFromUUID(me.ID), 128)},
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.PrefixFromUUID(me.ID)},
DERPMap: derpMap,
BlockEndpoints: false,
Logger: logger,
@@ -418,7 +418,7 @@ func StartClientDirect(t *testing.T, logger slog.Logger, serverURL *url.URL, der
})
// Wait for direct connection to be established.
peerIP := tailnet.IPFromUUID(peer.ID)
peerIP := tailnet.TailscaleServicePrefix.AddrFromUUID(peer.ID)
require.Eventually(t, func() bool {
t.Log("attempting ping to peer to judge direct connection")
ctx := testutil.Context(t, testutil.WaitShort)
+1 -1
View File
@@ -267,7 +267,7 @@ func handleTestSubprocess(t *testing.T) {
if me.ShouldRunTests {
// Wait for connectivity.
peerIP := tailnet.IPFromUUID(peer.ID)
peerIP := tailnet.TailscaleServicePrefix.AddrFromUUID(peer.ID)
if !conn.AwaitReachable(testutil.Context(t, testutil.WaitLong), peerIP) {
t.Fatalf("peer %v did not become reachable", peerIP)
}
+4 -4
View File
@@ -48,13 +48,13 @@ func TestSuite(t *testing.T, _ slog.Logger, serverURL *url.URL, conn *tailnet.Co
t.Run("Connectivity", func(t *testing.T) {
t.Parallel()
peerIP := tailnet.IPFromUUID(peer.ID)
peerIP := tailnet.TailscaleServicePrefix.AddrFromUUID(peer.ID)
_, _, _, err := conn.Ping(testutil.Context(t, testutil.WaitLong), peerIP)
require.NoError(t, err, "ping peer")
})
t.Run("RestartDERP", func(t *testing.T) {
peerIP := tailnet.IPFromUUID(peer.ID)
peerIP := tailnet.TailscaleServicePrefix.AddrFromUUID(peer.ID)
_, _, _, err := conn.Ping(testutil.Context(t, testutil.WaitLong), peerIP)
require.NoError(t, err, "ping peer")
sendRestart(t, serverURL, true, false)
@@ -63,7 +63,7 @@ func TestSuite(t *testing.T, _ slog.Logger, serverURL *url.URL, conn *tailnet.Co
})
t.Run("RestartCoordinator", func(t *testing.T) {
peerIP := tailnet.IPFromUUID(peer.ID)
peerIP := tailnet.TailscaleServicePrefix.AddrFromUUID(peer.ID)
_, _, _, err := conn.Ping(testutil.Context(t, testutil.WaitLong), peerIP)
require.NoError(t, err, "ping peer")
sendRestart(t, serverURL, false, true)
@@ -72,7 +72,7 @@ func TestSuite(t *testing.T, _ slog.Logger, serverURL *url.URL, conn *tailnet.Co
})
t.Run("RestartBoth", func(t *testing.T) {
peerIP := tailnet.IPFromUUID(peer.ID)
peerIP := tailnet.TailscaleServicePrefix.AddrFromUUID(peer.ID)
_, _, _, err := conn.Ping(testutil.Context(t, testutil.WaitLong), peerIP)
require.NoError(t, err, "ping peer")
sendRestart(t, serverURL, true, true)
+1 -1
View File
@@ -80,7 +80,7 @@ func (a AgentCoordinateeAuth) Authorize(req *proto.CoordinateRequest) error {
return xerrors.Errorf("invalid address bits, expected 128, got %d", pre.Bits())
}
if IPFromUUID(a.ID).Compare(pre.Addr()) != 0 &&
if TailscaleServicePrefix.AddrFromUUID(a.ID).Compare(pre.Addr()) != 0 &&
legacyWorkspaceAgentIP.Compare(pre.Addr()) != 0 {
return xerrors.Errorf("invalid node address, got %s", pre.Addr().String())
}