WIP
This commit is contained in:
parent
7a25e1b6e6
commit
c19b2f53dd
@ -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 {
|
||||||
|
ID string `yaml:"id"`
|
||||||
Domain string `yaml:"domain"`
|
Domain string `yaml:"domain"`
|
||||||
CIDRs []string `yaml:"cidrs"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin is used for accessing all information contained within an admin.tgz.
|
// Admin is used for accessing all information contained within an admin.tgz.
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
package entrypoint
|
package entrypoint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
crypticnet "cryptic-net"
|
||||||
"cryptic-net/admin"
|
"cryptic-net/admin"
|
||||||
"cryptic-net/bootstrap"
|
"cryptic-net/bootstrap"
|
||||||
|
"cryptic-net/garage"
|
||||||
"cryptic-net/nebula"
|
"cryptic-net/nebula"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cryptic-io/pmux/pmuxlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func randStr(l int) string {
|
func randStr(l int) string {
|
||||||
@ -40,6 +48,236 @@ func readAdmin(path string) (admin.Admin, error) {
|
|||||||
return admin.FromReader(f)
|
return admin.FromReader(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func garageInitializeGlobalBucket(
|
||||||
|
env *crypticnet.Env, globalBucketCreds garage.S3APICredentials,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx = env.Context
|
||||||
|
thisHost = env.Bootstrap.ThisHost()
|
||||||
|
thisDaemon = env.ThisDaemon()
|
||||||
|
allocs = thisDaemon.Storage.Allocations
|
||||||
|
)
|
||||||
|
|
||||||
|
adminClient := garage.NewAdminClient(
|
||||||
|
net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(allocs[0].AdminPort)),
|
||||||
|
env.Bootstrap.GarageAdminToken,
|
||||||
|
)
|
||||||
|
|
||||||
|
// first attempt to import the key
|
||||||
|
err := adminClient.Do(ctx, nil, "POST", "/v0/key/import", map[string]string{
|
||||||
|
"accessKeyId": globalBucketCreds.ID,
|
||||||
|
"secretAccessKey": globalBucketCreds.Secret,
|
||||||
|
"name": "shared-global-bucket-key",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("importing global bucket key into garage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create global bucket
|
||||||
|
err = adminClient.Do(ctx, nil, "POST", "/v0/bucket", map[string]string{
|
||||||
|
"globalAlias": garage.GlobalBucket,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating global bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve newly created bucket's id
|
||||||
|
var getBucketRes struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = adminClient.Do(
|
||||||
|
ctx, &getBucketRes,
|
||||||
|
"GET", "/v0/bucket?globalAlias="+garage.GlobalBucket, nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fetching global bucket id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow shared global bucket key to perform all operations
|
||||||
|
err = adminClient.Do(ctx, nil, "POST", "/v0/bucket/allow", map[string]interface{}{
|
||||||
|
"bucketId": getBucketRes.ID,
|
||||||
|
"accessKeyId": globalBucketCreds.ID,
|
||||||
|
"permissions": map[string]bool{
|
||||||
|
"read": true,
|
||||||
|
"write": true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("granting permissions to shared global bucket key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
pmuxDoneCh := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
pmuxlib.Run(ctx, pmuxConfig)
|
||||||
|
close(pmuxDoneCh)
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
<-pmuxDoneCh
|
||||||
|
}()
|
||||||
|
|
||||||
|
globalBucketCreds := garage.S3APICredentials{} // TODO
|
||||||
|
|
||||||
|
// TODO wait for garage to be confirmed as booted up
|
||||||
|
// TODO apply layout
|
||||||
|
|
||||||
|
if err := garageInitializeGlobalBucket(env, globalBucketCreds); err != nil {
|
||||||
|
return fmt.Errorf("initializing shared global bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("TODO: create and output admin.tgz")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
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",
|
||||||
@ -92,7 +330,12 @@ var subCmdAdminMakeBootstrap = subCmd{
|
|||||||
return fmt.Errorf("couldn't find host into for %q in garage, has `cryptic-net hosts add` been run yet?", *name)
|
return fmt.Errorf("couldn't find host into for %q in garage, has `cryptic-net hosts add` been run yet?", *name)
|
||||||
}
|
}
|
||||||
|
|
||||||
nebulaHostCert, err := nebula.NewHostCert(adm.NebulaCACert, host.Name, host.Nebula.IP)
|
ip := net.ParseIP(host.Nebula.IP)
|
||||||
|
if ip == nil {
|
||||||
|
return fmt.Errorf("invalid IP stored with host %q: %q", *name, host.Nebula.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
nebulaHostCert, err := nebula.NewHostCert(adm.NebulaCACert, host.Name, ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating new nebula host key/cert: %w", err)
|
return fmt.Errorf("creating new nebula host key/cert: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -89,7 +75,10 @@ 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,
|
||||||
|
Loading…
Reference in New Issue
Block a user