diff --git a/go-workspace/src/cmd/entrypoint/admin.go b/go-workspace/src/cmd/entrypoint/admin.go index 8247777..0f0c104 100644 --- a/go-workspace/src/cmd/entrypoint/admin.go +++ b/go-workspace/src/cmd/entrypoint/admin.go @@ -6,7 +6,9 @@ import ( "cryptic-net/nebula" "errors" "fmt" + "net" "os" + "strings" ) func readAdmin(path string) (admin.Admin, error) { @@ -30,6 +32,98 @@ func readAdmin(path string) (admin.Admin, error) { return admin.FromReader(f) } +var subCmdAdminCreateNetwork = subCmd{ + name: "create-network", + descr: "Creates a new cryptic-net network, outputting the resulting admin.tgz to stdout", + do: func(subCmdCtx subCmdCtx) error { + + flags := subCmdCtx.flagSet(false) + + daemonYmlPath := flags.StringP( + "config-path", "c", "", + "Optional path to a daemon.yml file to load configuration from.", + ) + + dumpConfig := flags.Bool( + "dump-config", false, + "Write the default configuration file to stdout and exit.", + ) + + domain := flags.StringP( + "domain", "d", "", + "Domain name that should be used as the root domain in the network.", + ) + + ipCIDRStrs := flags.StringP( + "ip-cidrs", "i", "", + "Comma-separated list of CIDRs which denote what IPs hosts on the network can be assigned.", + ) + + if err := flags.Parse(subCmdCtx.args); err != nil { + return fmt.Errorf("parsing flags: %w", err) + } + + env := subCmdCtx.env + + if *dumpConfig { + return writeBuiltinDaemonYml(env, os.Stdout) + } + + if *domain == "" { + return errors.New("--domain is required") + } + + *domain = strings.TrimRight(strings.TrimLeft(*domain, "."), ".") + + var ( + ipCIDRs []*net.IPNet + thisIP net.IP + ) + + for _, ipCIDRStr := range strings.Split(*ipCIDRStrs, ",") { + + ipCIDRStr = strings.TrimSpace(ipCIDRStr) + if ipCIDRStr == "" { + continue + } + + ip, ipCIDR, err := net.ParseCIDR(ipCIDRStr) + if err != nil { + return fmt.Errorf("could not parse CIDR %q: %w", ipCIDRStr, err) + } + + thisIP = ip // we just need one IP from a CIDR, it doesn't matter which. + ipCIDRs = append(ipCIDRs, ipCIDR) + } + + runtimeDirPath := env.RuntimeDirPath + + fmt.Fprintf(os.Stderr, "will use runtime directory %q for temporary state\n", runtimeDirPath) + + if err := os.MkdirAll(runtimeDirPath, 0700); err != nil { + return fmt.Errorf("creating directory %q: %w", runtimeDirPath, err) + } + + defer func() { + fmt.Fprintf(os.Stderr, "cleaning up runtime directory %q\n", runtimeDirPath) + if err := os.RemoveAll(runtimeDirPath); err != nil { + fmt.Fprintf(os.Stderr, "error removing temporary directory %q: %v", runtimeDirPath, err) + } + }() + + if err := writeMergedDaemonYml(env, *daemonYmlPath); err != nil { + return fmt.Errorf("merging and writing daemon.yml file: %w", err) + } + + daemon := env.ThisDaemon() + + if len(daemon.Storage.Allocations) < 3 { + return fmt.Errorf("daemon.yml with at least 3 allocations was not provided") + } + + }, +} + var subCmdAdminMakeBootstrap = subCmd{ name: "make-bootstrap", descr: "Creates a new bootstrap.tgz file for a particular host and writes it to stdout", diff --git a/go-workspace/src/cmd/entrypoint/daemon.go b/go-workspace/src/cmd/entrypoint/daemon.go index 2f1702a..5955d39 100644 --- a/go-workspace/src/cmd/entrypoint/daemon.go +++ b/go-workspace/src/cmd/entrypoint/daemon.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net" "os" "path/filepath" @@ -17,11 +16,8 @@ import ( crypticnet "cryptic-net" "cryptic-net/bootstrap" "cryptic-net/garage" - "cryptic-net/yamlutil" "github.com/cryptic-io/pmux/pmuxlib" - "github.com/imdario/mergo" - "gopkg.in/yaml.v3" ) // The daemon sub-command deals with starting an actual cryptic-net daemon @@ -46,42 +42,6 @@ import ( // // * (On exit) cleans up the runtime directory. -func writeDaemonYml(userDaemonYmlPath, builtinDaemonYmlPath, runtimeDirPath string) error { - - var fullDaemonYml map[string]interface{} - - if err := yamlutil.LoadYamlFile(&fullDaemonYml, builtinDaemonYmlPath); err != nil { - return fmt.Errorf("parsing builtin daemon.yml file: %w", err) - } - - if userDaemonYmlPath != "" { - - var daemonYml map[string]interface{} - if err := yamlutil.LoadYamlFile(&daemonYml, userDaemonYmlPath); err != nil { - return fmt.Errorf("parsing %q: %w", userDaemonYmlPath, err) - } - - err := mergo.Merge(&fullDaemonYml, daemonYml, mergo.WithOverride) - if err != nil { - return fmt.Errorf("merging contents of file %q: %w", userDaemonYmlPath, err) - } - } - - fullDaemonYmlB, err := yaml.Marshal(fullDaemonYml) - - if err != nil { - return fmt.Errorf("yaml marshaling daemon config: %w", err) - } - - daemonYmlPath := filepath.Join(runtimeDirPath, "daemon.yml") - - if err := ioutil.WriteFile(daemonYmlPath, fullDaemonYmlB, 0400); err != nil { - return fmt.Errorf("writing daemon.yml file to %q: %w", daemonYmlPath, err) - } - - return nil -} - func copyBootstrapToDataDir(env *crypticnet.Env, r io.Reader) error { path := env.DataDirBootstrapPath() @@ -284,28 +244,8 @@ var subCmdDaemon = subCmd{ env := subCmdCtx.env - s3Client, err := env.Bootstrap.GlobalBucketS3APIClient() - if err != nil { - return fmt.Errorf("creating client for global bucket: %w", err) - } - - appDirPath := env.AppDirPath - - builtinDaemonYmlPath := filepath.Join(appDirPath, "etc", "daemon.yml") - if *dumpConfig { - - builtinDaemonYml, err := os.ReadFile(builtinDaemonYmlPath) - - if err != nil { - return fmt.Errorf("reading default daemon.yml at %q: %w", builtinDaemonYmlPath, err) - } - - if _, err := os.Stdout.Write(builtinDaemonYml); err != nil { - return fmt.Errorf("writing default daemon.yml to stdout: %w", err) - } - - return nil + return writeBuiltinDaemonYml(env, os.Stdout) } runtimeDirPath := env.RuntimeDirPath @@ -360,8 +300,8 @@ var subCmdDaemon = subCmd{ } } - if err := writeDaemonYml(*daemonYmlPath, builtinDaemonYmlPath, runtimeDirPath); err != nil { - return fmt.Errorf("generating daemon.yml file: %w", err) + if err := writeMergedDaemonYml(env, *daemonYmlPath); err != nil { + return fmt.Errorf("merging and writing daemon.yml file: %w", err) } { @@ -412,6 +352,14 @@ var subCmdDaemon = subCmd{ for { + // create s3Client anew on every loop, in case the topology has + // changed and we should be connecting to a different garage + // endpoint. + s3Client, err := env.Bootstrap.GlobalBucketS3APIClient() + if err != nil { + return fmt.Errorf("creating client for global bucket: %w", err) + } + if err := runDaemonPmuxOnce(env, s3Client); errors.Is(err, context.Canceled) { return nil diff --git a/go-workspace/src/cmd/entrypoint/daemon_yml.go b/go-workspace/src/cmd/entrypoint/daemon_yml.go new file mode 100644 index 0000000..2be5d0d --- /dev/null +++ b/go-workspace/src/cmd/entrypoint/daemon_yml.go @@ -0,0 +1,72 @@ +package entrypoint + +import ( + crypticnet "cryptic-net" + "cryptic-net/yamlutil" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/imdario/mergo" + "gopkg.in/yaml.v3" +) + +func builtinDaemonYmlPath(env *crypticnet.Env) string { + return filepath.Join(env.AppDirPath, "etc", "daemon.yml") +} + +func writeBuiltinDaemonYml(env *crypticnet.Env, w io.Writer) error { + + builtinDaemonYmlPath := builtinDaemonYmlPath(env) + + builtinDaemonYml, err := os.ReadFile(builtinDaemonYmlPath) + if err != nil { + return fmt.Errorf("reading default daemon.yml at %q: %w", builtinDaemonYmlPath, err) + } + + if _, err := w.Write(builtinDaemonYml); err != nil { + return fmt.Errorf("writing default daemon.yml: %w", err) + } + + return nil +} + +func writeMergedDaemonYml(env *crypticnet.Env, userDaemonYmlPath string) error { + + builtinDaemonYmlPath := builtinDaemonYmlPath(env) + + var fullDaemonYml map[string]interface{} + + if err := yamlutil.LoadYamlFile(&fullDaemonYml, builtinDaemonYmlPath); err != nil { + return fmt.Errorf("parsing builtin daemon.yml file: %w", err) + } + + if userDaemonYmlPath != "" { + + var daemonYml map[string]interface{} + if err := yamlutil.LoadYamlFile(&daemonYml, userDaemonYmlPath); err != nil { + return fmt.Errorf("parsing %q: %w", userDaemonYmlPath, err) + } + + err := mergo.Merge(&fullDaemonYml, daemonYml, mergo.WithOverride) + if err != nil { + return fmt.Errorf("merging contents of file %q: %w", userDaemonYmlPath, err) + } + } + + fullDaemonYmlB, err := yaml.Marshal(fullDaemonYml) + + if err != nil { + return fmt.Errorf("yaml marshaling daemon config: %w", err) + } + + daemonYmlPath := filepath.Join(env.RuntimeDirPath, "daemon.yml") + + if err := ioutil.WriteFile(daemonYmlPath, fullDaemonYmlB, 0400); err != nil { + return fmt.Errorf("writing daemon.yml file to %q: %w", daemonYmlPath, err) + } + + return nil +} diff --git a/go-workspace/src/nebula/nebula.go b/go-workspace/src/nebula/nebula.go index eab83e9..81c0252 100644 --- a/go-workspace/src/nebula/nebula.go +++ b/go-workspace/src/nebula/nebula.go @@ -14,15 +14,6 @@ import ( "golang.org/x/crypto/curve25519" ) -// TODO this should one day not be hardcoded -var ipCIDRMask = func() net.IPMask { - _, ipNet, err := net.ParseCIDR("10.10.0.0/16") - if err != nil { - panic(err) - } - return ipNet.Mask -}() - // HostCert contains the certificate and private key files which will need to // be present on a particular host. Each file is PEM encoded. type HostCert struct { @@ -122,7 +113,7 @@ func NewHostCert( // NewCACert generates a CACert. The domain should be the network's root domain, // and is included in the signing certificate's Name field. -func NewCACert(domain string) (CACert, error) { +func NewCACert(domain string, ipCIDRS []*net.IPNet) (CACert, error) { pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { @@ -135,6 +126,7 @@ func NewCACert(domain string) (CACert, error) { caCrt := cert.NebulaCertificate{ Details: cert.NebulaCertificateDetails{ Name: fmt.Sprintf("%s cryptic-net root cert", domain), + Ips: ipCIDRS, NotBefore: now, NotAfter: expireAt, PublicKey: pubKey,