mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-27 10:47:35 +00:00
ipn/ipnlocal,tailcfg: Identify client using NodeKey in tka RPCs
Updates https://github.com/tailscale/corp/pull/7024 Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
parent
e97d5634bf
commit
ebd1637e50
@ -55,9 +55,6 @@ func (b *LocalBackend) tkaSyncIfNeededLocked(nm *netmap.NetworkMap) error {
|
|||||||
// If the feature flag is not enabled, pretend we don't exist.
|
// If the feature flag is not enabled, pretend we don't exist.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if nm.SelfNode == nil {
|
|
||||||
return errors.New("SelfNode missing")
|
|
||||||
}
|
|
||||||
|
|
||||||
isEnabled := b.tka != nil
|
isEnabled := b.tka != nil
|
||||||
wantEnabled := nm.TKAEnabled
|
wantEnabled := nm.TKAEnabled
|
||||||
@ -69,8 +66,9 @@ func (b *LocalBackend) tkaSyncIfNeededLocked(nm *netmap.NetworkMap) error {
|
|||||||
|
|
||||||
// Regardless of whether we are moving to disabled or enabled, we
|
// Regardless of whether we are moving to disabled or enabled, we
|
||||||
// need information from the tka bootstrap endpoint.
|
// need information from the tka bootstrap endpoint.
|
||||||
|
ourNodeKey := b.prefs.Persist.PrivateNodeKey.Public()
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
bs, err := b.tkaFetchBootstrap(nm.SelfNode.ID, ourHead)
|
bs, err := b.tkaFetchBootstrap(ourNodeKey, ourHead)
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("fetching bootstrap: %v", err)
|
return fmt.Errorf("fetching bootstrap: %v", err)
|
||||||
@ -202,9 +200,15 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key) error {
|
|||||||
if !b.CanSupportNetworkLock() {
|
if !b.CanSupportNetworkLock() {
|
||||||
return errors.New("network-lock is not supported in this configuration. Did you supply a --statedir?")
|
return errors.New("network-lock is not supported in this configuration. Did you supply a --statedir?")
|
||||||
}
|
}
|
||||||
nm := b.NetMap()
|
|
||||||
if nm == nil {
|
var ourNodeKey key.NodePublic
|
||||||
return errors.New("no netmap: are you logged into tailscale?")
|
b.mu.Lock()
|
||||||
|
if b.prefs != nil {
|
||||||
|
ourNodeKey = b.prefs.Persist.PrivateNodeKey.Public()
|
||||||
|
}
|
||||||
|
b.mu.Unlock()
|
||||||
|
if ourNodeKey.IsZero() {
|
||||||
|
return errors.New("no node-key: is tailscale logged in?")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates a genesis AUM representing trust in the provided keys.
|
// Generates a genesis AUM representing trust in the provided keys.
|
||||||
@ -226,7 +230,7 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Phase 1/2 of initialization: Transmit the genesis AUM to Control.
|
// Phase 1/2 of initialization: Transmit the genesis AUM to Control.
|
||||||
initResp, err := b.tkaInitBegin(nm, genesisAUM)
|
initResp, err := b.tkaInitBegin(ourNodeKey, genesisAUM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("tka init-begin RPC: %w", err)
|
return fmt.Errorf("tka init-begin RPC: %w", err)
|
||||||
}
|
}
|
||||||
@ -247,7 +251,7 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finalize enablement by transmitting signature for all nodes to Control.
|
// Finalize enablement by transmitting signature for all nodes to Control.
|
||||||
_, err = b.tkaInitFinish(nm, sigs)
|
_, err = b.tkaInitFinish(ourNodeKey, sigs)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,10 +274,11 @@ func signNodeKey(nodeInfo tailcfg.TKASignInfo, signer key.NLPrivate) (*tka.NodeK
|
|||||||
return &sig, nil
|
return &sig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) tkaInitBegin(nm *netmap.NetworkMap, aum tka.AUM) (*tailcfg.TKAInitBeginResponse, error) {
|
func (b *LocalBackend) tkaInitBegin(ourNodeKey key.NodePublic, aum tka.AUM) (*tailcfg.TKAInitBeginResponse, error) {
|
||||||
var req bytes.Buffer
|
var req bytes.Buffer
|
||||||
if err := json.NewEncoder(&req).Encode(tailcfg.TKAInitBeginRequest{
|
if err := json.NewEncoder(&req).Encode(tailcfg.TKAInitBeginRequest{
|
||||||
NodeID: nm.SelfNode.ID,
|
Version: tailcfg.CurrentCapabilityVersion,
|
||||||
|
NodeKey: ourNodeKey,
|
||||||
GenesisAUM: aum.Serialize(),
|
GenesisAUM: aum.Serialize(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, fmt.Errorf("encoding request: %v", err)
|
return nil, fmt.Errorf("encoding request: %v", err)
|
||||||
@ -311,10 +316,11 @@ func (b *LocalBackend) tkaInitBegin(nm *netmap.NetworkMap, aum tka.AUM) (*tailcf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) tkaInitFinish(nm *netmap.NetworkMap, nks map[tailcfg.NodeID]tkatype.MarshaledSignature) (*tailcfg.TKAInitFinishResponse, error) {
|
func (b *LocalBackend) tkaInitFinish(ourNodeKey key.NodePublic, nks map[tailcfg.NodeID]tkatype.MarshaledSignature) (*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{
|
||||||
NodeID: nm.SelfNode.ID,
|
Version: tailcfg.CurrentCapabilityVersion,
|
||||||
|
NodeKey: ourNodeKey,
|
||||||
Signatures: nks,
|
Signatures: nks,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, fmt.Errorf("encoding request: %v", err)
|
return nil, fmt.Errorf("encoding request: %v", err)
|
||||||
@ -354,9 +360,10 @@ func (b *LocalBackend) tkaInitFinish(nm *netmap.NetworkMap, nks map[tailcfg.Node
|
|||||||
|
|
||||||
// tkaFetchBootstrap sends a /machine/tka/bootstrap RPC to the control plane
|
// tkaFetchBootstrap sends a /machine/tka/bootstrap RPC to the control plane
|
||||||
// over noise. This is used to get values necessary to enable or disable TKA.
|
// over noise. This is used to get values necessary to enable or disable TKA.
|
||||||
func (b *LocalBackend) tkaFetchBootstrap(nodeID tailcfg.NodeID, head tka.AUMHash) (*tailcfg.TKABootstrapResponse, error) {
|
func (b *LocalBackend) tkaFetchBootstrap(ourNodeKey key.NodePublic, head tka.AUMHash) (*tailcfg.TKABootstrapResponse, error) {
|
||||||
bootstrapReq := tailcfg.TKABootstrapRequest{
|
bootstrapReq := tailcfg.TKABootstrapRequest{
|
||||||
NodeID: nodeID,
|
Version: tailcfg.CurrentCapabilityVersion,
|
||||||
|
NodeKey: ourNodeKey,
|
||||||
}
|
}
|
||||||
if !head.IsZero() {
|
if !head.IsZero() {
|
||||||
head, err := head.MarshalText()
|
head, err := head.MarshalText()
|
||||||
|
@ -17,10 +17,12 @@ import (
|
|||||||
|
|
||||||
"tailscale.com/control/controlclient"
|
"tailscale.com/control/controlclient"
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tka"
|
"tailscale.com/tka"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/netmap"
|
"tailscale.com/types/netmap"
|
||||||
|
"tailscale.com/types/persist"
|
||||||
)
|
)
|
||||||
|
|
||||||
func fakeControlClient(t *testing.T, c *http.Client) *controlclient.Auto {
|
func fakeControlClient(t *testing.T, c *http.Client) *controlclient.Auto {
|
||||||
@ -62,6 +64,7 @@ func fakeNoiseServer(t *testing.T, handler http.HandlerFunc) (*httptest.Server,
|
|||||||
|
|
||||||
func TestTKAEnablementFlow(t *testing.T) {
|
func TestTKAEnablementFlow(t *testing.T) {
|
||||||
networkLockAvailable = func() bool { return true } // Enable the feature flag
|
networkLockAvailable = func() bool { return true } // Enable the feature flag
|
||||||
|
nodePriv := key.NewNode()
|
||||||
|
|
||||||
// Make a fake TKA authority, getting a usable genesis AUM which
|
// Make a fake TKA authority, getting a usable genesis AUM which
|
||||||
// our mock server can communicate.
|
// our mock server can communicate.
|
||||||
@ -83,8 +86,11 @@ func TestTKAEnablementFlow(t *testing.T) {
|
|||||||
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if body.NodeID != 420 {
|
if body.Version != tailcfg.CurrentCapabilityVersion {
|
||||||
t.Errorf("bootstrap nodeID=%v, want 420", body.NodeID)
|
t.Errorf("bootstrap CapVer = %v, want %v", body.Version, tailcfg.CurrentCapabilityVersion)
|
||||||
|
}
|
||||||
|
if body.NodeKey != nodePriv.Public() {
|
||||||
|
t.Errorf("bootstrap nodeKey=%v, want %v", body.NodeKey, nodePriv.Public())
|
||||||
}
|
}
|
||||||
if body.Head != "" {
|
if body.Head != "" {
|
||||||
t.Errorf("bootstrap head=%s, want empty hash", body.Head)
|
t.Errorf("bootstrap head=%s, want empty hash", body.Head)
|
||||||
@ -112,11 +118,13 @@ func TestTKAEnablementFlow(t *testing.T) {
|
|||||||
cc: cc,
|
cc: cc,
|
||||||
ccAuto: cc,
|
ccAuto: cc,
|
||||||
logf: t.Logf,
|
logf: t.Logf,
|
||||||
|
prefs: &ipn.Prefs{
|
||||||
|
Persist: &persist.Persist{PrivateNodeKey: nodePriv},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
err = b.tkaSyncIfNeededLocked(&netmap.NetworkMap{
|
err = b.tkaSyncIfNeededLocked(&netmap.NetworkMap{
|
||||||
SelfNode: &tailcfg.Node{ID: 420},
|
|
||||||
TKAEnabled: true,
|
TKAEnabled: true,
|
||||||
TKAHead: tka.AUMHash{},
|
TKAHead: tka.AUMHash{},
|
||||||
})
|
})
|
||||||
@ -136,6 +144,7 @@ func TestTKADisablementFlow(t *testing.T) {
|
|||||||
networkLockAvailable = func() bool { return true } // Enable the feature flag
|
networkLockAvailable = func() bool { return true } // Enable the feature flag
|
||||||
temp := t.TempDir()
|
temp := t.TempDir()
|
||||||
os.Mkdir(filepath.Join(temp, "tka"), 0755)
|
os.Mkdir(filepath.Join(temp, "tka"), 0755)
|
||||||
|
nodePriv := key.NewNode()
|
||||||
|
|
||||||
// Make a fake TKA authority, to seed local state.
|
// Make a fake TKA authority, to seed local state.
|
||||||
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
|
disablementSecret := bytes.Repeat([]byte{0xa5}, 32)
|
||||||
@ -153,6 +162,7 @@ func TestTKADisablementFlow(t *testing.T) {
|
|||||||
t.Fatalf("tka.Create() failed: %v", err)
|
t.Fatalf("tka.Create() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnWrongSecret := false
|
||||||
ts, client := fakeNoiseServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts, client := fakeNoiseServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
@ -161,14 +171,11 @@ func TestTKADisablementFlow(t *testing.T) {
|
|||||||
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var disablement []byte
|
if body.Version != tailcfg.CurrentCapabilityVersion {
|
||||||
switch body.NodeID {
|
t.Errorf("bootstrap CapVer = %v, want %v", body.Version, tailcfg.CurrentCapabilityVersion)
|
||||||
case 42:
|
}
|
||||||
disablement = bytes.Repeat([]byte{0x42}, 32) // wrong secret
|
if body.NodeKey != nodePriv.Public() {
|
||||||
case 420:
|
t.Errorf("nodeKey=%v, want %v", body.NodeKey, nodePriv.Public())
|
||||||
disablement = disablementSecret
|
|
||||||
default:
|
|
||||||
t.Errorf("bootstrap nodeID=%v, wanted 42 or 420", body.NodeID)
|
|
||||||
}
|
}
|
||||||
var head tka.AUMHash
|
var head tka.AUMHash
|
||||||
if err := head.UnmarshalText([]byte(body.Head)); err != nil {
|
if err := head.UnmarshalText([]byte(body.Head)); err != nil {
|
||||||
@ -178,6 +185,13 @@ func TestTKADisablementFlow(t *testing.T) {
|
|||||||
t.Errorf("reported head = %x, want %x", head, authority.Head())
|
t.Errorf("reported head = %x, want %x", head, authority.Head())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var disablement []byte
|
||||||
|
if returnWrongSecret {
|
||||||
|
disablement = bytes.Repeat([]byte{0x42}, 32) // wrong secret
|
||||||
|
} else {
|
||||||
|
disablement = disablementSecret
|
||||||
|
}
|
||||||
|
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
out := tailcfg.TKABootstrapResponse{
|
out := tailcfg.TKABootstrapResponse{
|
||||||
DisablementSecret: disablement,
|
DisablementSecret: disablement,
|
||||||
@ -203,13 +217,15 @@ func TestTKADisablementFlow(t *testing.T) {
|
|||||||
authority: authority,
|
authority: authority,
|
||||||
storage: chonk,
|
storage: chonk,
|
||||||
},
|
},
|
||||||
|
prefs: &ipn.Prefs{
|
||||||
|
Persist: &persist.Persist{PrivateNodeKey: nodePriv},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that the wrong disablement secret does not shut down the authority.
|
// Test that the wrong disablement secret does not shut down the authority.
|
||||||
// NodeID == 42 indicates this scenario to our mock server.
|
returnWrongSecret = true
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
err = b.tkaSyncIfNeededLocked(&netmap.NetworkMap{
|
err = b.tkaSyncIfNeededLocked(&netmap.NetworkMap{
|
||||||
SelfNode: &tailcfg.Node{ID: 42},
|
|
||||||
TKAEnabled: false,
|
TKAEnabled: false,
|
||||||
TKAHead: authority.Head(),
|
TKAHead: authority.Head(),
|
||||||
})
|
})
|
||||||
@ -222,10 +238,9 @@ func TestTKADisablementFlow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test the correct disablement secret shuts down the authority.
|
// Test the correct disablement secret shuts down the authority.
|
||||||
// NodeID == 420 indicates this scenario to our mock server.
|
returnWrongSecret = false
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
err = b.tkaSyncIfNeededLocked(&netmap.NetworkMap{
|
err = b.tkaSyncIfNeededLocked(&netmap.NetworkMap{
|
||||||
SelfNode: &tailcfg.Node{ID: 420},
|
|
||||||
TKAEnabled: false,
|
TKAEnabled: false,
|
||||||
TKAHead: authority.Head(),
|
TKAHead: authority.Head(),
|
||||||
})
|
})
|
||||||
|
@ -12,9 +12,11 @@ import (
|
|||||||
// TKAInitBeginRequest submits a genesis AUM to seed the creation of the
|
// TKAInitBeginRequest submits a genesis AUM to seed the creation of the
|
||||||
// tailnet's key authority.
|
// tailnet's key authority.
|
||||||
type TKAInitBeginRequest struct {
|
type TKAInitBeginRequest struct {
|
||||||
// NodeID is the node of the initiating client.
|
// Version is the client's capabilities.
|
||||||
// It must match the machine key being used to communicate over noise.
|
Version CapabilityVersion
|
||||||
NodeID NodeID
|
|
||||||
|
// NodeKey is the client's current node key.
|
||||||
|
NodeKey key.NodePublic
|
||||||
|
|
||||||
// GenesisAUM is the initial (genesis) AUM that the node generated
|
// GenesisAUM is the initial (genesis) AUM that the node generated
|
||||||
// to bootstrap tailnet key authority state.
|
// to bootstrap tailnet key authority state.
|
||||||
@ -58,8 +60,11 @@ type TKAInitBeginResponse struct {
|
|||||||
// This RPC finalizes initialization of the tailnet key authority
|
// This RPC finalizes initialization of the tailnet key authority
|
||||||
// by submitting node-key signatures for all existing nodes.
|
// by submitting node-key signatures for all existing nodes.
|
||||||
type TKAInitFinishRequest struct {
|
type TKAInitFinishRequest struct {
|
||||||
// NodeID is the node ID of the initiating client.
|
// Version is the client's capabilities.
|
||||||
NodeID NodeID
|
Version CapabilityVersion
|
||||||
|
|
||||||
|
// NodeKey is the client's current node key.
|
||||||
|
NodeKey key.NodePublic
|
||||||
|
|
||||||
// Signatures are serialized tka.NodeKeySignatures for all nodes
|
// Signatures are serialized tka.NodeKeySignatures for all nodes
|
||||||
// in the tailnet.
|
// in the tailnet.
|
||||||
@ -101,8 +106,12 @@ type TKAInfo struct {
|
|||||||
// TKABootstrapRequest is sent by a node to get information necessary for
|
// TKABootstrapRequest is sent by a node to get information necessary for
|
||||||
// enabling or disabling the tailnet key authority.
|
// enabling or disabling the tailnet key authority.
|
||||||
type TKABootstrapRequest struct {
|
type TKABootstrapRequest struct {
|
||||||
// NodeID is the node ID of the initiating client.
|
// Version is the client's capabilities.
|
||||||
NodeID NodeID
|
Version CapabilityVersion
|
||||||
|
|
||||||
|
// NodeKey is the client's current node key.
|
||||||
|
NodeKey key.NodePublic
|
||||||
|
|
||||||
// Head represents the node's head AUMHash (tka.Authority.Head), if
|
// Head represents the node's head AUMHash (tka.Authority.Head), if
|
||||||
// network lock is enabled.
|
// network lock is enabled.
|
||||||
Head string
|
Head string
|
||||||
@ -122,8 +131,12 @@ type TKABootstrapResponse struct {
|
|||||||
// state (TKA). Values of type tka.AUMHash are encoded as strings in their
|
// state (TKA). Values of type tka.AUMHash are encoded as strings in their
|
||||||
// MarshalText form.
|
// MarshalText form.
|
||||||
type TKASyncOfferRequest struct {
|
type TKASyncOfferRequest struct {
|
||||||
// NodeID is the node ID of the initiating client.
|
// Version is the client's capabilities.
|
||||||
NodeID NodeID
|
Version CapabilityVersion
|
||||||
|
|
||||||
|
// NodeKey is the client's current node key.
|
||||||
|
NodeKey key.NodePublic
|
||||||
|
|
||||||
// Head represents the node's head AUMHash (tka.Authority.Head). This
|
// Head represents the node's head AUMHash (tka.Authority.Head). This
|
||||||
// corresponds to tka.SyncOffer.Head.
|
// corresponds to tka.SyncOffer.Head.
|
||||||
Head string
|
Head string
|
||||||
@ -151,8 +164,12 @@ type TKASyncOfferResponse struct {
|
|||||||
// TKASyncSendRequest encodes AUMs that a node believes the control plane
|
// TKASyncSendRequest encodes AUMs that a node believes the control plane
|
||||||
// is missing.
|
// is missing.
|
||||||
type TKASyncSendRequest struct {
|
type TKASyncSendRequest struct {
|
||||||
// NodeID is the node ID of the initiating client.
|
// Version is the client's capabilities.
|
||||||
NodeID NodeID
|
Version CapabilityVersion
|
||||||
|
|
||||||
|
// NodeKey is the client's current node key.
|
||||||
|
NodeKey key.NodePublic
|
||||||
|
|
||||||
// MissingAUMs encodes AUMs that the node believes the control plane
|
// MissingAUMs encodes AUMs that the node believes the control plane
|
||||||
// is missing.
|
// is missing.
|
||||||
MissingAUMs []tkatype.MarshaledAUM
|
MissingAUMs []tkatype.MarshaledAUM
|
||||||
|
Loading…
x
Reference in New Issue
Block a user