2023-01-27 13:37:20 -08:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
2022-08-11 10:43:09 -07:00
package cli
import (
2022-12-13 09:33:13 -08:00
"bytes"
2022-08-11 10:43:09 -07:00
"context"
2022-11-23 11:19:30 -08:00
"crypto/rand"
2022-11-04 12:12:51 -07:00
"encoding/hex"
2022-11-14 15:04:10 -08:00
"encoding/json"
2022-08-11 10:43:09 -07:00
"errors"
2022-11-14 15:04:10 -08:00
"flag"
2022-08-11 10:43:09 -07:00
"fmt"
2022-11-14 15:04:10 -08:00
"os"
2022-08-11 10:43:09 -07:00
"strconv"
"strings"
2022-11-14 15:04:10 -08:00
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
2022-08-11 10:43:09 -07:00
"github.com/peterbourgon/ff/v3/ffcli"
2022-11-14 15:04:10 -08:00
"tailscale.com/ipn/ipnstate"
2022-08-11 10:43:09 -07:00
"tailscale.com/tka"
"tailscale.com/types/key"
)
var netlockCmd = & ffcli . Command {
2022-09-15 13:51:23 -04:00
Name : "lock" ,
ShortUsage : "lock <sub-command> <arguments>" ,
2022-12-01 09:19:28 -08:00
ShortHelp : "Manage tailnet lock" ,
2022-12-04 22:55:57 -08:00
LongHelp : "Manage tailnet lock" ,
2022-09-15 13:51:23 -04:00
Subcommands : [ ] * ffcli . Command {
nlInitCmd ,
nlStatusCmd ,
nlAddCmd ,
nlRemoveCmd ,
2022-10-31 16:47:51 -07:00
nlSignCmd ,
2022-11-04 12:12:51 -07:00
nlDisableCmd ,
nlDisablementKDFCmd ,
2022-11-14 15:04:10 -08:00
nlLogCmd ,
2022-11-28 16:39:03 -08:00
nlLocalDisableCmd ,
2022-09-15 13:51:23 -04:00
} ,
Exec : runNetworkLockStatus ,
2022-08-11 10:43:09 -07:00
}
2022-11-23 11:19:30 -08:00
var nlInitArgs struct {
numDisablements int
disablementForSupport bool
confirm bool
}
2022-08-11 10:43:09 -07:00
var nlInitCmd = & ffcli . Command {
Name : "init" ,
2022-11-23 11:19:30 -08:00
ShortUsage : "init [--gen-disablement-for-support] --gen-disablements N <trusted-key>..." ,
ShortHelp : "Initialize tailnet lock" ,
LongHelp : strings . TrimSpace ( `
2022-12-01 09:19:28 -08:00
The ' tailscale lock init ' command initializes tailnet lock for the
entire tailnet . The tailnet lock keys specified are those initially
trusted to sign nodes or to make further changes to tailnet lock .
2022-11-23 11:19:30 -08:00
2022-12-01 09:19:28 -08:00
You can identify the tailnet lock key for a node you wish to trust by
running ' tailscale lock ' on that node , and copying the node ' s tailnet
lock key .
2022-11-23 11:19:30 -08:00
2022-12-01 09:19:28 -08:00
To disable tailnet lock , use the ' tailscale lock disable ' command
along with one of the disablement secrets .
2022-11-23 11:19:30 -08:00
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
} ) ( ) ,
2022-08-11 10:43:09 -07:00
}
func runNetworkLockInit ( ctx context . Context , args [ ] string ) error {
st , err := localClient . NetworkLockStatus ( ctx )
if err != nil {
return fixTailscaledConnectError ( err )
}
if st . Enabled {
2022-12-01 09:19:28 -08:00
return errors . New ( "tailnet lock is already enabled" )
2022-08-11 10:43:09 -07:00
}
2022-11-04 12:12:51 -07:00
// Parse initially-trusted keys & disablement values.
keys , disablementValues , err := parseNLArgs ( args , true , true )
2022-09-15 13:51:23 -04:00
if err != nil {
return err
2022-08-11 10:43:09 -07:00
}
2022-12-13 09:33:13 -08:00
// Common mistake: Not specifying the current node's key as one of the trusted keys.
foundSelfKey := false
for _ , k := range keys {
2023-01-03 09:39:55 -08:00
keyID , err := k . ID ( )
if err != nil {
return err
}
if bytes . Equal ( keyID , st . PublicKey . KeyID ( ) ) {
2022-12-13 09:33:13 -08:00
foundSelfKey = true
break
}
}
if ! foundSelfKey {
return errors . New ( "the tailnet lock key of the current node must be one of the trusted keys during initialization" )
}
2022-12-01 09:19:28 -08:00
fmt . Println ( "You are initializing tailnet lock with the following trusted signing keys:" )
2022-11-23 11:19:30 -08:00
for _ , k := range keys {
2022-11-30 12:30:44 -08:00
fmt . Printf ( " - tlpub:%x (%s key)\n" , k . Public , k . Kind . String ( ) )
2022-11-23 11:19:30 -08:00
}
fmt . Println ( )
if ! nlInitArgs . confirm {
fmt . Printf ( "%d disablement secrets will be generated.\n" , nlInitArgs . numDisablements )
if nlInitArgs . disablementForSupport {
2022-12-01 09:19:28 -08:00
fmt . Println ( "A disablement secret will be generated and transmitted to Tailscale support." )
2022-11-23 11:19:30 -08:00
}
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 ) )
2022-12-01 09:19:28 -08:00
fmt . Println ( "A disablement secret for Tailscale support has been generated and will be transmitted to Tailscale upon initialization." )
2022-11-23 11:19:30 -08:00
}
// 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 {
2022-08-11 10:43:09 -07:00
return err
}
2022-11-23 11:19:30 -08:00
fmt . Println ( "Initialization complete." )
2022-08-11 10:43:09 -07:00
return nil
}
2023-01-11 14:50:24 -08:00
var nlStatusArgs struct {
json bool
}
2022-08-11 10:43:09 -07:00
var nlStatusCmd = & ffcli . Command {
Name : "status" ,
ShortUsage : "status" ,
2022-12-09 10:24:21 -08:00
ShortHelp : "Outputs the state of tailnet lock" ,
LongHelp : "Outputs the state of tailnet lock" ,
2022-08-11 10:43:09 -07:00
Exec : runNetworkLockStatus ,
2023-01-11 14:50:24 -08:00
FlagSet : ( func ( ) * flag . FlagSet {
fs := newFlagSet ( "lock status" )
fs . BoolVar ( & nlStatusArgs . json , "json" , false , "output in JSON format (WARNING: format subject to change)" )
return fs
} ) ( ) ,
2022-08-11 10:43:09 -07:00
}
func runNetworkLockStatus ( ctx context . Context , args [ ] string ) error {
st , err := localClient . NetworkLockStatus ( ctx )
if err != nil {
return fixTailscaledConnectError ( err )
}
2023-01-11 14:50:24 -08:00
if nlStatusArgs . json {
enc := json . NewEncoder ( os . Stdout )
enc . SetIndent ( "" , " " )
return enc . Encode ( st )
}
2022-08-11 10:43:09 -07:00
if st . Enabled {
2022-12-01 09:19:28 -08:00
fmt . Println ( "Tailnet lock is ENABLED." )
2022-08-11 10:43:09 -07:00
} else {
2022-12-01 09:19:28 -08:00
fmt . Println ( "Tailnet lock is NOT enabled." )
2022-08-11 10:43:09 -07:00
}
2022-11-03 10:25:20 -07:00
fmt . Println ( )
2022-11-30 11:06:51 -08:00
if st . Enabled && st . NodeKey != nil && ! st . PublicKey . IsZero ( ) {
2022-11-03 10:25:20 -07:00
if st . NodeKeySigned {
2022-12-01 09:19:28 -08:00
fmt . Println ( "This node is accessible under tailnet lock." )
2022-11-03 10:25:20 -07:00
} else {
2022-11-23 11:19:30 -08:00
fmt . Println ( "This node is LOCKED OUT by tailnet-lock, and action is required to establish connectivity." )
2022-11-30 12:30:44 -08:00
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 ( ) )
2022-11-03 10:25:20 -07:00
}
fmt . Println ( )
}
2022-11-14 17:29:49 +05:00
if ! st . PublicKey . IsZero ( ) {
2022-11-30 12:30:44 -08:00
fmt . Printf ( "This node's tailnet-lock key: %s\n" , st . PublicKey . CLIString ( ) )
2022-11-14 17:29:49 +05:00
fmt . Println ( )
2022-08-11 10:43:09 -07:00
}
2022-11-03 10:25:20 -07:00
if st . Enabled && len ( st . TrustedKeys ) > 0 {
2022-12-01 09:19:28 -08:00
fmt . Println ( "Trusted signing keys:" )
2022-11-03 10:25:20 -07:00
for _ , k := range st . TrustedKeys {
var line strings . Builder
line . WriteString ( "\t" )
2022-11-30 12:30:44 -08:00
line . WriteString ( k . Key . CLIString ( ) )
2022-11-03 10:25:20 -07:00
line . WriteString ( "\t" )
line . WriteString ( fmt . Sprint ( k . Votes ) )
line . WriteString ( "\t" )
if k . Key == st . PublicKey {
2022-12-13 09:33:13 -08:00
line . WriteString ( "(self)" )
2022-11-03 10:25:20 -07:00
}
fmt . Println ( line . String ( ) )
}
}
2022-11-30 13:04:43 -08:00
if st . Enabled && len ( st . FilteredPeers ) > 0 {
fmt . Println ( )
2022-12-01 09:19:28 -08:00
fmt . Println ( "The following nodes are locked out by tailnet lock and cannot connect to other nodes:" )
2022-11-30 13:04:43 -08:00
for _ , p := range st . FilteredPeers {
var line strings . Builder
line . WriteString ( "\t" )
line . WriteString ( p . Name )
line . WriteString ( "\t" )
for i , addr := range p . TailscaleIPs {
line . WriteString ( addr . String ( ) )
if i < len ( p . TailscaleIPs ) - 1 {
line . WriteString ( ", " )
}
}
line . WriteString ( "\t" )
line . WriteString ( string ( p . StableID ) )
fmt . Println ( line . String ( ) )
}
}
2022-08-11 10:43:09 -07:00
return nil
}
2022-09-15 13:51:23 -04:00
var nlAddCmd = & ffcli . Command {
Name : "add" ,
ShortUsage : "add <public-key>..." ,
2022-12-01 09:19:28 -08:00
ShortHelp : "Adds one or more trusted signing keys to tailnet lock" ,
2022-12-04 22:55:57 -08:00
LongHelp : "Adds one or more trusted signing keys to tailnet lock" ,
2022-09-15 13:51:23 -04:00
Exec : func ( ctx context . Context , args [ ] string ) error {
return runNetworkLockModify ( ctx , args , nil )
} ,
}
2023-03-01 12:47:29 -08:00
var nlRemoveArgs struct {
resign bool
}
2022-09-15 13:51:23 -04:00
var nlRemoveCmd = & ffcli . Command {
Name : "remove" ,
2023-03-01 12:47:29 -08:00
ShortUsage : "remove [--re-sign=false] <public-key>..." ,
2022-12-01 09:19:28 -08:00
ShortHelp : "Removes one or more trusted signing keys from tailnet lock" ,
2022-12-04 22:55:57 -08:00
LongHelp : "Removes one or more trusted signing keys from tailnet lock" ,
2023-03-01 12:47:29 -08:00
Exec : runNetworkLockRemove ,
FlagSet : ( func ( ) * flag . FlagSet {
fs := newFlagSet ( "lock remove" )
fs . BoolVar ( & nlRemoveArgs . resign , "re-sign" , true , "resign signatures which would be invalidated by removal of trusted signing keys" )
return fs
} ) ( ) ,
}
func runNetworkLockRemove ( ctx context . Context , args [ ] string ) error {
removeKeys , _ , err := parseNLArgs ( args , true , false )
if err != nil {
return err
}
st , err := localClient . NetworkLockStatus ( ctx )
if err != nil {
return fixTailscaledConnectError ( err )
}
if ! st . Enabled {
return errors . New ( "tailnet lock is not enabled" )
}
if nlRemoveArgs . resign {
// Validate we are not removing trust in ourselves while resigning. This is because
// we resign with our own key, so the signatures would be immediately invalid.
for _ , k := range removeKeys {
kID , err := k . ID ( )
if err != nil {
return fmt . Errorf ( "computing KeyID for key %v: %w" , k , err )
}
if bytes . Equal ( st . PublicKey . KeyID ( ) , kID ) {
return errors . New ( "cannot remove local trusted signing key while resigning; run command on a different node or with --re-sign=false" )
}
}
// Resign affected signatures for each of the keys we are removing.
for _ , k := range removeKeys {
kID , _ := k . ID ( ) // err already checked above
sigs , err := localClient . NetworkLockAffectedSigs ( ctx , kID )
if err != nil {
return fmt . Errorf ( "affected sigs for key %X: %w" , kID , err )
}
for _ , sigBytes := range sigs {
var sig tka . NodeKeySignature
if err := sig . Unserialize ( sigBytes ) ; err != nil {
return fmt . Errorf ( "failed decoding signature: %w" , err )
}
var nodeKey key . NodePublic
if err := nodeKey . UnmarshalBinary ( sig . Pubkey ) ; err != nil {
return fmt . Errorf ( "failed decoding pubkey for signature: %w" , err )
}
// Safety: NetworkLockAffectedSigs() verifies all signatures before
// successfully returning.
rotationKey , _ := sig . UnverifiedWrappingPublic ( )
if err := localClient . NetworkLockSign ( ctx , nodeKey , [ ] byte ( rotationKey ) ) ; err != nil {
return fmt . Errorf ( "failed to sign %v: %w" , nodeKey , err )
}
}
}
}
return localClient . NetworkLockModify ( ctx , nil , removeKeys )
2022-09-15 13:51:23 -04:00
}
2022-11-04 12:12:51 -07:00
// parseNLArgs parses a slice of strings into slices of tka.Key & disablement
// values/secrets.
// The keys encoded in args should be specified using their key.NLPublic.MarshalText
// representation with an optional '?<votes>' suffix.
// Disablement values or secrets must be encoded in hex with a prefix of 'disablement:' or
// 'disablement-secret:'.
//
// If any element could not be parsed,
// a nil slice is returned along with an appropriate error.
func parseNLArgs ( args [ ] string , parseKeys , parseDisablements bool ) ( keys [ ] tka . Key , disablements [ ] [ ] byte , err error ) {
2022-09-15 13:51:23 -04:00
for i , a := range args {
2022-11-04 12:12:51 -07:00
if parseDisablements && ( strings . HasPrefix ( a , "disablement:" ) || strings . HasPrefix ( a , "disablement-secret:" ) ) {
b , err := hex . DecodeString ( a [ strings . Index ( a , ":" ) + 1 : ] )
if err != nil {
return nil , nil , fmt . Errorf ( "parsing disablement %d: %v" , i + 1 , err )
}
disablements = append ( disablements , b )
continue
}
if ! parseKeys {
return nil , nil , fmt . Errorf ( "parsing argument %d: expected value with \"disablement:\" or \"disablement-secret:\" prefix, got %q" , i + 1 , a )
}
2022-09-15 13:51:23 -04:00
var nlpk key . NLPublic
spl := strings . SplitN ( a , "?" , 2 )
if err := nlpk . UnmarshalText ( [ ] byte ( spl [ 0 ] ) ) ; err != nil {
2022-11-04 12:12:51 -07:00
return nil , nil , fmt . Errorf ( "parsing key %d: %v" , i + 1 , err )
2022-09-15 13:51:23 -04:00
}
k := tka . Key {
Kind : tka . Key25519 ,
Public : nlpk . Verifier ( ) ,
Votes : 1 ,
}
if len ( spl ) > 1 {
votes , err := strconv . Atoi ( spl [ 1 ] )
if err != nil {
2022-11-04 12:12:51 -07:00
return nil , nil , fmt . Errorf ( "parsing key %d votes: %v" , i + 1 , err )
2022-09-15 13:51:23 -04:00
}
k . Votes = uint ( votes )
}
keys = append ( keys , k )
}
2022-11-04 12:12:51 -07:00
return keys , disablements , nil
2022-09-15 13:51:23 -04:00
}
func runNetworkLockModify ( ctx context . Context , addArgs , removeArgs [ ] string ) error {
st , err := localClient . NetworkLockStatus ( ctx )
if err != nil {
return fixTailscaledConnectError ( err )
}
2022-11-03 10:25:20 -07:00
if ! st . Enabled {
2022-12-01 09:19:28 -08:00
return errors . New ( "tailnet lock is not enabled" )
2022-09-15 13:51:23 -04:00
}
2022-11-04 12:12:51 -07:00
addKeys , _ , err := parseNLArgs ( addArgs , true , false )
2022-09-15 13:51:23 -04:00
if err != nil {
return err
}
2022-11-04 12:12:51 -07:00
removeKeys , _ , err := parseNLArgs ( removeArgs , true , false )
2022-09-15 13:51:23 -04:00
if err != nil {
return err
}
2022-12-04 22:55:57 -08:00
if err := localClient . NetworkLockModify ( ctx , addKeys , removeKeys ) ; err != nil {
2022-09-15 13:51:23 -04:00
return err
}
return nil
}
2022-10-31 16:47:51 -07:00
var nlSignCmd = & ffcli . Command {
Name : "sign" ,
2022-11-04 12:12:51 -07:00
ShortUsage : "sign <node-key> [<rotation-key>]" ,
2022-12-01 09:19:28 -08:00
ShortHelp : "Signs a node key and transmits the signature to the coordination server" ,
2022-12-04 22:55:57 -08:00
LongHelp : "Signs a node key and transmits the signature to the coordination server" ,
2022-10-31 16:47:51 -07:00
Exec : runNetworkLockSign ,
}
func runNetworkLockSign ( ctx context . Context , args [ ] string ) error {
2022-11-04 12:12:51 -07:00
var (
nodeKey key . NodePublic
rotationKey key . NLPublic
)
if len ( args ) == 0 || len ( args ) > 2 {
return errors . New ( "usage: lock sign <node-key> [<rotation-key>]" )
}
if err := nodeKey . UnmarshalText ( [ ] byte ( args [ 0 ] ) ) ; err != nil {
return fmt . Errorf ( "decoding node-key: %w" , err )
}
if len ( args ) > 1 {
if err := rotationKey . UnmarshalText ( [ ] byte ( args [ 1 ] ) ) ; err != nil {
return fmt . Errorf ( "decoding rotation-key: %w" , err )
2022-10-31 16:47:51 -07:00
}
2022-11-04 12:12:51 -07:00
}
2022-10-31 16:47:51 -07:00
2022-11-04 12:12:51 -07:00
return localClient . NetworkLockSign ( ctx , nodeKey , [ ] byte ( rotationKey . Verifier ( ) ) )
}
var nlDisableCmd = & ffcli . Command {
Name : "disable" ,
ShortUsage : "disable <disablement-secret>" ,
2022-12-01 09:19:28 -08:00
ShortHelp : "Consumes a disablement secret to shut down tailnet lock for the tailnet" ,
LongHelp : strings . TrimSpace ( `
The ' tailscale lock disable ' command uses the specified disablement
secret to disable tailnet lock .
If tailnet lock is re - enabled , new disablement secrets can be generated .
Once this secret is used , it has been distributed
to all nodes in the tailnet and should be considered public .
` ) ,
2022-12-04 22:55:57 -08:00
Exec : runNetworkLockDisable ,
2022-11-04 12:12:51 -07:00
}
func runNetworkLockDisable ( ctx context . Context , args [ ] string ) error {
_ , secrets , err := parseNLArgs ( args , false , true )
if err != nil {
return err
}
if len ( secrets ) != 1 {
return errors . New ( "usage: lock disable <disablement-secret>" )
2022-10-31 16:47:51 -07:00
}
2022-11-04 12:12:51 -07:00
return localClient . NetworkLockDisable ( ctx , secrets [ 0 ] )
}
2022-11-28 16:39:03 -08:00
var nlLocalDisableCmd = & ffcli . Command {
Name : "local-disable" ,
ShortUsage : "local-disable" ,
2022-12-01 09:19:28 -08:00
ShortHelp : "Disables tailnet lock for this node only" ,
LongHelp : strings . TrimSpace ( `
The ' tailscale lock local - disable ' command disables tailnet lock for only
the current node .
If the current node is locked out , this does not mean that it can initiate
connections in a tailnet with tailnet lock enabled . Rather , this means
that the current node will accept traffic from other nodes in the tailnet
that are locked out .
` ) ,
2022-12-04 22:55:57 -08:00
Exec : runNetworkLockLocalDisable ,
2022-11-28 16:39:03 -08:00
}
func runNetworkLockLocalDisable ( ctx context . Context , args [ ] string ) error {
return localClient . NetworkLockForceLocalDisable ( ctx )
}
2022-11-04 12:12:51 -07:00
var nlDisablementKDFCmd = & ffcli . Command {
Name : "disablement-kdf" ,
ShortUsage : "disablement-kdf <hex-encoded-disablement-secret>" ,
2022-11-23 11:19:30 -08:00
ShortHelp : "Computes a disablement value from a disablement secret (advanced users only)" ,
2022-12-04 22:55:57 -08:00
LongHelp : "Computes a disablement value from a disablement secret (advanced users only)" ,
2022-11-04 12:12:51 -07:00
Exec : runNetworkLockDisablementKDF ,
}
func runNetworkLockDisablementKDF ( ctx context . Context , args [ ] string ) error {
if len ( args ) != 1 {
return errors . New ( "usage: lock disablement-kdf <hex-encoded-disablement-secret>" )
}
secret , err := hex . DecodeString ( args [ 0 ] )
if err != nil {
return err
}
fmt . Printf ( "disablement:%x\n" , tka . DisablementKDF ( secret ) )
return nil
2022-10-31 16:47:51 -07:00
}
2022-11-14 15:04:10 -08:00
var nlLogArgs struct {
limit int
2023-01-11 14:50:24 -08:00
json bool
2022-11-14 15:04:10 -08:00
}
var nlLogCmd = & ffcli . Command {
Name : "log" ,
ShortUsage : "log [--limit N]" ,
2022-12-01 09:19:28 -08:00
ShortHelp : "List changes applied to tailnet lock" ,
2022-12-04 22:55:57 -08:00
LongHelp : "List changes applied to tailnet lock" ,
2022-11-14 15:04:10 -08:00
Exec : runNetworkLockLog ,
FlagSet : ( func ( ) * flag . FlagSet {
fs := newFlagSet ( "lock log" )
fs . IntVar ( & nlLogArgs . limit , "limit" , 50 , "max number of updates to list" )
2023-01-11 14:50:24 -08:00
fs . BoolVar ( & nlLogArgs . json , "json" , false , "output in JSON format (WARNING: format subject to change)" )
2022-11-14 15:04:10 -08:00
return fs
} ) ( ) ,
}
func nlDescribeUpdate ( update ipnstate . NetworkLockUpdate , color bool ) ( string , error ) {
terminalYellow := ""
terminalClear := ""
if color {
terminalYellow = "\x1b[33m"
terminalClear = "\x1b[0m"
}
var stanza strings . Builder
printKey := func ( key * tka . Key , prefix string ) {
fmt . Fprintf ( & stanza , "%sType: %s\n" , prefix , key . Kind . String ( ) )
2023-01-03 09:39:55 -08:00
if keyID , err := key . ID ( ) ; err == nil {
fmt . Fprintf ( & stanza , "%sKeyID: %x\n" , prefix , keyID )
} else {
// Older versions of the client shouldn't explode when they encounter an
// unknown key type.
fmt . Fprintf ( & stanza , "%sKeyID: <Error: %v>\n" , prefix , err )
}
2022-11-14 15:04:10 -08:00
if key . Meta != nil {
fmt . Fprintf ( & stanza , "%sMetadata: %+v\n" , prefix , key . Meta )
}
}
var aum tka . AUM
if err := aum . Unserialize ( update . Raw ) ; err != nil {
return "" , fmt . Errorf ( "decoding: %w" , err )
}
fmt . Fprintf ( & stanza , "%supdate %x (%s)%s\n" , terminalYellow , update . Hash , update . Change , terminalClear )
switch update . Change {
case tka . AUMAddKey . String ( ) :
printKey ( aum . Key , "" )
case tka . AUMRemoveKey . String ( ) :
fmt . Fprintf ( & stanza , "KeyID: %x\n" , aum . KeyID )
case tka . AUMUpdateKey . String ( ) :
fmt . Fprintf ( & stanza , "KeyID: %x\n" , aum . KeyID )
if aum . Votes != nil {
fmt . Fprintf ( & stanza , "Votes: %d\n" , aum . Votes )
}
if aum . Meta != nil {
fmt . Fprintf ( & stanza , "Metadata: %+v\n" , aum . Meta )
}
case tka . AUMCheckpoint . String ( ) :
fmt . Fprintln ( & stanza , "Disablement values:" )
for _ , v := range aum . State . DisablementSecrets {
fmt . Fprintf ( & stanza , " - %x\n" , v )
}
fmt . Fprintln ( & stanza , "Keys:" )
for _ , k := range aum . State . Keys {
printKey ( & k , " " )
}
default :
// Print a JSON encoding of the AUM as a fallback.
e := json . NewEncoder ( & stanza )
e . SetIndent ( "" , "\t" )
if err := e . Encode ( aum ) ; err != nil {
return "" , err
}
stanza . WriteRune ( '\n' )
}
return stanza . String ( ) , nil
}
func runNetworkLockLog ( ctx context . Context , args [ ] string ) error {
updates , err := localClient . NetworkLockLog ( ctx , nlLogArgs . limit )
if err != nil {
return fixTailscaledConnectError ( err )
}
2023-01-11 14:50:24 -08:00
if nlLogArgs . json {
enc := json . NewEncoder ( os . Stdout )
enc . SetIndent ( "" , " " )
return enc . Encode ( updates )
}
2022-11-14 15:04:10 -08:00
useColor := isatty . IsTerminal ( os . Stdout . Fd ( ) )
stdOut := colorable . NewColorableStdout ( )
for _ , update := range updates {
stanza , err := nlDescribeUpdate ( update , useColor )
if err != nil {
return err
}
fmt . Fprintln ( stdOut , stanza )
}
return nil
}