refactor: move IPsec certificate handling to network host

This commit is contained in:
joshsiv-mulga
2026-10-06 15:39:43 +10:00
parent a8d218accb
commit 454defb005
5 changed files with 44 additions and 11 deletions
+2 -2
View File
@@ -1668,7 +1668,7 @@ func runAdminInit(cmd *cobra.Command, args []string) {
if ipsecEnabled {
caCertPath := filepath.Join(configDir, "ca.pem")
caKeyPath := filepath.Join(configDir, "ca.key")
if err := admin.GenerateIPSecPeerCert(configDir, caCertPath, caKeyPath, node, bindIP); err != nil {
if err := host.GenerateIPSecPeerCert(configDir, caCertPath, caKeyPath, node, bindIP); err != nil {
fmt.Fprintf(os.Stderr, "Error generating IPsec peer certificate: %v\n", err)
os.Exit(1)
}
@@ -2655,7 +2655,7 @@ func runAdminJoin(cmd *cobra.Command, args []string) {
if ipsecEnabled {
caCertPath := filepath.Join(configDir, "ca.pem")
caKeyPath := filepath.Join(configDir, "ca.key")
if err := admin.GenerateIPSecPeerCert(configDir, caCertPath, caKeyPath, node, bindIP); err != nil {
if err := host.GenerateIPSecPeerCert(configDir, caCertPath, caKeyPath, node, bindIP); err != nil {
fmt.Fprintf(os.Stderr, "Error generating IPsec peer certificate: %v\n", err)
os.Exit(1)
}
+1 -1
View File
@@ -1297,7 +1297,7 @@ fi
# explicitly given. Reasons to pin:
# 1. IPsec identity: ovs-monitor-ipsec uses chassis-id as the IKEv2
# `@<name>` peer identity. Our per-node IPsec peer cert
# (admin.GenerateIPSecPeerCert) carries the cluster node name as CN
# (host.GenerateIPSecPeerCert) carries the cluster node name as CN
# + dnsName SAN — see spx admin init/join, which take --node NAME and
# bake NAME into the cert. Leaving chassis-id as the package-generated
# UUID would cause `received AUTHENTICATION_FAILED` because charon
@@ -1,4 +1,4 @@
package admin
package host
import (
"crypto/rand"
@@ -29,6 +29,10 @@ const (
ipsecCACertSymlink = "ca.pem"
)
// ipsecCertKeyBits is the RSA key size for IPsec peer certificates. It is a
// seam so tests can lower it for faster key generation.
var ipsecCertKeyBits = 4096
// id-kp-ipsecIKE per RFC 4945 §5.1.3.12. strongSwan accepts certs carrying
// this EKU as IKEv2 peer credentials; ovs-monitor-ipsec's auto-generated
// strongSwan config consumes them as-is.
@@ -61,7 +65,7 @@ func GenerateIPSecPeerCert(configDir, caCertPath, caKeyPath, hostname, nodeIP st
return err
}
peerKey, err := rsa.GenerateKey(rand.Reader, certKeyBits)
peerKey, err := rsa.GenerateKey(rand.Reader, ipsecCertKeyBits)
if err != nil {
return fmt.Errorf("ipsec peer cert: generate key: %w", err)
}
@@ -1,19 +1,49 @@
package admin
package host
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestGenerateIPSecPeerCert shares a CA across subtests so the 4096-bit
// CA-key generation runs once (~0.7s).
func init() { ipsecCertKeyBits = 2048 }
// writeTestCA writes a throwaway self-signed RSA CA in the PEM/PKCS8 form the
// IPsec issuer loads.
func writeTestCA(t *testing.T, certPath, keyPath string) {
t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "test CA"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
BasicConstraintsValid: true,
IsCA: true,
}
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
require.NoError(t, err)
keyDER, err := x509.MarshalPKCS8PrivateKey(key)
require.NoError(t, err)
require.NoError(t, os.WriteFile(certPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0o644))
require.NoError(t, os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), 0o600))
}
// TestGenerateIPSecPeerCert shares a CA across subtests so the CA-key
// generation runs once.
//
//nolint:tparallel // the parent cannot be parallel: it overrides the charonCATrustDir/charonRereadCAs package vars for the duration of the test
func TestGenerateIPSecPeerCert(t *testing.T) {
@@ -33,7 +63,7 @@ func TestGenerateIPSecPeerCert(t *testing.T) {
caDir := t.TempDir()
caCertPath := filepath.Join(caDir, "ca.pem")
caKeyPath := filepath.Join(caDir, "ca.key")
require.NoError(t, GenerateCACert(caCertPath, caKeyPath))
writeTestCA(t, caCertPath, caKeyPath)
t.Run("happy path", func(t *testing.T) {
t.Parallel()
+1 -2
View File
@@ -12,7 +12,6 @@ import (
"strings"
"time"
"github.com/mulgadc/spinifex/spinifex/admin"
"github.com/mulgadc/spinifex/spinifex/bootstrap/config"
"github.com/mulgadc/spinifex/spinifex/foundation/telemetry"
hostcommand "github.com/mulgadc/spinifex/spinifex/runtime/host/command"
@@ -274,7 +273,7 @@ func EnableOVNIPSec(ctx context.Context, configPath string, clusterConfig *confi
// turn on encapsulation, once ovs-monitor-ipsec is up to act on both.
func configureLocalIPSec(configPath string) error {
configDir := filepath.Dir(configPath)
certPath, keyPath := admin.IPSecCertPaths(configDir)
certPath, keyPath := IPSecCertPaths(configDir)
caCertPath := filepath.Join(configDir, "ca.pem")
for _, p := range []string{certPath, keyPath, caCertPath} {