This commit is contained in:
Brian Picciano 2022-10-16 17:51:51 +02:00
parent 0a826f86da
commit fb151865b4
4 changed files with 187 additions and 46 deletions

View File

@ -27,8 +27,8 @@ const (
// CreationParams are general parameters used when creating a new network. These // CreationParams are general parameters used when creating a new network. These
// are available to all hosts within the network via their bootstrap files. // are available to all hosts within the network via their bootstrap files.
type CreationParams struct { type CreationParams struct {
Domain string `yaml:"domain"` ID string `yaml:"id"`
CIDRs []string `yaml:"cidrs"` Domain string `yaml:"domain"`
} }
// Admin is used for accessing all information contained within an admin.tgz. // Admin is used for accessing all information contained within an admin.tgz.

View File

@ -1,14 +1,29 @@
package entrypoint package entrypoint
import ( import (
"context"
"cryptic-net/admin" "cryptic-net/admin"
"cryptic-net/bootstrap" "cryptic-net/bootstrap"
"cryptic-net/nebula" "cryptic-net/nebula"
"crypto/rand"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"net"
"os" "os"
"strings"
"github.com/cryptic-io/pmux/pmuxlib"
) )
func randStr(l int) string {
b := make([]byte, l)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return hex.EncodeToString(b)
}
func readAdmin(path string) (admin.Admin, error) { func readAdmin(path string) (admin.Admin, error) {
if path == "-" { if path == "-" {
@ -30,6 +45,155 @@ func readAdmin(path string) (admin.Admin, error) {
return admin.FromReader(f) 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.",
)
subnetStr := flags.StringP(
"subnet", "s", "",
"CIDR which denotes the subnet that 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 == "" || *subnetStr == "" {
return errors.New("--domain and --subnet are required")
}
*domain = strings.TrimRight(strings.TrimLeft(*domain, "."), ".")
ip, subnet, err := net.ParseCIDR(*subnetStr)
if err != nil {
return fmt.Errorf("parsing %q as a CIDR: %w", *subnetStr, err)
}
hostName := "genesis"
adminCreationParams := admin.CreationParams{
ID: randStr(32),
Domain: *domain,
}
garageRPCSecret := randStr(32)
{
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")
}
nebulaCACert, err := nebula.NewCACert(*domain, subnet)
if err != nil {
return fmt.Errorf("creating nebula CA cert: %w", err)
}
nebulaHostCert, err := nebula.NewHostCert(nebulaCACert, hostName, ip)
if err != nil {
return fmt.Errorf("creating nebula cert for host: %w", err)
}
host := bootstrap.Host{
Name: hostName,
Nebula: bootstrap.NebulaHost{
IP: ip.String(),
},
}
env.Bootstrap = bootstrap.Bootstrap{
AdminCreationParams: adminCreationParams,
Hosts: map[string]bootstrap.Host{
hostName: host,
},
HostName: hostName,
NebulaHostCert: nebulaHostCert,
GarageRPCSecret: garageRPCSecret,
}
// this will also write the bootstrap file
if err := mergeDaemonIntoBootstrap(env); err != nil {
return fmt.Errorf("merging daemon.yml into bootstrap data: %w", err)
}
for key, val := range env.ToMap() {
if err := os.Setenv(key, val); err != nil {
return fmt.Errorf("failed to set %q to %q: %w", key, val, err)
}
}
garageChildrenPmuxProcConfigs, err := garageChildrenPmuxProcConfigs(env)
if err != nil {
return fmt.Errorf("generating garage children configs: %w", err)
}
pmuxConfig := pmuxlib.Config{
Processes: append(
[]pmuxlib.ProcessConfig{
nebulaEntrypointPmuxProcConfig(),
garageApplyLayoutDiffPmuxProcConfig(env),
},
garageChildrenPmuxProcConfigs...,
),
}
ctx, cancel := context.WithCancel(env.Context)
defer cancel()
pmuxDoneCh := make(chan struct{})
go func() {
pmuxlib.Run(ctx, pmuxConfig)
close(pmuxDoneCh)
}()
},
}
var subCmdAdminMakeBootstrap = subCmd{ var subCmdAdminMakeBootstrap = subCmd{
name: "make-bootstrap", name: "make-bootstrap",
descr: "Creates a new bootstrap.tgz file for a particular host and writes it to stdout", descr: "Creates a new bootstrap.tgz file for a particular host and writes it to stdout",

View File

@ -6,7 +6,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path/filepath"
"sync" "sync"
"time" "time"
@ -179,28 +178,8 @@ var subCmdDaemon = subCmd{
env := subCmdCtx.env 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 { if *dumpConfig {
return writeBuiltinDaemonYml(env, os.Stdout)
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
} }
runtimeDirPath := env.RuntimeDirPath runtimeDirPath := env.RuntimeDirPath
@ -256,7 +235,7 @@ var subCmdDaemon = subCmd{
} }
if err := writeMergedDaemonYml(env, *daemonYmlPath); err != nil { if err := writeMergedDaemonYml(env, *daemonYmlPath); err != nil {
return fmt.Errorf("generating daemon.yml file: %w", err) return fmt.Errorf("merging and writing daemon.yml file: %w", err)
} }
// we update this Host's data using whatever configuration has been // we update this Host's data using whatever configuration has been
@ -276,6 +255,14 @@ var subCmdDaemon = subCmd{
for { 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) { if err := runDaemonPmuxOnce(env, s3Client); errors.Is(err, context.Canceled) {
return nil return nil

View File

@ -14,15 +14,6 @@ import (
"golang.org/x/crypto/curve25519" "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 // HostCert contains the certificate and private key files which will need to
// be present on a particular host. Each file is PEM encoded. // be present on a particular host. Each file is PEM encoded.
type HostCert struct { type HostCert struct {
@ -41,7 +32,7 @@ type CACert struct {
// NewHostCert generates a new key/cert for a nebula host using the CA key // NewHostCert generates a new key/cert for a nebula host using the CA key
// which will be found in the adminFS. // which will be found in the adminFS.
func NewHostCert( func NewHostCert(
caCert CACert, hostName, hostIP string, caCert CACert, hostName string, ip net.IP,
) ( ) (
HostCert, error, HostCert, error,
) { ) {
@ -66,14 +57,9 @@ func NewHostCert(
expireAt := caCrt.Details.NotAfter.Add(-1 * time.Second) expireAt := caCrt.Details.NotAfter.Add(-1 * time.Second)
ip := net.ParseIP(hostIP) subnet := caCrt.Details.Subnets[0]
if ip == nil { if !subnet.Contains(ip) {
return HostCert{}, fmt.Errorf("invalid host ip %q", hostIP) return HostCert{}, fmt.Errorf("invalid ip %q, not contained by network subnet %q", ip, subnet)
}
ipNet := &net.IPNet{
IP: ip,
Mask: ipCIDRMask,
} }
var hostPub, hostKey []byte var hostPub, hostKey []byte
@ -88,8 +74,11 @@ func NewHostCert(
hostCrt := cert.NebulaCertificate{ hostCrt := cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{ Details: cert.NebulaCertificateDetails{
Name: hostName, Name: hostName,
Ips: []*net.IPNet{ipNet}, Ips: []*net.IPNet{{
IP: ip,
Mask: subnet.Mask,
}},
NotBefore: time.Now(), NotBefore: time.Now(),
NotAfter: expireAt, NotAfter: expireAt,
PublicKey: hostPub, PublicKey: hostPub,
@ -122,7 +111,7 @@ func NewHostCert(
// NewCACert generates a CACert. The domain should be the network's root domain, // NewCACert generates a CACert. The domain should be the network's root domain,
// and is included in the signing certificate's Name field. // and is included in the signing certificate's Name field.
func NewCACert(domain string) (CACert, error) { func NewCACert(domain string, subnet *net.IPNet) (CACert, error) {
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil { if err != nil {
@ -135,6 +124,7 @@ func NewCACert(domain string) (CACert, error) {
caCrt := cert.NebulaCertificate{ caCrt := cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{ Details: cert.NebulaCertificateDetails{
Name: fmt.Sprintf("%s cryptic-net root cert", domain), Name: fmt.Sprintf("%s cryptic-net root cert", domain),
Subnets: []*net.IPNet{subnet},
NotBefore: now, NotBefore: now,
NotAfter: expireAt, NotAfter: expireAt,
PublicKey: pubKey, PublicKey: pubKey,