From d229a57d3a121bb3d4dfd33947f2ffd7de39ce13 Mon Sep 17 00:00:00 2001 From: joshsiv-mulga Date: Fri, 22 May 2026 15:48:13 +1000 Subject: [PATCH] feat(config): add network.ipsec_enabled cluster flag (default true) Add IPSecEnabled to NetworkConfig in both the cluster TOML schema and the formation handshake payload. Default true so AWS-parity intra-AZ encryption is on by default. Operators can flip false in [network] for trusted single-rack lab topologies. Phase 2.5 (bead mulga-siv-125.3.5) consumes the flag from admin init (strongSwan provisioning), formation server (propagates to joiners), and daemon startup (ovs-vsctl ipsec_encapsulation toggle). --- spinifex/config/config.go | 8 +++++++ spinifex/config/config_test.go | 42 +++++++++++++++++++++++++++++++++ spinifex/formation/formation.go | 5 ++++ 3 files changed, 55 insertions(+) diff --git a/spinifex/config/config.go b/spinifex/config/config.go index ab8d766e3..be11751e3 100644 --- a/spinifex/config/config.go +++ b/spinifex/config/config.go @@ -46,6 +46,10 @@ type ExternalPool struct { type NetworkConfig struct { ExternalMode string `mapstructure:"external_mode"` // "pool" or "" (disabled) ExternalPools []ExternalPool `mapstructure:"external_pools"` // One or more IP pools + // IPSecEnabled toggles OVN native IPsec on every node for intra-AZ Geneve + // (AES-256-GCM via strongSwan). Default true. Operators can flip off for + // trusted single-rack lab topologies; production edge stays on. + IPSecEnabled bool `mapstructure:"ipsec_enabled"` } // BootstrapConfig holds the default VPC infrastructure IDs written by admin init. @@ -205,6 +209,10 @@ func LoadConfig(configPath string) (*ClusterConfig, error) { viper.SetEnvPrefix("SPINIFEX") viper.AutomaticEnv() + // network.ipsec_enabled defaults to true (intra-AZ Geneve encrypted via OVN + // native IPsec). Operators must explicitly set false to disable. + viper.SetDefault("network.ipsec_enabled", true) + // Try to load config file if it exists if configPath != "" { // Check if file exists diff --git a/spinifex/config/config_test.go b/spinifex/config/config_test.go index 002058371..fefea1cf2 100644 --- a/spinifex/config/config_test.go +++ b/spinifex/config/config_test.go @@ -375,6 +375,48 @@ external_interface = "enp0s3" assert.Equal(t, "enp0s3", n.VPCD.ExternalInterface) } +func TestLoadConfig_NetworkIPSecEnabledDefaultsTrue(t *testing.T) { + resetViper(t) + dir := t.TempDir() + path := filepath.Join(dir, "spinifex.toml") + + // No [network] block at all — IPSec must default to true so AWS-parity + // edge deployments encrypt intra-AZ Geneve without operator opt-in. + toml := ` +node = "n1" + +[nodes.n1] +region = "us-east-1" +` + require.NoError(t, os.WriteFile(path, []byte(toml), 0600)) + + cfg, err := LoadConfig(path) + require.NoError(t, err) + assert.True(t, cfg.Network.IPSecEnabled, "default") +} + +func TestLoadConfig_NetworkIPSecEnabledExplicitFalse(t *testing.T) { + resetViper(t) + dir := t.TempDir() + path := filepath.Join(dir, "spinifex.toml") + + // Operator escape hatch for trusted single-rack lab deployments. + toml := ` +node = "n1" + +[network] +ipsec_enabled = false + +[nodes.n1] +region = "us-east-1" +` + require.NoError(t, os.WriteFile(path, []byte(toml), 0600)) + + cfg, err := LoadConfig(path) + require.NoError(t, err) + assert.False(t, cfg.Network.IPSecEnabled) +} + func TestLoadConfig_NetworkPoolDHCPSourceRejected(t *testing.T) { resetViper(t) dir := t.TempDir() diff --git a/spinifex/formation/formation.go b/spinifex/formation/formation.go index 54000bd41..35a6447d2 100644 --- a/spinifex/formation/formation.go +++ b/spinifex/formation/formation.go @@ -71,6 +71,11 @@ type NetworkConfig struct { PoolPrefixLen int `json:"pool_prefix_len"` PoolDNSServers []string `json:"pool_dns_servers,omitempty"` + // IPSecEnabled propagates the cluster-wide intra-AZ IPsec toggle so the + // joining node provisions strongSwan and flips OVS ipsec_encapsulation to + // match the rest of the cluster. + IPSecEnabled bool `json:"ipsec_enabled"` + BootstrapAccountId string `json:"bootstrap_account_id,omitempty"` BootstrapVpcId string `json:"bootstrap_vpc_id,omitempty"` BootstrapSubnetId string `json:"bootstrap_subnet_id,omitempty"`