cmd/tailscale/cli: stabilise the output of tailscale lock status --json
This patch stabilises the JSON output, and improves it in the following
ways:
* The AUM hash in Head uses the base32-encoded form of an AUM hash,
consistent with how it's presented elsewhere
* TrustedKeys are the same format as the keys as `tailnet lock log --json`
* SigKind, Pubkey and KeyID are all presented consistently with other
JSON output in NodeKeySignature
* FilteredPeers don't have a NodeKeySignature, because it will always
be empty
For reference, here's the JSON output from the CLI prior to this change:
```json
{
"Enabled": true,
"Head": [
196,
69,
63,
243,
213,
133,
123,
46,
183,
203,
143,
34,
184,
85,
80,
1,
221,
92,
49,
213,
93,
106,
5,
206,
176,
250,
58,
165,
155,
136,
11,
13
],
"PublicKey": "nlpub:0f99af5c02216193963ce9304bb4ca418846eddebe237f37a6de1c59097ed0b8",
"NodeKey": "nodekey:8abfe98b38151748919f6e346ad16436201c3ecd453b01e9d6d3a38e1826000d",
"NodeKeySigned": true,
"NodeKeySignature": {
"SigKind": 1,
"Pubkey": "bnCKv+mLOBUXSJGfbjRq0WQ2IBw+zUU7AenW06OOGCYADQ==",
"KeyID": "D5mvXAIhYZOWPOkwS7TKQYhG7d6+I383pt4cWQl+0Lg=",
"Signature": "4DPW4v6MyLLwQ8AMDm27BVDGABjeC9gg1EfqRdKgzVXi/mJDwY9PTAoX0+0WTRs5SUksWjY0u1CLxq5xgjFGBA==",
"Nested": null,
"WrappingPubkey": "D5mvXAIhYZOWPOkwS7TKQYhG7d6+I383pt4cWQl+0Lg="
},
"TrustedKeys": [
{
"Key": "nlpub:0f99af5c02216193963ce9304bb4ca418846eddebe237f37a6de1c59097ed0b8",
"Metadata": null,
"Votes": 1
},
{
"Key": "nlpub:de2254c040e728140d92bc967d51284e9daea103a28a97a215694c5bda2128b8",
"Metadata": null,
"Votes": 1
}
],
"VisiblePeers": [
{
"Name": "signing2.taila62b.unknown.c.ts.net.",
"ID": 7525920332164264,
"StableID": "nRX6TbAWm121DEVEL",
"TailscaleIPs": [
"100.110.67.20",
"fd7a:115c:a1e0::9c01:4314"
],
"NodeKey": "nodekey:10bf4a5c168051d700a29123cd81568377849da458abef4b328794ca9cae4313",
"NodeKeySignature": {
"SigKind": 1,
"Pubkey": "bnAQv0pcFoBR1wCikSPNgVaDd4SdpFir70syh5TKnK5DEw==",
"KeyID": "D5mvXAIhYZOWPOkwS7TKQYhG7d6+I383pt4cWQl+0Lg=",
"Signature": "h9fhwHiNdkTqOGVQNdW6AVFoio6MFaFobPiK9ydywgmtYxcExJ38b76Tabdc56aNLxf8IfCaRw2VYPcQG2J/AA==",
"Nested": null,
"WrappingPubkey": "3iJUwEDnKBQNkryWfVEoTp2uoQOiipeiFWlMW9ohKLg="
}
}
],
"FilteredPeers": [
{
"Name": "node3.taila62b.unknown.c.ts.net.",
"ID": 5200614049042386,
"StableID": "n3jAr7KNch11DEVEL",
"TailscaleIPs": [
"100.95.29.124",
"fd7a:115c:a1e0::f901:1d7c"
],
"NodeKey": "nodekey:454d2c8602c10574c5ec3a6790f159714802012b7b8bb8d2ab47d637f9df1d7b",
"NodeKeySignature": {
"SigKind": 0,
"Pubkey": null,
"KeyID": null,
"Signature": null,
"Nested": null,
"WrappingPubkey": null
}
}
],
"StateID": 16885615198276932820
}
```
Updates https://github.com/tailscale/corp/issues/22355
Updates https://github.com/tailscale/tailscale/issues/17619
Signed-off-by: Alex Chan <alexc@tailscale.com>
Change-Id: I65b58ff4520033e6b70fc3b1ba7fc91c1f70a960
This commit is contained in:
+14
-12
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_tailnetlock
|
||||
|
||||
package jsonoutput
|
||||
|
||||
import (
|
||||
@@ -14,7 +16,7 @@ import (
|
||||
"tailscale.com/tka"
|
||||
)
|
||||
|
||||
// PrintNetworkLockJSONV1 prints the stored TKA state as a JSON object to the CLI,
|
||||
// PrintNetworkLockLogJSONV1 prints the stored TKA state as a JSON object to the CLI,
|
||||
// in a stable "v1" format.
|
||||
//
|
||||
// This format includes:
|
||||
@@ -22,7 +24,7 @@ import (
|
||||
// - the AUM hash as a base32-encoded string
|
||||
// - the raw AUM as base64-encoded bytes
|
||||
// - the expanded AUM, which prints named fields for consumption by other tools
|
||||
func PrintNetworkLockJSONV1(out io.Writer, updates []ipnstate.NetworkLockUpdate) error {
|
||||
func PrintNetworkLockLogJSONV1(out io.Writer, updates []ipnstate.NetworkLockUpdate) error {
|
||||
messages := make([]logMessageV1, len(updates))
|
||||
|
||||
for i, update := range updates {
|
||||
@@ -64,7 +66,7 @@ func toLogMessageV1(aum tka.AUM, update ipnstate.NetworkLockUpdate) logMessageV1
|
||||
expandedAUM.PrevAUMHash = aum.PrevAUMHash.String()
|
||||
}
|
||||
if key := aum.Key; key != nil {
|
||||
expandedAUM.Key = toExpandedKeyV1(key)
|
||||
expandedAUM.Key = toTKAKeyV1(key)
|
||||
}
|
||||
if keyID := aum.KeyID; keyID != nil {
|
||||
expandedAUM.KeyID = fmt.Sprintf("tlpub:%x", keyID)
|
||||
@@ -78,7 +80,7 @@ func toLogMessageV1(aum tka.AUM, update ipnstate.NetworkLockUpdate) logMessageV1
|
||||
expandedState.DisablementSecrets = append(expandedState.DisablementSecrets, fmt.Sprintf("%x", secret))
|
||||
}
|
||||
for _, key := range state.Keys {
|
||||
expandedState.Keys = append(expandedState.Keys, toExpandedKeyV1(&key))
|
||||
expandedState.Keys = append(expandedState.Keys, toTKAKeyV1(&key))
|
||||
}
|
||||
expandedState.StateID1 = state.StateID1
|
||||
expandedState.StateID2 = state.StateID2
|
||||
@@ -102,10 +104,10 @@ func toLogMessageV1(aum tka.AUM, update ipnstate.NetworkLockUpdate) logMessageV1
|
||||
}
|
||||
}
|
||||
|
||||
// toExpandedKeyV1 converts a [tka.Key] to the JSON output returned
|
||||
// toTKAKeyV1 converts a [tka.Key] to the JSON output returned
|
||||
// by the CLI.
|
||||
func toExpandedKeyV1(key *tka.Key) expandedKeyV1 {
|
||||
return expandedKeyV1{
|
||||
func toTKAKeyV1(key *tka.Key) tkaKeyV1 {
|
||||
return tkaKeyV1{
|
||||
Kind: key.Kind.String(),
|
||||
Votes: key.Votes,
|
||||
Public: fmt.Sprintf("tlpub:%x", key.Public),
|
||||
@@ -137,7 +139,7 @@ type expandedAUMV1 struct {
|
||||
|
||||
// Key encodes a public key to be added to the key authority.
|
||||
// This field is used for AddKey AUMs.
|
||||
Key expandedKeyV1 `json:"Key,omitzero"`
|
||||
Key tkaKeyV1 `json:"Key,omitzero"`
|
||||
|
||||
// KeyID references a public key which is part of the key authority.
|
||||
// This field is used for RemoveKey and UpdateKey AUMs.
|
||||
@@ -156,10 +158,10 @@ type expandedAUMV1 struct {
|
||||
Signatures []expandedSignatureV1 `json:"Signatures,omitzero"`
|
||||
}
|
||||
|
||||
// expandedAUMV1 is the expanded version of a [tka.Key], which describes
|
||||
// tkaKeyV1 is the expanded version of a [tka.Key], which describes
|
||||
// the public components of a key known to network-lock.
|
||||
type expandedKeyV1 struct {
|
||||
Kind string
|
||||
type tkaKeyV1 struct {
|
||||
Kind string `json:"Kind,omitzero"`
|
||||
|
||||
// Votes describes the weight applied to signatures using this key.
|
||||
Votes uint
|
||||
@@ -186,7 +188,7 @@ type expandedStateV1 struct {
|
||||
//
|
||||
// 1. The signing nodes currently trusted by the TKA.
|
||||
// 2. Ephemeral keys that were used to generate pre-signed auth keys.
|
||||
Keys []expandedKeyV1
|
||||
Keys []tkaKeyV1
|
||||
|
||||
// StateID's are nonce's, generated on enablement and fixed for
|
||||
// the lifetime of the Tailnet Key Authority.
|
||||
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_tailnetlock
|
||||
|
||||
package jsonoutput
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
jsonv1 "encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/tka"
|
||||
)
|
||||
|
||||
// PrintNetworkLockStatusJSONV1 prints the current Tailnet Lock status
|
||||
// as a JSON object to the CLI, in a stable "v1" format.
|
||||
func PrintNetworkLockStatusJSONV1(out io.Writer, status *ipnstate.NetworkLockStatus) error {
|
||||
responseEnvelope := ResponseEnvelope{
|
||||
SchemaVersion: "1",
|
||||
}
|
||||
|
||||
var result any
|
||||
if status.Enabled {
|
||||
result = struct {
|
||||
ResponseEnvelope
|
||||
tailnetLockEnabledStatusV1
|
||||
}{
|
||||
ResponseEnvelope: responseEnvelope,
|
||||
tailnetLockEnabledStatusV1: toTailnetLockEnabledStatusV1(status),
|
||||
}
|
||||
} else {
|
||||
result = struct {
|
||||
ResponseEnvelope
|
||||
tailnetLockDisabledStatusV1
|
||||
}{
|
||||
ResponseEnvelope: responseEnvelope,
|
||||
tailnetLockDisabledStatusV1: toTailnetLockDisabledStatusV1(status),
|
||||
}
|
||||
}
|
||||
|
||||
enc := jsonv1.NewEncoder(out)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(result)
|
||||
}
|
||||
|
||||
func toTailnetLockDisabledStatusV1(status *ipnstate.NetworkLockStatus) tailnetLockDisabledStatusV1 {
|
||||
out := tailnetLockDisabledStatusV1{
|
||||
tailnetLockStatusV1Base: tailnetLockStatusV1Base{
|
||||
Enabled: status.Enabled,
|
||||
},
|
||||
}
|
||||
if !status.PublicKey.IsZero() {
|
||||
out.PublicKey = status.PublicKey.CLIString()
|
||||
}
|
||||
if nk := status.NodeKey; nk != nil {
|
||||
out.NodeKey = nk.String()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toTailnetLockEnabledStatusV1(status *ipnstate.NetworkLockStatus) tailnetLockEnabledStatusV1 {
|
||||
out := tailnetLockEnabledStatusV1{
|
||||
tailnetLockStatusV1Base: tailnetLockStatusV1Base{
|
||||
Enabled: status.Enabled,
|
||||
},
|
||||
}
|
||||
|
||||
if status.Head != nil {
|
||||
var head tka.AUMHash
|
||||
h := status.Head
|
||||
copy(head[:], h[:])
|
||||
out.Head = head.String()
|
||||
}
|
||||
if !status.PublicKey.IsZero() {
|
||||
out.PublicKey = status.PublicKey.CLIString()
|
||||
}
|
||||
if nk := status.NodeKey; nk != nil {
|
||||
out.NodeKey = nk.String()
|
||||
}
|
||||
out.NodeKeySigned = status.NodeKeySigned
|
||||
if sig := status.NodeKeySignature; sig != nil {
|
||||
out.NodeKeySignature = toTKANodeKeySignatureV1(sig)
|
||||
}
|
||||
for _, key := range status.TrustedKeys {
|
||||
out.TrustedKeys = append(out.TrustedKeys, ipnTKAKeytoTKAKeyV1(&key))
|
||||
}
|
||||
for _, vp := range status.VisiblePeers {
|
||||
out.VisiblePeers = append(out.VisiblePeers, tkaTrustedPeerV1{
|
||||
tkaPeerV1: toTKAPeerV1(vp),
|
||||
NodeKeySignature: toTKANodeKeySignatureV1(&vp.NodeKeySignature),
|
||||
})
|
||||
}
|
||||
for _, fp := range status.FilteredPeers {
|
||||
out.FilteredPeers = append(out.FilteredPeers, toTKAPeerV1(fp))
|
||||
}
|
||||
out.StateID = status.StateID
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// toTKAKeyV1 converts an [ipnstate.TKAKey] to the JSON output returned
|
||||
// by the CLI.
|
||||
func ipnTKAKeytoTKAKeyV1(key *ipnstate.TKAKey) tkaKeyV1 {
|
||||
return tkaKeyV1{
|
||||
Kind: key.Kind,
|
||||
Votes: key.Votes,
|
||||
Public: key.Key.CLIString(),
|
||||
Meta: key.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
type tailnetLockStatusV1Base struct {
|
||||
// Enabled is true if Tailnet Lock is enabled.
|
||||
Enabled bool
|
||||
|
||||
// PublicKey describes the node's network-lock public key.
|
||||
PublicKey string `json:"PublicKey,omitzero"`
|
||||
|
||||
// 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 string `json:"NodeKey,omitzero"`
|
||||
}
|
||||
|
||||
// tailnetLockDisabledStatusV1 is the JSON representation of the Tailnet Lock status
|
||||
// when Tailnet Lock is disabled.
|
||||
type tailnetLockDisabledStatusV1 struct {
|
||||
tailnetLockStatusV1Base
|
||||
}
|
||||
|
||||
// tailnetLockEnabledStatusV1 is the JSON representation of the Tailnet Lock status.
|
||||
type tailnetLockEnabledStatusV1 struct {
|
||||
tailnetLockStatusV1Base
|
||||
|
||||
// Head describes the AUM hash of the leaf AUM.
|
||||
Head string `json:"Head,omitzero"`
|
||||
|
||||
// NodeKeySigned is true if our node is authorized by Tailnet Lock.
|
||||
NodeKeySigned bool
|
||||
|
||||
// NodeKeySignature is the current signature of this node's key.
|
||||
NodeKeySignature *tkaNodeKeySignatureV1
|
||||
|
||||
// TrustedKeys describes the keys currently trusted to make changes
|
||||
// to network-lock.
|
||||
TrustedKeys []tkaKeyV1
|
||||
|
||||
// VisiblePeers describes peers which are visible in the netmap that
|
||||
// have valid Tailnet Lock signatures signatures.
|
||||
VisiblePeers []tkaTrustedPeerV1
|
||||
|
||||
// FilteredPeers describes peers which were removed from the netmap
|
||||
// (i.e. no connectivity) because they failed Tailnet Lock
|
||||
// checks.
|
||||
FilteredPeers []tkaPeerV1
|
||||
|
||||
// StateID is a nonce associated with the Tailnet Lock authority,
|
||||
// generated upon enablement. This field is empty if Tailnet Lock
|
||||
// is disabled.
|
||||
StateID uint64 `json:"State,omitzero"`
|
||||
}
|
||||
|
||||
// tkaPeerV1 is the JSON representation of an [ipnstate.TKAPeer], which describes
|
||||
// a peer and its Tailnet Lock details.
|
||||
type tkaPeerV1 struct {
|
||||
// Stable ID, i.e. [tailcfg.StableNodeID]
|
||||
ID string
|
||||
|
||||
// DNS name
|
||||
DNSName string
|
||||
|
||||
// Tailscale IP(s) assigned to this node
|
||||
TailscaleIPs []string
|
||||
|
||||
// The node's public key
|
||||
NodeKey string
|
||||
}
|
||||
|
||||
// tkaPeerV1 is the JSON representation of a trusted [ipnstate.TKAPeer], which
|
||||
// has a node key signature.
|
||||
type tkaTrustedPeerV1 struct {
|
||||
tkaPeerV1
|
||||
|
||||
// The node's key signature
|
||||
NodeKeySignature *tkaNodeKeySignatureV1 `json:"NodeKeySignature,omitzero"`
|
||||
}
|
||||
|
||||
func toTKAPeerV1(peer *ipnstate.TKAPeer) tkaPeerV1 {
|
||||
out := tkaPeerV1{
|
||||
DNSName: peer.Name,
|
||||
ID: string(peer.StableID),
|
||||
}
|
||||
for _, ip := range peer.TailscaleIPs {
|
||||
out.TailscaleIPs = append(out.TailscaleIPs, ip.String())
|
||||
}
|
||||
out.NodeKey = peer.NodeKey.String()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// tkaNodeKeySignatureV1 is the JSON representation of a [tka.NodeKeySignature],
|
||||
// which describes a signature that authorizes a specific node key.
|
||||
type tkaNodeKeySignatureV1 struct {
|
||||
// SigKind identifies the variety of signature.
|
||||
SigKind string
|
||||
|
||||
// PublicKey identifies the key.NodePublic which is being authorized.
|
||||
// SigCredential signatures do not use this field.
|
||||
PublicKey string `json:"PublicKey,omitzero"`
|
||||
|
||||
// KeyID identifies which key in the tailnet key authority should
|
||||
// be used to verify this signature. Only set for SigDirect and
|
||||
// SigCredential signature kinds.
|
||||
KeyID string `json:"KeyID,omitzero"`
|
||||
|
||||
// Signature is the packed (R, S) ed25519 signature over all other
|
||||
// fields of the structure.
|
||||
Signature string
|
||||
|
||||
// Nested describes a NodeKeySignature which authorizes the node-key
|
||||
// used as Pubkey. Only used for SigRotation signatures.
|
||||
Nested *tkaNodeKeySignatureV1 `json:"Nested,omitzero"`
|
||||
|
||||
// WrappingPubkey specifies the ed25519 public key which must be used
|
||||
// to sign a Signature which embeds this one.
|
||||
WrappingPublicKey string `json:"WrappingPublicKey,omitzero"`
|
||||
}
|
||||
|
||||
func toTKANodeKeySignatureV1(sig *tka.NodeKeySignature) *tkaNodeKeySignatureV1 {
|
||||
out := tkaNodeKeySignatureV1{
|
||||
SigKind: sig.SigKind.String(),
|
||||
}
|
||||
if len(sig.Pubkey) > 0 {
|
||||
out.PublicKey = fmt.Sprintf("tlpub:%x", sig.Pubkey)
|
||||
}
|
||||
if len(sig.KeyID) > 0 {
|
||||
out.KeyID = fmt.Sprintf("tlpub:%x", sig.KeyID)
|
||||
}
|
||||
out.Signature = base64.URLEncoding.EncodeToString(sig.Signature)
|
||||
if sig.Nested != nil {
|
||||
out.Nested = toTKANodeKeySignatureV1(sig.Nested)
|
||||
}
|
||||
if len(sig.WrappingPubkey) > 0 {
|
||||
out.WrappingPublicKey = fmt.Sprintf("tlpub:%x", sig.WrappingPubkey)
|
||||
}
|
||||
return &out
|
||||
}
|
||||
@@ -195,7 +195,7 @@ func runNetworkLockInit(ctx context.Context, args []string) error {
|
||||
}
|
||||
|
||||
var nlStatusArgs struct {
|
||||
json bool
|
||||
json jsonoutput.JSONSchemaVersion
|
||||
}
|
||||
|
||||
var nlStatusCmd = &ffcli.Command{
|
||||
@@ -205,7 +205,7 @@ var nlStatusCmd = &ffcli.Command{
|
||||
Exec: runNetworkLockStatus,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("lock status")
|
||||
fs.BoolVar(&nlStatusArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
|
||||
fs.Var(&nlStatusArgs.json, "json", "output in JSON format")
|
||||
return fs
|
||||
})(),
|
||||
}
|
||||
@@ -220,10 +220,12 @@ func runNetworkLockStatus(ctx context.Context, args []string) error {
|
||||
return fixTailscaledConnectError(err)
|
||||
}
|
||||
|
||||
if nlStatusArgs.json {
|
||||
enc := jsonv1.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(st)
|
||||
if nlStatusArgs.json.IsSet {
|
||||
if nlStatusArgs.json.Value == 1 {
|
||||
return jsonoutput.PrintNetworkLockStatusJSONV1(os.Stdout, st)
|
||||
} else {
|
||||
return fmt.Errorf("unrecognised version: %q", nlStatusArgs.json.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if st.Enabled {
|
||||
@@ -713,7 +715,7 @@ func runNetworkLockLog(ctx context.Context, args []string) error {
|
||||
func printNetworkLockLog(updates []ipnstate.NetworkLockUpdate, out io.Writer, jsonSchema jsonoutput.JSONSchemaVersion, useColor bool) error {
|
||||
if jsonSchema.IsSet {
|
||||
if jsonSchema.Value == 1 {
|
||||
return jsonoutput.PrintNetworkLockJSONV1(out, updates)
|
||||
return jsonoutput.PrintNetworkLockLogJSONV1(out, updates)
|
||||
} else {
|
||||
return fmt.Errorf("unrecognised version: %q", jsonSchema.Value)
|
||||
}
|
||||
|
||||
@@ -5,12 +5,16 @@ package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"go4.org/mem"
|
||||
"tailscale.com/cmd/tailscale/cli/jsonoutput"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/tkatype"
|
||||
)
|
||||
|
||||
@@ -183,7 +187,6 @@ KeyID: tlpub:0202
|
||||
|
||||
t.Run("json-1", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Logf("BOOM")
|
||||
|
||||
var outBuf bytes.Buffer
|
||||
json := jsonoutput.JSONSchemaVersion{
|
||||
@@ -195,10 +198,172 @@ KeyID: tlpub:0202
|
||||
printNetworkLockLog(updates, &outBuf, json, useColor)
|
||||
|
||||
want := jsonV1
|
||||
t.Logf("%s", outBuf.String())
|
||||
|
||||
if diff := cmp.Diff(outBuf.String(), want); diff != "" {
|
||||
t.Fatalf("wrong output (-got, +want):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNetworkLockStatusOutput(t *testing.T) {
|
||||
aum := tka.AUM{
|
||||
MessageKind: tka.AUMNoOp,
|
||||
}
|
||||
h := aum.Hash()
|
||||
head := [32]byte(h[:])
|
||||
|
||||
nodeKey1 := key.NodePublicFromRaw32(mem.B(bytes.Repeat([]byte{1}, 32)))
|
||||
nodeKey2 := key.NodePublicFromRaw32(mem.B(bytes.Repeat([]byte{2}, 32)))
|
||||
nodeKey3 := key.NodePublicFromRaw32(mem.B(bytes.Repeat([]byte{3}, 32)))
|
||||
|
||||
nlPub := key.NLPublicFromEd25519Unsafe(bytes.Repeat([]byte{4}, 32))
|
||||
|
||||
trustedNlPub := key.NLPublicFromEd25519Unsafe(bytes.Repeat([]byte{5}, 32))
|
||||
|
||||
tailnetIPv4_A, tailnetIPv6_A := netip.MustParseAddr("100.99.99.99"), netip.MustParseAddr("fd7a:115c:a1e0::701:b62a")
|
||||
tailnetIPv4_B, tailnetIPv6_B := netip.MustParseAddr("100.88.88.88"), netip.MustParseAddr("fd7a:115c:a1e0::4101:512f")
|
||||
|
||||
t.Run("json-1", func(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
Name string
|
||||
Status ipnstate.NetworkLockStatus
|
||||
Want string
|
||||
}{
|
||||
{
|
||||
Name: "tailnet-lock-disabled",
|
||||
Status: ipnstate.NetworkLockStatus{Enabled: false},
|
||||
Want: `{
|
||||
"SchemaVersion": "1",
|
||||
"Enabled": false
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "tailnet-lock-disabled-with-keys",
|
||||
Status: ipnstate.NetworkLockStatus{
|
||||
Enabled: false,
|
||||
NodeKey: &nodeKey1,
|
||||
PublicKey: trustedNlPub,
|
||||
},
|
||||
Want: `{
|
||||
"SchemaVersion": "1",
|
||||
"Enabled": false,
|
||||
"PublicKey": "tlpub:0505050505050505050505050505050505050505050505050505050505050505",
|
||||
"NodeKey": "nodekey:0101010101010101010101010101010101010101010101010101010101010101"
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "tailnet-lock-enabled",
|
||||
Status: ipnstate.NetworkLockStatus{
|
||||
Enabled: true,
|
||||
Head: &head,
|
||||
PublicKey: nlPub,
|
||||
NodeKey: &nodeKey1,
|
||||
NodeKeySigned: false,
|
||||
NodeKeySignature: nil,
|
||||
TrustedKeys: []ipnstate.TKAKey{
|
||||
{
|
||||
Kind: tka.Key25519.String(),
|
||||
Votes: 1,
|
||||
Key: trustedNlPub,
|
||||
Metadata: map[string]string{"en": "one", "de": "eins", "es": "uno"},
|
||||
},
|
||||
},
|
||||
VisiblePeers: []*ipnstate.TKAPeer{
|
||||
{
|
||||
Name: "authentic-associate",
|
||||
ID: tailcfg.NodeID(1234),
|
||||
StableID: tailcfg.StableNodeID("1234_AAAA_TEST"),
|
||||
TailscaleIPs: []netip.Addr{tailnetIPv4_A, tailnetIPv6_A},
|
||||
NodeKey: nodeKey2,
|
||||
NodeKeySignature: tka.NodeKeySignature{
|
||||
SigKind: tka.SigDirect,
|
||||
Pubkey: []byte("22222222222222222222222222222222"),
|
||||
KeyID: []byte("44444444444444444444444444444444"),
|
||||
Signature: []byte("1234567890"),
|
||||
WrappingPubkey: []byte("0987654321"),
|
||||
},
|
||||
},
|
||||
},
|
||||
FilteredPeers: []*ipnstate.TKAPeer{
|
||||
{
|
||||
Name: "bogus-bandit",
|
||||
ID: tailcfg.NodeID(5678),
|
||||
StableID: tailcfg.StableNodeID("5678_BBBB_TEST"),
|
||||
TailscaleIPs: []netip.Addr{tailnetIPv4_B, tailnetIPv6_B},
|
||||
NodeKey: nodeKey3,
|
||||
},
|
||||
},
|
||||
StateID: 98989898,
|
||||
},
|
||||
Want: `{
|
||||
"SchemaVersion": "1",
|
||||
"Enabled": true,
|
||||
"PublicKey": "tlpub:0404040404040404040404040404040404040404040404040404040404040404",
|
||||
"NodeKey": "nodekey:0101010101010101010101010101010101010101010101010101010101010101",
|
||||
"Head": "WYIVHDR7JUIXBWAJT5UPSCAILEXB7OMINDFEFEPOPNTUCNXMY2KA",
|
||||
"NodeKeySigned": false,
|
||||
"NodeKeySignature": null,
|
||||
"TrustedKeys": [
|
||||
{
|
||||
"Kind": "25519",
|
||||
"Votes": 1,
|
||||
"Public": "tlpub:0505050505050505050505050505050505050505050505050505050505050505",
|
||||
"Meta": {
|
||||
"de": "eins",
|
||||
"en": "one",
|
||||
"es": "uno"
|
||||
}
|
||||
}
|
||||
],
|
||||
"VisiblePeers": [
|
||||
{
|
||||
"ID": "1234_AAAA_TEST",
|
||||
"DNSName": "authentic-associate",
|
||||
"TailscaleIPs": [
|
||||
"100.99.99.99",
|
||||
"fd7a:115c:a1e0::701:b62a"
|
||||
],
|
||||
"NodeKey": "nodekey:0202020202020202020202020202020202020202020202020202020202020202",
|
||||
"NodeKeySignature": {
|
||||
"SigKind": "direct",
|
||||
"PublicKey": "tlpub:3232323232323232323232323232323232323232323232323232323232323232",
|
||||
"KeyID": "tlpub:3434343434343434343434343434343434343434343434343434343434343434",
|
||||
"Signature": "MTIzNDU2Nzg5MA==",
|
||||
"WrappingPublicKey": "tlpub:30393837363534333231"
|
||||
}
|
||||
}
|
||||
],
|
||||
"FilteredPeers": [
|
||||
{
|
||||
"ID": "5678_BBBB_TEST",
|
||||
"DNSName": "bogus-bandit",
|
||||
"TailscaleIPs": [
|
||||
"100.88.88.88",
|
||||
"fd7a:115c:a1e0::4101:512f"
|
||||
],
|
||||
"NodeKey": "nodekey:0303030303030303030303030303030303030303030303030303030303030303"
|
||||
}
|
||||
],
|
||||
"State": 98989898
|
||||
}
|
||||
`,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var outBuf bytes.Buffer
|
||||
err := jsonoutput.PrintNetworkLockStatusJSONV1(&outBuf, &tt.Status)
|
||||
if err != nil {
|
||||
t.Fatalf("PrintNetworkLockStatusJSONV1: %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(outBuf.String(), tt.Want); diff != "" {
|
||||
t.Fatalf("wrong output (-got, +want):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user