tailscale/tsconsensus/authorization.go
2025-03-17 09:09:26 -07:00

139 lines
2.9 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package tsconsensus
import (
"context"
"errors"
"net/netip"
"sync"
"time"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tsnet"
"tailscale.com/types/views"
"tailscale.com/util/set"
)
type statusGetter interface {
getStatus(context.Context) (*ipnstate.Status, error)
}
type tailscaleStatusGetter struct {
ts *tsnet.Server
mu sync.Mutex // protects the following
lastStatus *ipnstate.Status
lastStatusTime time.Time
}
func (sg *tailscaleStatusGetter) fetchStatus(ctx context.Context) (*ipnstate.Status, error) {
lc, err := sg.ts.LocalClient()
if err != nil {
return nil, err
}
return lc.Status(ctx)
}
func (sg *tailscaleStatusGetter) getStatus(ctx context.Context) (*ipnstate.Status, error) {
sg.mu.Lock()
defer sg.mu.Unlock()
if sg.lastStatus != nil && time.Since(sg.lastStatusTime) < 1*time.Second {
return sg.lastStatus, nil
}
status, err := sg.fetchStatus(ctx)
if err != nil {
return nil, err
}
sg.lastStatus = status
sg.lastStatusTime = time.Now()
return status, nil
}
type authorization struct {
sg statusGetter
tag string
mu sync.Mutex
peers *peers // protected by mu
}
func newAuthorization(ts *tsnet.Server, tag string) *authorization {
return &authorization{
sg: &tailscaleStatusGetter{
ts: ts,
},
tag: tag,
}
}
func (a *authorization) Refresh(ctx context.Context) error {
tStatus, err := a.sg.getStatus(ctx)
if err != nil {
return err
}
if tStatus == nil {
return errors.New("no status")
}
if tStatus.BackendState != ipn.Running.String() {
return errors.New("ts Server is not running")
}
a.mu.Lock()
defer a.mu.Unlock()
a.peers = newPeers(tStatus, a.tag)
return nil
}
func (a *authorization) AllowsHost(addr netip.Addr) bool {
if a.peers == nil {
return false
}
a.mu.Lock()
defer a.mu.Unlock()
return a.peers.peerExists(addr, a.tag)
}
func (a *authorization) SelfAllowed() bool {
if a.peers == nil {
return false
}
a.mu.Lock()
defer a.mu.Unlock()
return a.peers.status.Self.Tags != nil && views.SliceContains(*a.peers.status.Self.Tags, a.tag)
}
func (a *authorization) AllowedPeers() views.Slice[*ipnstate.PeerStatus] {
if a.peers == nil {
return views.Slice[*ipnstate.PeerStatus]{}
}
a.mu.Lock()
defer a.mu.Unlock()
return views.SliceOf(a.peers.allowedPeers)
}
type peers struct {
status *ipnstate.Status
allowedRemoteAddrs set.Set[netip.Addr]
allowedPeers []*ipnstate.PeerStatus
}
func (ps *peers) peerExists(a netip.Addr, tag string) bool {
return ps.allowedRemoteAddrs.Contains(a)
}
func newPeers(status *ipnstate.Status, tag string) *peers {
ps := &peers{
status: status,
allowedRemoteAddrs: set.Set[netip.Addr]{},
}
for _, p := range status.Peer {
if p.Tags != nil && views.SliceContains(*p.Tags, tag) {
ps.allowedPeers = append(ps.allowedPeers, p)
ps.allowedRemoteAddrs.AddSlice(p.TailscaleIPs)
}
}
return ps
}