Browse Source

types/key,cmd/tailscale/cli: support tlpub prefix for tailnet-lock keys

Signed-off-by: Tom DNetto <[email protected]>
Tom DNetto 3 years ago
parent
commit
74c1f632f6
4 changed files with 39 additions and 23 deletions
  1. 4 18
      cmd/tailscale/cli/network-lock.go
  2. 1 1
      ipn/ipnlocal/network-lock.go
  3. 25 4
      types/key/nl.go
  4. 9 0
      types/key/nl_test.go

+ 4 - 18
cmd/tailscale/cli/network-lock.go

@@ -99,7 +99,7 @@ func runNetworkLockInit(ctx context.Context, args []string) error {
 
 	fmt.Println("You are initializing tailnet lock with trust in the following keys:")
 	for _, k := range keys {
-		fmt.Printf(" - %x (%s key)\n", k.Public, k.Kind.String())
+		fmt.Printf(" - tlpub:%x (%s key)\n", k.Public, k.Kind.String())
 	}
 	fmt.Println()
 
@@ -172,37 +172,23 @@ func runNetworkLockStatus(ctx context.Context, args []string) error {
 		if st.NodeKeySigned {
 			fmt.Println("This node is accessible under tailnet-lock.")
 		} else {
-			p, err := st.PublicKey.MarshalText()
-			if err != nil {
-				return err
-			}
-
 			fmt.Println("This node is LOCKED OUT by tailnet-lock, and action is required to establish connectivity.")
-			fmt.Printf("Run the following command on a node with a trusted key:\n\ttailscale lock sign %v %s\n", st.NodeKey, p)
+			fmt.Printf("Run the following command on a node with a trusted key:\n\ttailscale lock sign %v %s\n", st.NodeKey, st.PublicKey.CLIString())
 		}
 		fmt.Println()
 	}
 
 	if !st.PublicKey.IsZero() {
-		p, err := st.PublicKey.MarshalText()
-		if err != nil {
-			return err
-		}
-		fmt.Printf("This node's tailnet-lock key: %s\n", p)
+		fmt.Printf("This node's tailnet-lock key: %s\n", st.PublicKey.CLIString())
 		fmt.Println()
 	}
 
 	if st.Enabled && len(st.TrustedKeys) > 0 {
 		fmt.Println("Keys trusted to make changes to tailnet-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(k.Key.CLIString())
 			line.WriteString("\t")
 			line.WriteString(fmt.Sprint(k.Votes))
 			line.WriteString("\t")

+ 1 - 1
ipn/ipnlocal/network-lock.go

@@ -499,7 +499,7 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byt
 
 	b.logf("Generated genesis AUM to initialize network lock, trusting the following keys:")
 	for i, k := range genesisAUM.State.Keys {
-		b.logf(" - key[%d] = nlpub:%x with %d votes", i, k.Public, k.Votes)
+		b.logf(" - key[%d] = tlpub:%x with %d votes", i, k.Public, k.Votes)
 	}
 
 	// Phase 1/2 of initialization: Transmit the genesis AUM to Control.

+ 25 - 4
types/key/nl.go

@@ -15,12 +15,19 @@ import (
 
 const (
 	// nlPrivateHexPrefix is the prefix used to identify a
-	// hex-encoded network-lock key.
+	// hex-encoded tailnet-lock key.
 	nlPrivateHexPrefix = "nlpriv:"
 
 	// nlPublicHexPrefix is the prefix used to identify the public
-	// side of a hex-encoded network-lock key.
+	// side of a hex-encoded tailnet-lock key.
 	nlPublicHexPrefix = "nlpub:"
+
+	// nlPublicHexPrefixCLI is the prefix used for tailnet-lock keys
+	// when shown on the CLI.
+	// It's not practical for us to change the prefix everywhere due to
+	// compatibility with existing clients, but we can support both prefixes
+	// as well as use the CLI form when presenting to the user.
+	nlPublicHexPrefixCLI = "tlpub:"
 )
 
 // NLPrivate is a node-managed network-lock key, used for signing
@@ -116,16 +123,30 @@ func NLPublicFromEd25519Unsafe(public ed25519.PublicKey) NLPublic {
 	return out
 }
 
-// MarshalText implements encoding.TextUnmarshaler.
+// UnmarshalText implements encoding.TextUnmarshaler. This function
+// is able to decode both the CLI form (tlpub:<hex>) & the
+// regular form (nlpub:<hex>).
 func (k *NLPublic) UnmarshalText(b []byte) error {
+	if mem.HasPrefix(mem.B(b), mem.S(nlPublicHexPrefixCLI)) {
+		return parseHex(k.k[:], mem.B(b), mem.S(nlPublicHexPrefixCLI))
+	}
 	return parseHex(k.k[:], mem.B(b), mem.S(nlPublicHexPrefix))
 }
 
-// MarshalText implements encoding.TextMarshaler.
+// MarshalText implements encoding.TextMarshaler, emitting a
+// representation of the form nlpub:<hex>.
 func (k NLPublic) MarshalText() ([]byte, error) {
 	return toHex(k.k[:], nlPublicHexPrefix), nil
 }
 
+// CLIString returns a marshalled representation suitable for use
+// with tailnet lock commands, of the form tlpub:<hex> instead of
+// the nlpub:<hex> form emitted by MarshalText. Both forms can
+// be decoded by UnmarshalText.
+func (k NLPublic) CLIString() string {
+	return string(toHex(k.k[:], nlPublicHexPrefixCLI))
+}
+
 // Verifier returns a ed25519.PublicKey that can be used to
 // verify signatures.
 func (k NLPublic) Verifier() ed25519.PublicKey {

+ 9 - 0
types/key/nl_test.go

@@ -37,4 +37,13 @@ func TestNLPrivate(t *testing.T) {
 	if !bytes.Equal(decodedPub.k[:], pub.k[:]) {
 		t.Error("decoded and generated NLPublic bytes differ")
 	}
+
+	// Test decoding with CLI prefix: 'nlpub:' => 'tlpub:'
+	decodedPub = NLPublic{}
+	if err := decodedPub.UnmarshalText([]byte(pub.CLIString())); err != nil {
+		t.Fatal(err)
+	}
+	if !bytes.Equal(decodedPub.k[:], pub.k[:]) {
+		t.Error("decoded and generated NLPublic bytes differ (CLI prefix)")
+	}
 }