mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
ipn,types/persist: store disallowed TKA's in prefs, lock local-disable
Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
parent
659e7837c6
commit
f1130421f0
@ -853,6 +853,21 @@ func (lc *LocalClient) NetworkLockLog(ctx context.Context, maxEntries int) ([]ip
|
|||||||
return decodeJSON[[]ipnstate.NetworkLockUpdate](body)
|
return decodeJSON[[]ipnstate.NetworkLockUpdate](body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NetworkLockForceLocalDisable forcibly shuts down network lock on this node.
|
||||||
|
func (lc *LocalClient) NetworkLockForceLocalDisable(ctx context.Context) error {
|
||||||
|
// This endpoint expects an empty JSON stanza as the payload.
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := json.NewEncoder(&b).Encode(struct{}{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := lc.send(ctx, "POST", "/localapi/v0/tka/force-local-disable", 200, &b); err != nil {
|
||||||
|
return fmt.Errorf("error: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// SetServeConfig sets or replaces the serving settings.
|
// SetServeConfig sets or replaces the serving settings.
|
||||||
// If config is nil, settings are cleared and serving is disabled.
|
// If config is nil, settings are cleared and serving is disabled.
|
||||||
func (lc *LocalClient) SetServeConfig(ctx context.Context, config *ipn.ServeConfig) error {
|
func (lc *LocalClient) SetServeConfig(ctx context.Context, config *ipn.ServeConfig) error {
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
nlDisableCmd,
|
nlDisableCmd,
|
||||||
nlDisablementKDFCmd,
|
nlDisablementKDFCmd,
|
||||||
nlLogCmd,
|
nlLogCmd,
|
||||||
|
nlLocalDisableCmd,
|
||||||
},
|
},
|
||||||
Exec: runNetworkLockStatus,
|
Exec: runNetworkLockStatus,
|
||||||
}
|
}
|
||||||
@ -348,6 +349,17 @@ func runNetworkLockDisable(ctx context.Context, args []string) error {
|
|||||||
return localClient.NetworkLockDisable(ctx, secrets[0])
|
return localClient.NetworkLockDisable(ctx, secrets[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nlLocalDisableCmd = &ffcli.Command{
|
||||||
|
Name: "local-disable",
|
||||||
|
ShortUsage: "local-disable",
|
||||||
|
ShortHelp: "Disables the currently-active tailnet lock for this node",
|
||||||
|
Exec: runNetworkLockLocalDisable,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runNetworkLockLocalDisable(ctx context.Context, args []string) error {
|
||||||
|
return localClient.NetworkLockForceLocalDisable(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
var nlDisablementKDFCmd = &ffcli.Command{
|
var nlDisablementKDFCmd = &ffcli.Command{
|
||||||
Name: "disablement-kdf",
|
Name: "disablement-kdf",
|
||||||
ShortUsage: "disablement-kdf <hex-encoded-disablement-secret>",
|
ShortUsage: "disablement-kdf <hex-encoded-disablement-secret>",
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"tailscale.com/tka"
|
"tailscale.com/tka"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/netmap"
|
"tailscale.com/types/netmap"
|
||||||
|
"tailscale.com/types/persist"
|
||||||
"tailscale.com/types/tkatype"
|
"tailscale.com/types/tkatype"
|
||||||
"tailscale.com/util/mak"
|
"tailscale.com/util/mak"
|
||||||
)
|
)
|
||||||
@ -134,7 +135,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
if wantEnabled && !isEnabled {
|
if wantEnabled && !isEnabled {
|
||||||
if err := b.tkaBootstrapFromGenesisLocked(bs.GenesisAUM); err != nil {
|
if err := b.tkaBootstrapFromGenesisLocked(bs.GenesisAUM, prefs.Persist()); err != nil {
|
||||||
return fmt.Errorf("bootstrap: %w", err)
|
return fmt.Errorf("bootstrap: %w", err)
|
||||||
}
|
}
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
@ -278,7 +279,7 @@ func (b *LocalBackend) chonkPathLocked() string {
|
|||||||
// tailnet key authority, based on the given genesis AUM.
|
// tailnet key authority, based on the given genesis AUM.
|
||||||
//
|
//
|
||||||
// b.mu must be held.
|
// b.mu must be held.
|
||||||
func (b *LocalBackend) tkaBootstrapFromGenesisLocked(g tkatype.MarshaledAUM) error {
|
func (b *LocalBackend) tkaBootstrapFromGenesisLocked(g tkatype.MarshaledAUM, persist *persist.Persist) error {
|
||||||
if err := b.CanSupportNetworkLock(); err != nil {
|
if err := b.CanSupportNetworkLock(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -288,6 +289,19 @@ func (b *LocalBackend) tkaBootstrapFromGenesisLocked(g tkatype.MarshaledAUM) err
|
|||||||
return fmt.Errorf("reading genesis: %v", err)
|
return fmt.Errorf("reading genesis: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if persist != nil && len(persist.DisallowedTKAStateIDs) > 0 {
|
||||||
|
if genesis.State == nil {
|
||||||
|
return errors.New("invalid genesis: missing State")
|
||||||
|
}
|
||||||
|
bootstrapStateID := fmt.Sprintf("%d:%d", genesis.State.StateID1, genesis.State.StateID2)
|
||||||
|
|
||||||
|
for _, stateID := range persist.DisallowedTKAStateIDs {
|
||||||
|
if stateID == bootstrapStateID {
|
||||||
|
return fmt.Errorf("TKA with stateID of %q is disallowed on this node", stateID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
chonkDir := b.chonkPathLocked()
|
chonkDir := b.chonkPathLocked()
|
||||||
if err := os.Mkdir(filepath.Dir(chonkDir), 0755); err != nil && !os.IsExist(err) {
|
if err := os.Mkdir(filepath.Dir(chonkDir), 0755); err != nil && !os.IsExist(err) {
|
||||||
return fmt.Errorf("creating chonk root dir: %v", err)
|
return fmt.Errorf("creating chonk root dir: %v", err)
|
||||||
@ -495,6 +509,31 @@ func (b *LocalBackend) NetworkLockKeyTrustedForTest(keyID tkatype.KeyID) bool {
|
|||||||
return b.tka.authority.KeyTrusted(keyID)
|
return b.tka.authority.KeyTrusted(keyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NetworkLockForceLocalDisable shuts down TKA locally, and denylists the current
|
||||||
|
// TKA from being initialized locally in future.
|
||||||
|
func (b *LocalBackend) NetworkLockForceLocalDisable() error {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if b.tka == nil {
|
||||||
|
return errNetworkLockNotActive
|
||||||
|
}
|
||||||
|
|
||||||
|
id1, id2 := b.tka.authority.StateIDs()
|
||||||
|
stateID := fmt.Sprintf("%d:%d", id1, id2)
|
||||||
|
|
||||||
|
newPrefs := b.pm.CurrentPrefs().AsStruct().Clone() // .Persist should always be initialized here.
|
||||||
|
newPrefs.Persist.DisallowedTKAStateIDs = append(newPrefs.Persist.DisallowedTKAStateIDs, stateID)
|
||||||
|
if err := b.pm.SetPrefs(newPrefs.View()); err != nil {
|
||||||
|
return fmt.Errorf("saving prefs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(b.chonkPathLocked()); err != nil {
|
||||||
|
return fmt.Errorf("deleting TKA state: %w", err)
|
||||||
|
}
|
||||||
|
b.tka = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NetworkLockSign signs the given node-key and submits it to the control plane.
|
// NetworkLockSign signs the given node-key and submits it to the control plane.
|
||||||
// rotationPublic, if specified, must be an ed25519 public key.
|
// rotationPublic, if specified, must be an ed25519 public key.
|
||||||
func (b *LocalBackend) NetworkLockSign(nodeKey key.NodePublic, rotationPublic []byte) error {
|
func (b *LocalBackend) NetworkLockSign(nodeKey key.NodePublic, rotationPublic []byte) error {
|
||||||
|
@ -778,3 +778,103 @@ func TestTKASign(t *testing.T) {
|
|||||||
t.Errorf("NetworkLockSign() failed: %v", err)
|
t.Errorf("NetworkLockSign() failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTKAForceDisable(t *testing.T) {
|
||||||
|
envknob.Setenv("TAILSCALE_USE_WIP_CODE", "1")
|
||||||
|
defer envknob.Setenv("TAILSCALE_USE_WIP_CODE", "")
|
||||||
|
nodePriv := key.NewNode()
|
||||||
|
|
||||||
|
// Make a fake TKA authority, to seed local state.
|
||||||
|
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
|
||||||
|
nlPriv := key.NewNLPrivate()
|
||||||
|
key := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
|
||||||
|
|
||||||
|
pm := must.Get(newProfileManager(new(mem.Store), t.Logf, ""))
|
||||||
|
must.Do(pm.SetPrefs((&ipn.Prefs{
|
||||||
|
Persist: &persist.Persist{
|
||||||
|
PrivateNodeKey: nodePriv,
|
||||||
|
NetworkLockKey: nlPriv,
|
||||||
|
},
|
||||||
|
}).View()))
|
||||||
|
|
||||||
|
temp := t.TempDir()
|
||||||
|
tkaPath := filepath.Join(temp, "tka-profile", string(pm.CurrentProfile().ID))
|
||||||
|
os.Mkdir(tkaPath, 0755)
|
||||||
|
chonk, err := tka.ChonkDir(tkaPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
authority, genesis, err := tka.Create(chonk, tka.State{
|
||||||
|
Keys: []tka.Key{key},
|
||||||
|
DisablementSecrets: [][]byte{tka.DisablementKDF(disablementSecret)},
|
||||||
|
}, nlPriv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("tka.Create() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ts, client := fakeNoiseServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/machine/tka/bootstrap":
|
||||||
|
body := new(tailcfg.TKABootstrapRequest)
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if body.Version != tailcfg.CurrentCapabilityVersion {
|
||||||
|
t.Errorf("bootstrap CapVer = %v, want %v", body.Version, tailcfg.CurrentCapabilityVersion)
|
||||||
|
}
|
||||||
|
if body.NodeKey != nodePriv.Public() {
|
||||||
|
t.Errorf("nodeKey=%v, want %v", body.NodeKey, nodePriv.Public())
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(200)
|
||||||
|
out := tailcfg.TKABootstrapResponse{
|
||||||
|
GenesisAUM: genesis.Serialize(),
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(out); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Errorf("unhandled endpoint path: %v", r.URL.Path)
|
||||||
|
w.WriteHeader(404)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cc := fakeControlClient(t, client)
|
||||||
|
b := LocalBackend{
|
||||||
|
varRoot: temp,
|
||||||
|
cc: cc,
|
||||||
|
ccAuto: cc,
|
||||||
|
logf: t.Logf,
|
||||||
|
tka: &tkaState{
|
||||||
|
authority: authority,
|
||||||
|
storage: chonk,
|
||||||
|
},
|
||||||
|
pm: pm,
|
||||||
|
store: pm.Store(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.NetworkLockForceLocalDisable(); err != nil {
|
||||||
|
t.Fatalf("NetworkLockForceLocalDisable() failed: %v", err)
|
||||||
|
}
|
||||||
|
if b.tka != nil {
|
||||||
|
t.Fatal("tka was not shut down")
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(b.chonkPathLocked()); err == nil || !os.IsNotExist(err) {
|
||||||
|
t.Errorf("os.Stat(chonkDir) = %v, want ErrNotExist", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.tkaSyncIfNeeded(&netmap.NetworkMap{
|
||||||
|
TKAEnabled: true,
|
||||||
|
TKAHead: authority.Head(),
|
||||||
|
}, pm.CurrentPrefs())
|
||||||
|
if err != nil && err.Error() != "bootstrap: TKA with stateID of \"0:0\" is disallowed on this node" {
|
||||||
|
t.Errorf("tkaSyncIfNeededLocked() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.tka != nil {
|
||||||
|
t.Fatal("tka was re-initalized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -89,6 +89,7 @@
|
|||||||
"tka/sign": (*Handler).serveTKASign,
|
"tka/sign": (*Handler).serveTKASign,
|
||||||
"tka/status": (*Handler).serveTKAStatus,
|
"tka/status": (*Handler).serveTKAStatus,
|
||||||
"tka/disable": (*Handler).serveTKADisable,
|
"tka/disable": (*Handler).serveTKADisable,
|
||||||
|
"tka/force-local-disable": (*Handler).serveTKALocalDisable,
|
||||||
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
|
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
|
||||||
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
|
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
|
||||||
"whois": (*Handler).serveWhoIs,
|
"whois": (*Handler).serveWhoIs,
|
||||||
@ -1243,6 +1244,30 @@ func (h *Handler) serveTKADisable(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveTKALocalDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !h.PermitWrite {
|
||||||
|
http.Error(w, "network-lock modify access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "use POST", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require a JSON stanza for the body as an additional CSRF protection.
|
||||||
|
var req struct{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "invalid JSON body", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.b.NetworkLockForceLocalDisable(); err != nil {
|
||||||
|
http.Error(w, "network-lock local disable failed: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(200)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) serveTKALog(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) serveTKALog(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
http.Error(w, "use GET", http.StatusMethodNotAllowed)
|
http.Error(w, "use GET", http.StatusMethodNotAllowed)
|
||||||
|
@ -714,3 +714,8 @@ func (a *Authority) Keys() []Key {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StateIDs returns the stateIDs for this tailnet key authority.
|
||||||
|
func (a *Authority) StateIDs() (uint64, uint64) {
|
||||||
|
return a.state.StateID1, a.state.StateID2
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
@ -39,6 +40,12 @@ type Persist struct {
|
|||||||
UserProfile tailcfg.UserProfile
|
UserProfile tailcfg.UserProfile
|
||||||
NetworkLockKey key.NLPrivate
|
NetworkLockKey key.NLPrivate
|
||||||
NodeID tailcfg.StableNodeID
|
NodeID tailcfg.StableNodeID
|
||||||
|
|
||||||
|
// DisallowedTKAStateIDs stores the tka.State.StateID values which
|
||||||
|
// this node will not operate network lock on. This is used to
|
||||||
|
// prevent bootstrapping TKA onto a key authority which was forcibly
|
||||||
|
// disabled.
|
||||||
|
DisallowedTKAStateIDs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicNodeKey returns the public key for the node key.
|
// PublicNodeKey returns the public key for the node key.
|
||||||
@ -70,7 +77,8 @@ func (p *Persist) Equals(p2 *Persist) bool {
|
|||||||
p.LoginName == p2.LoginName &&
|
p.LoginName == p2.LoginName &&
|
||||||
p.UserProfile == p2.UserProfile &&
|
p.UserProfile == p2.UserProfile &&
|
||||||
p.NetworkLockKey.Equal(p2.NetworkLockKey) &&
|
p.NetworkLockKey.Equal(p2.NetworkLockKey) &&
|
||||||
p.NodeID == p2.NodeID
|
p.NodeID == p2.NodeID &&
|
||||||
|
reflect.DeepEqual(p.DisallowedTKAStateIDs, p2.DisallowedTKAStateIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Persist) Pretty() string {
|
func (p *Persist) Pretty() string {
|
||||||
|
@ -20,6 +20,7 @@ func (src *Persist) Clone() *Persist {
|
|||||||
}
|
}
|
||||||
dst := new(Persist)
|
dst := new(Persist)
|
||||||
*dst = *src
|
*dst = *src
|
||||||
|
dst.DisallowedTKAStateIDs = append(src.DisallowedTKAStateIDs[:0:0], src.DisallowedTKAStateIDs...)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,4 +35,5 @@ func (src *Persist) Clone() *Persist {
|
|||||||
UserProfile tailcfg.UserProfile
|
UserProfile tailcfg.UserProfile
|
||||||
NetworkLockKey key.NLPrivate
|
NetworkLockKey key.NLPrivate
|
||||||
NodeID tailcfg.StableNodeID
|
NodeID tailcfg.StableNodeID
|
||||||
|
DisallowedTKAStateIDs []string
|
||||||
}{})
|
}{})
|
||||||
|
@ -22,7 +22,7 @@ func fieldsOf(t reflect.Type) (fields []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPersistEqual(t *testing.T) {
|
func TestPersistEqual(t *testing.T) {
|
||||||
persistHandles := []string{"LegacyFrontendPrivateMachineKey", "PrivateNodeKey", "OldPrivateNodeKey", "Provider", "LoginName", "UserProfile", "NetworkLockKey", "NodeID"}
|
persistHandles := []string{"LegacyFrontendPrivateMachineKey", "PrivateNodeKey", "OldPrivateNodeKey", "Provider", "LoginName", "UserProfile", "NetworkLockKey", "NodeID", "DisallowedTKAStateIDs"}
|
||||||
if have := fieldsOf(reflect.TypeOf(Persist{})); !reflect.DeepEqual(have, persistHandles) {
|
if have := fieldsOf(reflect.TypeOf(Persist{})); !reflect.DeepEqual(have, persistHandles) {
|
||||||
t.Errorf("Persist.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
t.Errorf("Persist.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
||||||
have, persistHandles)
|
have, persistHandles)
|
||||||
@ -133,6 +133,11 @@ func TestPersistEqual(t *testing.T) {
|
|||||||
&Persist{NodeID: "abc"},
|
&Persist{NodeID: "abc"},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
&Persist{DisallowedTKAStateIDs: nil},
|
||||||
|
&Persist{DisallowedTKAStateIDs: []string{"0:0"}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
if got := test.a.Equals(test.b); got != test.want {
|
if got := test.a.Equals(test.b); got != test.want {
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/structs"
|
"tailscale.com/types/structs"
|
||||||
|
"tailscale.com/types/views"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=Persist
|
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=Persist
|
||||||
@ -72,6 +73,9 @@ func (v PersistView) LoginName() string { return v.ж.LoginName
|
|||||||
func (v PersistView) UserProfile() tailcfg.UserProfile { return v.ж.UserProfile }
|
func (v PersistView) UserProfile() tailcfg.UserProfile { return v.ж.UserProfile }
|
||||||
func (v PersistView) NetworkLockKey() key.NLPrivate { return v.ж.NetworkLockKey }
|
func (v PersistView) NetworkLockKey() key.NLPrivate { return v.ж.NetworkLockKey }
|
||||||
func (v PersistView) NodeID() tailcfg.StableNodeID { return v.ж.NodeID }
|
func (v PersistView) NodeID() tailcfg.StableNodeID { return v.ж.NodeID }
|
||||||
|
func (v PersistView) DisallowedTKAStateIDs() views.Slice[string] {
|
||||||
|
return views.SliceOf(v.ж.DisallowedTKAStateIDs)
|
||||||
|
}
|
||||||
|
|
||||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _PersistViewNeedsRegeneration = Persist(struct {
|
var _PersistViewNeedsRegeneration = Persist(struct {
|
||||||
@ -84,4 +88,5 @@ func (v PersistView) NodeID() tailcfg.StableNodeID { return v.ж.NodeID }
|
|||||||
UserProfile tailcfg.UserProfile
|
UserProfile tailcfg.UserProfile
|
||||||
NetworkLockKey key.NLPrivate
|
NetworkLockKey key.NLPrivate
|
||||||
NodeID tailcfg.StableNodeID
|
NodeID tailcfg.StableNodeID
|
||||||
|
DisallowedTKAStateIDs []string
|
||||||
}{})
|
}{})
|
||||||
|
Loading…
Reference in New Issue
Block a user