isle/go/daemon/children/nebula.go

227 lines
5.1 KiB
Go
Raw Permalink Normal View History

package children
import (
"context"
"fmt"
"io"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/toolkit"
"isle/yamlutil"
"net"
"path/filepath"
2024-11-17 13:19:46 +00:00
"strconv"
"code.betamike.com/micropelago/pmux/pmuxlib"
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"github.com/slackhq/nebula/cert"
"gopkg.in/yaml.v3"
)
// waitForNebula waits for the nebula interface to have been started up. It does
// this by attempting to create a UDP connection which has the nebula IP set as
// its source. If this succeeds we can assume that at the very least the nebula
// interface has been initialized.
func waitForNebula(
ctx context.Context, logger *mlog.Logger, hostBootstrap bootstrap.Bootstrap,
) error {
var (
ip = net.IP(hostBootstrap.ThisHost().IP().AsSlice())
lUDPAddr = &net.UDPAddr{IP: ip, Port: 0}
rUDPAddr = &net.UDPAddr{IP: ip, Port: 45535}
)
ctx = mctx.Annotate(ctx, "lUDPAddr", lUDPAddr, "rUDPAddr", rUDPAddr)
until(
ctx,
logger,
"Checking if nebula is online by creating UDP socket from nebula IP",
func(context.Context) error {
conn, err := net.DialUDP("udp", lUDPAddr, rUDPAddr)
if err != nil {
return err
}
conn.Close()
return nil
},
)
return ctx.Err()
}
func nebulaConfig(
deviceNamer *NebulaDeviceNamer,
networkConfig daecommon.NetworkConfig,
hostBootstrap bootstrap.Bootstrap,
) (
any, error,
) {
var (
lighthouseHostIPs []string
staticHostMap = yamlutil.OrderedMap[string, []string]{}
)
for _, host := range hostBootstrap.HostsOrdered() {
if host.Nebula.PublicAddr == "" {
continue
}
ip := host.IP().String()
lighthouseHostIPs = append(lighthouseHostIPs, ip)
staticHostMap[ip] = []string{host.Nebula.PublicAddr}
}
caCertPEM, err := hostBootstrap.CAPublicCredentials.Cert.Unwrap().MarshalToPEM()
if err != nil {
return nil, fmt.Errorf("marshaling CA cert to PEM: :%w", err)
}
hostCertPEM, err := hostBootstrap.PublicCredentials.Cert.Unwrap().MarshalToPEM()
if err != nil {
return nil, fmt.Errorf("marshaling host cert to PEM: :%w", err)
}
hostKeyPEM := cert.MarshalX25519PrivateKey(
hostBootstrap.PrivateCredentials.EncryptingPrivateKey.Bytes(),
)
2024-11-17 13:19:46 +00:00
firewall := networkConfig.VPN.Firewall
for _, alloc := range networkConfig.Storage.Allocations {
firewall.Inbound = append(
firewall.Inbound,
daecommon.ConfigFirewallRule{
Port: strconv.Itoa(alloc.S3APIPort),
Proto: "tcp",
Host: "any",
},
daecommon.ConfigFirewallRule{
Port: strconv.Itoa(alloc.RPCPort),
Proto: "tcp",
Host: "any",
},
)
}
type m = yamlutil.OrderedMap[string, any]
config := m{
"pki": m{
"ca": string(caCertPEM),
"cert": string(hostCertPEM),
"key": string(hostKeyPEM),
},
"static_host_map": staticHostMap,
"punchy": m{
"punch": true,
"respond": true,
},
"tun": m{
"dev": deviceNamer.getName(
hostBootstrap.NetworkCreationParams.ID,
hostBootstrap.ThisHost().IP(),
),
},
2024-11-17 13:19:46 +00:00
"firewall": firewall,
}
if publicAddr := networkConfig.VPN.PublicAddr; publicAddr == "" {
config["listen"] = m{
"host": "0.0.0.0",
"port": "0",
}
config["lighthouse"] = m{
"hosts": lighthouseHostIPs,
}
} else {
host, port, err := net.SplitHostPort(publicAddr)
if err != nil {
return nil, fmt.Errorf(
"parsing public address %q: %w", publicAddr, err,
)
}
// This helps with integration testing, so we can set a test to listen
// on some local IP without conflicting with something else running on
// the host.
if hostIP := net.ParseIP(host); hostIP == nil || !hostIP.IsLoopback() {
host = "0.0.0.0"
}
config["listen"] = m{
"host": host,
"port": port,
}
config["lighthouse"] = m{
"hosts": []string{},
"am_lighthouse": true,
}
}
return config, nil
}
func nebulaWriteConfig(
ctx context.Context,
logger *mlog.Logger,
runtimeDirPath string,
deviceNamer *NebulaDeviceNamer,
networkConfig daecommon.NetworkConfig,
hostBootstrap bootstrap.Bootstrap,
) (
string, bool, error,
) {
config, err := nebulaConfig(deviceNamer, networkConfig, hostBootstrap)
if err != nil {
return "", false, fmt.Errorf("creating nebula config: %w", err)
}
nebulaYmlPath := filepath.Join(runtimeDirPath, "nebula.yml")
changed, err := toolkit.WriteFileCheckChanged(
ctx, logger, nebulaYmlPath, 0600, func(w io.Writer) error {
return yaml.NewEncoder(w).Encode(config)
},
)
if err != nil {
return "", false, fmt.Errorf(
"writing nebula.yml to %q: %w", nebulaYmlPath, err,
)
}
return nebulaYmlPath, changed, nil
}
func nebulaPmuxProc(
ctx context.Context,
logger *mlog.Logger,
runtimeDirPath, binDirPath string,
deviceNamer *NebulaDeviceNamer,
networkConfig daecommon.NetworkConfig,
hostBootstrap bootstrap.Bootstrap,
) (
*pmuxlib.Process, error,
) {
nebulaYmlPath, _, err := nebulaWriteConfig(
ctx, logger, runtimeDirPath, deviceNamer, networkConfig, hostBootstrap,
)
if err != nil {
return nil, fmt.Errorf("writing nebula config: %w", err)
}
cfg := pmuxlib.ProcessConfig{
Cmd: filepath.Join(binDirPath, "nebula"),
Args: []string{"-config", nebulaYmlPath},
}
cfg = withPmuxLoggers(ctx, logger, "nebula", cfg)
return pmuxlib.NewProcess(cfg), nil
}