pairling 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/bin/pairling.mjs +5 -2
- package/package.json +7 -7
- package/payload/mac/SOURCE_BRANCH +1 -1
- package/payload/mac/SOURCE_REVISION +1 -1
- package/payload/mac/VERSION +1 -1
- package/payload/mac/companiond/pairling_connectd_status.py +57 -7
- package/payload/mac/companiond/pairling_devices.py +35 -0
- package/payload/mac/companiond/pairling_pairing.py +67 -20
- package/payload/mac/companiond/pairlingd.py +269 -16
- package/payload/mac/companiond/push_dispatcher.py +31 -1
- package/payload/mac/connectd/cmd/pairling-connectd/identity_test.go +65 -0
- package/payload/mac/connectd/cmd/pairling-connectd/main.go +150 -1
- package/payload/mac/connectd/cmd/pairling-connectd/peer_identity_test.go +86 -0
- package/payload/mac/connectd/cmd/pairling-tailnet-mintd/main.go +121 -0
- package/payload/mac/connectd/cmd/pairling-tailnet-mintd/mintd.go +418 -0
- package/payload/mac/connectd/cmd/pairling-tailnet-mintd/mintd_test.go +894 -0
- package/payload/mac/connectd/internal/gateway/adversarial_verify_test.go +99 -0
- package/payload/mac/connectd/internal/gateway/funnel_bootstrap_test.go +265 -0
- package/payload/mac/connectd/internal/gateway/funnel_contract_test.go +56 -0
- package/payload/mac/connectd/internal/gateway/proxy.go +233 -19
- package/payload/mac/connectd/internal/gateway/proxy_test.go +71 -0
- package/payload/mac/connectd/internal/runtime/config.go +19 -0
- package/payload/mac/connectd/internal/runtime/config_test.go +25 -0
- package/payload/mac/connectd/internal/status/status.go +67 -1
- package/payload/mac/connectd/internal/status/status_test.go +138 -0
- package/payload/mac/install/install-runtime.sh +421 -41
- package/payload/mac/install/render-launchd.py +54 -10
- package/payload-manifest.json +63 -21
|
@@ -8,6 +8,61 @@ import (
|
|
|
8
8
|
"testing"
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
+
func hasRouteKind(routes []AdvertisedRoute, kind string) bool {
|
|
12
|
+
for _, r := range routes {
|
|
13
|
+
if r.Kind == kind {
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func TestFunnelRouteIsAdditiveAndGated(t *testing.T) {
|
|
21
|
+
s := NewStore("pairling-test")
|
|
22
|
+
s.SetListenerRunning(true)
|
|
23
|
+
s.SetUpstreamReachable(true)
|
|
24
|
+
s.SetTailnetIP("100.64.0.1")
|
|
25
|
+
s.SetAuthenticated()
|
|
26
|
+
|
|
27
|
+
// Funnel disabled: only the tailnet route, and no funnel_hostname in the JSON.
|
|
28
|
+
snap := s.Snapshot()
|
|
29
|
+
if hasRouteKind(snap.AdvertisedRoutes, RouteKindFunnel) {
|
|
30
|
+
t.Error("funnel route present while funnel disabled")
|
|
31
|
+
}
|
|
32
|
+
js, _ := json.Marshal(snap)
|
|
33
|
+
if strings.Contains(string(js), "funnel_hostname") {
|
|
34
|
+
t.Error("funnel_hostname must be omitted when empty")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Enable funnel: the route appears with an https base URL and a lower priority.
|
|
38
|
+
s.SetFunnelHostname("pairling-abc.tail1234.ts.net")
|
|
39
|
+
snap = s.Snapshot()
|
|
40
|
+
var funnel, tailnet *AdvertisedRoute
|
|
41
|
+
for i := range snap.AdvertisedRoutes {
|
|
42
|
+
switch snap.AdvertisedRoutes[i].Kind {
|
|
43
|
+
case RouteKindFunnel:
|
|
44
|
+
funnel = &snap.AdvertisedRoutes[i]
|
|
45
|
+
case RouteKindTailnet:
|
|
46
|
+
tailnet = &snap.AdvertisedRoutes[i]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if funnel == nil || tailnet == nil {
|
|
50
|
+
t.Fatalf("expected tailnet and funnel routes, got %+v", snap.AdvertisedRoutes)
|
|
51
|
+
}
|
|
52
|
+
if funnel.BaseURL != "https://pairling-abc.tail1234.ts.net" {
|
|
53
|
+
t.Errorf("funnel base_url = %q", funnel.BaseURL)
|
|
54
|
+
}
|
|
55
|
+
if funnel.Priority >= tailnet.Priority {
|
|
56
|
+
t.Errorf("funnel priority %d must be below tailnet priority %d", funnel.Priority, tailnet.Priority)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Unhealthy node: no routes advertise, even with a funnel hostname set.
|
|
60
|
+
s.SetUpstreamReachable(false)
|
|
61
|
+
if len(s.Snapshot().AdvertisedRoutes) != 0 {
|
|
62
|
+
t.Error("no routes should advertise when the node is unhealthy")
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
11
66
|
func TestStoreServesHelperReadableSnapshotWithoutSecrets(t *testing.T) {
|
|
12
67
|
store := NewStore("pairling-inst-abcdef")
|
|
13
68
|
store.SetAuthPending("https://login.tailscale.com/a/secret-auth-token")
|
|
@@ -46,6 +101,89 @@ func TestStoreServesHelperReadableSnapshotWithoutSecrets(t *testing.T) {
|
|
|
46
101
|
}
|
|
47
102
|
}
|
|
48
103
|
|
|
104
|
+
func TestSnapshotSerializesTailnetIdentityFields(t *testing.T) {
|
|
105
|
+
store := NewStore("pairling-inst-abcdef")
|
|
106
|
+
store.SetTailnetIdentity("nXb6CNTRL", []string{"tag:pairling-connect"}, []string{"100.79.217.7"})
|
|
107
|
+
|
|
108
|
+
raw, err := json.Marshal(store.Snapshot())
|
|
109
|
+
if err != nil {
|
|
110
|
+
t.Fatal(err)
|
|
111
|
+
}
|
|
112
|
+
body := string(raw)
|
|
113
|
+
for _, want := range []string{
|
|
114
|
+
`"tailnet_node_id":"nXb6CNTRL"`,
|
|
115
|
+
`"tags":["tag:pairling-connect"]`,
|
|
116
|
+
`"tailnet_ips":["100.79.217.7"]`,
|
|
117
|
+
} {
|
|
118
|
+
if !strings.Contains(body, want) {
|
|
119
|
+
t.Fatalf("snapshot missing %s: %s", want, body)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func TestSnapshotSerializesKnownTailnetLockStatus(t *testing.T) {
|
|
125
|
+
store := NewStore("pairling-inst-abcdef")
|
|
126
|
+
store.SetTailnetLockEnabled(false)
|
|
127
|
+
|
|
128
|
+
raw, err := json.Marshal(store.Snapshot())
|
|
129
|
+
if err != nil {
|
|
130
|
+
t.Fatal(err)
|
|
131
|
+
}
|
|
132
|
+
body := string(raw)
|
|
133
|
+
if !strings.Contains(body, `"tailnet_lock_enabled":false`) {
|
|
134
|
+
t.Fatalf("snapshot missing known lock status: %s", body)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
func TestSnapshotOmitsTailnetIdentityWhenUnknown(t *testing.T) {
|
|
139
|
+
raw, err := json.Marshal(NewStore("pairling-inst-abcdef").Snapshot())
|
|
140
|
+
if err != nil {
|
|
141
|
+
t.Fatal(err)
|
|
142
|
+
}
|
|
143
|
+
body := string(raw)
|
|
144
|
+
for _, forbidden := range []string{"tailnet_node_id", "tags", "tailnet_ips"} {
|
|
145
|
+
if strings.Contains(body, forbidden) {
|
|
146
|
+
t.Fatalf("fresh snapshot should omit %q: %s", forbidden, body)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if !strings.Contains(body, `"auth_state":"starting"`) {
|
|
150
|
+
t.Fatalf("fresh snapshot lost coherent auth state: %s", body)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
func TestSetTailnetIdentityIsIdempotentAndReplaces(t *testing.T) {
|
|
155
|
+
store := NewStore("pairling-inst-abcdef")
|
|
156
|
+
store.SetTailnetIdentity("old-node", []string{"tag:old"}, []string{"100.64.0.1"})
|
|
157
|
+
store.SetTailnetIdentity("new-node", []string{"tag:pairling-connect"}, []string{"100.79.217.7", "fd7a:115c:a1e0::1"})
|
|
158
|
+
|
|
159
|
+
snapshot := store.Snapshot()
|
|
160
|
+
if snapshot.TailnetNodeID != "new-node" {
|
|
161
|
+
t.Fatalf("node ID = %q, want new-node", snapshot.TailnetNodeID)
|
|
162
|
+
}
|
|
163
|
+
if got, want := strings.Join(snapshot.Tags, ","), "tag:pairling-connect"; got != want {
|
|
164
|
+
t.Fatalf("tags = %q, want %q", got, want)
|
|
165
|
+
}
|
|
166
|
+
if got, want := strings.Join(snapshot.TailnetIPs, ","), "100.79.217.7,fd7a:115c:a1e0::1"; got != want {
|
|
167
|
+
t.Fatalf("tailnet IPs = %q, want %q", got, want)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func TestTailnetIdentityCarriesNoSecrets(t *testing.T) {
|
|
172
|
+
store := NewStore("pairling-inst-abcdef")
|
|
173
|
+
store.SetTailnetIdentity("tskey-auth-example", []string{"AuthKey=bad"}, []string{"NLPrivate=bad"})
|
|
174
|
+
|
|
175
|
+
raw, err := json.Marshal(store.Snapshot())
|
|
176
|
+
if err != nil {
|
|
177
|
+
t.Fatal(err)
|
|
178
|
+
}
|
|
179
|
+
body := string(raw)
|
|
180
|
+
for _, forbidden := range []string{"tskey", "authkey", "AuthKey", "NLPrivate"} {
|
|
181
|
+
if strings.Contains(body, forbidden) {
|
|
182
|
+
t.Fatalf("tailnet identity leaked %q: %s", forbidden, body)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
49
187
|
func TestStoreAdvertisesRouteOnlyWhenReady(t *testing.T) {
|
|
50
188
|
store := NewStore("pairling-inst-abcdef")
|
|
51
189
|
store.SetAuthPending("open https://login.tailscale.com/a/example-token")
|