headscale/hscontrol/mapper/tail_test.go
Kristoffer Dalby f7b0cbbbea
Some checks are pending
Build / build (push) Waiting to run
Build documentation / build (push) Waiting to run
Build documentation / deploy (push) Blocked by required conditions
Tests / test (push) Waiting to run
wrap policy in policy manager interface (#2255)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-26 15:16:06 +01:00

276 lines
6.5 KiB
Go

package mapper
import (
"encoding/json"
"net/netip"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)
func TestTailNode(t *testing.T) {
mustNK := func(str string) key.NodePublic {
var k key.NodePublic
_ = k.UnmarshalText([]byte(str))
return k
}
mustDK := func(str string) key.DiscoPublic {
var k key.DiscoPublic
_ = k.UnmarshalText([]byte(str))
return k
}
mustMK := func(str string) key.MachinePublic {
var k key.MachinePublic
_ = k.UnmarshalText([]byte(str))
return k
}
hiview := func(hoin tailcfg.Hostinfo) tailcfg.HostinfoView {
return hoin.View()
}
created := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
lastSeen := time.Date(2009, time.November, 10, 23, 9, 0, 0, time.UTC)
expire := time.Date(2500, time.November, 11, 23, 0, 0, 0, time.UTC)
tests := []struct {
name string
node *types.Node
pol *policy.ACLPolicy
dnsConfig *tailcfg.DNSConfig
baseDomain string
want *tailcfg.Node
wantErr bool
}{
{
name: "empty-node",
node: &types.Node{
GivenName: "empty",
Hostinfo: &tailcfg.Hostinfo{},
},
pol: &policy.ACLPolicy{},
dnsConfig: &tailcfg.DNSConfig{},
baseDomain: "",
want: &tailcfg.Node{
Name: "empty",
StableID: "0",
Addresses: []netip.Prefix{},
AllowedIPs: []netip.Prefix{},
DERP: "127.3.3.40:0",
Hostinfo: hiview(tailcfg.Hostinfo{}),
Tags: []string{},
PrimaryRoutes: []netip.Prefix{},
MachineAuthorized: true,
CapMap: tailcfg.NodeCapMap{
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
},
},
wantErr: false,
},
{
name: "minimal-node",
node: &types.Node{
ID: 0,
MachineKey: mustMK(
"mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
),
NodeKey: mustNK(
"nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
),
DiscoKey: mustDK(
"discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
),
IPv4: iap("100.64.0.1"),
Hostname: "mini",
GivenName: "mini",
UserID: 0,
User: types.User{
Name: "mini",
},
ForcedTags: []string{},
AuthKey: &types.PreAuthKey{},
LastSeen: &lastSeen,
Expiry: &expire,
Hostinfo: &tailcfg.Hostinfo{},
Routes: []types.Route{
{
Prefix: tsaddr.AllIPv4(),
Advertised: true,
Enabled: true,
IsPrimary: false,
},
{
Prefix: netip.MustParsePrefix("192.168.0.0/24"),
Advertised: true,
Enabled: true,
IsPrimary: true,
},
{
Prefix: netip.MustParsePrefix("172.0.0.0/10"),
Advertised: true,
Enabled: false,
IsPrimary: true,
},
},
CreatedAt: created,
},
pol: &policy.ACLPolicy{},
dnsConfig: &tailcfg.DNSConfig{},
baseDomain: "",
want: &tailcfg.Node{
ID: 0,
StableID: "0",
Name: "mini",
User: 0,
Key: mustNK(
"nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
),
KeyExpiry: expire,
Machine: mustMK(
"mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
),
DiscoKey: mustDK(
"discokey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
),
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")},
AllowedIPs: []netip.Prefix{
netip.MustParsePrefix("100.64.0.1/32"),
tsaddr.AllIPv4(),
netip.MustParsePrefix("192.168.0.0/24"),
},
DERP: "127.3.3.40:0",
Hostinfo: hiview(tailcfg.Hostinfo{}),
Created: created,
Tags: []string{},
PrimaryRoutes: []netip.Prefix{
netip.MustParsePrefix("192.168.0.0/24"),
},
LastSeen: &lastSeen,
MachineAuthorized: true,
CapMap: tailcfg.NodeCapMap{
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
},
},
wantErr: false,
},
// TODO: Add tests to check other aspects of the node conversion:
// - With tags and policy
// - dnsconfig and basedomain
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
polMan, _ := policy.NewPolicyManagerForTest(tt.pol, []types.User{}, types.Nodes{tt.node})
cfg := &types.Config{
BaseDomain: tt.baseDomain,
DNSConfig: tt.dnsConfig,
RandomizeClientPort: false,
}
got, err := tailNode(
tt.node,
0,
polMan,
cfg,
)
if (err != nil) != tt.wantErr {
t.Errorf("tailNode() error = %v, wantErr %v", err, tt.wantErr)
return
}
if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
t.Errorf("tailNode() unexpected result (-want +got):\n%s", diff)
}
})
}
}
func TestNodeExpiry(t *testing.T) {
tp := func(t time.Time) *time.Time {
return &t
}
tests := []struct {
name string
exp *time.Time
wantTime time.Time
wantTimeZero bool
}{
{
name: "no-expiry",
exp: nil,
wantTimeZero: true,
},
{
name: "zero-expiry",
exp: &time.Time{},
wantTimeZero: true,
},
{
name: "localtime",
exp: tp(time.Time{}.Local()),
wantTimeZero: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
node := &types.Node{
GivenName: "test",
Expiry: tt.exp,
}
tn, err := tailNode(
node,
0,
&policy.PolicyManagerV1{},
&types.Config{},
)
if err != nil {
t.Fatalf("nodeExpiry() error = %v", err)
}
// Round trip the node through JSON to ensure the time is serialized correctly
seri, err := json.Marshal(tn)
if err != nil {
t.Fatalf("nodeExpiry() error = %v", err)
}
var deseri tailcfg.Node
err = json.Unmarshal(seri, &deseri)
if err != nil {
t.Fatalf("nodeExpiry() error = %v", err)
}
if tt.wantTimeZero {
if !deseri.KeyExpiry.IsZero() {
t.Errorf("nodeExpiry() = %v, want zero", deseri.KeyExpiry)
}
} else if deseri.KeyExpiry != tt.wantTime {
t.Errorf("nodeExpiry() = %v, want %v", deseri.KeyExpiry, tt.wantTime)
}
})
}
}