From 9132b31e43bf5cf41a0cf10d1c2fb8d3ca534db4 Mon Sep 17 00:00:00 2001 From: Tom DNetto Date: Mon, 29 Aug 2022 14:02:12 -0700 Subject: [PATCH] tailcfg: refactor/implement wire structs for TKA Signed-off-by: Tom DNetto --- tailcfg/tailcfg.go | 96 +++------------------------ tailcfg/tka.go | 161 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 87 deletions(-) create mode 100644 tailcfg/tka.go diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 9835037da..d8a3ea338 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -903,11 +903,6 @@ type MapRequest struct { Stream bool // if true, multiple MapResponse objects are returned Hostinfo *Hostinfo - // TKA describes request parameters relating to a local instance of - // the tailnet key authority. This field is omitted if a local instance - // is not running. - TKA *TKAMapRequest `json:",omitempty"` - // Endpoints are the client's magicsock UDP ip:port endpoints (IPv4 or IPv6). Endpoints []string // EndpointTypes are the types of the corresponding endpoints in Endpoints. @@ -1347,9 +1342,15 @@ type MapResponse struct { // ControlTime, if non-zero, is the current timestamp according to the control server. ControlTime *time.Time `json:",omitempty"` - // TKA, if non-nil, describes updates for the local instance of the - // tailnet key authority. - TKA *TKAMapResponse `json:",omitempty"` + // TKAInfo describes the control plane's view of tailnet + // key authority (TKA) state. + // + // An initial nil TKAInfo indicates that the control plane + // believes TKA should not be enabled. An initial non-nil TKAInfo + // indicates the control plane believes TKA should be enabled. + // A nil TKAInfo in a mapresponse stream (i.e. a 'delta' mapresponse) + // indicates no change from the value sent earlier. + TKAInfo *TKAInfo `json:",omitempty"` // Debug is normally nil, except for when the control server // is setting debug settings on a node. @@ -1853,85 +1854,6 @@ type PeerChange struct { Capabilities *[]string `json:",omitempty"` } -// TKAInitBeginRequest submits a genesis AUM to seed the creation of the -// tailnet's key authority. -type TKAInitBeginRequest struct { - NodeID NodeID // NodeID of the initiating client - - GenesisAUM tkatype.MarshaledAUM -} - -// TKASignInfo describes information about an existing node that needs -// to be signed into a node-key signature. -type TKASignInfo struct { - NodeID NodeID // NodeID of the node-key being signed - NodePublic key.NodePublic - - // RotationPubkey specifies the public key which may sign - // a NodeKeySignature (NKS), which rotates the node key. - // - // This is necessary so the node can rotate its node-key without - // talking to a node which holds a trusted network-lock key. - // It does this by nesting the original NKS in a 'rotation' NKS, - // which it then signs with the key corresponding to RotationPubkey. - // - // This field expects a raw ed25519 public key. - RotationPubkey []byte -} - -// TKAInitBeginResponse describes node information which must be signed to -// complete initialization of the tailnets' key authority. -type TKAInitBeginResponse struct { - NeedSignatures []TKASignInfo -} - -// TKAInitFinishRequest finalizes initialization of the tailnet key authority -// by submitting node-key signatures for all existing nodes. -type TKAInitFinishRequest struct { - NodeID NodeID // NodeID of the initiating client - - Signatures map[NodeID]tkatype.MarshaledSignature -} - -// TKAInitFinishResponse describes the successful enablement of the tailnet's -// key authority. -type TKAInitFinishResponse struct{} - -// TKAMapRequest describes request parameters relating to the tailnet key -// authority instance on this node. This information is transmitted as -// part of the MapRequest. -type TKAMapRequest struct { - // Head is the AUMHash of the latest authority update message committed - // by this node. - Head string // tka.AUMHash.String -} - -// TKAMapResponse encodes information for the tailnet key authority -// instance on the node. This information is transmitted as -// part of the MapResponse. -// -// If there are no updates to be transmitted (in other words, if both -// control and the node have the same head hash), len(Updates) == 0 and -// WantSync is false. -// -// If control has updates that build off the head hash reported by the -// node, they are simply transmitted in Updates (avoiding the more -// expensive synchronization process). -// -// In all other cases, WantSync is set to true, and the node is expected -// to reach out to control separately to synchronize. -type TKAMapResponse struct { - // Updates is any AUMs that control believes the node should apply. - Updates []tkatype.MarshaledAUM `json:",omitempty"` - - // WantSync is set by control to request the node complete AUM - // synchronization. - // - // TODO(tom): Implement AUM synchronization, probably as noise endpoints - // /machine/tka/sync/offer & /machine/tka/sync/send. - WantSync bool `json:",omitempty"` -} - // DerpMagicIP is a fake WireGuard endpoint IP address that means to // use DERP. When used (in the Node.DERP field), the port number of // the WireGuard endpoint is the DERP region ID number to use. diff --git a/tailcfg/tka.go b/tailcfg/tka.go new file mode 100644 index 000000000..fc59b8f68 --- /dev/null +++ b/tailcfg/tka.go @@ -0,0 +1,161 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tailcfg + +import ( + "tailscale.com/types/key" + "tailscale.com/types/tkatype" +) + +// TKAInitBeginRequest submits a genesis AUM to seed the creation of the +// tailnet's key authority. +type TKAInitBeginRequest struct { + // NodeID is the node of the initiating client. + // It must match the machine key being used to communicate over noise. + NodeID NodeID + + // GenesisAUM is the initial (genesis) AUM that the node generated + // to bootstrap tailnet key authority state. + GenesisAUM tkatype.MarshaledAUM +} + +// TKASignInfo describes information about an existing node that needs +// to be signed into a node-key signature. +type TKASignInfo struct { + // NodeID is the ID of the node which needs a signature. It must + // correspond to NodePublic. + NodeID NodeID + // NodePublic is the node (Wireguard) public key which is being + // signed. + NodePublic key.NodePublic + + // RotationPubkey specifies the public key which may sign + // a NodeKeySignature (NKS), which rotates the node key. + // + // This is necessary so the node can rotate its node-key without + // talking to a node which holds a trusted network-lock key. + // It does this by nesting the original NKS in a 'rotation' NKS, + // which it then signs with the key corresponding to RotationPubkey. + // + // This field expects a raw ed25519 public key. + RotationPubkey []byte +} + +// TKAInitBeginResponse is the JSON response from a /tka/init/begin RPC. +// This structure describes node information which must be signed to +// complete initialization of the tailnets' key authority. +type TKAInitBeginResponse struct { + // NeedSignatures specify information about the nodes in your tailnet + // which need initial signatures to function once the tailnet key + // authority is in use. The generated signatures should then be + // submitted in a /tka/init/finish RPC. + NeedSignatures []TKASignInfo +} + +// TKAInitFinishRequest is the JSON request of a /tka/init/finish RPC. +// This RPC finalizes initialization of the tailnet key authority +// by submitting node-key signatures for all existing nodes. +type TKAInitFinishRequest struct { + // NodeID is the node ID of the initiating client. + NodeID NodeID + + // Signatures are serialized tka.NodeKeySignatures for all nodes + // in the tailnet. + Signatures map[NodeID]tkatype.MarshaledSignature +} + +// TKAInitFinishResponse is the JSON response from a /tka/init/finish RPC. +// This schema describes the successful enablement of the tailnet's +// key authority. +type TKAInitFinishResponse struct { + // Nothing. (yet?) +} + +// TKAInfo encodes the control plane's view of tailnet key authority (TKA) +// state. This information is transmitted as part of the MapResponse. +type TKAInfo struct { + // Head describes the hash of the latest AUM applied to the authority. + // Head is encoded as tka.AUMHash.MarshalText. + // + // If the Head state differs to that known locally, the node should perform + // synchronization via a separate RPC. + // + // TODO(tom): Implement AUM synchronization as noise endpoints + // /machine/tka/sync/offer & /machine/tka/sync/send. + Head string `json:",omitempty"` + + // Disabled indicates the control plane believes TKA should be disabled, + // and the node should reach out to fetch a disablement + // secret. If the disablement secret verifies, then the node should then + // disable TKA locally. + // This field exists to disambiguate a nil TKAInfo in a delta mapresponse + // from a nil TKAInfo indicating TKA should be disabled. + // + // TODO(tom): Implement /machine/tka/boostrap as a noise endpoint, to + // communicate the genesis AUM & any disablement secrets. + Disabled bool `json:",omitempty"` +} + +// TKABootstrapRequest is sent by a node to get information necessary for +// enabling or disabling the tailnet key authority. +type TKABootstrapRequest struct { + // Head represents the node's head AUMHash (tka.Authority.Head), if + // network lock is enabled. + Head string +} + +// TKABootstrapResponse encodes values necessary to enable or disable +// the tailnet key authority (TKA). +type TKABootstrapResponse struct { + // GenesisAUM returns the initial AUM necessary to initialize TKA. + GenesisAUM tkatype.MarshaledAUM `json:",omitempty"` + + // DisablementSecret encodes a secret necessary to disable TKA. + DisablementSecret []byte `json:",omitempty"` +} + +// TKASyncOfferRequest encodes a request to synchronize tailnet key authority +// state (TKA). Values of type tka.AUMHash are encoded as strings in their +// MarshalText form. +type TKASyncOfferRequest struct { + // Head represents the node's head AUMHash (tka.Authority.Head). This + // corresponds to tka.SyncOffer.Head. + Head string + // Ancestors represents a selection of ancestor AUMHash values ascending + // from the current head. This corresponds to tka.SyncOffer.Ancestors. + Ancestors []string +} + +// TKASyncOfferResponse encodes a response in synchronizing a node's +// tailnet key authority state. Values of type tka.AUMHash are encoded as +// strings in their MarshalText form. +type TKASyncOfferResponse struct { + // Head represents the control plane's head AUMHash (tka.Authority.Head). + // This corresponds to tka.SyncOffer.Head. + Head string + // Ancestors represents a selection of ancestor AUMHash values ascending + // from the control plane's head. This corresponds to + // tka.SyncOffer.Ancestors. + Ancestors []string + // MissingAUMs encodes AUMs that the control plane believes the node + // is missing. + MissingAUMs []tkatype.MarshaledAUM +} + +// TKASyncSendRequest encodes AUMs that a node believes the control plane +// is missing. +type TKASyncSendRequest struct { + // MissingAUMs encodes AUMs that the node believes the control plane + // is missing. + MissingAUMs []tkatype.MarshaledAUM +} + +// TKASyncSendResponse encodes the control plane's response to a node +// submitting AUMs during AUM synchronization. +type TKASyncSendResponse struct { + // Head represents the control plane's head AUMHash (tka.Authority.Head), + // after applying the missing AUMs. + Head string +}