diff options
| author | Nick Khyl <nickk@tailscale.com> | 2024-12-05 13:16:48 -0600 |
|---|---|---|
| committer | Nick Khyl <nickk@tailscale.com> | 2024-12-05 13:16:48 -0600 |
| commit | 0267fe83b200f1702a2fa0a395442c02a053fadb (patch) | |
| tree | 63654c55225eeb834de59a5a0bc8d19033c6145b /ipn | |
| parent | 87546a5edf6b6503a87eeb2d666baba57398a066 (diff) | |
| download | tailscale-1.78.0.tar.xz tailscale-1.78.0.zip | |
VERSION.txt: this is v1.78.0v1.78.0
Signed-off-by: Nick Khyl <nickk@tailscale.com>
Diffstat (limited to 'ipn')
| -rw-r--r-- | ipn/ipnlocal/breaktcp_darwin.go | 60 | ||||
| -rw-r--r-- | ipn/ipnlocal/breaktcp_linux.go | 60 | ||||
| -rw-r--r-- | ipn/ipnlocal/expiry_test.go | 602 | ||||
| -rw-r--r-- | ipn/ipnlocal/peerapi_h2c.go | 40 | ||||
| -rw-r--r-- | ipn/ipnlocal/testdata/example.com-key.pem | 54 | ||||
| -rw-r--r-- | ipn/ipnlocal/testdata/example.com.pem | 50 | ||||
| -rw-r--r-- | ipn/ipnlocal/testdata/rootCA.pem | 58 | ||||
| -rw-r--r-- | ipn/ipnserver/proxyconnect_js.go | 20 | ||||
| -rw-r--r-- | ipn/ipnserver/server_test.go | 92 | ||||
| -rw-r--r-- | ipn/localapi/disabled_stubs.go | 30 | ||||
| -rw-r--r-- | ipn/localapi/pprof.go | 56 | ||||
| -rw-r--r-- | ipn/policy/policy.go | 94 | ||||
| -rw-r--r-- | ipn/store/awsstore/store_aws.go | 372 | ||||
| -rw-r--r-- | ipn/store/awsstore/store_aws_stub.go | 36 | ||||
| -rw-r--r-- | ipn/store/awsstore/store_aws_test.go | 328 | ||||
| -rw-r--r-- | ipn/store/stores_test.go | 358 | ||||
| -rw-r--r-- | ipn/store_test.go | 96 |
17 files changed, 1203 insertions, 1203 deletions
diff --git a/ipn/ipnlocal/breaktcp_darwin.go b/ipn/ipnlocal/breaktcp_darwin.go index 13566198c..289e760e1 100644 --- a/ipn/ipnlocal/breaktcp_darwin.go +++ b/ipn/ipnlocal/breaktcp_darwin.go @@ -1,30 +1,30 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package ipnlocal - -import ( - "log" - - "golang.org/x/sys/unix" -) - -func init() { - breakTCPConns = breakTCPConnsDarwin -} - -func breakTCPConnsDarwin() error { - var matched int - for fd := 0; fd < 1000; fd++ { - _, err := unix.GetsockoptTCPConnectionInfo(fd, unix.IPPROTO_TCP, unix.TCP_CONNECTION_INFO) - if err == nil { - matched++ - err = unix.Close(fd) - log.Printf("debug: closed TCP fd %v: %v", fd, err) - } - } - if matched == 0 { - log.Printf("debug: no TCP connections found") - } - return nil -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package ipnlocal
+
+import (
+ "log"
+
+ "golang.org/x/sys/unix"
+)
+
+func init() {
+ breakTCPConns = breakTCPConnsDarwin
+}
+
+func breakTCPConnsDarwin() error {
+ var matched int
+ for fd := 0; fd < 1000; fd++ {
+ _, err := unix.GetsockoptTCPConnectionInfo(fd, unix.IPPROTO_TCP, unix.TCP_CONNECTION_INFO)
+ if err == nil {
+ matched++
+ err = unix.Close(fd)
+ log.Printf("debug: closed TCP fd %v: %v", fd, err)
+ }
+ }
+ if matched == 0 {
+ log.Printf("debug: no TCP connections found")
+ }
+ return nil
+}
diff --git a/ipn/ipnlocal/breaktcp_linux.go b/ipn/ipnlocal/breaktcp_linux.go index b82f65212..d078103cf 100644 --- a/ipn/ipnlocal/breaktcp_linux.go +++ b/ipn/ipnlocal/breaktcp_linux.go @@ -1,30 +1,30 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package ipnlocal - -import ( - "log" - - "golang.org/x/sys/unix" -) - -func init() { - breakTCPConns = breakTCPConnsLinux -} - -func breakTCPConnsLinux() error { - var matched int - for fd := 0; fd < 1000; fd++ { - _, err := unix.GetsockoptTCPInfo(fd, unix.IPPROTO_TCP, unix.TCP_INFO) - if err == nil { - matched++ - err = unix.Close(fd) - log.Printf("debug: closed TCP fd %v: %v", fd, err) - } - } - if matched == 0 { - log.Printf("debug: no TCP connections found") - } - return nil -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package ipnlocal
+
+import (
+ "log"
+
+ "golang.org/x/sys/unix"
+)
+
+func init() {
+ breakTCPConns = breakTCPConnsLinux
+}
+
+func breakTCPConnsLinux() error {
+ var matched int
+ for fd := 0; fd < 1000; fd++ {
+ _, err := unix.GetsockoptTCPInfo(fd, unix.IPPROTO_TCP, unix.TCP_INFO)
+ if err == nil {
+ matched++
+ err = unix.Close(fd)
+ log.Printf("debug: closed TCP fd %v: %v", fd, err)
+ }
+ }
+ if matched == 0 {
+ log.Printf("debug: no TCP connections found")
+ }
+ return nil
+}
diff --git a/ipn/ipnlocal/expiry_test.go b/ipn/ipnlocal/expiry_test.go index af1aa337b..efc18133f 100644 --- a/ipn/ipnlocal/expiry_test.go +++ b/ipn/ipnlocal/expiry_test.go @@ -1,301 +1,301 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package ipnlocal - -import ( - "fmt" - "reflect" - "strings" - "testing" - "time" - - "tailscale.com/tailcfg" - "tailscale.com/tstest" - "tailscale.com/types/key" - "tailscale.com/types/netmap" -) - -func TestFlagExpiredPeers(t *testing.T) { - n := func(id tailcfg.NodeID, name string, expiry time.Time, mod ...func(*tailcfg.Node)) *tailcfg.Node { - n := &tailcfg.Node{ID: id, Name: name, KeyExpiry: expiry} - for _, f := range mod { - f(n) - } - return n - } - - now := time.Unix(1673373129, 0) - - timeInPast := now.Add(-1 * time.Hour) - timeInFuture := now.Add(1 * time.Hour) - - timeBeforeEpoch := flagExpiredPeersEpoch.Add(-1 * time.Second) - if now.Before(timeBeforeEpoch) { - panic("current time in test cannot be before epoch") - } - - var expiredKey key.NodePublic - if err := expiredKey.UnmarshalText([]byte("nodekey:6da774d5d7740000000000000000000000000000000000000000000000000000")); err != nil { - panic(err) - } - - tests := []struct { - name string - controlTime *time.Time - netmap *netmap.NetworkMap - want []tailcfg.NodeView - }{ - { - name: "no_expiry", - controlTime: &now, - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeInFuture), - }), - }, - want: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeInFuture), - }), - }, - { - name: "expiry", - controlTime: &now, - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeInPast), - }), - }, - want: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeInPast, func(n *tailcfg.Node) { - n.Expired = true - n.Key = expiredKey - }), - }), - }, - { - name: "bad_ControlTime", - // controlTime here is intentionally before our hardcoded epoch - controlTime: &timeBeforeEpoch, - - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeBeforeEpoch.Add(-1*time.Hour)), // before ControlTime - }), - }, - want: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeBeforeEpoch.Add(-1*time.Hour)), // should have expired, but ControlTime is before epoch - }), - }, - { - name: "tagged_node", - controlTime: &now, - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", time.Time{}), // tagged node; zero expiry - }), - }, - want: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", time.Time{}), // not expired - }), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - em := newExpiryManager(t.Logf) - em.clock = tstest.NewClock(tstest.ClockOpts{Start: now}) - if tt.controlTime != nil { - em.onControlTime(*tt.controlTime) - } - em.flagExpiredPeers(tt.netmap, now) - if !reflect.DeepEqual(tt.netmap.Peers, tt.want) { - t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(tt.netmap.Peers), formatNodes(tt.want)) - } - }) - } -} - -func TestNextPeerExpiry(t *testing.T) { - n := func(id tailcfg.NodeID, name string, expiry time.Time, mod ...func(*tailcfg.Node)) *tailcfg.Node { - n := &tailcfg.Node{ID: id, Name: name, KeyExpiry: expiry} - for _, f := range mod { - f(n) - } - return n - } - - now := time.Unix(1675725516, 0) - - noExpiry := time.Time{} - timeInPast := now.Add(-1 * time.Hour) - timeInFuture := now.Add(1 * time.Hour) - timeInMoreFuture := now.Add(2 * time.Hour) - - tests := []struct { - name string - netmap *netmap.NetworkMap - want time.Time - }{ - { - name: "no_expiry", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", noExpiry), - n(2, "bar", noExpiry), - }), - SelfNode: n(3, "self", noExpiry).View(), - }, - want: noExpiry, - }, - { - name: "future_expiry_from_peer", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", noExpiry), - n(2, "bar", timeInFuture), - }), - SelfNode: n(3, "self", noExpiry).View(), - }, - want: timeInFuture, - }, - { - name: "future_expiry_from_self", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", noExpiry), - n(2, "bar", noExpiry), - }), - SelfNode: n(3, "self", timeInFuture).View(), - }, - want: timeInFuture, - }, - { - name: "future_expiry_from_multiple_peers", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - n(2, "bar", timeInMoreFuture), - }), - SelfNode: n(3, "self", noExpiry).View(), - }, - want: timeInFuture, - }, - { - name: "future_expiry_from_peer_and_self", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInMoreFuture), - }), - SelfNode: n(2, "self", timeInFuture).View(), - }, - want: timeInFuture, - }, - { - name: "only_self", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{}), - SelfNode: n(1, "self", timeInFuture).View(), - }, - want: timeInFuture, - }, - { - name: "peer_already_expired", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInPast), - }), - SelfNode: n(2, "self", timeInFuture).View(), - }, - want: timeInFuture, - }, - { - name: "self_already_expired", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInFuture), - }), - SelfNode: n(2, "self", timeInPast).View(), - }, - want: timeInFuture, - }, - { - name: "all_nodes_already_expired", - netmap: &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInPast), - }), - SelfNode: n(2, "self", timeInPast).View(), - }, - want: noExpiry, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - em := newExpiryManager(t.Logf) - em.clock = tstest.NewClock(tstest.ClockOpts{Start: now}) - got := em.nextPeerExpiry(tt.netmap, now) - if !got.Equal(tt.want) { - t.Errorf("got %q, want %q", got.Format(time.RFC3339), tt.want.Format(time.RFC3339)) - } else if !got.IsZero() && got.Before(now) { - t.Errorf("unexpectedly got expiry %q before now %q", got.Format(time.RFC3339), now.Format(time.RFC3339)) - } - }) - } - - t.Run("ClockSkew", func(t *testing.T) { - t.Logf("local time: %q", now.Format(time.RFC3339)) - em := newExpiryManager(t.Logf) - em.clock = tstest.NewClock(tstest.ClockOpts{Start: now}) - - // The local clock is "running fast"; our clock skew is -2h - em.clockDelta.Store(-2 * time.Hour) - t.Logf("'real' time: %q", now.Add(-2*time.Hour).Format(time.RFC3339)) - - // If we don't adjust for the local time, this would return a - // time in the past. - nm := &netmap.NetworkMap{ - Peers: nodeViews([]*tailcfg.Node{ - n(1, "foo", timeInPast), - }), - } - got := em.nextPeerExpiry(nm, now) - want := now.Add(30 * time.Second) - if !got.Equal(want) { - t.Errorf("got %q, want %q", got.Format(time.RFC3339), want.Format(time.RFC3339)) - } - }) -} - -func formatNodes(nodes []tailcfg.NodeView) string { - var sb strings.Builder - for i, n := range nodes { - if i > 0 { - sb.WriteString(", ") - } - fmt.Fprintf(&sb, "(%d, %q", n.ID(), n.Name()) - - if n.Online() != nil { - fmt.Fprintf(&sb, ", online=%v", *n.Online()) - } - if n.LastSeen() != nil { - fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen().Unix()) - } - if n.Key() != (key.NodePublic{}) { - fmt.Fprintf(&sb, ", key=%v", n.Key().String()) - } - if n.Expired() { - fmt.Fprintf(&sb, ", expired=true") - } - sb.WriteString(")") - } - return sb.String() -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package ipnlocal
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "tailscale.com/tailcfg"
+ "tailscale.com/tstest"
+ "tailscale.com/types/key"
+ "tailscale.com/types/netmap"
+)
+
+func TestFlagExpiredPeers(t *testing.T) {
+ n := func(id tailcfg.NodeID, name string, expiry time.Time, mod ...func(*tailcfg.Node)) *tailcfg.Node {
+ n := &tailcfg.Node{ID: id, Name: name, KeyExpiry: expiry}
+ for _, f := range mod {
+ f(n)
+ }
+ return n
+ }
+
+ now := time.Unix(1673373129, 0)
+
+ timeInPast := now.Add(-1 * time.Hour)
+ timeInFuture := now.Add(1 * time.Hour)
+
+ timeBeforeEpoch := flagExpiredPeersEpoch.Add(-1 * time.Second)
+ if now.Before(timeBeforeEpoch) {
+ panic("current time in test cannot be before epoch")
+ }
+
+ var expiredKey key.NodePublic
+ if err := expiredKey.UnmarshalText([]byte("nodekey:6da774d5d7740000000000000000000000000000000000000000000000000000")); err != nil {
+ panic(err)
+ }
+
+ tests := []struct {
+ name string
+ controlTime *time.Time
+ netmap *netmap.NetworkMap
+ want []tailcfg.NodeView
+ }{
+ {
+ name: "no_expiry",
+ controlTime: &now,
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeInFuture),
+ }),
+ },
+ want: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeInFuture),
+ }),
+ },
+ {
+ name: "expiry",
+ controlTime: &now,
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeInPast),
+ }),
+ },
+ want: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeInPast, func(n *tailcfg.Node) {
+ n.Expired = true
+ n.Key = expiredKey
+ }),
+ }),
+ },
+ {
+ name: "bad_ControlTime",
+ // controlTime here is intentionally before our hardcoded epoch
+ controlTime: &timeBeforeEpoch,
+
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeBeforeEpoch.Add(-1*time.Hour)), // before ControlTime
+ }),
+ },
+ want: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeBeforeEpoch.Add(-1*time.Hour)), // should have expired, but ControlTime is before epoch
+ }),
+ },
+ {
+ name: "tagged_node",
+ controlTime: &now,
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", time.Time{}), // tagged node; zero expiry
+ }),
+ },
+ want: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", time.Time{}), // not expired
+ }),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ em := newExpiryManager(t.Logf)
+ em.clock = tstest.NewClock(tstest.ClockOpts{Start: now})
+ if tt.controlTime != nil {
+ em.onControlTime(*tt.controlTime)
+ }
+ em.flagExpiredPeers(tt.netmap, now)
+ if !reflect.DeepEqual(tt.netmap.Peers, tt.want) {
+ t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(tt.netmap.Peers), formatNodes(tt.want))
+ }
+ })
+ }
+}
+
+func TestNextPeerExpiry(t *testing.T) {
+ n := func(id tailcfg.NodeID, name string, expiry time.Time, mod ...func(*tailcfg.Node)) *tailcfg.Node {
+ n := &tailcfg.Node{ID: id, Name: name, KeyExpiry: expiry}
+ for _, f := range mod {
+ f(n)
+ }
+ return n
+ }
+
+ now := time.Unix(1675725516, 0)
+
+ noExpiry := time.Time{}
+ timeInPast := now.Add(-1 * time.Hour)
+ timeInFuture := now.Add(1 * time.Hour)
+ timeInMoreFuture := now.Add(2 * time.Hour)
+
+ tests := []struct {
+ name string
+ netmap *netmap.NetworkMap
+ want time.Time
+ }{
+ {
+ name: "no_expiry",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", noExpiry),
+ n(2, "bar", noExpiry),
+ }),
+ SelfNode: n(3, "self", noExpiry).View(),
+ },
+ want: noExpiry,
+ },
+ {
+ name: "future_expiry_from_peer",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", noExpiry),
+ n(2, "bar", timeInFuture),
+ }),
+ SelfNode: n(3, "self", noExpiry).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "future_expiry_from_self",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", noExpiry),
+ n(2, "bar", noExpiry),
+ }),
+ SelfNode: n(3, "self", timeInFuture).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "future_expiry_from_multiple_peers",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ n(2, "bar", timeInMoreFuture),
+ }),
+ SelfNode: n(3, "self", noExpiry).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "future_expiry_from_peer_and_self",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInMoreFuture),
+ }),
+ SelfNode: n(2, "self", timeInFuture).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "only_self",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{}),
+ SelfNode: n(1, "self", timeInFuture).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "peer_already_expired",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInPast),
+ }),
+ SelfNode: n(2, "self", timeInFuture).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "self_already_expired",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInFuture),
+ }),
+ SelfNode: n(2, "self", timeInPast).View(),
+ },
+ want: timeInFuture,
+ },
+ {
+ name: "all_nodes_already_expired",
+ netmap: &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInPast),
+ }),
+ SelfNode: n(2, "self", timeInPast).View(),
+ },
+ want: noExpiry,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ em := newExpiryManager(t.Logf)
+ em.clock = tstest.NewClock(tstest.ClockOpts{Start: now})
+ got := em.nextPeerExpiry(tt.netmap, now)
+ if !got.Equal(tt.want) {
+ t.Errorf("got %q, want %q", got.Format(time.RFC3339), tt.want.Format(time.RFC3339))
+ } else if !got.IsZero() && got.Before(now) {
+ t.Errorf("unexpectedly got expiry %q before now %q", got.Format(time.RFC3339), now.Format(time.RFC3339))
+ }
+ })
+ }
+
+ t.Run("ClockSkew", func(t *testing.T) {
+ t.Logf("local time: %q", now.Format(time.RFC3339))
+ em := newExpiryManager(t.Logf)
+ em.clock = tstest.NewClock(tstest.ClockOpts{Start: now})
+
+ // The local clock is "running fast"; our clock skew is -2h
+ em.clockDelta.Store(-2 * time.Hour)
+ t.Logf("'real' time: %q", now.Add(-2*time.Hour).Format(time.RFC3339))
+
+ // If we don't adjust for the local time, this would return a
+ // time in the past.
+ nm := &netmap.NetworkMap{
+ Peers: nodeViews([]*tailcfg.Node{
+ n(1, "foo", timeInPast),
+ }),
+ }
+ got := em.nextPeerExpiry(nm, now)
+ want := now.Add(30 * time.Second)
+ if !got.Equal(want) {
+ t.Errorf("got %q, want %q", got.Format(time.RFC3339), want.Format(time.RFC3339))
+ }
+ })
+}
+
+func formatNodes(nodes []tailcfg.NodeView) string {
+ var sb strings.Builder
+ for i, n := range nodes {
+ if i > 0 {
+ sb.WriteString(", ")
+ }
+ fmt.Fprintf(&sb, "(%d, %q", n.ID(), n.Name())
+
+ if n.Online() != nil {
+ fmt.Fprintf(&sb, ", online=%v", *n.Online())
+ }
+ if n.LastSeen() != nil {
+ fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen().Unix())
+ }
+ if n.Key() != (key.NodePublic{}) {
+ fmt.Fprintf(&sb, ", key=%v", n.Key().String())
+ }
+ if n.Expired() {
+ fmt.Fprintf(&sb, ", expired=true")
+ }
+ sb.WriteString(")")
+ }
+ return sb.String()
+}
diff --git a/ipn/ipnlocal/peerapi_h2c.go b/ipn/ipnlocal/peerapi_h2c.go index fbfa86398..e6335fe2b 100644 --- a/ipn/ipnlocal/peerapi_h2c.go +++ b/ipn/ipnlocal/peerapi_h2c.go @@ -1,20 +1,20 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !ios && !android && !js - -package ipnlocal - -import ( - "net/http" - - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" -) - -func init() { - addH2C = func(s *http.Server) { - h2s := &http2.Server{} - s.Handler = h2c.NewHandler(s.Handler, h2s) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !ios && !android && !js
+
+package ipnlocal
+
+import (
+ "net/http"
+
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/h2c"
+)
+
+func init() {
+ addH2C = func(s *http.Server) {
+ h2s := &http2.Server{}
+ s.Handler = h2c.NewHandler(s.Handler, h2s)
+ }
+}
diff --git a/ipn/ipnlocal/testdata/example.com-key.pem b/ipn/ipnlocal/testdata/example.com-key.pem index 06902f4c9..9020553f1 100644 --- a/ipn/ipnlocal/testdata/example.com-key.pem +++ b/ipn/ipnlocal/testdata/example.com-key.pem @@ -1,28 +1,28 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCejQaJrntrJSgE -QtScyTU6TXOU+v1FdFjrsyHFK5mjV1C5pVQxnLn93GRshtIrGOLLrd3Wv2TVYZOX -xH7f1ZLFbneDURCXbS+7nmsg+TLHRSRKfODbE3oYZj7NSJ163CCvwSJKTdmLpXbn -ui9F04tyk0zxO4Wre4ukwf6xtse8G5zl2RJrueiVAiouTG/pJdIS08dGQa0GM1n9 -Aesa+TerlZcpRZR6X402yQqa8q/QqbIuzrlfDmgOb8sm6T8+JMtj3hEvnYdpMVOg -w/XiTlX0v/YrB9sVQ9XnqGsqwTL0OMG0choMNKipwLi2n+XPSCIiRhi666zNNivE -K1qaPS5RAgMBAAECggEAV9dAGQWPISR70CiKjLa5A60nbRHFQjackTE0c32daC6W -7dOYGsh/DxOMm8fyJqhp9nhEYJa3MbUWxU27ER3NbA6wrhM6gvqeKG8zYRhPNrGq -0o3vMdDPozb6cldZ0Fimz1jMO6h373NjtiyjxibWqkrLpRbaDtCq5EQKbMEcVa2D -Xt5hxCOaCA3OZ/mAcGUNFmDNgNsGP/r6eXdI5pbqnUNMPkv/JsHl8h2HuyKUm4hf -TRnXPAak6DkUod9QXYFKVBVPa5pjiO09e0aiMUvJ8vYd/6bNIsAKWLPa1PYuUE2l -kg8Nik+P/XLzffKsLxiFKY0nCqrorM9K5q7baofGdQKBgQDPujjebFg6OKw6MS3S -PESopvL//C/XgtgifcSSZCWzIZRVBVTbbJCGRtqFzF0XO4YRX3EOAyD/L7wYUPzO -+W3AU2W3/DVJYdcm2CASABbHNy0kk52LI0HHAssbFDgyB9XuuWP+vVZk7B5OmCAD -Bppuj6Mnu03i282nKNJzvRiVnwKBgQDDZUXv22K8y7GkKw/ZW/wQP2zBNtFc15he -1EOyUGHlXuQixnDSaqonkwec6IOlo7Sx/vwO/7+v4Jzc24Wq3DFAmMu/EYJgvI+m -m3kpB4H7Xus4JqnhxqN7GB7zOdguCWZF1HLemZNZlVrUjG5mQ9cizzvvYptnQDLq -FEJ1hddWDwKBgB+vy276Xfb7oCH8UH4KXXrQhK7RvEaGmgug3bRq/Gk3zRWvC4Ox -KtagxkK0qtqZZNkPkwJNLeJfWLTo3beAyuIUlqabHVHFT/mH7FRymQbofsVekyCf -TzBZV7wYuH3BPjv9IajBHwWkEvdwMyni/vmwhXXRF49schF2o6uuA6sHAoGBAL1J -Xnb+EKjUq0JedPwcIBOdXb3PXQKT2QgEmZAkTrHlOxx1INa2fh/YT4ext9a+wE2u -tn/RQeEfttY90z+yEASEAN0YGTWddYvxEW6t1z2stjGvQuN1ium0dEcrwkDW2jzL -knwSSqx+A3/kiw6GqeMO3wEIhYOArdIVzkwLXJABAoGAOXLGhz5u5FWjF3zAeYme -uHTU/3Z3jeI80PvShGrgAakPOBt3cIFpUaiOEslcqqgDUSGE3EnmkRqaEch+UapF -ty6Zz7cKjXhQSWOjew1uUW2ANNEpsnYbmZOOnfvosd7jfHSVbL6KIhWmIdC6h0NP -c/bJnTXEEVsWjLZTwYaq0Us= +-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCejQaJrntrJSgE
+QtScyTU6TXOU+v1FdFjrsyHFK5mjV1C5pVQxnLn93GRshtIrGOLLrd3Wv2TVYZOX
+xH7f1ZLFbneDURCXbS+7nmsg+TLHRSRKfODbE3oYZj7NSJ163CCvwSJKTdmLpXbn
+ui9F04tyk0zxO4Wre4ukwf6xtse8G5zl2RJrueiVAiouTG/pJdIS08dGQa0GM1n9
+Aesa+TerlZcpRZR6X402yQqa8q/QqbIuzrlfDmgOb8sm6T8+JMtj3hEvnYdpMVOg
+w/XiTlX0v/YrB9sVQ9XnqGsqwTL0OMG0choMNKipwLi2n+XPSCIiRhi666zNNivE
+K1qaPS5RAgMBAAECggEAV9dAGQWPISR70CiKjLa5A60nbRHFQjackTE0c32daC6W
+7dOYGsh/DxOMm8fyJqhp9nhEYJa3MbUWxU27ER3NbA6wrhM6gvqeKG8zYRhPNrGq
+0o3vMdDPozb6cldZ0Fimz1jMO6h373NjtiyjxibWqkrLpRbaDtCq5EQKbMEcVa2D
+Xt5hxCOaCA3OZ/mAcGUNFmDNgNsGP/r6eXdI5pbqnUNMPkv/JsHl8h2HuyKUm4hf
+TRnXPAak6DkUod9QXYFKVBVPa5pjiO09e0aiMUvJ8vYd/6bNIsAKWLPa1PYuUE2l
+kg8Nik+P/XLzffKsLxiFKY0nCqrorM9K5q7baofGdQKBgQDPujjebFg6OKw6MS3S
+PESopvL//C/XgtgifcSSZCWzIZRVBVTbbJCGRtqFzF0XO4YRX3EOAyD/L7wYUPzO
++W3AU2W3/DVJYdcm2CASABbHNy0kk52LI0HHAssbFDgyB9XuuWP+vVZk7B5OmCAD
+Bppuj6Mnu03i282nKNJzvRiVnwKBgQDDZUXv22K8y7GkKw/ZW/wQP2zBNtFc15he
+1EOyUGHlXuQixnDSaqonkwec6IOlo7Sx/vwO/7+v4Jzc24Wq3DFAmMu/EYJgvI+m
+m3kpB4H7Xus4JqnhxqN7GB7zOdguCWZF1HLemZNZlVrUjG5mQ9cizzvvYptnQDLq
+FEJ1hddWDwKBgB+vy276Xfb7oCH8UH4KXXrQhK7RvEaGmgug3bRq/Gk3zRWvC4Ox
+KtagxkK0qtqZZNkPkwJNLeJfWLTo3beAyuIUlqabHVHFT/mH7FRymQbofsVekyCf
+TzBZV7wYuH3BPjv9IajBHwWkEvdwMyni/vmwhXXRF49schF2o6uuA6sHAoGBAL1J
+Xnb+EKjUq0JedPwcIBOdXb3PXQKT2QgEmZAkTrHlOxx1INa2fh/YT4ext9a+wE2u
+tn/RQeEfttY90z+yEASEAN0YGTWddYvxEW6t1z2stjGvQuN1ium0dEcrwkDW2jzL
+knwSSqx+A3/kiw6GqeMO3wEIhYOArdIVzkwLXJABAoGAOXLGhz5u5FWjF3zAeYme
+uHTU/3Z3jeI80PvShGrgAakPOBt3cIFpUaiOEslcqqgDUSGE3EnmkRqaEch+UapF
+ty6Zz7cKjXhQSWOjew1uUW2ANNEpsnYbmZOOnfvosd7jfHSVbL6KIhWmIdC6h0NP
+c/bJnTXEEVsWjLZTwYaq0Us=
-----END PRIVATE KEY-----
\ No newline at end of file diff --git a/ipn/ipnlocal/testdata/example.com.pem b/ipn/ipnlocal/testdata/example.com.pem index 588850813..65e7110a8 100644 --- a/ipn/ipnlocal/testdata/example.com.pem +++ b/ipn/ipnlocal/testdata/example.com.pem @@ -1,26 +1,26 @@ ------BEGIN CERTIFICATE----- -MIIEcDCCAtigAwIBAgIRAPmUKRkyFAkVVxFblB/233cwDQYJKoZIhvcNAQELBQAw -gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnJv -bWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWljaGFlbCBKLiBGcm9tYmVyZ2VyKTFB -MD8GA1UEAww4bWtjZXJ0IGZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hh -ZWwgSi4gRnJvbWJlcmdlcikwHhcNMjMwMjA3MjAzNDE4WhcNMjUwNTA3MTkzNDE4 -WjBlMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxOjA4 -BgNVBAsMMWZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hhZWwgSi4gRnJv -bWJlcmdlcikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCejQaJrntr -JSgEQtScyTU6TXOU+v1FdFjrsyHFK5mjV1C5pVQxnLn93GRshtIrGOLLrd3Wv2TV -YZOXxH7f1ZLFbneDURCXbS+7nmsg+TLHRSRKfODbE3oYZj7NSJ163CCvwSJKTdmL -pXbnui9F04tyk0zxO4Wre4ukwf6xtse8G5zl2RJrueiVAiouTG/pJdIS08dGQa0G -M1n9Aesa+TerlZcpRZR6X402yQqa8q/QqbIuzrlfDmgOb8sm6T8+JMtj3hEvnYdp -MVOgw/XiTlX0v/YrB9sVQ9XnqGsqwTL0OMG0choMNKipwLi2n+XPSCIiRhi666zN -NivEK1qaPS5RAgMBAAGjYDBeMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr -BgEFBQcDATAfBgNVHSMEGDAWgBTXyq2jQVrnqQKL8fB9C4L0QJftwDAWBgNVHREE -DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAQWzpOaBkRR4M+WqB -CsT4ARyM6WpZ+jpeSblCzPdlDRW+50G1HV7K930zayq4DwncPY/SqSn0Q31WuzZv -bTWHkWa+MLPGYANHsusOmMR8Eh16G4+5+GGf8psWa0npAYO35cuNkyyCCc1LEB4M -NrzCB2+KZ+SyOdfCCA5VzEKN3I8wvVLaYovi24Zjwv+0uETG92TlZmLQRhj8uPxN -deeLM45aBkQZSYCbGMDVDK/XYKBkNLn3kxD/eZeXxxr41v4pH44+46FkYcYJzdn8 -ccAg5LRGieqTozhLiXARNK1vTy6kR1l/Az8DIx6GN4sP2/LMFYFijiiOCDKS1wWA -xQgZeHt4GIuBym+Kd+Z5KXcP0AT+47Cby3+B10Kq8vHwjTELiF0UFeEYYMdynPAW -pbEwVLhsfMsBqFtj3dsxHr8Kz3rnarOYzkaw7EMZnLAthb2CN7y5uGV9imQC5RMI -/qZdRSuCYZ3A1E/WJkGbPY/YdPql/IE+LIAgKGFHZZNftBCo +-----BEGIN CERTIFICATE-----
+MIIEcDCCAtigAwIBAgIRAPmUKRkyFAkVVxFblB/233cwDQYJKoZIhvcNAQELBQAw
+gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnJv
+bWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWljaGFlbCBKLiBGcm9tYmVyZ2VyKTFB
+MD8GA1UEAww4bWtjZXJ0IGZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hh
+ZWwgSi4gRnJvbWJlcmdlcikwHhcNMjMwMjA3MjAzNDE4WhcNMjUwNTA3MTkzNDE4
+WjBlMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxOjA4
+BgNVBAsMMWZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hhZWwgSi4gRnJv
+bWJlcmdlcikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCejQaJrntr
+JSgEQtScyTU6TXOU+v1FdFjrsyHFK5mjV1C5pVQxnLn93GRshtIrGOLLrd3Wv2TV
+YZOXxH7f1ZLFbneDURCXbS+7nmsg+TLHRSRKfODbE3oYZj7NSJ163CCvwSJKTdmL
+pXbnui9F04tyk0zxO4Wre4ukwf6xtse8G5zl2RJrueiVAiouTG/pJdIS08dGQa0G
+M1n9Aesa+TerlZcpRZR6X402yQqa8q/QqbIuzrlfDmgOb8sm6T8+JMtj3hEvnYdp
+MVOgw/XiTlX0v/YrB9sVQ9XnqGsqwTL0OMG0choMNKipwLi2n+XPSCIiRhi666zN
+NivEK1qaPS5RAgMBAAGjYDBeMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
+BgEFBQcDATAfBgNVHSMEGDAWgBTXyq2jQVrnqQKL8fB9C4L0QJftwDAWBgNVHREE
+DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAQWzpOaBkRR4M+WqB
+CsT4ARyM6WpZ+jpeSblCzPdlDRW+50G1HV7K930zayq4DwncPY/SqSn0Q31WuzZv
+bTWHkWa+MLPGYANHsusOmMR8Eh16G4+5+GGf8psWa0npAYO35cuNkyyCCc1LEB4M
+NrzCB2+KZ+SyOdfCCA5VzEKN3I8wvVLaYovi24Zjwv+0uETG92TlZmLQRhj8uPxN
+deeLM45aBkQZSYCbGMDVDK/XYKBkNLn3kxD/eZeXxxr41v4pH44+46FkYcYJzdn8
+ccAg5LRGieqTozhLiXARNK1vTy6kR1l/Az8DIx6GN4sP2/LMFYFijiiOCDKS1wWA
+xQgZeHt4GIuBym+Kd+Z5KXcP0AT+47Cby3+B10Kq8vHwjTELiF0UFeEYYMdynPAW
+pbEwVLhsfMsBqFtj3dsxHr8Kz3rnarOYzkaw7EMZnLAthb2CN7y5uGV9imQC5RMI
+/qZdRSuCYZ3A1E/WJkGbPY/YdPql/IE+LIAgKGFHZZNftBCo
-----END CERTIFICATE-----
\ No newline at end of file diff --git a/ipn/ipnlocal/testdata/rootCA.pem b/ipn/ipnlocal/testdata/rootCA.pem index 88a16f47a..28bd25467 100644 --- a/ipn/ipnlocal/testdata/rootCA.pem +++ b/ipn/ipnlocal/testdata/rootCA.pem @@ -1,30 +1,30 @@ ------BEGIN CERTIFICATE----- -MIIFEDCCA3igAwIBAgIRANf5NdPojIfj70wMfJVYUg8wDQYJKoZIhvcNAQELBQAw -gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnJv -bWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWljaGFlbCBKLiBGcm9tYmVyZ2VyKTFB -MD8GA1UEAww4bWtjZXJ0IGZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hh -ZWwgSi4gRnJvbWJlcmdlcikwHhcNMjMwMjA3MjAzNDE4WhcNMzMwMjA3MjAzNDE4 -WjCBnzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTowOAYDVQQLDDFm -cm9tYmVyZ2VyQHN0YXJkdXN0LmxvY2FsIChNaWNoYWVsIEouIEZyb21iZXJnZXIp -MUEwPwYDVQQDDDhta2NlcnQgZnJvbWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWlj -aGFlbCBKLiBGcm9tYmVyZ2VyKTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC -ggGBAL5uXNnrZ6dgjcvK0Hc7ZNUIRYEWst9qbO0P9H7le08pJ6d9T2BUWruZtVjk -Q12msv5/bVWHhVk8dZclI9FLXuMsIrocH8bsoP4wruPMyRyp6EedSKODN51fFSRv -/jHbS5vzUVAWTYy9qYmd6qL0uhsHCZCCT6gfigamHPUFKM3sHDn5ZHWvySMwcyGl -AicmPAIkBWqiCZAkB5+WM7+oyRLjmrIalfWIZYxW/rojGLwTfneHv6J5WjVQnpJB -ayWCzCzaiXukK9MeBWeTOe8UfVN0Engd74/rjLWvjbfC+uZSr6RVkZvs2jANLwPF -zgzBPHgRPfAhszU1NNAMjnNQ47+OMOTKRt7e6jYzhO5fyO1qVAAvGBqcfpj+JfDk -cccaUMhUvdiGrhGf1V1tN/PislxvALirzcFipjD01isBKwn0fxRugzvJNrjEo8RA -RvbcdeKcwex7M0o/Cd0+G2B13gZNOFvR33PmG7iTpp7IUrUKfQg28I83Sp8tMY3s -ljJSawIDAQABo0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgwBgEB/wIB -ADAdBgNVHQ4EFgQU18qto0Fa56kCi/HwfQuC9ECX7cAwDQYJKoZIhvcNAQELBQAD -ggGBAAzs96LwZVOsRSlBdQqMo8oMAvs7HgnYbXt8SqaACLX3+kJ3cV/vrCE3iJrW -ma4CiQbxS/HqsiZjota5m4lYeEevRnUDpXhp+7ugZTiz33Flm1RU99c9UYfQ+919 -ANPAKeqNpoPco/HF5Bz0ocepjcfKQrVZZNTj6noLs8o12FHBLO5976AcF9mqlNfh -8/F0gDJXq6+x7VT5y8u0rY004XKPRe3CklRt8kpeMiP6mhRyyUehOaHeIbNx8ubi -Pi44ByN/ueAnuRhF9zYtyZVZZOaSLysJge01tuPXF8rBXGruoJIv35xTTBa9BzaP -YDOGbGn1ZnajdNagHqCba8vjTLDSpqMvgRj3TFrGHdETA2LDQat38uVxX8gxm68K -va5Tyv7n+6BQ5YTpJjTPnmSJKaXZrrhdLPvG0OU2TxeEsvbcm5LFQofirOOw86Se -vzF2cQ94mmHRZiEk0Av3NO0jF93ELDrBCuiccVyEKq6TknuvPQlutCXKDOYSEb8I -MHctBg== +-----BEGIN CERTIFICATE-----
+MIIFEDCCA3igAwIBAgIRANf5NdPojIfj70wMfJVYUg8wDQYJKoZIhvcNAQELBQAw
+gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnJv
+bWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWljaGFlbCBKLiBGcm9tYmVyZ2VyKTFB
+MD8GA1UEAww4bWtjZXJ0IGZyb21iZXJnZXJAc3RhcmR1c3QubG9jYWwgKE1pY2hh
+ZWwgSi4gRnJvbWJlcmdlcikwHhcNMjMwMjA3MjAzNDE4WhcNMzMwMjA3MjAzNDE4
+WjCBnzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTowOAYDVQQLDDFm
+cm9tYmVyZ2VyQHN0YXJkdXN0LmxvY2FsIChNaWNoYWVsIEouIEZyb21iZXJnZXIp
+MUEwPwYDVQQDDDhta2NlcnQgZnJvbWJlcmdlckBzdGFyZHVzdC5sb2NhbCAoTWlj
+aGFlbCBKLiBGcm9tYmVyZ2VyKTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC
+ggGBAL5uXNnrZ6dgjcvK0Hc7ZNUIRYEWst9qbO0P9H7le08pJ6d9T2BUWruZtVjk
+Q12msv5/bVWHhVk8dZclI9FLXuMsIrocH8bsoP4wruPMyRyp6EedSKODN51fFSRv
+/jHbS5vzUVAWTYy9qYmd6qL0uhsHCZCCT6gfigamHPUFKM3sHDn5ZHWvySMwcyGl
+AicmPAIkBWqiCZAkB5+WM7+oyRLjmrIalfWIZYxW/rojGLwTfneHv6J5WjVQnpJB
+ayWCzCzaiXukK9MeBWeTOe8UfVN0Engd74/rjLWvjbfC+uZSr6RVkZvs2jANLwPF
+zgzBPHgRPfAhszU1NNAMjnNQ47+OMOTKRt7e6jYzhO5fyO1qVAAvGBqcfpj+JfDk
+cccaUMhUvdiGrhGf1V1tN/PislxvALirzcFipjD01isBKwn0fxRugzvJNrjEo8RA
+RvbcdeKcwex7M0o/Cd0+G2B13gZNOFvR33PmG7iTpp7IUrUKfQg28I83Sp8tMY3s
+ljJSawIDAQABo0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgwBgEB/wIB
+ADAdBgNVHQ4EFgQU18qto0Fa56kCi/HwfQuC9ECX7cAwDQYJKoZIhvcNAQELBQAD
+ggGBAAzs96LwZVOsRSlBdQqMo8oMAvs7HgnYbXt8SqaACLX3+kJ3cV/vrCE3iJrW
+ma4CiQbxS/HqsiZjota5m4lYeEevRnUDpXhp+7ugZTiz33Flm1RU99c9UYfQ+919
+ANPAKeqNpoPco/HF5Bz0ocepjcfKQrVZZNTj6noLs8o12FHBLO5976AcF9mqlNfh
+8/F0gDJXq6+x7VT5y8u0rY004XKPRe3CklRt8kpeMiP6mhRyyUehOaHeIbNx8ubi
+Pi44ByN/ueAnuRhF9zYtyZVZZOaSLysJge01tuPXF8rBXGruoJIv35xTTBa9BzaP
+YDOGbGn1ZnajdNagHqCba8vjTLDSpqMvgRj3TFrGHdETA2LDQat38uVxX8gxm68K
+va5Tyv7n+6BQ5YTpJjTPnmSJKaXZrrhdLPvG0OU2TxeEsvbcm5LFQofirOOw86Se
+vzF2cQ94mmHRZiEk0Av3NO0jF93ELDrBCuiccVyEKq6TknuvPQlutCXKDOYSEb8I
+MHctBg==
-----END CERTIFICATE-----
\ No newline at end of file diff --git a/ipn/ipnserver/proxyconnect_js.go b/ipn/ipnserver/proxyconnect_js.go index 368221e22..27448fa0d 100644 --- a/ipn/ipnserver/proxyconnect_js.go +++ b/ipn/ipnserver/proxyconnect_js.go @@ -1,10 +1,10 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package ipnserver - -import "net/http" - -func (s *Server) handleProxyConnectConn(w http.ResponseWriter, r *http.Request) { - panic("unreachable") -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package ipnserver
+
+import "net/http"
+
+func (s *Server) handleProxyConnectConn(w http.ResponseWriter, r *http.Request) {
+ panic("unreachable")
+}
diff --git a/ipn/ipnserver/server_test.go b/ipn/ipnserver/server_test.go index b7d5ea144..49fb4d01f 100644 --- a/ipn/ipnserver/server_test.go +++ b/ipn/ipnserver/server_test.go @@ -1,46 +1,46 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package ipnserver - -import ( - "context" - "sync" - "testing" -) - -func TestWaiterSet(t *testing.T) { - var s waiterSet - - wantLen := func(want int, when string) { - t.Helper() - if got := len(s); got != want { - t.Errorf("%s: len = %v; want %v", when, got, want) - } - } - wantLen(0, "initial") - var mu sync.Mutex - ctx, cancel := context.WithCancel(context.Background()) - - ready, cleanup := s.add(&mu, ctx) - wantLen(1, "after add") - - select { - case <-ready: - t.Fatal("should not be ready") - default: - } - s.wakeAll() - <-ready - - wantLen(1, "after fire") - cleanup() - wantLen(0, "after cleanup") - - // And again but on an already-expired ctx. - cancel() - ready, cleanup = s.add(&mu, ctx) - <-ready // shouldn't block - cleanup() - wantLen(0, "at end") -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package ipnserver
+
+import (
+ "context"
+ "sync"
+ "testing"
+)
+
+func TestWaiterSet(t *testing.T) {
+ var s waiterSet
+
+ wantLen := func(want int, when string) {
+ t.Helper()
+ if got := len(s); got != want {
+ t.Errorf("%s: len = %v; want %v", when, got, want)
+ }
+ }
+ wantLen(0, "initial")
+ var mu sync.Mutex
+ ctx, cancel := context.WithCancel(context.Background())
+
+ ready, cleanup := s.add(&mu, ctx)
+ wantLen(1, "after add")
+
+ select {
+ case <-ready:
+ t.Fatal("should not be ready")
+ default:
+ }
+ s.wakeAll()
+ <-ready
+
+ wantLen(1, "after fire")
+ cleanup()
+ wantLen(0, "after cleanup")
+
+ // And again but on an already-expired ctx.
+ cancel()
+ ready, cleanup = s.add(&mu, ctx)
+ <-ready // shouldn't block
+ cleanup()
+ wantLen(0, "at end")
+}
diff --git a/ipn/localapi/disabled_stubs.go b/ipn/localapi/disabled_stubs.go index c744f34d5..230553c14 100644 --- a/ipn/localapi/disabled_stubs.go +++ b/ipn/localapi/disabled_stubs.go @@ -1,15 +1,15 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build ios || android || js - -package localapi - -import ( - "net/http" - "runtime" -) - -func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) { - http.Error(w, "disabled on "+runtime.GOOS, http.StatusNotFound) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build ios || android || js
+
+package localapi
+
+import (
+ "net/http"
+ "runtime"
+)
+
+func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) {
+ http.Error(w, "disabled on "+runtime.GOOS, http.StatusNotFound)
+}
diff --git a/ipn/localapi/pprof.go b/ipn/localapi/pprof.go index 8c9429b31..5cc4daca1 100644 --- a/ipn/localapi/pprof.go +++ b/ipn/localapi/pprof.go @@ -1,28 +1,28 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !ios && !android && !js - -// We don't include it on mobile where we're more memory constrained and -// there's no CLI to get at the results anyway. - -package localapi - -import ( - "net/http" - "net/http/pprof" -) - -func init() { - servePprofFunc = servePprof -} - -func servePprof(w http.ResponseWriter, r *http.Request) { - name := r.FormValue("name") - switch name { - case "profile": - pprof.Profile(w, r) - default: - pprof.Handler(name).ServeHTTP(w, r) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !ios && !android && !js
+
+// We don't include it on mobile where we're more memory constrained and
+// there's no CLI to get at the results anyway.
+
+package localapi
+
+import (
+ "net/http"
+ "net/http/pprof"
+)
+
+func init() {
+ servePprofFunc = servePprof
+}
+
+func servePprof(w http.ResponseWriter, r *http.Request) {
+ name := r.FormValue("name")
+ switch name {
+ case "profile":
+ pprof.Profile(w, r)
+ default:
+ pprof.Handler(name).ServeHTTP(w, r)
+ }
+}
diff --git a/ipn/policy/policy.go b/ipn/policy/policy.go index 494a0dc40..834706f31 100644 --- a/ipn/policy/policy.go +++ b/ipn/policy/policy.go @@ -1,47 +1,47 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Package policy contains various policy decisions that need to be -// shared between the node client & control server. -package policy - -import ( - "tailscale.com/tailcfg" -) - -// IsInterestingService reports whether service s on the given operating -// system (a version.OS value) is an interesting enough port to report -// to our peer nodes for discovery purposes. -func IsInterestingService(s tailcfg.Service, os string) bool { - switch s.Proto { - case tailcfg.PeerAPI4, tailcfg.PeerAPI6, tailcfg.PeerAPIDNS: - return true - } - if s.Proto != tailcfg.TCP { - return false - } - if os != "windows" { - // For non-Windows machines, assume all TCP listeners - // are interesting enough. We don't see listener spam - // there. - return true - } - // Windows has tons of TCP listeners. We need to move to a denylist - // model later, but for now we just allow some common ones: - switch s.Port { - case 22, // ssh - 80, // http - 443, // https (but no hostname, so little useless) - 3389, // rdp - 5900, // vnc - 32400, // plex - - // And now some arbitrary HTTP dev server ports: - // Eventually we'll remove this and make all ports - // work, once we nicely filter away noisy system - // ports. - 8000, 8080, 8443, 8888: - return true - } - return false -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package policy contains various policy decisions that need to be
+// shared between the node client & control server.
+package policy
+
+import (
+ "tailscale.com/tailcfg"
+)
+
+// IsInterestingService reports whether service s on the given operating
+// system (a version.OS value) is an interesting enough port to report
+// to our peer nodes for discovery purposes.
+func IsInterestingService(s tailcfg.Service, os string) bool {
+ switch s.Proto {
+ case tailcfg.PeerAPI4, tailcfg.PeerAPI6, tailcfg.PeerAPIDNS:
+ return true
+ }
+ if s.Proto != tailcfg.TCP {
+ return false
+ }
+ if os != "windows" {
+ // For non-Windows machines, assume all TCP listeners
+ // are interesting enough. We don't see listener spam
+ // there.
+ return true
+ }
+ // Windows has tons of TCP listeners. We need to move to a denylist
+ // model later, but for now we just allow some common ones:
+ switch s.Port {
+ case 22, // ssh
+ 80, // http
+ 443, // https (but no hostname, so little useless)
+ 3389, // rdp
+ 5900, // vnc
+ 32400, // plex
+
+ // And now some arbitrary HTTP dev server ports:
+ // Eventually we'll remove this and make all ports
+ // work, once we nicely filter away noisy system
+ // ports.
+ 8000, 8080, 8443, 8888:
+ return true
+ }
+ return false
+}
diff --git a/ipn/store/awsstore/store_aws.go b/ipn/store/awsstore/store_aws.go index 0fb78d45a..84059af67 100644 --- a/ipn/store/awsstore/store_aws.go +++ b/ipn/store/awsstore/store_aws.go @@ -1,186 +1,186 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build linux && !ts_omit_aws - -// Package awsstore contains an ipn.StateStore implementation using AWS SSM. -package awsstore - -import ( - "context" - "errors" - "fmt" - "regexp" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/aws/arn" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/ssm" - ssmTypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" - "tailscale.com/ipn" - "tailscale.com/ipn/store/mem" - "tailscale.com/types/logger" -) - -const ( - parameterNameRxStr = `^parameter(/.*)` -) - -var parameterNameRx = regexp.MustCompile(parameterNameRxStr) - -// awsSSMClient is an interface allowing us to mock the couple of -// API calls we are leveraging with the AWSStore provider -type awsSSMClient interface { - GetParameter(ctx context.Context, - params *ssm.GetParameterInput, - optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) - - PutParameter(ctx context.Context, - params *ssm.PutParameterInput, - optFns ...func(*ssm.Options)) (*ssm.PutParameterOutput, error) -} - -// store is a store which leverages AWS SSM parameter store -// to persist the state -type awsStore struct { - ssmClient awsSSMClient - ssmARN arn.ARN - - memory mem.Store -} - -// New returns a new ipn.StateStore using the AWS SSM storage -// location given by ssmARN. -// -// Note that we store the entire store in a single parameter -// key, therefore if the state is above 8kb, it can cause -// Tailscaled to only only store new state in-memory and -// restarting Tailscaled can fail until you delete your state -// from the AWS Parameter Store. -func New(_ logger.Logf, ssmARN string) (ipn.StateStore, error) { - return newStore(ssmARN, nil) -} - -// newStore is NewStore, but for tests. If client is non-nil, it's -// used instead of making one. -func newStore(ssmARN string, client awsSSMClient) (ipn.StateStore, error) { - s := &awsStore{ - ssmClient: client, - } - - var err error - - // Parse the ARN - if s.ssmARN, err = arn.Parse(ssmARN); err != nil { - return nil, fmt.Errorf("unable to parse the ARN correctly: %v", err) - } - - // Validate the ARN corresponds to the SSM service - if s.ssmARN.Service != "ssm" { - return nil, fmt.Errorf("invalid service %q, expected 'ssm'", s.ssmARN.Service) - } - - // Validate the ARN corresponds to a parameter store resource - if !parameterNameRx.MatchString(s.ssmARN.Resource) { - return nil, fmt.Errorf("invalid resource %q, expected to match %v", s.ssmARN.Resource, parameterNameRxStr) - } - - if s.ssmClient == nil { - var cfg aws.Config - if cfg, err = config.LoadDefaultConfig( - context.TODO(), - config.WithRegion(s.ssmARN.Region), - ); err != nil { - return nil, err - } - s.ssmClient = ssm.NewFromConfig(cfg) - } - - // Hydrate cache with the potentially current state - if err := s.LoadState(); err != nil { - return nil, err - } - return s, nil - -} - -// LoadState attempts to read the state from AWS SSM parameter store key. -func (s *awsStore) LoadState() error { - param, err := s.ssmClient.GetParameter( - context.TODO(), - &ssm.GetParameterInput{ - Name: aws.String(s.ParameterName()), - WithDecryption: aws.Bool(true), - }, - ) - - if err != nil { - var pnf *ssmTypes.ParameterNotFound - if errors.As(err, &pnf) { - // Create the parameter as it does not exist yet - // and return directly as it is defacto empty - return s.persistState() - } - return err - } - - // Load the content in-memory - return s.memory.LoadFromJSON([]byte(*param.Parameter.Value)) -} - -// ParameterName returns the parameter name extracted from -// the provided ARN -func (s *awsStore) ParameterName() (name string) { - values := parameterNameRx.FindStringSubmatch(s.ssmARN.Resource) - if len(values) == 2 { - name = values[1] - } - return -} - -// String returns the awsStore and the ARN of the SSM parameter store -// configured to store the state -func (s *awsStore) String() string { return fmt.Sprintf("awsStore(%q)", s.ssmARN.String()) } - -// ReadState implements the Store interface. -func (s *awsStore) ReadState(id ipn.StateKey) (bs []byte, err error) { - return s.memory.ReadState(id) -} - -// WriteState implements the Store interface. -func (s *awsStore) WriteState(id ipn.StateKey, bs []byte) (err error) { - // Write the state in-memory - if err = s.memory.WriteState(id, bs); err != nil { - return - } - - // Persist the state in AWS SSM parameter store - return s.persistState() -} - -// PersistState saves the states into the AWS SSM parameter store -func (s *awsStore) persistState() error { - // Generate JSON from in-memory cache - bs, err := s.memory.ExportToJSON() - if err != nil { - return err - } - - // Store in AWS SSM parameter store. - // - // We use intelligent tiering so that when the state is below 4kb, it uses Standard tiering - // which is free. However, if it exceeds 4kb it switches the parameter to advanced tiering - // doubling the capacity to 8kb per the following docs: - // https://aws.amazon.com/about-aws/whats-new/2019/08/aws-systems-manager-parameter-store-announces-intelligent-tiering-to-enable-automatic-parameter-tier-selection/ - _, err = s.ssmClient.PutParameter( - context.TODO(), - &ssm.PutParameterInput{ - Name: aws.String(s.ParameterName()), - Value: aws.String(string(bs)), - Overwrite: aws.Bool(true), - Tier: ssmTypes.ParameterTierIntelligentTiering, - Type: ssmTypes.ParameterTypeSecureString, - }, - ) - return err -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build linux && !ts_omit_aws
+
+// Package awsstore contains an ipn.StateStore implementation using AWS SSM.
+package awsstore
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "regexp"
+
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/aws/arn"
+ "github.com/aws/aws-sdk-go-v2/config"
+ "github.com/aws/aws-sdk-go-v2/service/ssm"
+ ssmTypes "github.com/aws/aws-sdk-go-v2/service/ssm/types"
+ "tailscale.com/ipn"
+ "tailscale.com/ipn/store/mem"
+ "tailscale.com/types/logger"
+)
+
+const (
+ parameterNameRxStr = `^parameter(/.*)`
+)
+
+var parameterNameRx = regexp.MustCompile(parameterNameRxStr)
+
+// awsSSMClient is an interface allowing us to mock the couple of
+// API calls we are leveraging with the AWSStore provider
+type awsSSMClient interface {
+ GetParameter(ctx context.Context,
+ params *ssm.GetParameterInput,
+ optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error)
+
+ PutParameter(ctx context.Context,
+ params *ssm.PutParameterInput,
+ optFns ...func(*ssm.Options)) (*ssm.PutParameterOutput, error)
+}
+
+// store is a store which leverages AWS SSM parameter store
+// to persist the state
+type awsStore struct {
+ ssmClient awsSSMClient
+ ssmARN arn.ARN
+
+ memory mem.Store
+}
+
+// New returns a new ipn.StateStore using the AWS SSM storage
+// location given by ssmARN.
+//
+// Note that we store the entire store in a single parameter
+// key, therefore if the state is above 8kb, it can cause
+// Tailscaled to only only store new state in-memory and
+// restarting Tailscaled can fail until you delete your state
+// from the AWS Parameter Store.
+func New(_ logger.Logf, ssmARN string) (ipn.StateStore, error) {
+ return newStore(ssmARN, nil)
+}
+
+// newStore is NewStore, but for tests. If client is non-nil, it's
+// used instead of making one.
+func newStore(ssmARN string, client awsSSMClient) (ipn.StateStore, error) {
+ s := &awsStore{
+ ssmClient: client,
+ }
+
+ var err error
+
+ // Parse the ARN
+ if s.ssmARN, err = arn.Parse(ssmARN); err != nil {
+ return nil, fmt.Errorf("unable to parse the ARN correctly: %v", err)
+ }
+
+ // Validate the ARN corresponds to the SSM service
+ if s.ssmARN.Service != "ssm" {
+ return nil, fmt.Errorf("invalid service %q, expected 'ssm'", s.ssmARN.Service)
+ }
+
+ // Validate the ARN corresponds to a parameter store resource
+ if !parameterNameRx.MatchString(s.ssmARN.Resource) {
+ return nil, fmt.Errorf("invalid resource %q, expected to match %v", s.ssmARN.Resource, parameterNameRxStr)
+ }
+
+ if s.ssmClient == nil {
+ var cfg aws.Config
+ if cfg, err = config.LoadDefaultConfig(
+ context.TODO(),
+ config.WithRegion(s.ssmARN.Region),
+ ); err != nil {
+ return nil, err
+ }
+ s.ssmClient = ssm.NewFromConfig(cfg)
+ }
+
+ // Hydrate cache with the potentially current state
+ if err := s.LoadState(); err != nil {
+ return nil, err
+ }
+ return s, nil
+
+}
+
+// LoadState attempts to read the state from AWS SSM parameter store key.
+func (s *awsStore) LoadState() error {
+ param, err := s.ssmClient.GetParameter(
+ context.TODO(),
+ &ssm.GetParameterInput{
+ Name: aws.String(s.ParameterName()),
+ WithDecryption: aws.Bool(true),
+ },
+ )
+
+ if err != nil {
+ var pnf *ssmTypes.ParameterNotFound
+ if errors.As(err, &pnf) {
+ // Create the parameter as it does not exist yet
+ // and return directly as it is defacto empty
+ return s.persistState()
+ }
+ return err
+ }
+
+ // Load the content in-memory
+ return s.memory.LoadFromJSON([]byte(*param.Parameter.Value))
+}
+
+// ParameterName returns the parameter name extracted from
+// the provided ARN
+func (s *awsStore) ParameterName() (name string) {
+ values := parameterNameRx.FindStringSubmatch(s.ssmARN.Resource)
+ if len(values) == 2 {
+ name = values[1]
+ }
+ return
+}
+
+// String returns the awsStore and the ARN of the SSM parameter store
+// configured to store the state
+func (s *awsStore) String() string { return fmt.Sprintf("awsStore(%q)", s.ssmARN.String()) }
+
+// ReadState implements the Store interface.
+func (s *awsStore) ReadState(id ipn.StateKey) (bs []byte, err error) {
+ return s.memory.ReadState(id)
+}
+
+// WriteState implements the Store interface.
+func (s *awsStore) WriteState(id ipn.StateKey, bs []byte) (err error) {
+ // Write the state in-memory
+ if err = s.memory.WriteState(id, bs); err != nil {
+ return
+ }
+
+ // Persist the state in AWS SSM parameter store
+ return s.persistState()
+}
+
+// PersistState saves the states into the AWS SSM parameter store
+func (s *awsStore) persistState() error {
+ // Generate JSON from in-memory cache
+ bs, err := s.memory.ExportToJSON()
+ if err != nil {
+ return err
+ }
+
+ // Store in AWS SSM parameter store.
+ //
+ // We use intelligent tiering so that when the state is below 4kb, it uses Standard tiering
+ // which is free. However, if it exceeds 4kb it switches the parameter to advanced tiering
+ // doubling the capacity to 8kb per the following docs:
+ // https://aws.amazon.com/about-aws/whats-new/2019/08/aws-systems-manager-parameter-store-announces-intelligent-tiering-to-enable-automatic-parameter-tier-selection/
+ _, err = s.ssmClient.PutParameter(
+ context.TODO(),
+ &ssm.PutParameterInput{
+ Name: aws.String(s.ParameterName()),
+ Value: aws.String(string(bs)),
+ Overwrite: aws.Bool(true),
+ Tier: ssmTypes.ParameterTierIntelligentTiering,
+ Type: ssmTypes.ParameterTypeSecureString,
+ },
+ )
+ return err
+}
diff --git a/ipn/store/awsstore/store_aws_stub.go b/ipn/store/awsstore/store_aws_stub.go index 8d2156ce9..7be8b858d 100644 --- a/ipn/store/awsstore/store_aws_stub.go +++ b/ipn/store/awsstore/store_aws_stub.go @@ -1,18 +1,18 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !linux || ts_omit_aws - -package awsstore - -import ( - "fmt" - "runtime" - - "tailscale.com/ipn" - "tailscale.com/types/logger" -) - -func New(logger.Logf, string) (ipn.StateStore, error) { - return nil, fmt.Errorf("AWS store is not supported on %v", runtime.GOOS) -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !linux || ts_omit_aws
+
+package awsstore
+
+import (
+ "fmt"
+ "runtime"
+
+ "tailscale.com/ipn"
+ "tailscale.com/types/logger"
+)
+
+func New(logger.Logf, string) (ipn.StateStore, error) {
+ return nil, fmt.Errorf("AWS store is not supported on %v", runtime.GOOS)
+}
diff --git a/ipn/store/awsstore/store_aws_test.go b/ipn/store/awsstore/store_aws_test.go index f6c8fedb3..54e6e18cb 100644 --- a/ipn/store/awsstore/store_aws_test.go +++ b/ipn/store/awsstore/store_aws_test.go @@ -1,164 +1,164 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -//go:build linux - -package awsstore - -import ( - "context" - "testing" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/aws/arn" - "github.com/aws/aws-sdk-go-v2/service/ssm" - ssmTypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" - "tailscale.com/ipn" - "tailscale.com/tstest" -) - -type mockedAWSSSMClient struct { - value string -} - -func (sp *mockedAWSSSMClient) GetParameter(_ context.Context, input *ssm.GetParameterInput, _ ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { - output := new(ssm.GetParameterOutput) - if sp.value == "" { - return output, &ssmTypes.ParameterNotFound{} - } - - output.Parameter = &ssmTypes.Parameter{ - Value: aws.String(sp.value), - } - - return output, nil -} - -func (sp *mockedAWSSSMClient) PutParameter(_ context.Context, input *ssm.PutParameterInput, _ ...func(*ssm.Options)) (*ssm.PutParameterOutput, error) { - sp.value = *input.Value - return new(ssm.PutParameterOutput), nil -} - -func TestAWSStoreString(t *testing.T) { - store := &awsStore{ - ssmARN: arn.ARN{ - Service: "ssm", - Region: "eu-west-1", - AccountID: "123456789", - Resource: "parameter/foo", - }, - } - want := "awsStore(\"arn::ssm:eu-west-1:123456789:parameter/foo\")" - if got := store.String(); got != want { - t.Errorf("AWSStore.String = %q; want %q", got, want) - } -} - -func TestNewAWSStore(t *testing.T) { - tstest.PanicOnLog() - - mc := &mockedAWSSSMClient{} - storeParameterARN := arn.ARN{ - Service: "ssm", - Region: "eu-west-1", - AccountID: "123456789", - Resource: "parameter/foo", - } - - s, err := newStore(storeParameterARN.String(), mc) - if err != nil { - t.Fatalf("creating aws store failed: %v", err) - } - testStoreSemantics(t, s) - - // Build a brand new file store and check that both IDs written - // above are still there. - s2, err := newStore(storeParameterARN.String(), mc) - if err != nil { - t.Fatalf("creating second aws store failed: %v", err) - } - store2 := s.(*awsStore) - - // This is specific to the test, with the non-mocked API, LoadState() should - // have been already called and successful as no err is returned from NewAWSStore() - s2.(*awsStore).LoadState() - - expected := map[ipn.StateKey]string{ - "foo": "bar", - "baz": "quux", - } - for id, want := range expected { - bs, err := store2.ReadState(id) - if err != nil { - t.Errorf("reading %q (2nd store): %v", id, err) - } - if string(bs) != want { - t.Errorf("reading %q (2nd store): got %q, want %q", id, string(bs), want) - } - } -} - -func testStoreSemantics(t *testing.T, store ipn.StateStore) { - t.Helper() - - tests := []struct { - // if true, data is data to write. If false, data is expected - // output of read. - write bool - id ipn.StateKey - data string - // If write=false, true if we expect a not-exist error. - notExists bool - }{ - { - id: "foo", - notExists: true, - }, - { - write: true, - id: "foo", - data: "bar", - }, - { - id: "foo", - data: "bar", - }, - { - id: "baz", - notExists: true, - }, - { - write: true, - id: "baz", - data: "quux", - }, - { - id: "foo", - data: "bar", - }, - { - id: "baz", - data: "quux", - }, - } - - for _, test := range tests { - if test.write { - if err := store.WriteState(test.id, []byte(test.data)); err != nil { - t.Errorf("writing %q to %q: %v", test.data, test.id, err) - } - } else { - bs, err := store.ReadState(test.id) - if err != nil { - if test.notExists && err == ipn.ErrStateNotExist { - continue - } - t.Errorf("reading %q: %v", test.id, err) - continue - } - if string(bs) != test.data { - t.Errorf("reading %q: got %q, want %q", test.id, string(bs), test.data) - } - } - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build linux
+
+package awsstore
+
+import (
+ "context"
+ "testing"
+
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/aws/arn"
+ "github.com/aws/aws-sdk-go-v2/service/ssm"
+ ssmTypes "github.com/aws/aws-sdk-go-v2/service/ssm/types"
+ "tailscale.com/ipn"
+ "tailscale.com/tstest"
+)
+
+type mockedAWSSSMClient struct {
+ value string
+}
+
+func (sp *mockedAWSSSMClient) GetParameter(_ context.Context, input *ssm.GetParameterInput, _ ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) {
+ output := new(ssm.GetParameterOutput)
+ if sp.value == "" {
+ return output, &ssmTypes.ParameterNotFound{}
+ }
+
+ output.Parameter = &ssmTypes.Parameter{
+ Value: aws.String(sp.value),
+ }
+
+ return output, nil
+}
+
+func (sp *mockedAWSSSMClient) PutParameter(_ context.Context, input *ssm.PutParameterInput, _ ...func(*ssm.Options)) (*ssm.PutParameterOutput, error) {
+ sp.value = *input.Value
+ return new(ssm.PutParameterOutput), nil
+}
+
+func TestAWSStoreString(t *testing.T) {
+ store := &awsStore{
+ ssmARN: arn.ARN{
+ Service: "ssm",
+ Region: "eu-west-1",
+ AccountID: "123456789",
+ Resource: "parameter/foo",
+ },
+ }
+ want := "awsStore(\"arn::ssm:eu-west-1:123456789:parameter/foo\")"
+ if got := store.String(); got != want {
+ t.Errorf("AWSStore.String = %q; want %q", got, want)
+ }
+}
+
+func TestNewAWSStore(t *testing.T) {
+ tstest.PanicOnLog()
+
+ mc := &mockedAWSSSMClient{}
+ storeParameterARN := arn.ARN{
+ Service: "ssm",
+ Region: "eu-west-1",
+ AccountID: "123456789",
+ Resource: "parameter/foo",
+ }
+
+ s, err := newStore(storeParameterARN.String(), mc)
+ if err != nil {
+ t.Fatalf("creating aws store failed: %v", err)
+ }
+ testStoreSemantics(t, s)
+
+ // Build a brand new file store and check that both IDs written
+ // above are still there.
+ s2, err := newStore(storeParameterARN.String(), mc)
+ if err != nil {
+ t.Fatalf("creating second aws store failed: %v", err)
+ }
+ store2 := s.(*awsStore)
+
+ // This is specific to the test, with the non-mocked API, LoadState() should
+ // have been already called and successful as no err is returned from NewAWSStore()
+ s2.(*awsStore).LoadState()
+
+ expected := map[ipn.StateKey]string{
+ "foo": "bar",
+ "baz": "quux",
+ }
+ for id, want := range expected {
+ bs, err := store2.ReadState(id)
+ if err != nil {
+ t.Errorf("reading %q (2nd store): %v", id, err)
+ }
+ if string(bs) != want {
+ t.Errorf("reading %q (2nd store): got %q, want %q", id, string(bs), want)
+ }
+ }
+}
+
+func testStoreSemantics(t *testing.T, store ipn.StateStore) {
+ t.Helper()
+
+ tests := []struct {
+ // if true, data is data to write. If false, data is expected
+ // output of read.
+ write bool
+ id ipn.StateKey
+ data string
+ // If write=false, true if we expect a not-exist error.
+ notExists bool
+ }{
+ {
+ id: "foo",
+ notExists: true,
+ },
+ {
+ write: true,
+ id: "foo",
+ data: "bar",
+ },
+ {
+ id: "foo",
+ data: "bar",
+ },
+ {
+ id: "baz",
+ notExists: true,
+ },
+ {
+ write: true,
+ id: "baz",
+ data: "quux",
+ },
+ {
+ id: "foo",
+ data: "bar",
+ },
+ {
+ id: "baz",
+ data: "quux",
+ },
+ }
+
+ for _, test := range tests {
+ if test.write {
+ if err := store.WriteState(test.id, []byte(test.data)); err != nil {
+ t.Errorf("writing %q to %q: %v", test.data, test.id, err)
+ }
+ } else {
+ bs, err := store.ReadState(test.id)
+ if err != nil {
+ if test.notExists && err == ipn.ErrStateNotExist {
+ continue
+ }
+ t.Errorf("reading %q: %v", test.id, err)
+ continue
+ }
+ if string(bs) != test.data {
+ t.Errorf("reading %q: got %q, want %q", test.id, string(bs), test.data)
+ }
+ }
+ }
+}
diff --git a/ipn/store/stores_test.go b/ipn/store/stores_test.go index ea09e6ea6..69aa79193 100644 --- a/ipn/store/stores_test.go +++ b/ipn/store/stores_test.go @@ -1,179 +1,179 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package store - -import ( - "path/filepath" - "testing" - - "tailscale.com/ipn" - "tailscale.com/ipn/store/mem" - "tailscale.com/tstest" - "tailscale.com/types/logger" -) - -func TestNewStore(t *testing.T) { - regOnce.Do(registerDefaultStores) - t.Cleanup(func() { - knownStores = map[string]Provider{} - registerDefaultStores() - }) - knownStores = map[string]Provider{} - - type store1 struct { - ipn.StateStore - path string - } - - type store2 struct { - ipn.StateStore - path string - } - - Register("arn:", func(_ logger.Logf, path string) (ipn.StateStore, error) { - return &store1{new(mem.Store), path}, nil - }) - Register("kube:", func(_ logger.Logf, path string) (ipn.StateStore, error) { - return &store2{new(mem.Store), path}, nil - }) - Register("mem:", func(_ logger.Logf, path string) (ipn.StateStore, error) { - return new(mem.Store), nil - }) - - path := "mem:abcd" - if s, err := New(t.Logf, path); err != nil { - t.Fatalf("%q: %v", path, err) - } else if _, ok := s.(*mem.Store); !ok { - t.Fatalf("%q: got: %T, want: %T", path, s, new(mem.Store)) - } - - path = "arn:foo" - if s, err := New(t.Logf, path); err != nil { - t.Fatalf("%q: %v", path, err) - } else if _, ok := s.(*store1); !ok { - t.Fatalf("%q: got: %T, want: %T", path, s, new(store1)) - } - - path = "kube:abcd" - if s, err := New(t.Logf, path); err != nil { - t.Fatalf("%q: %v", path, err) - } else if _, ok := s.(*store2); !ok { - t.Fatalf("%q: got: %T, want: %T", path, s, new(store2)) - } - - path = filepath.Join(t.TempDir(), "state") - if s, err := New(t.Logf, path); err != nil { - t.Fatalf("%q: %v", path, err) - } else if _, ok := s.(*FileStore); !ok { - t.Fatalf("%q: got: %T, want: %T", path, s, new(FileStore)) - } -} - -func testStoreSemantics(t *testing.T, store ipn.StateStore) { - t.Helper() - - tests := []struct { - // if true, data is data to write. If false, data is expected - // output of read. - write bool - id ipn.StateKey - data string - // If write=false, true if we expect a not-exist error. - notExists bool - }{ - { - id: "foo", - notExists: true, - }, - { - write: true, - id: "foo", - data: "bar", - }, - { - id: "foo", - data: "bar", - }, - { - id: "baz", - notExists: true, - }, - { - write: true, - id: "baz", - data: "quux", - }, - { - id: "foo", - data: "bar", - }, - { - id: "baz", - data: "quux", - }, - } - - for _, test := range tests { - if test.write { - if err := store.WriteState(test.id, []byte(test.data)); err != nil { - t.Errorf("writing %q to %q: %v", test.data, test.id, err) - } - } else { - bs, err := store.ReadState(test.id) - if err != nil { - if test.notExists && err == ipn.ErrStateNotExist { - continue - } - t.Errorf("reading %q: %v", test.id, err) - continue - } - if string(bs) != test.data { - t.Errorf("reading %q: got %q, want %q", test.id, string(bs), test.data) - } - } - } -} - -func TestMemoryStore(t *testing.T) { - tstest.PanicOnLog() - - store := new(mem.Store) - testStoreSemantics(t, store) -} - -func TestFileStore(t *testing.T) { - tstest.PanicOnLog() - - dir := t.TempDir() - path := filepath.Join(dir, "test-file-store.conf") - - store, err := NewFileStore(nil, path) - if err != nil { - t.Fatalf("creating file store failed: %v", err) - } - - testStoreSemantics(t, store) - - // Build a brand new file store and check that both IDs written - // above are still there. - store, err = NewFileStore(nil, path) - if err != nil { - t.Fatalf("creating second file store failed: %v", err) - } - - expected := map[ipn.StateKey]string{ - "foo": "bar", - "baz": "quux", - } - for key, want := range expected { - bs, err := store.ReadState(key) - if err != nil { - t.Errorf("reading %q (2nd store): %v", key, err) - continue - } - if string(bs) != want { - t.Errorf("reading %q (2nd store): got %q, want %q", key, bs, want) - } - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package store
+
+import (
+ "path/filepath"
+ "testing"
+
+ "tailscale.com/ipn"
+ "tailscale.com/ipn/store/mem"
+ "tailscale.com/tstest"
+ "tailscale.com/types/logger"
+)
+
+func TestNewStore(t *testing.T) {
+ regOnce.Do(registerDefaultStores)
+ t.Cleanup(func() {
+ knownStores = map[string]Provider{}
+ registerDefaultStores()
+ })
+ knownStores = map[string]Provider{}
+
+ type store1 struct {
+ ipn.StateStore
+ path string
+ }
+
+ type store2 struct {
+ ipn.StateStore
+ path string
+ }
+
+ Register("arn:", func(_ logger.Logf, path string) (ipn.StateStore, error) {
+ return &store1{new(mem.Store), path}, nil
+ })
+ Register("kube:", func(_ logger.Logf, path string) (ipn.StateStore, error) {
+ return &store2{new(mem.Store), path}, nil
+ })
+ Register("mem:", func(_ logger.Logf, path string) (ipn.StateStore, error) {
+ return new(mem.Store), nil
+ })
+
+ path := "mem:abcd"
+ if s, err := New(t.Logf, path); err != nil {
+ t.Fatalf("%q: %v", path, err)
+ } else if _, ok := s.(*mem.Store); !ok {
+ t.Fatalf("%q: got: %T, want: %T", path, s, new(mem.Store))
+ }
+
+ path = "arn:foo"
+ if s, err := New(t.Logf, path); err != nil {
+ t.Fatalf("%q: %v", path, err)
+ } else if _, ok := s.(*store1); !ok {
+ t.Fatalf("%q: got: %T, want: %T", path, s, new(store1))
+ }
+
+ path = "kube:abcd"
+ if s, err := New(t.Logf, path); err != nil {
+ t.Fatalf("%q: %v", path, err)
+ } else if _, ok := s.(*store2); !ok {
+ t.Fatalf("%q: got: %T, want: %T", path, s, new(store2))
+ }
+
+ path = filepath.Join(t.TempDir(), "state")
+ if s, err := New(t.Logf, path); err != nil {
+ t.Fatalf("%q: %v", path, err)
+ } else if _, ok := s.(*FileStore); !ok {
+ t.Fatalf("%q: got: %T, want: %T", path, s, new(FileStore))
+ }
+}
+
+func testStoreSemantics(t *testing.T, store ipn.StateStore) {
+ t.Helper()
+
+ tests := []struct {
+ // if true, data is data to write. If false, data is expected
+ // output of read.
+ write bool
+ id ipn.StateKey
+ data string
+ // If write=false, true if we expect a not-exist error.
+ notExists bool
+ }{
+ {
+ id: "foo",
+ notExists: true,
+ },
+ {
+ write: true,
+ id: "foo",
+ data: "bar",
+ },
+ {
+ id: "foo",
+ data: "bar",
+ },
+ {
+ id: "baz",
+ notExists: true,
+ },
+ {
+ write: true,
+ id: "baz",
+ data: "quux",
+ },
+ {
+ id: "foo",
+ data: "bar",
+ },
+ {
+ id: "baz",
+ data: "quux",
+ },
+ }
+
+ for _, test := range tests {
+ if test.write {
+ if err := store.WriteState(test.id, []byte(test.data)); err != nil {
+ t.Errorf("writing %q to %q: %v", test.data, test.id, err)
+ }
+ } else {
+ bs, err := store.ReadState(test.id)
+ if err != nil {
+ if test.notExists && err == ipn.ErrStateNotExist {
+ continue
+ }
+ t.Errorf("reading %q: %v", test.id, err)
+ continue
+ }
+ if string(bs) != test.data {
+ t.Errorf("reading %q: got %q, want %q", test.id, string(bs), test.data)
+ }
+ }
+ }
+}
+
+func TestMemoryStore(t *testing.T) {
+ tstest.PanicOnLog()
+
+ store := new(mem.Store)
+ testStoreSemantics(t, store)
+}
+
+func TestFileStore(t *testing.T) {
+ tstest.PanicOnLog()
+
+ dir := t.TempDir()
+ path := filepath.Join(dir, "test-file-store.conf")
+
+ store, err := NewFileStore(nil, path)
+ if err != nil {
+ t.Fatalf("creating file store failed: %v", err)
+ }
+
+ testStoreSemantics(t, store)
+
+ // Build a brand new file store and check that both IDs written
+ // above are still there.
+ store, err = NewFileStore(nil, path)
+ if err != nil {
+ t.Fatalf("creating second file store failed: %v", err)
+ }
+
+ expected := map[ipn.StateKey]string{
+ "foo": "bar",
+ "baz": "quux",
+ }
+ for key, want := range expected {
+ bs, err := store.ReadState(key)
+ if err != nil {
+ t.Errorf("reading %q (2nd store): %v", key, err)
+ continue
+ }
+ if string(bs) != want {
+ t.Errorf("reading %q (2nd store): got %q, want %q", key, bs, want)
+ }
+ }
+}
diff --git a/ipn/store_test.go b/ipn/store_test.go index fcc082d8a..330f67969 100644 --- a/ipn/store_test.go +++ b/ipn/store_test.go @@ -1,48 +1,48 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package ipn - -import ( - "bytes" - "sync" - "testing" - - "tailscale.com/util/mak" -) - -type memStore struct { - mu sync.Mutex - writes int - m map[StateKey][]byte -} - -func (s *memStore) ReadState(k StateKey) ([]byte, error) { - s.mu.Lock() - defer s.mu.Unlock() - return bytes.Clone(s.m[k]), nil -} - -func (s *memStore) WriteState(k StateKey, v []byte) error { - s.mu.Lock() - defer s.mu.Unlock() - mak.Set(&s.m, k, bytes.Clone(v)) - s.writes++ - return nil -} - -func TestWriteState(t *testing.T) { - var ss StateStore = new(memStore) - WriteState(ss, "foo", []byte("bar")) - WriteState(ss, "foo", []byte("bar")) - got, err := ss.ReadState("foo") - if err != nil { - t.Fatal(err) - } - if want := []byte("bar"); !bytes.Equal(got, want) { - t.Errorf("got %q; want %q", got, want) - } - if got, want := ss.(*memStore).writes, 1; got != want { - t.Errorf("got %d writes; want %d", got, want) - } -} +// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package ipn
+
+import (
+ "bytes"
+ "sync"
+ "testing"
+
+ "tailscale.com/util/mak"
+)
+
+type memStore struct {
+ mu sync.Mutex
+ writes int
+ m map[StateKey][]byte
+}
+
+func (s *memStore) ReadState(k StateKey) ([]byte, error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ return bytes.Clone(s.m[k]), nil
+}
+
+func (s *memStore) WriteState(k StateKey, v []byte) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ mak.Set(&s.m, k, bytes.Clone(v))
+ s.writes++
+ return nil
+}
+
+func TestWriteState(t *testing.T) {
+ var ss StateStore = new(memStore)
+ WriteState(ss, "foo", []byte("bar"))
+ WriteState(ss, "foo", []byte("bar"))
+ got, err := ss.ReadState("foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if want := []byte("bar"); !bytes.Equal(got, want) {
+ t.Errorf("got %q; want %q", got, want)
+ }
+ if got, want := ss.(*memStore).writes, 1; got != want {
+ t.Errorf("got %d writes; want %d", got, want)
+ }
+}
|
