Define WireGuard PersistentKeepAlive via Annotation (#31)

* Add WireGuardPersistentKeepAlive to mesh.Node

* Connect to configuration

* Shorten keepalive key

* Fix casing on keepalive

* Add annotated keepalive value to peer functions
This commit is contained in:
Francis Nguyen 2020-02-13 02:16:55 -07:00 committed by GitHub
parent a6afc3247d
commit 6de0f9805a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 86 deletions

View File

@ -56,6 +56,7 @@ const (
lastSeenAnnotationKey = "kilo.squat.ai/last-seen" lastSeenAnnotationKey = "kilo.squat.ai/last-seen"
leaderAnnotationKey = "kilo.squat.ai/leader" leaderAnnotationKey = "kilo.squat.ai/leader"
locationAnnotationKey = "kilo.squat.ai/location" locationAnnotationKey = "kilo.squat.ai/location"
persistentKeepaliveKey = "kilo.squat.ai/persistent-keepalive"
wireGuardIPAnnotationKey = "kilo.squat.ai/wireguard-ip" wireGuardIPAnnotationKey = "kilo.squat.ai/wireguard-ip"
regionLabelKey = "topology.kubernetes.io/region" regionLabelKey = "topology.kubernetes.io/region"
@ -262,6 +263,15 @@ func translateNode(node *v1.Node) *mesh.Node {
if !ok { if !ok {
internalIP = node.ObjectMeta.Annotations[internalIPAnnotationKey] internalIP = node.ObjectMeta.Annotations[internalIPAnnotationKey]
} }
// Set Wireguard PersistentKeepalive setting for the node.
var persistentKeepalive int64
if keepAlive, ok := node.ObjectMeta.Annotations[persistentKeepaliveKey]; !ok {
persistentKeepalive = 0
} else {
if persistentKeepalive, err = strconv.ParseInt(keepAlive, 10, 64); err != nil {
persistentKeepalive = 0
}
}
var lastSeen int64 var lastSeen int64
if ls, ok := node.ObjectMeta.Annotations[lastSeenAnnotationKey]; !ok { if ls, ok := node.ObjectMeta.Annotations[lastSeenAnnotationKey]; !ok {
lastSeen = 0 lastSeen = 0
@ -275,14 +285,15 @@ func translateNode(node *v1.Node) *mesh.Node {
// remote node's agent has not yet set its IP address; // remote node's agent has not yet set its IP address;
// in this case the IP will be nil and // in this case the IP will be nil and
// the mesh can wait for the node to be updated. // the mesh can wait for the node to be updated.
ExternalIP: normalizeIP(externalIP), ExternalIP: normalizeIP(externalIP),
InternalIP: normalizeIP(internalIP), InternalIP: normalizeIP(internalIP),
Key: []byte(node.ObjectMeta.Annotations[keyAnnotationKey]), Key: []byte(node.ObjectMeta.Annotations[keyAnnotationKey]),
LastSeen: lastSeen, LastSeen: lastSeen,
Leader: leader, Leader: leader,
Location: location, Location: location,
Name: node.Name, Name: node.Name,
Subnet: subnet, PersistentKeepalive: int(persistentKeepalive),
Subnet: subnet,
// WireGuardIP can fail to parse if the node is not a leader or if // WireGuardIP can fail to parse if the node is not a leader or if
// the node's agent has not yet reconciled. In either case, the IP // the node's agent has not yet reconciled. In either case, the IP
// will parse as nil. // will parse as nil.

View File

@ -111,6 +111,15 @@ func TestTranslateNode(t *testing.T) {
ExternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(24, 32)}, ExternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(24, 32)},
}, },
}, },
{
name: "wireguard persistent keepalive override",
annotations: map[string]string{
persistentKeepaliveKey: "25",
},
out: &mesh.Node{
PersistentKeepalive: 25,
},
},
{ {
name: "internal IP override", name: "internal IP override",
annotations: map[string]string{ annotations: map[string]string{
@ -139,20 +148,22 @@ func TestTranslateNode(t *testing.T) {
lastSeenAnnotationKey: "1000000000", lastSeenAnnotationKey: "1000000000",
leaderAnnotationKey: "", leaderAnnotationKey: "",
locationAnnotationKey: "b", locationAnnotationKey: "b",
persistentKeepaliveKey: "25",
wireGuardIPAnnotationKey: "10.4.0.1/16", wireGuardIPAnnotationKey: "10.4.0.1/16",
}, },
labels: map[string]string{ labels: map[string]string{
regionLabelKey: "a", regionLabelKey: "a",
}, },
out: &mesh.Node{ out: &mesh.Node{
ExternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(24, 32)}, ExternalIP: &net.IPNet{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(24, 32)},
InternalIP: &net.IPNet{IP: net.ParseIP("10.1.0.2"), Mask: net.CIDRMask(32, 32)}, InternalIP: &net.IPNet{IP: net.ParseIP("10.1.0.2"), Mask: net.CIDRMask(32, 32)},
Key: []byte("foo"), Key: []byte("foo"),
LastSeen: 1000000000, LastSeen: 1000000000,
Leader: true, Leader: true,
Location: "b", Location: "b",
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)}, PersistentKeepalive: 25,
WireGuardIP: &net.IPNet{IP: net.ParseIP("10.4.0.1"), Mask: net.CIDRMask(16, 32)}, Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
WireGuardIP: &net.IPNet{IP: net.ParseIP("10.4.0.1"), Mask: net.CIDRMask(16, 32)},
}, },
subnet: "10.2.1.0/24", subnet: "10.2.1.0/24",
}, },

View File

@ -79,11 +79,12 @@ type Node struct {
LastSeen int64 LastSeen int64
// Leader is a suggestion to Kilo that // Leader is a suggestion to Kilo that
// the node wants to lead its segment. // the node wants to lead its segment.
Leader bool Leader bool
Location string Location string
Name string Name string
Subnet *net.IPNet PersistentKeepalive int
WireGuardIP *net.IPNet Subnet *net.IPNet
WireGuardIP *net.IPNet
} }
// Ready indicates whether or not the node is ready. // Ready indicates whether or not the node is ready.

View File

@ -64,6 +64,9 @@ type segment struct {
hostnames []string hostnames []string
// leader is the index of the leader of the segment. // leader is the index of the leader of the segment.
leader int leader int
// persistentKeepalive is the interval in seconds of the emission
// of keepalive packets to the peer.
persistentKeepalive int
// privateIPs is a slice of private IPs of all peers in the segment. // privateIPs is a slice of private IPs of all peers in the segment.
privateIPs []net.IP privateIPs []net.IP
// wireGuardIP is the allocated IP address of the WireGuard // wireGuardIP is the allocated IP address of the WireGuard
@ -117,14 +120,15 @@ func NewTopology(nodes map[string]*Node, peers map[string]*Peer, granularity Gra
privateIPs = append(privateIPs, node.InternalIP.IP) privateIPs = append(privateIPs, node.InternalIP.IP)
} }
t.segments = append(t.segments, &segment{ t.segments = append(t.segments, &segment{
allowedIPs: allowedIPs, allowedIPs: allowedIPs,
endpoint: topoMap[location][leader].ExternalIP.IP, endpoint: topoMap[location][leader].ExternalIP.IP,
key: topoMap[location][leader].Key, key: topoMap[location][leader].Key,
location: location, location: location,
cidrs: cidrs, cidrs: cidrs,
hostnames: hostnames, hostnames: hostnames,
leader: leader, leader: leader,
privateIPs: privateIPs, privateIPs: privateIPs,
persistentKeepalive: topoMap[location][leader].PersistentKeepalive,
}) })
} }
// Sort the Topology segments so the result is stable. // Sort the Topology segments so the result is stable.
@ -334,7 +338,8 @@ func (t *Topology) Conf() *wireguard.Conf {
IP: s.endpoint, IP: s.endpoint,
Port: uint32(t.port), Port: uint32(t.port),
}, },
PublicKey: s.key, PublicKey: s.key,
PersistentKeepalive: s.persistentKeepalive,
} }
c.Peers = append(c.Peers, peer) c.Peers = append(c.Peers, peer)
} }
@ -363,7 +368,8 @@ func (t *Topology) AsPeer() *wireguard.Peer {
IP: s.endpoint, IP: s.endpoint,
Port: uint32(t.port), Port: uint32(t.port),
}, },
PublicKey: s.key, PersistentKeepalive: s.persistentKeepalive,
PublicKey: s.key,
} }
} }
return nil return nil
@ -379,7 +385,8 @@ func (t *Topology) PeerConf(name string) *wireguard.Conf {
IP: s.endpoint, IP: s.endpoint,
Port: uint32(t.port), Port: uint32(t.port),
}, },
PublicKey: s.key, PersistentKeepalive: s.persistentKeepalive,
PublicKey: s.key,
} }
c.Peers = append(c.Peers, peer) c.Peers = append(c.Peers, peer)
} }

View File

@ -39,12 +39,13 @@ func setup(t *testing.T) (map[string]*Node, map[string]*Peer, []byte, uint32) {
i2 := &net.IPNet{IP: net.ParseIP("192.168.0.2").To4(), Mask: net.CIDRMask(32, 32)} i2 := &net.IPNet{IP: net.ParseIP("192.168.0.2").To4(), Mask: net.CIDRMask(32, 32)}
nodes := map[string]*Node{ nodes := map[string]*Node{
"a": { "a": {
Name: "a", Name: "a",
ExternalIP: e1, ExternalIP: e1,
InternalIP: i1, InternalIP: i1,
Location: "1", Location: "1",
Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)}, Subnet: &net.IPNet{IP: net.ParseIP("10.2.1.0"), Mask: net.CIDRMask(24, 32)},
Key: []byte("key1"), Key: []byte("key1"),
PersistentKeepalive: 25,
}, },
"b": { "b": {
Name: "b", Name: "b",
@ -117,14 +118,15 @@ func TestNewTopology(t *testing.T) {
wireGuardCIDR: &net.IPNet{IP: w1, Mask: net.CIDRMask(16, 32)}, wireGuardCIDR: &net.IPNet{IP: w1, Mask: net.CIDRMask(16, 32)},
segments: []*segment{ segments: []*segment{
{ {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].ExternalIP.IP, endpoint: nodes["a"].ExternalIP.IP,
key: nodes["a"].Key, key: nodes["a"].Key,
location: nodes["a"].Location, location: nodes["a"].Location,
cidrs: []*net.IPNet{nodes["a"].Subnet}, cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"}, hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP}, privateIPs: []net.IP{nodes["a"].InternalIP.IP},
wireGuardIP: w1, persistentKeepalive: nodes["a"].PersistentKeepalive,
wireGuardIP: w1,
}, },
{ {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
@ -153,14 +155,15 @@ func TestNewTopology(t *testing.T) {
wireGuardCIDR: &net.IPNet{IP: w2, Mask: net.CIDRMask(16, 32)}, wireGuardCIDR: &net.IPNet{IP: w2, Mask: net.CIDRMask(16, 32)},
segments: []*segment{ segments: []*segment{
{ {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].ExternalIP.IP, endpoint: nodes["a"].ExternalIP.IP,
key: nodes["a"].Key, key: nodes["a"].Key,
location: nodes["a"].Location, location: nodes["a"].Location,
cidrs: []*net.IPNet{nodes["a"].Subnet}, cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"}, hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP}, privateIPs: []net.IP{nodes["a"].InternalIP.IP},
wireGuardIP: w1, persistentKeepalive: nodes["a"].PersistentKeepalive,
wireGuardIP: w1,
}, },
{ {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
@ -189,14 +192,15 @@ func TestNewTopology(t *testing.T) {
wireGuardCIDR: nil, wireGuardCIDR: nil,
segments: []*segment{ segments: []*segment{
{ {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].ExternalIP.IP, endpoint: nodes["a"].ExternalIP.IP,
key: nodes["a"].Key, key: nodes["a"].Key,
location: nodes["a"].Location, location: nodes["a"].Location,
cidrs: []*net.IPNet{nodes["a"].Subnet}, cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"}, hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP}, privateIPs: []net.IP{nodes["a"].InternalIP.IP},
wireGuardIP: w1, persistentKeepalive: nodes["a"].PersistentKeepalive,
wireGuardIP: w1,
}, },
{ {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, nodes["c"].Subnet, nodes["c"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
@ -225,14 +229,15 @@ func TestNewTopology(t *testing.T) {
wireGuardCIDR: &net.IPNet{IP: w1, Mask: net.CIDRMask(16, 32)}, wireGuardCIDR: &net.IPNet{IP: w1, Mask: net.CIDRMask(16, 32)},
segments: []*segment{ segments: []*segment{
{ {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].ExternalIP.IP, endpoint: nodes["a"].ExternalIP.IP,
key: nodes["a"].Key, key: nodes["a"].Key,
location: nodes["a"].Name, location: nodes["a"].Name,
cidrs: []*net.IPNet{nodes["a"].Subnet}, cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"}, hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP}, privateIPs: []net.IP{nodes["a"].InternalIP.IP},
wireGuardIP: w1, persistentKeepalive: nodes["a"].PersistentKeepalive,
wireGuardIP: w1,
}, },
{ {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
@ -271,14 +276,15 @@ func TestNewTopology(t *testing.T) {
wireGuardCIDR: &net.IPNet{IP: w2, Mask: net.CIDRMask(16, 32)}, wireGuardCIDR: &net.IPNet{IP: w2, Mask: net.CIDRMask(16, 32)},
segments: []*segment{ segments: []*segment{
{ {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].ExternalIP.IP, endpoint: nodes["a"].ExternalIP.IP,
key: nodes["a"].Key, key: nodes["a"].Key,
location: nodes["a"].Name, location: nodes["a"].Name,
cidrs: []*net.IPNet{nodes["a"].Subnet}, cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"}, hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP}, privateIPs: []net.IP{nodes["a"].InternalIP.IP},
wireGuardIP: w1, persistentKeepalive: nodes["a"].PersistentKeepalive,
wireGuardIP: w1,
}, },
{ {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
@ -317,14 +323,15 @@ func TestNewTopology(t *testing.T) {
wireGuardCIDR: &net.IPNet{IP: w3, Mask: net.CIDRMask(16, 32)}, wireGuardCIDR: &net.IPNet{IP: w3, Mask: net.CIDRMask(16, 32)},
segments: []*segment{ segments: []*segment{
{ {
allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["a"].Subnet, nodes["a"].InternalIP, {IP: w1, Mask: net.CIDRMask(32, 32)}},
endpoint: nodes["a"].ExternalIP.IP, endpoint: nodes["a"].ExternalIP.IP,
key: nodes["a"].Key, key: nodes["a"].Key,
location: nodes["a"].Name, location: nodes["a"].Name,
cidrs: []*net.IPNet{nodes["a"].Subnet}, cidrs: []*net.IPNet{nodes["a"].Subnet},
hostnames: []string{"a"}, hostnames: []string{"a"},
privateIPs: []net.IP{nodes["a"].InternalIP.IP}, privateIPs: []net.IP{nodes["a"].InternalIP.IP},
wireGuardIP: w1, persistentKeepalive: nodes["a"].PersistentKeepalive,
wireGuardIP: w1,
}, },
{ {
allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}}, allowedIPs: []*net.IPNet{nodes["b"].Subnet, nodes["b"].InternalIP, {IP: w2, Mask: net.CIDRMask(32, 32)}},
@ -1027,6 +1034,7 @@ AllowedIPs = 10.5.0.3/24
[Peer] [Peer]
PublicKey = key1 PublicKey = key1
Endpoint = 10.1.0.1:51820 Endpoint = 10.1.0.1:51820
PersistentKeepalive = 25
AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32 AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32
[Peer] [Peer]
@ -1051,6 +1059,7 @@ AllowedIPs = 10.5.0.3/24
[Peer] [Peer]
PublicKey = key1 PublicKey = key1
Endpoint = 10.1.0.1:51820 Endpoint = 10.1.0.1:51820
PersistentKeepalive = 25
AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32 AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32
[Peer] [Peer]
@ -1104,6 +1113,7 @@ AllowedIPs = 10.5.0.3/24
[Peer] [Peer]
PublicKey = key1 PublicKey = key1
Endpoint = 10.1.0.1:51820 Endpoint = 10.1.0.1:51820
PersistentKeepalive = 25
AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32 AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32
[Peer] [Peer]
@ -1133,6 +1143,7 @@ AllowedIPs = 10.5.0.3/24
[Peer] [Peer]
PublicKey = key1 PublicKey = key1
Endpoint = 10.1.0.1:51820 Endpoint = 10.1.0.1:51820
PersistentKeepalive = 25
AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32 AllowedIPs = 10.2.1.0/24, 192.168.0.1/32, 10.4.0.1/32
[Peer] [Peer]