This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tailnetlock
+3 -3
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tailnetlock
@@ -67,11 +67,11 @@ func (b *UpdateBuilder) AddKey(key Key) error {
}
if _, err := b.state.GetKey(keyID); err == nil {
return fmt.Errorf("cannot add key %v: already exists", key)
return fmt.Errorf("cannot add key tlpub:%x: already exists", key.Public)
}
if len(b.state.Keys) >= maxKeys {
return fmt.Errorf("cannot add key %v: maximum number of keys reached", key)
return fmt.Errorf("cannot add key tlpub:%x: maximum number of keys reached", key.Public)
}
return b.mkUpdate(AUM{MessageKind: AUMAddKey, Key: &key})
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tailnetlock
+7 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build ts_omit_tailnetlock
@@ -8,6 +8,7 @@ package tka
import (
"crypto/ed25519"
"errors"
"time"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@@ -158,3 +159,8 @@ func SignByCredential(privKey []byte, wrapped *NodeKeySignature, nodeKey key.Nod
}
func (s NodeKeySignature) String() string { return "" }
type CompactionOptions struct {
MinChain int
MinAge time.Duration
}
+4 -7
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package tka
@@ -7,6 +7,7 @@ import (
"crypto/ed25519"
"errors"
"fmt"
"maps"
"tailscale.com/types/tkatype"
)
@@ -31,7 +32,7 @@ func (k KeyKind) String() string {
}
}
// Key describes the public components of a key known to network-lock.
// Key describes the public components of a key known to tailnet-lock.
type Key struct {
Kind KeyKind `cbor:"1,keyasint"`
@@ -64,9 +65,7 @@ func (k Key) Clone() Key {
if k.Meta != nil {
out.Meta = make(map[string]string, len(k.Meta))
for k, v := range k.Meta {
out.Meta[k] = v
}
maps.Copy(out.Meta, k.Meta)
}
return out
@@ -105,8 +104,6 @@ func (k Key) Ed25519() (ed25519.PublicKey, error) {
}
}
const maxMetaBytes = 512
func (k Key) StaticValidate() error {
if k.Votes > 4096 {
return fmt.Errorf("excessive key weight: %d > 4096", k.Votes)
+35
View File
@@ -0,0 +1,35 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package tka
import (
"time"
)
const (
// Upper bound on checkpoint elements, chosen arbitrarily. Intended
// to cap the size of large AUMs.
maxDisablementValues = 32
maxKeys = 512
// Max amount of metadata that can be associated with a key, chosen arbitrarily.
// Intended to avoid people abusing TKA as a key-value score.
maxMetaBytes = 512
// Max iterations searching for any intersection during the sync process.
maxSyncIter = 2000
// Max iterations searching for a head intersection during the sync process.
maxSyncHeadIntersectionIter = 1000
// Limit on scanning AUM trees, chosen arbitrarily.
maxScanIterations = 2000
)
var (
CompactionDefaults = CompactionOptions{
MinChain: 24, // Keep at minimum 24 AUMs since head.
MinAge: 14 * 24 * time.Hour, // Keep 2 weeks of AUMs.
}
)
+4 -4
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tailnetlock
@@ -178,7 +178,7 @@ func (s NodeKeySignature) UnverifiedAuthorizingKeyID() (tkatype.KeyID, error) {
return s.authorizingKeyID()
}
// authorizingKeyID returns the KeyID of the key trusted by network-lock which authorizes
// authorizingKeyID returns the KeyID of the key trusted by tailnet-lock which authorizes
// this signature.
func (s NodeKeySignature) authorizingKeyID() (tkatype.KeyID, error) {
switch s.SigKind {
@@ -349,14 +349,14 @@ func (s *NodeKeySignature) rotationDetails() (*RotationDetails, error) {
// ResignNKS re-signs a node-key signature for a new node-key.
//
// This only matters on network-locked tailnets, because node-key signatures are
// This only matters on tailnet-locked tailnets, because node-key signatures are
// how other nodes know that a node-key is authentic. When the node-key is
// rotated then the existing signature becomes invalid, so this function is
// responsible for generating a new wrapping signature to certify the new node-key.
//
// The signature itself is a SigRotation signature, which embeds the old signature
// and certifies the new node-key as a replacement for the old by signing the new
// signature with RotationPubkey (which is the node's own network-lock key).
// signature with RotationPubkey (which is the node's own tailnet-lock key).
func ResignNKS(priv key.NLPrivate, nodeKey key.NodePublic, oldNKS tkatype.MarshaledSignature) (tkatype.MarshaledSignature, error) {
var oldSig NodeKeySignature
if err := oldSig.Unserialize(oldNKS); err != nil {
+38 -24
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tailnetlock
@@ -7,11 +7,13 @@ package tka
import (
"bytes"
"crypto/subtle"
"errors"
"fmt"
"golang.org/x/crypto/argon2"
"tailscale.com/types/tkatype"
"tailscale.com/util/testenv"
)
// ErrNoSuchKey is returned if the key referenced by a KeyID does not exist.
@@ -28,9 +30,13 @@ type State struct {
// is the same as the LastAUMHash.
LastAUMHash *AUMHash `cbor:"1,keyasint"`
// DisablementSecrets are KDF-derived values which can be used
// to turn off the TKA in the event of a consensus-breaking bug.
DisablementSecrets [][]byte `cbor:"2,keyasint"`
// DisablementValues are KDF-derived values used to verify that a caller
// possesses a valid DisablementSecret. These values are used during the
// Tailnet Lock deactivation process.
//
// These are safe to share publicly or store in the clear. They cannot be
// used to derive the original DisablementSecret.
DisablementValues [][]byte `cbor:"2,keyasint"`
// Keys are the public keys of either:
//
@@ -78,11 +84,11 @@ func (s State) Clone() State {
out.LastAUMHash = &dupe
}
if s.DisablementSecrets != nil {
out.DisablementSecrets = make([][]byte, len(s.DisablementSecrets))
for i := range s.DisablementSecrets {
out.DisablementSecrets[i] = make([]byte, len(s.DisablementSecrets[i]))
copy(out.DisablementSecrets[i], s.DisablementSecrets[i])
if s.DisablementValues != nil {
out.DisablementValues = make([][]byte, len(s.DisablementValues))
for i := range s.DisablementValues {
out.DisablementValues[i] = make([]byte, len(s.DisablementValues[i]))
copy(out.DisablementValues[i], s.DisablementValues[i])
}
}
@@ -113,7 +119,7 @@ var disablementSalt = []byte("tailscale network-lock disablement salt")
// key authority, but cannot be reversed to find the input secret.
//
// When the output of this function is stored in tka state (i.e. in
// tka.State.DisablementSecrets) a call to Authority.ValidDisablement()
// tka.State.DisablementValues) a call to Authority.ValidDisablement()
// with the input of this function as the argument will return true.
func DisablementKDF(secret []byte) []byte {
// time = 4 (3 recommended, booped to 4 to compensate for less memory)
@@ -126,8 +132,8 @@ func DisablementKDF(secret []byte) []byte {
// checkDisablement returns true for a valid disablement secret.
func (s State) checkDisablement(secret []byte) bool {
derived := DisablementKDF(secret)
for _, candidate := range s.DisablementSecrets {
if bytes.Equal(derived, candidate) {
for _, candidate := range s.DisablementValues {
if subtle.ConstantTimeCompare(derived, candidate) == 1 {
return true
}
}
@@ -247,30 +253,23 @@ func (s State) applyVerifiedAUM(update AUM) (State, error) {
}
}
// Upper bound on checkpoint elements, chosen arbitrarily. Intended to
// cap out insanely large AUMs.
const (
maxDisablementSecrets = 32
maxKeys = 512
)
// staticValidateCheckpoint validates that the state is well-formed for
// inclusion in a checkpoint AUM.
func (s *State) staticValidateCheckpoint() error {
if s.LastAUMHash != nil {
return errors.New("cannot specify a parent AUM")
}
if len(s.DisablementSecrets) == 0 {
if len(s.DisablementValues) == 0 {
return errors.New("at least one disablement secret required")
}
if numDS := len(s.DisablementSecrets); numDS > maxDisablementSecrets {
return fmt.Errorf("too many disablement secrets (%d, max %d)", numDS, maxDisablementSecrets)
if numDS := len(s.DisablementValues); numDS > maxDisablementValues {
return fmt.Errorf("too many disablement values (%d, max %d)", numDS, maxDisablementValues)
}
for i, ds := range s.DisablementSecrets {
for i, ds := range s.DisablementValues {
if len(ds) != disablementLength {
return fmt.Errorf("disablement[%d]: invalid length (got %d, want %d)", i, len(ds), disablementLength)
}
for j, ds2 := range s.DisablementSecrets {
for j, ds2 := range s.DisablementValues {
if i == j {
continue
}
@@ -315,3 +314,18 @@ func (s *State) staticValidateCheckpoint() error {
}
return nil
}
// CreateStateForTest creates a [State] that marks the given keys as trusted
// with an arbitrary disablement value.
//
// This is only for use in tests, and will panic if called outside a test.
func CreateStateForTest(keys ...Key) State {
testenv.AssertInTest()
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
return State{
Keys: keys,
DisablementValues: [][]byte{DisablementKDF(disablementSecret)},
}
}
+73 -33
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tailnetlock
@@ -9,13 +9,8 @@ import (
"errors"
"fmt"
"os"
)
const (
// Max iterations searching for any intersection.
maxSyncIter = 2000
// Max iterations searching for a head intersection.
maxSyncHeadIntersectionIter = 400
"tailscale.com/util/testenv"
)
// ErrNoIntersection is returned when a shared AUM could
@@ -67,18 +62,6 @@ func FromSyncOffer(offer SyncOffer) (head string, ancestors []string, err error)
return string(headBytes), ancestors, nil
}
const (
// The starting number of AUMs to skip when listing
// ancestors in a SyncOffer.
ancestorsSkipStart = 4
// How many bits to advance the skip count when listing
// ancestors in a SyncOffer.
//
// 2 bits, so (4<<2), so after skipping 4 it skips 16.
ancestorsSkipShift = 2
)
// SyncOffer returns an abbreviated description of the current AUM
// chain, which can be used to synchronize with another (untrusted)
// Authority instance.
@@ -99,20 +82,10 @@ func (a *Authority) SyncOffer(storage Chonk) (SyncOffer, error) {
Ancestors: make([]AUMHash, 0, 6), // 6 chosen arbitrarily.
}
// We send some subset of our ancestors to help the remote
// find a more-recent 'head intersection'.
// The number of AUMs between each ancestor entry gets
// exponentially larger.
var (
skipAmount uint64 = ancestorsSkipStart
curs AUMHash = a.Head()
)
for i := uint64(0); i < maxSyncHeadIntersectionIter; i++ {
if i > 0 && (i%skipAmount) == 0 {
out.Ancestors = append(out.Ancestors, curs)
skipAmount = skipAmount << ancestorsSkipShift
}
// We send all our checkpoints to help the remote find a
// more-recent 'head intersection'.
curs := a.Head()
for range maxSyncHeadIntersectionIter {
parent, err := storage.AUM(curs)
if err != nil {
if err != os.ErrNotExist {
@@ -125,6 +98,11 @@ func (a *Authority) SyncOffer(storage Chonk) (SyncOffer, error) {
if parent.Hash() == oldest {
break
}
if parent.MessageKind == AUMCheckpoint {
out.Ancestors = append(out.Ancestors, curs)
}
copy(curs[:], parent.PrevAUMHash)
}
@@ -281,3 +259,65 @@ func (a *Authority) MissingAUMs(storage Chonk, remoteOffer SyncOffer) ([]AUM, er
panic("unreachable")
}
// SeedNode is an authority-chonk pair that can be seeded by [SeedAUMs].
type SeedNode struct {
authority *Authority
storage Chonk
}
// CreateSeedNode creates a node for use with [SeedAUMs].
func CreateSeedNode(t testenv.TB, authority *Authority, storage Chonk) SeedNode {
t.Helper()
return SeedNode{authority, storage}
}
type SeedAUMConfig struct {
Count int
Signer Signer
Nodes []SeedNode
}
// SeedAUMs generates many AUMs by repeatedly adding and removing keys
// from the TKA.
//
// The AUMs are written to all the supplied nodes, so if you pass more
// than one, you can build up a long sync history.
//
// This is only for use in testing.
func SeedAUMs(t testenv.TB, config SeedAUMConfig) {
t.Helper()
if len(config.Nodes) == 0 {
panic("called SeedAUMs without any nodes")
}
primaryNode := config.Nodes[0]
// The key that we'll repeatedly add/remove in the TKA.
key := Key{Kind: Key25519, Public: []byte{1, 1, 1}, Votes: 1}
for i := 0; i < config.Count/2; i++ {
for _, action := range []string{"add", "remove"} {
updater := primaryNode.authority.NewUpdater(config.Signer)
if action == "add" {
if err := updater.AddKey(key); err != nil {
t.Fatalf("error from updater.AddKey: %v")
}
} else {
if err := updater.RemoveKey(key.MustID()); err != nil {
t.Fatalf("error from updater.RemoveKey: %v")
}
}
aum, err := updater.Finalize(primaryNode.storage)
if err != nil {
t.Fatalf("error from authority.Finalize: %v", err)
}
for _, n := range config.Nodes {
if err := n.authority.Inform(n.storage, aum); err != nil {
t.Fatalf("error from authority.Inform: %v", err)
}
}
}
}
}
+124 -53
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tailnetlock
@@ -20,6 +20,7 @@ import (
"github.com/fxamacker/cbor/v2"
"tailscale.com/atomicfile"
"tailscale.com/tstime"
"tailscale.com/util/set"
"tailscale.com/util/testenv"
)
@@ -286,14 +287,30 @@ func (c *Mem) PurgeAUMs(hashes []AUMHash) error {
// FS implements filesystem storage of TKA state.
//
// FS caches graph metadata derived from its storage directory. Mutations made
// through this instance invalidate the cache. Graph changes made through
// another FS or directly to the directory are not reflected in the indexes
// until they are invalidated or this instance is reopened.
//
// FS implements the Chonk interface.
type FS struct {
base string
mu sync.RWMutex
// aumIndex and parentIndex are initialized together by buildIndexLocked.
// They avoid repeatedly scanning every AUM file while walking the authority
// graph. AUM contents are still decoded from disk on demand. A nil aumIndex
// means the indexes need to be rebuilt.
aumIndex set.Set[AUMHash]
parentIndex map[AUMHash][]AUMHash
}
// ChonkDir returns an implementation of Chonk which uses the
// given directory to store TKA state.
// ChonkDir returns an implementation of Chonk which uses the given directory
// to store TKA state.
//
// The returned FS expects exclusive write access to dir for its lifetime.
// Graph changes made through another FS or directly to dir may not be reflected
// in queries until the returned FS is reopened.
func ChonkDir(dir string) (*FS, error) {
if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) {
return nil, fmt.Errorf("creating chonk root dir: %v", err)
@@ -354,7 +371,12 @@ func (c *FS) aumDir(h AUMHash) (dir, base string) {
func (c *FS) AUM(hash AUMHash) (AUM, error) {
c.mu.RLock()
defer c.mu.RUnlock()
return c.aumLocked(hash)
}
// aumLocked reads an AUM from disk. The caller must hold c.mu for reading or
// writing.
func (c *FS) aumLocked(hash AUMHash) (AUM, error) {
info, err := c.get(hash)
if err != nil {
if os.IsNotExist(err) {
@@ -401,20 +423,36 @@ func (c *FS) CommitTime(h AUMHash) (time.Time, error) {
return s.ModTime(), nil
}
// AUM returns any known AUMs with a specific parent hash.
// ChildAUMs returns any known AUMs with a specific parent hash.
func (c *FS) ChildAUMs(prevAUMHash AUMHash) ([]AUM, error) {
c.mu.RLock()
defer c.mu.RUnlock()
if c.aumIndex != nil {
defer c.mu.RUnlock()
return c.childAUMsFromIndexLocked(prevAUMHash)
}
c.mu.RUnlock()
var out []AUM
c.mu.Lock()
defer c.mu.Unlock()
if err := c.buildIndexLocked(); err != nil {
return nil, err
}
return c.childAUMsFromIndexLocked(prevAUMHash)
}
err := c.scanHashes(func(info *fsHashInfo) {
if info.AUM != nil && bytes.Equal(info.AUM.PrevAUMHash, prevAUMHash[:]) {
out = append(out, *info.AUM)
// childAUMsFromIndexLocked returns children from the in-memory index. The
// caller must hold c.mu for reading or writing.
func (c *FS) childAUMsFromIndexLocked(prevAUMHash AUMHash) ([]AUM, error) {
children := c.parentIndex[prevAUMHash]
out := make([]AUM, 0, len(children))
for i, h := range children {
aum, err := c.aumLocked(h)
if err != nil {
return nil, fmt.Errorf("reading child %d of %x: %w", i, prevAUMHash, err)
}
})
return out, err
out = append(out, aum)
}
return out, nil
}
func (c *FS) get(h AUMHash) (*fsHashInfo, error) {
@@ -442,72 +480,99 @@ func (c *FS) get(h AUMHash) (*fsHashInfo, error) {
// Heads returns AUMs for which there are no children. In other
// words, the latest AUM in all possible chains (the 'leaves').
//
// Heads is expected to be called infrequently compared to AUM() or
// ChildAUMs(), so we haven't put any work into maintaining an index.
// Instead, the full set of AUMs is scanned.
func (c *FS) Heads() ([]AUM, error) {
c.mu.RLock()
defer c.mu.RUnlock()
if c.aumIndex != nil {
defer c.mu.RUnlock()
return c.headsFromIndexLocked()
}
c.mu.RUnlock()
// Scan the complete list of AUMs, and build a list of all parent hashes.
// This tells us which AUMs have children.
var parentHashes []AUMHash
allAUMs, err := c.AllAUMs()
if err != nil {
c.mu.Lock()
defer c.mu.Unlock()
if err := c.buildIndexLocked(); err != nil {
return nil, err
}
return c.headsFromIndexLocked()
}
for _, h := range allAUMs {
aum, err := c.AUM(h)
if err != nil {
return nil, err
}
parent, hasParent := aum.Parent()
if !hasParent {
continue
}
if !slices.Contains(parentHashes, parent) {
parentHashes = append(parentHashes, parent)
}
}
// Now scan a second time, and only include AUMs which weren't marked as
// the parent of any other AUM.
// headsFromIndexLocked returns heads from the in-memory index. The caller
// must hold c.mu for reading or writing.
func (c *FS) headsFromIndexLocked() ([]AUM, error) {
out := make([]AUM, 0, 6) // 6 is arbitrary.
for _, h := range allAUMs {
if slices.Contains(parentHashes, h) {
for h := range c.aumIndex {
if len(c.parentIndex[h]) != 0 {
continue
}
aum, err := c.AUM(h)
aum, err := c.aumLocked(h)
if err != nil {
return nil, err
return nil, fmt.Errorf("reading head %x: %w", h, err)
}
out = append(out, aum)
}
return out, nil
}
// RemoveAll permanently and completely clears the TKA state.
func (c *FS) RemoveAll() error {
c.mu.Lock()
defer c.mu.Unlock()
c.invalidateIndexLocked()
return os.RemoveAll(c.base)
}
// AllAUMs returns all AUMs stored in the chonk.
func (c *FS) AllAUMs() ([]AUMHash, error) {
c.mu.RLock()
defer c.mu.RUnlock()
if c.aumIndex != nil {
out := slices.Collect(maps.Keys(c.aumIndex))
c.mu.RUnlock()
return out, nil
}
c.mu.RUnlock()
out := make([]AUMHash, 0, 6) // 6 is arbitrary.
err := c.scanHashes(func(info *fsHashInfo) {
if info.AUM != nil {
out = append(out, info.AUM.Hash())
c.mu.Lock()
defer c.mu.Unlock()
if err := c.buildIndexLocked(); err != nil {
return nil, err
}
return slices.Collect(maps.Keys(c.aumIndex)), nil
}
// buildIndexLocked builds indexes of the active AUMs in a single filesystem
// scan. If the index already exists, this is a no-op. The caller must hold
// c.mu for writing.
func (c *FS) buildIndexLocked() error {
if c.aumIndex != nil {
return nil
}
aumIndex := make(set.Set[AUMHash])
parentIndex := make(map[AUMHash][]AUMHash)
if err := c.scanHashes(func(info *fsHashInfo) {
if info.AUM == nil {
return
}
})
return out, err
h := info.AUM.Hash()
aumIndex.Add(h)
if parent, ok := info.AUM.Parent(); ok {
parentIndex[parent] = append(parentIndex[parent], h)
}
}); err != nil {
return err
}
c.aumIndex = aumIndex
c.parentIndex = parentIndex
return nil
}
// invalidateIndexLocked invalidates the in-memory indexes after storage is
// changed. The caller must hold c.mu for writing.
func (c *FS) invalidateIndexLocked() {
c.aumIndex = nil
c.parentIndex = nil
}
func (c *FS) scanHashes(eachHashInfo func(*fsHashInfo)) error {
@@ -594,6 +659,7 @@ func (c *FS) CommitVerifiedAUMs(updates []AUM) error {
c.mu.Lock()
defer c.mu.Unlock()
c.invalidateIndexLocked()
for i, aum := range updates {
h := aum.Hash()
err := c.commit(h, func(info *fsHashInfo) {
@@ -610,9 +676,14 @@ func (c *FS) CommitVerifiedAUMs(updates []AUM) error {
// PurgeAUMs marks the specified AUMs for deletion from storage.
func (c *FS) PurgeAUMs(hashes []AUMHash) error {
if len(hashes) == 0 {
return nil
}
c.mu.Lock()
defer c.mu.Unlock()
c.invalidateIndexLocked()
now := time.Now()
for i, h := range hashes {
stored, err := c.get(h)
@@ -715,7 +786,7 @@ func markActiveChain(storage Chonk, verdict map[AUMHash]retainState, minChain in
parent, hasParent := next.Parent()
if !hasParent {
// Genesis AUM (beginning of time). The chain isnt long enough to need truncating.
// Genesis AUM (beginning of time). The chain isn't long enough to need truncating.
return h, nil
}
+2 -5
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tailnetlock
@@ -31,9 +31,6 @@ var cborDecOpts = cbor.DecOptions{
MaxMapPairs: 1024,
}
// Arbitrarily chosen limit on scanning AUM trees.
const maxScanIterations = 2000
// Authority is a Tailnet Key Authority. This type is the main coupling
// point to the rest of the tailscale client.
//
@@ -570,7 +567,7 @@ func Bootstrap(storage Chonk, bootstrap AUM) (*Authority, error) {
// ValidDisablement returns true if the disablement secret was correct.
//
// If this method returns true, the caller should shut down the authority
// and purge all network-lock state.
// and purge all tailnet-lock state.
func (a *Authority) ValidDisablement(secret []byte) bool {
return a.state.checkDisablement(secret)
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_tailnetlock
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build ts_omit_tailnetlock