Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cross mesh granularity #328

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/kg/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ var (
availableGranularities = strings.Join([]string{
string(mesh.LogicalGranularity),
string(mesh.FullGranularity),
string(mesh.CrossGranularity),
}, ", ")
availableLogLevels = strings.Join([]string{
logLevelAll,
Expand Down Expand Up @@ -223,6 +224,7 @@ func runRoot(_ *cobra.Command, _ []string) error {
switch gr {
case mesh.LogicalGranularity:
case mesh.FullGranularity:
case mesh.CrossGranularity:
default:
return fmt.Errorf("mesh granularity %v unknown; possible values are: %s", granularity, availableGranularities)
}
Expand Down
5 changes: 4 additions & 1 deletion cmd/kgctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ var (
availableGranularities = strings.Join([]string{
string(mesh.LogicalGranularity),
string(mesh.FullGranularity),
string(mesh.CrossGranularity),
string(mesh.AutoGranularity),
}, ", ")
availableLogLevels = strings.Join([]string{
Expand Down Expand Up @@ -80,6 +81,7 @@ func runRoot(c *cobra.Command, _ []string) error {
switch opts.granularity {
case mesh.LogicalGranularity:
case mesh.FullGranularity:
case mesh.CrossGranularity:
case mesh.AutoGranularity:
default:
return fmt.Errorf("mesh granularity %s unknown; posible values are: %s", granularity, availableGranularities)
Expand Down Expand Up @@ -151,8 +153,9 @@ func determineGranularity(gr mesh.Granularity, ns []*mesh.Node) (mesh.Granularit
switch ret {
case mesh.LogicalGranularity:
case mesh.FullGranularity:
case mesh.CrossGranularity:
default:
return ret, fmt.Errorf("mesh granularity %s is not supported", opts.granularity)
return ret, fmt.Errorf("mesh granularity %s is not supported", ret)
}
return ret, nil
}
Expand Down
2 changes: 1 addition & 1 deletion docs/kg.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Flags:
--local Should Kilo manage routes within a location? (default true)
--log-level string Log level to use. Possible values: all, debug, info, warn, error, none (default "info")
--master string The address of the Kubernetes API server (overrides any value in kubeconfig).
--mesh-granularity string The granularity of the network mesh to create. Possible values: location, full (default "location")
--mesh-granularity string The granularity of the network mesh to create. Possible values: location, full, cross (default "location")
--mtu uint The MTU of the WireGuard interface created by Kilo. (default 1420)
--port int The port over which WireGuard peers should communicate. (default 51820)
--prioritise-private-addresses Prefer to assign a private IP address to the node's endpoint.
Expand Down
5 changes: 5 additions & 0 deletions docs/topology.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ kgctl graph | circo -Tsvg > cluster.svg

<img src="./graphs/full-mesh.svg" />

# Cross Mesh

In this topology all nodes within the same location are not encrypted. Traffic to any other node outside of current location is encrypted
with direct node-to-node encryption. To use this mesh specify `--mesh-granularity=cross`.

## Mixed

The `kilo.squat.ai/location` annotation can be used to create cluster mixing some fully meshed nodes and some nodes grouped by logical location.
Expand Down
1 change: 1 addition & 0 deletions pkg/k8s/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ func translateNode(node *v1.Node, topologyLabel string) *mesh.Node {
switch meshGranularity {
case mesh.LogicalGranularity:
case mesh.FullGranularity:
case mesh.CrossGranularity:
default:
meshGranularity = ""
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/mesh/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ const (
// FullGranularity indicates that the network should create
// a mesh between every node.
FullGranularity Granularity = "full"
// CrossGranularity indicates that network is encrypted only
// between nodes in different locations.
CrossGranularity Granularity = "cross"
// AutoGranularity can be used with kgctl to obtain
// the granularity automatically.
AutoGranularity Granularity = "auto"
Expand Down
32 changes: 25 additions & 7 deletions pkg/mesh/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,24 @@ func (t *Topology) Dot() (string, error) {
}

for i, s := range t.segments {
if err := g.AddSubGraph("kilo", subGraphName(s.location), nil); err != nil {
location := s.location
plainConnection := false
if s.nodeLocation != "" {
location = s.nodeLocation
plainConnection = true
}

if err := g.AddSubGraph("kilo", subGraphName(location), nil); err != nil {
return "", fmt.Errorf("failed to add subgraph")
}
if err := g.AddAttr(subGraphName(s.location), string(gographviz.Label), graphEscape(s.location)); err != nil {
if err := g.AddAttr(subGraphName(location), string(gographviz.Label), graphEscape(location)); err != nil {
return "", fmt.Errorf("failed to add label to subgraph")
}
if err := g.AddAttr(subGraphName(s.location), string(gographviz.Style), `"dashed,rounded"`); err != nil {
if err := g.AddAttr(subGraphName(location), string(gographviz.Style), `"dashed,rounded"`); err != nil {
return "", fmt.Errorf("failed to add style to subgraph")
}
for j := range s.cidrs {
if err := g.AddNode(subGraphName(s.location), graphEscape(s.hostnames[j]), nodeAttrs); err != nil {
if err := g.AddNode(subGraphName(location), graphEscape(s.hostnames[j]), nodeAttrs); err != nil {
return "", fmt.Errorf("failed to add node to subgraph")
}
var wg net.IP
Expand All @@ -75,11 +82,11 @@ func (t *Topology) Dot() (string, error) {
if s.privateIPs != nil {
priv = s.privateIPs[j]
}
if err := g.Nodes.Lookup[graphEscape(s.hostnames[j])].Attrs.Add(string(gographviz.Label), nodeLabel(s.location, s.hostnames[j], s.cidrs[j], priv, wg, endpoint)); err != nil {
if err := g.Nodes.Lookup[graphEscape(s.hostnames[j])].Attrs.Add(string(gographviz.Label), nodeLabel(location, s.hostnames[j], s.cidrs[j], priv, wg, endpoint)); err != nil {
return "", fmt.Errorf("failed to add label to node")
}
}
meshSubGraph(g, g.Relations.SortedChildren(subGraphName(s.location)), s.leader, nil)
meshSubGraph(g, g.Relations.SortedChildren(subGraphName(location)), s.leader, plainConnection, nil)
leaders[i] = graphEscape(s.hostnames[s.leader])
}
meshGraph(g, leaders, nil)
Expand Down Expand Up @@ -116,15 +123,26 @@ func meshGraph(g *gographviz.Graph, nodes []string, attrs gographviz.Attrs) {
if i == j {
continue
}
dsts := g.Edges.SrcToDsts[nodes[i]]
if dsts != nil && len(dsts[nodes[j]]) != 0 {
// nodes already connected via plain connection
continue
}

g.Edges.Add(&gographviz.Edge{Src: nodes[i], Dst: nodes[j], Dir: true, Attrs: attrs})
}
}
}

func meshSubGraph(g *gographviz.Graph, nodes []string, leader int, attrs gographviz.Attrs) {
func meshSubGraph(g *gographviz.Graph, nodes []string, leader int, plainConnection bool, attrs gographviz.Attrs) {
if attrs == nil {
attrs = make(gographviz.Attrs)
attrs[gographviz.Dir] = "both"
if plainConnection {
attrs[gographviz.Style] = "dotted"
attrs[gographviz.ArrowHead] = "none"
attrs[gographviz.ArrowTail] = "none"
}
}
for i := range nodes {
if i == leader {
Expand Down
2 changes: 1 addition & 1 deletion pkg/mesh/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (t *Topology) Routes(kiloIfaceName string, kiloIface, privIface, tunlIface
}
for _, segment := range t.segments {
// Add routes for the current segment if local is true.
if segment.location == t.location {
if (segment.location == t.location) || (t.nodeLocation != "" && segment.nodeLocation == t.nodeLocation) {
// If the local node does not have a private IP address,
// then skip adding routes, because the node is in its own location.
if local && t.privateIP != nil {
Expand Down
62 changes: 41 additions & 21 deletions pkg/mesh/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ type Topology struct {
// key is the private key of the node creating the topology.
key wgtypes.Key
port int
// Location is the logical location of the local host.
// location is the logical location of the local host.
location string
segments []*segment
peers []*Peer
// nodeLocation is the location annotation of the node. This is set only in cross location topology.
nodeLocation string
segments []*segment
peers []*Peer

// hostname is the hostname of the local host.
hostname string
Expand Down Expand Up @@ -71,8 +73,10 @@ type segment struct {
endpoint *wireguard.Endpoint
key wgtypes.Key
persistentKeepalive time.Duration
// Location is the logical location of this segment.
// location is the logical location of this segment.
location string
// nodeLocation is the node location annotation. This is set only for cross location topology.
nodeLocation string

// cidrs is a slice of subnets of all peers in the segment.
cidrs []*net.IPNet
Expand All @@ -91,14 +95,34 @@ type segment struct {
allowedLocationIPs []net.IPNet
}

// topoKey is used to group nodes into locations.
type topoKey struct {
location string
nodeLocation string
}

// NewTopology creates a new Topology struct from a given set of nodes and peers.
func NewTopology(nodes map[string]*Node, peers map[string]*Peer, granularity Granularity, hostname string, port int, key wgtypes.Key, subnet *net.IPNet, persistentKeepalive time.Duration, logger log.Logger) (*Topology, error) {
if logger == nil {
logger = log.NewNopLogger()
}
topoMap := make(map[string][]*Node)
topoMap := make(map[topoKey][]*Node)
var localLocation, localNodeLocation string
switch granularity {
case LogicalGranularity:
localLocation = logicalLocationPrefix + nodes[hostname].Location
if nodes[hostname].InternalIP == nil {
localLocation = nodeLocationPrefix + hostname
}
case FullGranularity:
localLocation = nodeLocationPrefix + hostname
case CrossGranularity:
localLocation = nodeLocationPrefix + hostname
localNodeLocation = logicalLocationPrefix + nodes[hostname].Location
}

for _, node := range nodes {
var location string
var location, nodeLocation string
switch granularity {
case LogicalGranularity:
location = logicalLocationPrefix + node.Location
Expand All @@ -109,25 +133,20 @@ func NewTopology(nodes map[string]*Node, peers map[string]*Peer, granularity Gra
}
case FullGranularity:
location = nodeLocationPrefix + node.Name
case CrossGranularity:
location = nodeLocationPrefix + node.Name
nodeLocation = logicalLocationPrefix + node.Location
}
topoMap[location] = append(topoMap[location], node)
}
var localLocation string
switch granularity {
case LogicalGranularity:
localLocation = logicalLocationPrefix + nodes[hostname].Location
if nodes[hostname].InternalIP == nil {
localLocation = nodeLocationPrefix + hostname
}
case FullGranularity:
localLocation = nodeLocationPrefix + hostname
key := topoKey{location: location, nodeLocation: nodeLocation}
topoMap[key] = append(topoMap[key], node)
}

t := Topology{
key: key,
port: port,
hostname: hostname,
location: localLocation,
nodeLocation: localNodeLocation,
persistentKeepalive: persistentKeepalive,
privateIP: nodes[hostname].InternalIP,
subnet: nodes[hostname].Subnet,
Expand All @@ -141,7 +160,7 @@ func NewTopology(nodes map[string]*Node, peers map[string]*Peer, granularity Gra
return topoMap[location][i].Name < topoMap[location][j].Name
})
leader := findLeader(topoMap[location])
if location == localLocation && topoMap[location][leader].Name == hostname {
if location.nodeLocation != "" || (location.location == localLocation && topoMap[location][leader].Name == hostname) {
t.leader = true
}
var allowedIPs []net.IPNet
Expand Down Expand Up @@ -181,7 +200,8 @@ func NewTopology(nodes map[string]*Node, peers map[string]*Peer, granularity Gra
endpoint: topoMap[location][leader].Endpoint,
key: topoMap[location][leader].Key,
persistentKeepalive: topoMap[location][leader].PersistentKeepalive,
location: location,
location: location.location,
nodeLocation: location.nodeLocation,
cidrs: cidrs,
hostnames: hostnames,
leader: leader,
Expand Down Expand Up @@ -225,7 +245,7 @@ func NewTopology(nodes map[string]*Node, peers map[string]*Peer, granularity Gra

// Now that the topology is ordered, update the discoveredEndpoints map
// add new ones by going through the ordered topology: segments, nodes
for _, node := range topoMap[segment.location] {
for _, node := range topoMap[topoKey{location: segment.location, nodeLocation: segment.nodeLocation}] {
for key := range node.DiscoveredEndpoints {
if _, ok := t.discoveredEndpoints[key]; !ok {
t.discoveredEndpoints[key] = node.DiscoveredEndpoints[key]
Expand Down Expand Up @@ -308,7 +328,7 @@ func (t *Topology) Conf() *wireguard.Conf {
},
}
for _, s := range t.segments {
if s.location == t.location {
if (s.location == t.location) || (t.nodeLocation != "" && t.nodeLocation == s.nodeLocation) {
continue
}
peer := wireguard.Peer{
Expand Down
Loading