Skip to content

Commit

Permalink
microcloud/cmd: Check uplink network config before setting up the clu…
Browse files Browse the repository at this point in the history
…ster

Fixes #210

Signed-off-by: Wesley Hershberger <[email protected]>
  • Loading branch information
MggMuggins committed Apr 4, 2024
1 parent 7c47cde commit fba259c
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 0 deletions.
81 changes: 81 additions & 0 deletions microcloud/cmd/microcloud/main_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string) error {
return err
}

err = validateSystems(s, systems)
if err != nil {
return err
}

err = setupCluster(s, systems)
if err != nil {
return err
Expand Down Expand Up @@ -414,6 +419,82 @@ func AddPeers(sh *service.Handler, systems map[string]InitSystem) error {
return nil
}

func ensureSystemsOutsideGateway(gateway string, localAddr string, systems map[string]InitSystem) error {
_, ip4Net, err := net.ParseCIDR(gateway)
if err != nil {
return fmt.Errorf("Invalid ipv4.gateway for UPLINK: %w", err)
}

localAddrIP := net.ParseIP(localAddr)
if localAddrIP == nil {
return fmt.Errorf("Invalid local address %q", localAddr)
}

if ip4Net.Contains(localAddrIP) {
return fmt.Errorf("UPLINK ipv4.gateway must not include local address %q", localAddr)
}

for _, system := range systems {
systemAddr := net.ParseIP(system.ServerInfo.Address)
if systemAddr == nil {
return fmt.Errorf("Invalid address %q for system %q", system.ServerInfo.Address, system.ServerInfo.Name)
}

if ip4Net.Contains(systemAddr) {
return fmt.Errorf("UPLINK ipv4.gateway must not include system address %q", systemAddr)
}
}

return nil
}

func validateSystems(s *service.Handler, systems map[string]InitSystem) error {
_, bootstrap := systems[s.Name]
if !bootstrap {
return nil
}

for _, curSystem := range systems {
for _, network := range curSystem.Networks {
if network.Type != "physical" || network.Name != "UPLINK" {
continue
}

ip4Gateway, hasIP4Gateway := network.Config["ipv4.gateway"]
ip6Gateway, hasIP6Gateway := network.Config["ipv6.gateway"]
ovnRanges, hasOVNRanges := network.Config["ipv4.ovn.ranges"]

if hasIP4Gateway {
err := ensureSystemsOutsideGateway(ip4Gateway, s.Address, systems)
if err != nil {
return err
}
}

if hasIP6Gateway {
err := ensureSystemsOutsideGateway(ip6Gateway, s.Address, systems)
if err != nil {
return err
}
}

if hasIP4Gateway && hasOVNRanges {
_, ip4GatewayNet, err := net.ParseCIDR(ip4Gateway)
if err != nil {
return fmt.Errorf("Invalid ipv4.gateway %q: %w", ip4Gateway, err)
}

_, err = shared.ParseIPRanges(ovnRanges, ip4GatewayNet)
if err != nil {
return fmt.Errorf("Invalid ipv4.ovn.ranges %q: %w", ovnRanges, err)
}
}
}
}

return nil
}

// setupCluster Bootstraps the cluster if necessary, adds all peers to the cluster, and completes any post cluster
// configuration.
func setupCluster(s *service.Handler, systems map[string]InitSystem) error {
Expand Down
5 changes: 5 additions & 0 deletions microcloud/cmd/microcloud/main_init_preseed.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ func (c *CmdControl) RunPreseed(cmd *cobra.Command, init bool) error {
}
}

err = validateSystems(s, systems)
if err != nil {
return err
}

return setupCluster(s, systems)
}

Expand Down
189 changes: 189 additions & 0 deletions microcloud/cmd/microcloud/main_init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package main

import (
"testing"

lxdAPI "github.com/canonical/lxd/shared/api"

"github.com/canonical/microcloud/microcloud/mdns"
"github.com/canonical/microcloud/microcloud/service"
)

func newSystemWithNetworks(systemName string, networks []lxdAPI.NetworksPost) InitSystem {
return InitSystem{
ServerInfo: mdns.ServerInfo{
Name: systemName,
Address: "192.168.1.28",
},
Networks: networks,
}
}

func newSystemWithUplinkNetConfig(systemName string, config map[string]string) InitSystem {
return newSystemWithNetworks(systemName, []lxdAPI.NetworksPost{{
Name: "UPLINK",
Type: "physical",
NetworkPut: lxdAPI.NetworkPut{
Config: config,
},
}})
}

func newTestHandler(addr string, t *testing.T) *service.Handler {
handler, err := service.NewHandler("testSystem", addr, "/tmp/microcloud_test_hander", true, true)
if err != nil {
t.Fatalf("Failed to create test service handler: %s", err)
}

return handler
}

func newTestSystemsMap(systems ...InitSystem) map[string]InitSystem {
systemsMap := map[string]InitSystem{
// The handler must have the same name as one of the systems in order
// for validateSystems to perform validation
// (we must be "bootstrapping a cluster")
"testSystem": newSystemWithNetworks("testSystem", []lxdAPI.NetworksPost{}),
}

for _, system := range systems {
systemsMap[system.ServerInfo.Name] = system
}

return systemsMap
}

func ensureValidateSystemsPasses(handler *service.Handler, testSystems []InitSystem, t *testing.T) {
for _, system := range testSystems {
systems := newTestSystemsMap(system)

err := validateSystems(handler, systems)
if err != nil {
t.Fatalf("Valid system %q failed validate: %s", system.ServerInfo.Name, err)
}
}
}

func ensureValidateSystemsFails(handler *service.Handler, testSystems []InitSystem, t *testing.T) {
for _, system := range testSystems {
systems := newTestSystemsMap(system)

err := validateSystems(handler, systems)
if err == nil {
t.Fatalf("Invalid system %q passed validation", system.ServerInfo.Name)
}
}
}

func TestValidateSystemsIP6(t *testing.T) {
handler := newTestHandler("fc00:feed:beef::bed1", t)

validSystems := []InitSystem{
newSystemWithUplinkNetConfig("64Net", map[string]string{
"ipv6.gateway": "fc00:bad:feed::1/64",
}),
}

ensureValidateSystemsPasses(handler, validSystems, t)

invalidSystems := []InitSystem{
newSystemWithUplinkNetConfig("uplinkInsideManagement6Net", map[string]string{
"ipv6.gateway": "fc00:feed:beef::1/64",
}),
}

ensureValidateSystemsFails(handler, invalidSystems, t)
}

func TestValidateSystemsIP4(t *testing.T) {
handler := newTestHandler("192.168.1.27", t)

validSystems := []InitSystem{
newSystemWithUplinkNetConfig("plainGateway", map[string]string{
"ipv4.gateway": "10.234.0.1/16",
}),
newSystemWithUplinkNetConfig("16Net", map[string]string{
"ipv4.gateway": "10.42.0.1/16",
"ipv4.ovn.ranges": "10.42.1.1-10.42.5.255",
}),
newSystemWithUplinkNetConfig("24Net", map[string]string{
"ipv4.gateway": "190.168.4.1/24",
"ipv4.ovn.ranges": "190.168.4.50-190.168.4.60",
}),
}

ensureValidateSystemsPasses(handler, validSystems, t)

invalidSystems := []InitSystem{
//"gatewayNotCIDR": newSystemWithUplinkNetwork(map[string]string{
// "ipv4.gateway": "192.168.1.1",
//}),
newSystemWithUplinkNetConfig("backwardsRange", map[string]string{
"ipv4.gateway": "10.42.0.1/16",
"ipv4.ovn.ranges": "10.42.5.255-10.42.1.1",
}),
newSystemWithUplinkNetConfig("rangesOutsideGateway", map[string]string{
"ipv4.gateway": "10.1.1.0/24",
"ipv4.ovn.ranges": "10.2.2.50-10.2.2.100",
}),
newSystemWithUplinkNetConfig("uplinkInsideManagementNet", map[string]string{
"ipv4.gateway": "192.168.1.1/24",
"ipv4.ovn.ranges": "192.168.1.50-192.168.1.200",
}),
newSystemWithUplinkNetConfig("uplinkInsideManagementNetNoRange", map[string]string{
"ipv4.gateway": "192.168.1.1/16",
}),
}

ensureValidateSystemsFails(handler, invalidSystems, t)
}

func TestValidateSystemsMultiSystem(t *testing.T) {
handler := newTestHandler("10.23.1.72", t)

sys1 := newSystemWithUplinkNetConfig("sys1", map[string]string{
"ipv4.gateway": "10.100.1.1/16",
})

sys2 := newSystemWithNetworks("sys2", []lxdAPI.NetworksPost{
{
Name: "default",
Type: "ovn",
NetworkPut: lxdAPI.NetworkPut{
Config: map[string]string{
"ipv4.address": "10.100.20.1/24",
},
},
},
})

systems := newTestSystemsMap(sys1, sys2)

err := validateSystems(handler, systems)
if err != nil {
t.Fatalf("InitSystems with conflicting uplink and default networks passed validation")
}

sys3 := newSystemWithUplinkNetConfig("sys3", map[string]string{
"ipv6.gateway": "fc00:bad:feed::1/64",
})

sys4 := newSystemWithNetworks("sys4", []lxdAPI.NetworksPost{
{
Name: "default",
Type: "ovn",
NetworkPut: lxdAPI.NetworkPut{
Config: map[string]string{
"ipv6.address": "fc00:bad:feed::1/32",
},
},
},
})

systems = newTestSystemsMap(sys3, sys4)

err = validateSystems(handler, systems)
if err != nil {
t.Fatalf("InitSystems with conflicting uplink and default networks passed validation")
}
}

0 comments on commit fba259c

Please sign in to comment.