summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorTom DNetto <tom@tailscale.com>2022-11-23 11:19:30 -0800
committerTom <twitchyliquid64@users.noreply.github.com>2022-11-28 10:39:04 -0800
commit5c8d2fa69598ced697c192c024d9dbf58991ab2f (patch)
treec8c86770dcc0aa9315e5ca587fdd63cd76c6aa61
parente8cc78b1afdd4b7358c39b8cfbfcd7ae7faf3bd3 (diff)
downloadtailscale-5c8d2fa69598ced697c192c024d9dbf58991ab2f.tar.xz
tailscale-5c8d2fa69598ced697c192c024d9dbf58991ab2f.zip
cmd/tailscale,ipn: improve UX of lock init command, cosmetic changes
Signed-off-by: Tom DNetto <tom@tailscale.com>
-rw-r--r--client/tailscale/localclient.go9
-rw-r--r--cmd/tailscale/cli/network-lock.go111
-rw-r--r--ipn/ipnlocal/network-lock.go13
-rw-r--r--ipn/localapi/localapi.go7
-rw-r--r--tailcfg/tka.go5
5 files changed, 115 insertions, 30 deletions
diff --git a/client/tailscale/localclient.go b/client/tailscale/localclient.go
index 2e2f6800f..9d006303d 100644
--- a/client/tailscale/localclient.go
+++ b/client/tailscale/localclient.go
@@ -787,14 +787,15 @@ func (lc *LocalClient) NetworkLockStatus(ctx context.Context) (*ipnstate.Network
// NetworkLockInit initializes the tailnet key authority.
//
// TODO(tom): Plumb through disablement secrets.
-func (lc *LocalClient) NetworkLockInit(ctx context.Context, keys []tka.Key, disablementValues [][]byte) (*ipnstate.NetworkLockStatus, error) {
+func (lc *LocalClient) NetworkLockInit(ctx context.Context, keys []tka.Key, disablementValues [][]byte, supportDisablement []byte) (*ipnstate.NetworkLockStatus, error) {
var b bytes.Buffer
type initRequest struct {
- Keys []tka.Key
- DisablementValues [][]byte
+ Keys []tka.Key
+ DisablementValues [][]byte
+ SupportDisablement []byte
}
- if err := json.NewEncoder(&b).Encode(initRequest{Keys: keys, DisablementValues: disablementValues}); err != nil {
+ if err := json.NewEncoder(&b).Encode(initRequest{Keys: keys, DisablementValues: disablementValues, SupportDisablement: supportDisablement}); err != nil {
return nil, err
}
diff --git a/cmd/tailscale/cli/network-lock.go b/cmd/tailscale/cli/network-lock.go
index a7c701761..26a5863fe 100644
--- a/cmd/tailscale/cli/network-lock.go
+++ b/cmd/tailscale/cli/network-lock.go
@@ -6,6 +6,7 @@ package cli
import (
"context"
+ "crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
@@ -40,11 +41,44 @@ var netlockCmd = &ffcli.Command{
Exec: runNetworkLockStatus,
}
+var nlInitArgs struct {
+ numDisablements int
+ disablementForSupport bool
+ confirm bool
+}
+
var nlInitCmd = &ffcli.Command{
Name: "init",
- ShortUsage: "init <public-key>...",
- ShortHelp: "Initialize the tailnet key authority",
- Exec: runNetworkLockInit,
+ ShortUsage: "init [--gen-disablement-for-support] --gen-disablements N <trusted-key>...",
+ ShortHelp: "Initialize tailnet lock",
+ LongHelp: strings.TrimSpace(`
+
+The 'tailscale lock init' command initializes tailnet lock across the
+entire tailnet. The specified keys are initially trusted to sign nodes
+or to make further changes to tailnet lock.
+
+You can identify the key for a node you wish to trust by running 'tailscale lock'
+on that node, and copying the node's tailnet lock key.
+
+In the event that tailnet lock need be disabled, it can be disabled using
+the 'tailscale lock disable' command and one of the disablement secrets.
+The number of disablement secrets to be generated is specified using the
+--gen-disablements flag. Initializing tailnet lock requires at least
+one disablement.
+
+If --gen-disablement-for-support is specified, an additional disablement secret
+will be generated and transmitted to Tailscale, which support can use to disable
+tailnet lock. We recommend setting this flag.
+
+`),
+ Exec: runNetworkLockInit,
+ FlagSet: (func() *flag.FlagSet {
+ fs := newFlagSet("lock init")
+ fs.IntVar(&nlInitArgs.numDisablements, "gen-disablements", 1, "number of disablement secrets to generate")
+ fs.BoolVar(&nlInitArgs.disablementForSupport, "gen-disablement-for-support", false, "generates and transmits a disablement secret for Tailscale support")
+ fs.BoolVar(&nlInitArgs.confirm, "confirm", false, "do not prompt for confirmation")
+ return fs
+ })(),
}
func runNetworkLockInit(ctx context.Context, args []string) error {
@@ -62,12 +96,55 @@ func runNetworkLockInit(ctx context.Context, args []string) error {
return err
}
- status, err := localClient.NetworkLockInit(ctx, keys, disablementValues)
- if err != nil {
+ 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.Println()
+
+ if !nlInitArgs.confirm {
+ fmt.Printf("%d disablement secrets will be generated.\n", nlInitArgs.numDisablements)
+ if nlInitArgs.disablementForSupport {
+ fmt.Println("A disablement secret for support will be generated and transmitted to Tailscale.")
+ }
+
+ genSupportFlag := ""
+ if nlInitArgs.disablementForSupport {
+ genSupportFlag = "--gen-disablement-for-support "
+ }
+ fmt.Println("\nIf this is correct, please re-run this command with the --confirm flag:")
+ fmt.Printf("\t%s lock init --confirm --gen-disablements %d %s%s", os.Args[0], nlInitArgs.numDisablements, genSupportFlag, strings.Join(args, " "))
+ fmt.Println()
+ return nil
+ }
+
+ fmt.Printf("%d disablement secrets have been generated and are printed below. Take note of them now, they WILL NOT be shown again.\n", nlInitArgs.numDisablements)
+ for i := 0; i < nlInitArgs.numDisablements; i++ {
+ var secret [32]byte
+ if _, err := rand.Read(secret[:]); err != nil {
+ return err
+ }
+ fmt.Printf("\tdisablement-secret:%X\n", secret[:])
+ disablementValues = append(disablementValues, tka.DisablementKDF(secret[:]))
+ }
+
+ var supportDisablement []byte
+ if nlInitArgs.disablementForSupport {
+ supportDisablement = make([]byte, 32)
+ if _, err := rand.Read(supportDisablement); err != nil {
+ return err
+ }
+ disablementValues = append(disablementValues, tka.DisablementKDF(supportDisablement))
+ fmt.Println("A disablement secret for support has been generated and will be transmitted to Tailscale upon initialization.")
+ }
+
+ // The state returned by NetworkLockInit likely doesn't contain the initialized state,
+ // because that has to tick through from netmaps.
+ if _, err := localClient.NetworkLockInit(ctx, keys, disablementValues, supportDisablement); err != nil {
return err
}
- fmt.Printf("Status: %+v\n\n", status)
+ fmt.Println("Initialization complete.")
return nil
}
@@ -84,18 +161,18 @@ func runNetworkLockStatus(ctx context.Context, args []string) error {
return fixTailscaledConnectError(err)
}
if st.Enabled {
- fmt.Println("Network-lock is ENABLED.")
+ fmt.Println("Tailnet-lock is ENABLED.")
} else {
- fmt.Println("Network-lock is NOT enabled.")
+ fmt.Println("Tailnet-lock is NOT enabled.")
}
fmt.Println()
if st.Enabled && st.NodeKey != nil {
if st.NodeKeySigned {
- fmt.Println("This node is trusted by network-lock.")
+ fmt.Println("This node is accessible under tailnet-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("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\n", st.NodeKey)
}
fmt.Println()
}
@@ -105,12 +182,12 @@ func runNetworkLockStatus(ctx context.Context, args []string) error {
if err != nil {
return err
}
- fmt.Printf("This node's public-key: %s\n", p)
+ fmt.Printf("This node's tailnet-lock key: %s\n", p)
fmt.Println()
}
if st.Enabled && len(st.TrustedKeys) > 0 {
- fmt.Println("Keys trusted to make changes to network-lock:")
+ fmt.Println("Keys trusted to make changes to tailnet-lock:")
for _, k := range st.TrustedKeys {
key, err := k.Key.MarshalText()
if err != nil {
@@ -204,7 +281,7 @@ func runNetworkLockModify(ctx context.Context, addArgs, removeArgs []string) err
return fixTailscaledConnectError(err)
}
if !st.Enabled {
- return errors.New("network-lock is not enabled")
+ return errors.New("tailnet-lock is not enabled")
}
addKeys, _, err := parseNLArgs(addArgs, true, false)
@@ -256,7 +333,7 @@ func runNetworkLockSign(ctx context.Context, args []string) error {
var nlDisableCmd = &ffcli.Command{
Name: "disable",
ShortUsage: "disable <disablement-secret>",
- ShortHelp: "Consumes a disablement secret to shut down network-lock across the tailnet",
+ ShortHelp: "Consumes a disablement secret to shut down tailnet-lock across the tailnet",
Exec: runNetworkLockDisable,
}
@@ -274,7 +351,7 @@ func runNetworkLockDisable(ctx context.Context, args []string) error {
var nlDisablementKDFCmd = &ffcli.Command{
Name: "disablement-kdf",
ShortUsage: "disablement-kdf <hex-encoded-disablement-secret>",
- ShortHelp: "Computes a disablement value from a disablement secret",
+ ShortHelp: "Computes a disablement value from a disablement secret (advanced users only)",
Exec: runNetworkLockDisablementKDF,
}
@@ -297,7 +374,7 @@ var nlLogArgs struct {
var nlLogCmd = &ffcli.Command{
Name: "log",
ShortUsage: "log [--limit N]",
- ShortHelp: "List changes applied to network-lock",
+ ShortHelp: "List changes applied to tailnet-lock",
Exec: runNetworkLockLog,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("lock log")
diff --git a/ipn/ipnlocal/network-lock.go b/ipn/ipnlocal/network-lock.go
index 780043bbb..8346ed7cf 100644
--- a/ipn/ipnlocal/network-lock.go
+++ b/ipn/ipnlocal/network-lock.go
@@ -403,7 +403,7 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
// needing signatures is returned as a response.
// The Finish RPC submits signatures for all these nodes, at which point
// Control has everything it needs to atomically enable network lock.
-func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byte) error {
+func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byte, supportDisablement []byte) error {
if err := b.CanSupportNetworkLock(); err != nil {
return err
}
@@ -471,7 +471,7 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byt
}
// Finalize enablement by transmitting signature for all nodes to Control.
- _, err = b.tkaInitFinish(ourNodeKey, sigs)
+ _, err = b.tkaInitFinish(ourNodeKey, sigs, supportDisablement)
return err
}
@@ -748,12 +748,13 @@ func (b *LocalBackend) tkaInitBegin(ourNodeKey key.NodePublic, aum tka.AUM) (*ta
return a, nil
}
-func (b *LocalBackend) tkaInitFinish(ourNodeKey key.NodePublic, nks map[tailcfg.NodeID]tkatype.MarshaledSignature) (*tailcfg.TKAInitFinishResponse, error) {
+func (b *LocalBackend) tkaInitFinish(ourNodeKey key.NodePublic, nks map[tailcfg.NodeID]tkatype.MarshaledSignature, supportDisablement []byte) (*tailcfg.TKAInitFinishResponse, error) {
var req bytes.Buffer
if err := json.NewEncoder(&req).Encode(tailcfg.TKAInitFinishRequest{
- Version: tailcfg.CurrentCapabilityVersion,
- NodeKey: ourNodeKey,
- Signatures: nks,
+ Version: tailcfg.CurrentCapabilityVersion,
+ NodeKey: ourNodeKey,
+ Signatures: nks,
+ SupportDisablement: supportDisablement,
}); err != nil {
return nil, fmt.Errorf("encoding request: %v", err)
}
diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go
index 93170ea3f..ec87b25cc 100644
--- a/ipn/localapi/localapi.go
+++ b/ipn/localapi/localapi.go
@@ -1161,8 +1161,9 @@ func (h *Handler) serveTKAInit(w http.ResponseWriter, r *http.Request) {
}
type initRequest struct {
- Keys []tka.Key
- DisablementValues [][]byte
+ Keys []tka.Key
+ DisablementValues [][]byte
+ SupportDisablement []byte
}
var req initRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -1170,7 +1171,7 @@ func (h *Handler) serveTKAInit(w http.ResponseWriter, r *http.Request) {
return
}
- if err := h.b.NetworkLockInit(req.Keys, req.DisablementValues); err != nil {
+ if err := h.b.NetworkLockInit(req.Keys, req.DisablementValues, req.SupportDisablement); err != nil {
http.Error(w, "initialization failed: "+err.Error(), http.StatusInternalServerError)
return
}
diff --git a/tailcfg/tka.go b/tailcfg/tka.go
index c1cec6a0f..6ce0b7cf5 100644
--- a/tailcfg/tka.go
+++ b/tailcfg/tka.go
@@ -69,6 +69,11 @@ type TKAInitFinishRequest struct {
// Signatures are serialized tka.NodeKeySignatures for all nodes
// in the tailnet.
Signatures map[NodeID]tkatype.MarshaledSignature
+
+ // SupportDisablement is a disablement secret for Tailscale support.
+ // This is only generated if --gen-disablement-for-support is specified
+ // in an invocation to 'tailscale lock init'.
+ SupportDisablement []byte `json:",omitempty"`
}
// TKAInitFinishResponse is the JSON response from a /tka/init/finish RPC.