From 4c31183781df831c37d2c85f83587f717cc80fd9 Mon Sep 17 00:00:00 2001 From: Tom DNetto Date: Thu, 3 Nov 2022 10:25:20 -0700 Subject: [PATCH] cmd/tailscale,ipn: minor fixes to tailscale lock commands * Fix broken add/remove key commands * Make lock status display whether the node is signed Signed-off-by: Tom DNetto --- cmd/tailscale/cli/network-lock.go | 41 ++++++++++++++++++++++++++++--- ipn/ipnlocal/network-lock.go | 31 ++++++++++++++++++++--- ipn/ipnstate/ipnstate.go | 20 ++++++++++++++- tka/key.go | 11 +++++++++ tka/tka.go | 9 +++++++ types/key/nl.go | 11 +++++++++ 6 files changed, 116 insertions(+), 7 deletions(-) diff --git a/cmd/tailscale/cli/network-lock.go b/cmd/tailscale/cli/network-lock.go index cb84dc33d..fc1c66499 100644 --- a/cmd/tailscale/cli/network-lock.go +++ b/cmd/tailscale/cli/network-lock.go @@ -82,11 +82,46 @@ func runNetworkLockStatus(ctx context.Context, args []string) error { } else { fmt.Println("Network-lock is NOT enabled.") } + fmt.Println() + + if st.Enabled && st.NodeKey != nil { + if st.NodeKeySigned { + fmt.Println("This node is trusted by network-lock.") + } else { + fmt.Println("This node IS NOT trusted by network-lock, and action is required to establish connectivity.") + fmt.Printf("Run the following command on a node with a network-lock key:\n\ttailscale lock sign %v\n", st.NodeKey) + } + fmt.Println() + } + p, err := st.PublicKey.MarshalText() if err != nil { return err } - fmt.Printf("our public-key: %s\n", p) + fmt.Printf("This node's public-key: %s\n", p) + fmt.Println() + + if st.Enabled && len(st.TrustedKeys) > 0 { + fmt.Println("Keys trusted to make changes to network-lock:") + for _, k := range st.TrustedKeys { + key, err := k.Key.MarshalText() + if err != nil { + return err + } + + var line strings.Builder + line.WriteString("\t") + line.WriteString(string(key)) + line.WriteString("\t") + line.WriteString(fmt.Sprint(k.Votes)) + line.WriteString("\t") + if k.Key == st.PublicKey { + line.WriteString("(us)") + } + fmt.Println(line.String()) + } + } + return nil } @@ -143,8 +178,8 @@ func runNetworkLockModify(ctx context.Context, addArgs, removeArgs []string) err if err != nil { return fixTailscaledConnectError(err) } - if st.Enabled { - return errors.New("network-lock is already enabled") + if !st.Enabled { + return errors.New("network-lock is not enabled") } addKeys, err := parseNLKeyArgs(addArgs) diff --git a/ipn/ipnlocal/network-lock.go b/ipn/ipnlocal/network-lock.go index 1e8d06bef..cbe742f90 100644 --- a/ipn/ipnlocal/network-lock.go +++ b/ipn/ipnlocal/network-lock.go @@ -329,10 +329,17 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus { b.mu.Lock() defer b.mu.Unlock() + var nodeKey *key.NodePublic + if p := b.pm.CurrentPrefs(); p.Valid() { + nkp := p.Persist().PublicNodeKey() + nodeKey = &nkp + } + if b.tka == nil { return &ipnstate.NetworkLockStatus{ Enabled: false, PublicKey: b.nlPrivKey.Public(), + NodeKey: nodeKey, } } @@ -340,10 +347,28 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus { h := b.tka.authority.Head() copy(head[:], h[:]) + var selfAuthorized bool + if b.netMap != nil { + selfAuthorized = b.tka.authority.NodeKeyAuthorized(b.netMap.SelfNode.Key, b.netMap.SelfNode.KeySignature) == nil + } + + keys := b.tka.authority.Keys() + outKeys := make([]ipnstate.TKAKey, len(keys)) + for i, k := range keys { + outKeys[i] = ipnstate.TKAKey{ + Key: key.NLPublicFromEd25519Unsafe(k.Public), + Metadata: k.Meta, + Votes: k.Votes, + } + } + return &ipnstate.NetworkLockStatus{ - Enabled: true, - Head: &head, - PublicKey: b.nlPrivKey.Public(), + Enabled: true, + Head: &head, + PublicKey: b.nlPrivKey.Public(), + NodeKey: nodeKey, + NodeKeySigned: selfAuthorized, + TrustedKeys: outKeys, } } diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index 06fd065b0..58f3084ba 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -67,6 +67,13 @@ type Status struct { User map[tailcfg.UserID]tailcfg.UserProfile } +// TKAKey describes a key trusted by network lock. +type TKAKey struct { + Key key.NLPublic + Metadata map[string]string + Votes uint +} + // NetworkLockStatus represents whether network-lock is enabled, // along with details about the locally-known state of the tailnet // key authority. @@ -78,8 +85,19 @@ type NetworkLockStatus struct { // if network lock is not enabled. Head *[32]byte - // PublicKey describes the nodes' network-lock public key. + // PublicKey describes the node's network-lock public key. PublicKey key.NLPublic + + // NodeKey describes the node's current node-key. This field is not + // populated if the node is not operating (i.e. waiting for a login). + NodeKey *key.NodePublic + + // NodeKeySigned is true if our node is authorized by network-lock. + NodeKeySigned bool + + // TrustedKeys describes the keys currently trusted to make changes + // to network-lock. + TrustedKeys []TKAKey } // TailnetStatus is information about a Tailscale network ("tailnet"). diff --git a/tka/key.go b/tka/key.go index aa0a253f9..01e07e62d 100644 --- a/tka/key.go +++ b/tka/key.go @@ -85,6 +85,17 @@ func (k Key) ID() tkatype.KeyID { } } +// Ed25519 returns the ed25519 public key encoded by Key. An error is +// returned for keys which do not represent ed25519 public keys. +func (k Key) Ed25519() (ed25519.PublicKey, error) { + switch k.Kind { + case Key25519: + return ed25519.PublicKey(k.Public), nil + default: + return nil, fmt.Errorf("key is of type %v, not ed25519", k.Kind) + } +} + const maxMetaBytes = 512 func (k Key) StaticValidate() error { diff --git a/tka/tka.go b/tka/tka.go index 2c7168afc..338912872 100644 --- a/tka/tka.go +++ b/tka/tka.go @@ -705,3 +705,12 @@ func (a *Authority) KeyTrusted(keyID tkatype.KeyID) bool { _, err := a.state.GetKey(keyID) return err == nil } + +// Keys returns the set of keys trusted by the tailnet key authority. +func (a *Authority) Keys() []Key { + out := make([]Key, len(a.state.Keys)) + for i := range a.state.Keys { + out[i] = a.state.Keys[i].Clone() + } + return out +} diff --git a/types/key/nl.go b/types/key/nl.go index 6eb237ce8..3f1564454 100644 --- a/types/key/nl.go +++ b/types/key/nl.go @@ -100,6 +100,17 @@ type NLPublic struct { k [ed25519.PublicKeySize]byte } +// NLPublicFromEd25519Unsafe converts an ed25519 public key into +// a type of NLPublic. +// +// New uses of this function should be avoided, as its possible to +// accidentally construct an NLPublic from a non network-lock key. +func NLPublicFromEd25519Unsafe(public ed25519.PublicKey) NLPublic { + var out NLPublic + copy(out.k[:], public) + return out +} + // MarshalText implements encoding.TextUnmarshaler. func (k *NLPublic) UnmarshalText(b []byte) error { return parseHex(k.k[:], mem.B(b), mem.S(nlPublicHexPrefix))