ipn/ipnlocal: filter peers with bad signatures when tka is enabled
Signed-off-by: Tom DNetto <tom@tailscale.com>pull/5821/head
parent
01ebef0f4f
commit
73db56af52
|
@ -277,6 +277,11 @@ func SSHPolicyFile() string { return String("TS_DEBUG_SSH_POLICY_FILE") }
|
||||||
// SSHIgnoreTailnetPolicy is whether to ignore the Tailnet SSH policy for development.
|
// SSHIgnoreTailnetPolicy is whether to ignore the Tailnet SSH policy for development.
|
||||||
func SSHIgnoreTailnetPolicy() bool { return Bool("TS_DEBUG_SSH_IGNORE_TAILNET_POLICY") }
|
func SSHIgnoreTailnetPolicy() bool { return Bool("TS_DEBUG_SSH_IGNORE_TAILNET_POLICY") }
|
||||||
|
|
||||||
|
|
||||||
|
// TKASkipSignatureCheck is whether to skip node-key signature checking for development.
|
||||||
|
func TKASkipSignatureCheck() bool { return Bool("TS_UNSAFE_SKIP_NKS_VERIFICATION") }
|
||||||
|
|
||||||
|
|
||||||
// NoLogsNoSupport reports whether the client's opted out of log uploads and
|
// NoLogsNoSupport reports whether the client's opted out of log uploads and
|
||||||
// technical support.
|
// technical support.
|
||||||
func NoLogsNoSupport() bool {
|
func NoLogsNoSupport() bool {
|
||||||
|
|
|
@ -778,6 +778,9 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
|
||||||
if err := b.tkaSyncIfNeededLocked(st.NetMap); err != nil {
|
if err := b.tkaSyncIfNeededLocked(st.NetMap); err != nil {
|
||||||
b.logf("[v1] TKA sync error: %v", err)
|
b.logf("[v1] TKA sync error: %v", err)
|
||||||
}
|
}
|
||||||
|
if !envknob.TKASkipSignatureCheck() {
|
||||||
|
b.tkaFilterNetmapLocked(st.NetMap)
|
||||||
|
}
|
||||||
if b.findExitNodeIDLocked(st.NetMap) {
|
if b.findExitNodeIDLocked(st.NetMap) {
|
||||||
prefsChanged = true
|
prefsChanged = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,39 @@ type tkaState struct {
|
||||||
storage *tka.FS
|
storage *tka.FS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tkaFilterNetmapLocked checks the signatures on each node key, dropping
|
||||||
|
// nodes from the netmap who's signature does not verify.
|
||||||
|
func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
|
||||||
|
if !envknob.UseWIPCode() {
|
||||||
|
return // Feature-flag till network-lock is in Alpha.
|
||||||
|
}
|
||||||
|
if b.tka == nil {
|
||||||
|
return // TKA not enabled.
|
||||||
|
}
|
||||||
|
|
||||||
|
toDelete := make(map[int]struct{}, len(nm.Peers))
|
||||||
|
for i, p := range nm.Peers {
|
||||||
|
if len(p.KeySignature) == 0 {
|
||||||
|
b.logf("Network lock is dropping peer %v(%v) due to missing signature", p.ID, p.StableID)
|
||||||
|
toDelete[i] = struct{}{}
|
||||||
|
} else {
|
||||||
|
if err := b.tka.authority.NodeKeyAuthorized(p.Key, p.KeySignature); err != nil {
|
||||||
|
b.logf("Network lock is dropping peer %v(%v) due to failed signature check: %v", p.ID, p.StableID, err)
|
||||||
|
toDelete[i] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nm.Peers is ordered, so deletion must be order-preserving.
|
||||||
|
peers := make([]*tailcfg.Node, 0, len(nm.Peers))
|
||||||
|
for i, p := range nm.Peers {
|
||||||
|
if _, delete := toDelete[i]; !delete {
|
||||||
|
peers = append(peers, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nm.Peers = peers
|
||||||
|
}
|
||||||
|
|
||||||
// tkaSyncIfNeededLocked examines TKA info reported from the control plane,
|
// tkaSyncIfNeededLocked examines TKA info reported from the control plane,
|
||||||
// performing the steps necessary to synchronize local tka state.
|
// performing the steps necessary to synchronize local tka state.
|
||||||
//
|
//
|
||||||
|
|
|
@ -15,7 +15,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
"tailscale.com/control/controlclient"
|
"tailscale.com/control/controlclient"
|
||||||
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
@ -484,3 +486,61 @@ func TestTKASync(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTKAFilterNetmap(t *testing.T) {
|
||||||
|
envknob.Setenv("TAILSCALE_USE_WIP_CODE", "1")
|
||||||
|
|
||||||
|
nlPriv := key.NewNLPrivate()
|
||||||
|
nlKey := tka.Key{Kind: tka.Key25519, Public: nlPriv.Public().Verifier(), Votes: 2}
|
||||||
|
storage := &tka.Mem{}
|
||||||
|
authority, _, err := tka.Create(storage, tka.State{
|
||||||
|
Keys: []tka.Key{nlKey},
|
||||||
|
DisablementSecrets: [][]byte{bytes.Repeat([]byte{0xa5}, 32)},
|
||||||
|
}, nlPriv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("tka.Create() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n1, n2, n3, n4, n5 := key.NewNode(), key.NewNode(), key.NewNode(), key.NewNode(), key.NewNode()
|
||||||
|
n1GoodSig, err := signNodeKey(tailcfg.TKASignInfo{NodePublic: n1.Public()}, nlPriv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
n4Sig, err := signNodeKey(tailcfg.TKASignInfo{NodePublic: n4.Public()}, nlPriv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
n4Sig.Signature[3] = 42 // mess up the signature
|
||||||
|
n4Sig.Signature[4] = 42 // mess up the signature
|
||||||
|
n5GoodSig, err := signNodeKey(tailcfg.TKASignInfo{NodePublic: n5.Public()}, nlPriv)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nm := netmap.NetworkMap{
|
||||||
|
Peers: []*tailcfg.Node{
|
||||||
|
{ID: 1, Key: n1.Public(), KeySignature: n1GoodSig.Serialize()},
|
||||||
|
{ID: 2, Key: n2.Public(), KeySignature: nil}, // missing sig
|
||||||
|
{ID: 3, Key: n3.Public(), KeySignature: n1GoodSig.Serialize()}, // someone elses sig
|
||||||
|
{ID: 4, Key: n4.Public(), KeySignature: n4Sig.Serialize()}, // messed-up signature
|
||||||
|
{ID: 5, Key: n5.Public(), KeySignature: n5GoodSig.Serialize()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &LocalBackend{
|
||||||
|
logf: t.Logf,
|
||||||
|
tka: &tkaState{authority: authority},
|
||||||
|
}
|
||||||
|
b.tkaFilterNetmapLocked(&nm)
|
||||||
|
|
||||||
|
want := []*tailcfg.Node{
|
||||||
|
{ID: 1, Key: n1.Public(), KeySignature: n1GoodSig.Serialize()},
|
||||||
|
{ID: 5, Key: n5.Public(), KeySignature: n5GoodSig.Serialize()},
|
||||||
|
}
|
||||||
|
nodePubComparer := cmp.Comparer(func(x, y key.NodePublic) bool {
|
||||||
|
return x.Raw32() == y.Raw32()
|
||||||
|
})
|
||||||
|
if diff := cmp.Diff(nm.Peers, want, nodePubComparer); diff != "" {
|
||||||
|
t.Errorf("filtered netmap differs (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue