mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
cmd/tailscale,ipn: improve UX of lock init command, cosmetic changes
Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
parent
e8cc78b1af
commit
5c8d2fa695
@ -787,14 +787,15 @@ func (lc *LocalClient) NetworkLockStatus(ctx context.Context) (*ipnstate.Network
|
|||||||
// NetworkLockInit initializes the tailnet key authority.
|
// NetworkLockInit initializes the tailnet key authority.
|
||||||
//
|
//
|
||||||
// TODO(tom): Plumb through disablement secrets.
|
// 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
|
var b bytes.Buffer
|
||||||
type initRequest struct {
|
type initRequest struct {
|
||||||
Keys []tka.Key
|
Keys []tka.Key
|
||||||
DisablementValues [][]byte
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -40,11 +41,44 @@
|
|||||||
Exec: runNetworkLockStatus,
|
Exec: runNetworkLockStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nlInitArgs struct {
|
||||||
|
numDisablements int
|
||||||
|
disablementForSupport bool
|
||||||
|
confirm bool
|
||||||
|
}
|
||||||
|
|
||||||
var nlInitCmd = &ffcli.Command{
|
var nlInitCmd = &ffcli.Command{
|
||||||
Name: "init",
|
Name: "init",
|
||||||
ShortUsage: "init <public-key>...",
|
ShortUsage: "init [--gen-disablement-for-support] --gen-disablements N <trusted-key>...",
|
||||||
ShortHelp: "Initialize the tailnet key authority",
|
ShortHelp: "Initialize tailnet lock",
|
||||||
Exec: runNetworkLockInit,
|
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 {
|
func runNetworkLockInit(ctx context.Context, args []string) error {
|
||||||
@ -62,12 +96,55 @@ func runNetworkLockInit(ctx context.Context, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
status, err := localClient.NetworkLockInit(ctx, keys, disablementValues)
|
fmt.Println("You are initializing tailnet lock with trust in the following keys:")
|
||||||
if err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Status: %+v\n\n", status)
|
fmt.Println("Initialization complete.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,18 +161,18 @@ func runNetworkLockStatus(ctx context.Context, args []string) error {
|
|||||||
return fixTailscaledConnectError(err)
|
return fixTailscaledConnectError(err)
|
||||||
}
|
}
|
||||||
if st.Enabled {
|
if st.Enabled {
|
||||||
fmt.Println("Network-lock is ENABLED.")
|
fmt.Println("Tailnet-lock is ENABLED.")
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Network-lock is NOT enabled.")
|
fmt.Println("Tailnet-lock is NOT enabled.")
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
if st.Enabled && st.NodeKey != nil {
|
if st.Enabled && st.NodeKey != nil {
|
||||||
if st.NodeKeySigned {
|
if st.NodeKeySigned {
|
||||||
fmt.Println("This node is trusted by network-lock.")
|
fmt.Println("This node is accessible under tailnet-lock.")
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("This node IS NOT trusted by network-lock, and action is required to establish connectivity.")
|
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 network-lock key:\n\ttailscale lock sign %v\n", st.NodeKey)
|
fmt.Printf("Run the following command on a node with a trusted key:\n\ttailscale lock sign %v\n", st.NodeKey)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
@ -105,12 +182,12 @@ func runNetworkLockStatus(ctx context.Context, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.Enabled && len(st.TrustedKeys) > 0 {
|
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 {
|
for _, k := range st.TrustedKeys {
|
||||||
key, err := k.Key.MarshalText()
|
key, err := k.Key.MarshalText()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -204,7 +281,7 @@ func runNetworkLockModify(ctx context.Context, addArgs, removeArgs []string) err
|
|||||||
return fixTailscaledConnectError(err)
|
return fixTailscaledConnectError(err)
|
||||||
}
|
}
|
||||||
if !st.Enabled {
|
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)
|
addKeys, _, err := parseNLArgs(addArgs, true, false)
|
||||||
@ -256,7 +333,7 @@ func runNetworkLockSign(ctx context.Context, args []string) error {
|
|||||||
var nlDisableCmd = &ffcli.Command{
|
var nlDisableCmd = &ffcli.Command{
|
||||||
Name: "disable",
|
Name: "disable",
|
||||||
ShortUsage: "disable <disablement-secret>",
|
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,
|
Exec: runNetworkLockDisable,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +351,7 @@ func runNetworkLockDisable(ctx context.Context, args []string) error {
|
|||||||
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>",
|
||||||
ShortHelp: "Computes a disablement value from a disablement secret",
|
ShortHelp: "Computes a disablement value from a disablement secret (advanced users only)",
|
||||||
Exec: runNetworkLockDisablementKDF,
|
Exec: runNetworkLockDisablementKDF,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +374,7 @@ func runNetworkLockDisablementKDF(ctx context.Context, args []string) error {
|
|||||||
var nlLogCmd = &ffcli.Command{
|
var nlLogCmd = &ffcli.Command{
|
||||||
Name: "log",
|
Name: "log",
|
||||||
ShortUsage: "log [--limit N]",
|
ShortUsage: "log [--limit N]",
|
||||||
ShortHelp: "List changes applied to network-lock",
|
ShortHelp: "List changes applied to tailnet-lock",
|
||||||
Exec: runNetworkLockLog,
|
Exec: runNetworkLockLog,
|
||||||
FlagSet: (func() *flag.FlagSet {
|
FlagSet: (func() *flag.FlagSet {
|
||||||
fs := newFlagSet("lock log")
|
fs := newFlagSet("lock log")
|
||||||
|
@ -403,7 +403,7 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
|
|||||||
// needing signatures is returned as a response.
|
// needing signatures is returned as a response.
|
||||||
// The Finish RPC submits signatures for all these nodes, at which point
|
// The Finish RPC submits signatures for all these nodes, at which point
|
||||||
// Control has everything it needs to atomically enable network lock.
|
// 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 {
|
if err := b.CanSupportNetworkLock(); err != nil {
|
||||||
return err
|
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.
|
// Finalize enablement by transmitting signature for all nodes to Control.
|
||||||
_, err = b.tkaInitFinish(ourNodeKey, sigs)
|
_, err = b.tkaInitFinish(ourNodeKey, sigs, supportDisablement)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,12 +748,13 @@ func (b *LocalBackend) tkaInitBegin(ourNodeKey key.NodePublic, aum tka.AUM) (*ta
|
|||||||
return a, nil
|
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
|
var req bytes.Buffer
|
||||||
if err := json.NewEncoder(&req).Encode(tailcfg.TKAInitFinishRequest{
|
if err := json.NewEncoder(&req).Encode(tailcfg.TKAInitFinishRequest{
|
||||||
Version: tailcfg.CurrentCapabilityVersion,
|
Version: tailcfg.CurrentCapabilityVersion,
|
||||||
NodeKey: ourNodeKey,
|
NodeKey: ourNodeKey,
|
||||||
Signatures: nks,
|
Signatures: nks,
|
||||||
|
SupportDisablement: supportDisablement,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, fmt.Errorf("encoding request: %v", err)
|
return nil, fmt.Errorf("encoding request: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1161,8 +1161,9 @@ func (h *Handler) serveTKAInit(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type initRequest struct {
|
type initRequest struct {
|
||||||
Keys []tka.Key
|
Keys []tka.Key
|
||||||
DisablementValues [][]byte
|
DisablementValues [][]byte
|
||||||
|
SupportDisablement []byte
|
||||||
}
|
}
|
||||||
var req initRequest
|
var req initRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
@ -1170,7 +1171,7 @@ type initRequest struct {
|
|||||||
return
|
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)
|
http.Error(w, "initialization failed: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,11 @@ type TKAInitFinishRequest struct {
|
|||||||
// Signatures are serialized tka.NodeKeySignatures for all nodes
|
// Signatures are serialized tka.NodeKeySignatures for all nodes
|
||||||
// in the tailnet.
|
// in the tailnet.
|
||||||
Signatures map[NodeID]tkatype.MarshaledSignature
|
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.
|
// TKAInitFinishResponse is the JSON response from a /tka/init/finish RPC.
|
||||||
|
Loading…
Reference in New Issue
Block a user