tailscale/tsconsensus/authorization_test.go

231 lines
4.9 KiB
Go
Raw Normal View History

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package tsconsensus
import (
"context"
"fmt"
"net/netip"
"testing"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/views"
)
type testStatusGetter struct {
status *ipnstate.Status
}
func (sg testStatusGetter) getStatus(ctx context.Context) (*ipnstate.Status, error) {
return sg.status, nil
}
const testTag string = "tag:clusterTag"
func makeAuthTestPeer(i int, tags views.Slice[string]) *ipnstate.PeerStatus {
return &ipnstate.PeerStatus{
ID: tailcfg.StableNodeID(fmt.Sprintf("%d", i)),
Tags: &tags,
TailscaleIPs: []netip.Addr{
netip.AddrFrom4([4]byte{100, 0, 0, byte(i)}),
netip.MustParseAddr(fmt.Sprintf("fd7a:115c:a1e0:0::%d", i)),
},
}
}
func makeAuthTestPeers(tags [][]string) []*ipnstate.PeerStatus {
peers := make([]*ipnstate.PeerStatus, len(tags))
for i, ts := range tags {
peers[i] = makeAuthTestPeer(i, views.SliceOf(ts))
}
return peers
}
func authForStatus(s *ipnstate.Status) *authorization {
return &authorization{
sg: testStatusGetter{
status: s,
},
tag: testTag,
}
}
func authForPeers(self *ipnstate.PeerStatus, peers []*ipnstate.PeerStatus) *authorization {
s := &ipnstate.Status{
BackendState: ipn.Running.String(),
Self: self,
Peer: map[key.NodePublic]*ipnstate.PeerStatus{},
}
for _, p := range peers {
s.Peer[key.NewNode().Public()] = p
}
return authForStatus(s)
}
func TestAuthRefreshErrorsNotRunning(t *testing.T) {
tests := []struct {
in *ipnstate.Status
expected string
}{
{
in: nil,
expected: "no status",
},
{
in: &ipnstate.Status{
BackendState: "NeedsMachineAuth",
},
expected: "ts Server is not running",
},
}
for _, tt := range tests {
t.Run(tt.expected, func(t *testing.T) {
ctx := t.Context()
a := authForStatus(tt.in)
err := a.Refresh(ctx)
if err == nil {
t.Fatalf("expected err to be non-nil")
}
if err.Error() != tt.expected {
t.Fatalf("expected: %s, got: %s", tt.expected, err.Error())
}
})
}
}
func TestAuthUnrefreshed(t *testing.T) {
a := authForStatus(nil)
if a.AllowsHost(netip.MustParseAddr("100.0.0.1")) {
t.Fatalf("never refreshed authorization, allowsHost: expected false, got true")
}
gotAllowedPeers := a.AllowedPeers()
if gotAllowedPeers.Len() != 0 {
t.Fatalf("never refreshed authorization, allowedPeers: expected [], got %v", gotAllowedPeers)
}
if a.SelfAllowed() != false {
t.Fatalf("never refreshed authorization, selfAllowed: expected false got true")
}
}
func TestAuthAllowsHost(t *testing.T) {
peerTags := [][]string{
{"woo"},
nil,
{"woo", testTag},
{testTag},
}
peers := makeAuthTestPeers(peerTags)
tests := []struct {
name string
peerStatus *ipnstate.PeerStatus
expected bool
}{
{
name: "tagged with different tag",
peerStatus: peers[0],
expected: false,
},
{
name: "not tagged",
peerStatus: peers[1],
expected: false,
},
{
name: "tags includes testTag",
peerStatus: peers[2],
expected: true,
},
{
name: "only tag is testTag",
peerStatus: peers[3],
expected: true,
},
}
a := authForPeers(nil, peers)
err := a.Refresh(t.Context())
if err != nil {
t.Fatal(err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// test we get the expected result for any of the peers TailscaleIPs
for _, addr := range tt.peerStatus.TailscaleIPs {
got := a.AllowsHost(addr)
if got != tt.expected {
t.Fatalf("allowed for peer with tags: %v, expected: %t, got %t", tt.peerStatus.Tags, tt.expected, got)
}
}
})
}
}
func TestAuthAllowedPeers(t *testing.T) {
ctx := t.Context()
peerTags := [][]string{
{"woo"},
nil,
{"woo", testTag},
{testTag},
}
peers := makeAuthTestPeers(peerTags)
a := authForPeers(nil, peers)
err := a.Refresh(ctx)
if err != nil {
t.Fatal(err)
}
ps := a.AllowedPeers()
if ps.Len() != 2 {
t.Fatalf("expected: 2, got: %d", ps.Len())
}
for _, i := range []int{2, 3} {
if !ps.ContainsFunc(func(p *ipnstate.PeerStatus) bool {
return p.ID == peers[i].ID
}) {
t.Fatalf("expected peers[%d] to be in AllowedPeers because it is tagged with testTag", i)
}
}
}
func TestAuthSelfAllowed(t *testing.T) {
tests := []struct {
name string
in []string
expected bool
}{
{
name: "self has different tag",
in: []string{"woo"},
expected: false,
},
{
name: "selfs tags include testTag",
in: []string{"woo", testTag},
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := t.Context()
self := makeAuthTestPeer(0, views.SliceOf(tt.in))
a := authForPeers(self, nil)
err := a.Refresh(ctx)
if err != nil {
t.Fatal(err)
}
got := a.SelfAllowed()
if got != tt.expected {
t.Fatalf("expected: %t, got: %t", tt.expected, got)
}
})
}
}