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).
This commit is contained in:
joshsiv-mulga
2026-05-22 15:48:13 +10:00
parent 747bd6dae7
commit d229a57d3a
3 changed files with 55 additions and 0 deletions
+8
View File
@@ -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
+42
View File
@@ -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()
+5
View File
@@ -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"`