package children import ( "context" "fmt" "io" "isle/bootstrap" "isle/daemon/daecommon" "isle/nebula" "isle/toolkit" "net" "path/filepath" "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() } // TODO this needs to be produce a deterministic config value. func nebulaConfig( networkConfig daecommon.NetworkConfig, hostBootstrap bootstrap.Bootstrap, ) ( map[string]any, error, ) { var ( lighthouseHostIPs []string staticHostMap = map[string][]string{} ) for _, host := range hostBootstrap.Hosts { 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(), ) 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", }, ) } config := map[string]any{ "pki": map[string]string{ "ca": string(caCertPEM), "cert": string(hostCertPEM), "key": string(hostKeyPEM), }, "static_host_map": staticHostMap, "punchy": map[string]bool{ "punch": true, "respond": true, }, "tun": map[string]any{ "dev": nebula.GetDeviceName(hostBootstrap.NetworkCreationParams.ID), }, "firewall": firewall, } if publicAddr := networkConfig.VPN.PublicAddr; publicAddr == "" { config["listen"] = map[string]string{ "host": "0.0.0.0", "port": "0", } config["lighthouse"] = map[string]any{ "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"] = map[string]string{ "host": host, "port": port, } config["lighthouse"] = map[string]any{ "hosts": []string{}, "am_lighthouse": true, } } return config, nil } func nebulaWriteConfig( ctx context.Context, logger *mlog.Logger, runtimeDirPath string, networkConfig daecommon.NetworkConfig, hostBootstrap bootstrap.Bootstrap, ) ( string, bool, error, ) { config, err := nebulaConfig(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, networkConfig daecommon.NetworkConfig, hostBootstrap bootstrap.Bootstrap, ) ( *pmuxlib.Process, error, ) { nebulaYmlPath, _, err := nebulaWriteConfig( ctx, logger, runtimeDirPath, 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 }