Compare commits
3 Commits
fb151865b4
...
3f3ad43cb2
Author | SHA1 | Date | |
---|---|---|---|
|
3f3ad43cb2 | ||
|
15c5c904a2 | ||
|
81d4a35b24 |
@ -16,13 +16,15 @@ tmp="$(mktemp -d -t cryptic-net-dnsmasq-entrypoint-XXX)"
|
|||||||
thisHostName=$(tar xzf "$_BOOTSTRAP_PATH" --to-stdout ./hostname)
|
thisHostName=$(tar xzf "$_BOOTSTRAP_PATH" --to-stdout ./hostname)
|
||||||
thisHostIP=$(cat "$tmp"/hosts/"$thisHostName".yml | yq '.nebula.ip')
|
thisHostIP=$(cat "$tmp"/hosts/"$thisHostName".yml | yq '.nebula.ip')
|
||||||
|
|
||||||
|
domain=$(tar xzf "$_BOOTSTRAP_PATH" --to-stdout ./admin/creation-params.yml | yq '.domain')
|
||||||
|
|
||||||
echo "listen-address=$thisHostIP" >> "$conf_path"
|
echo "listen-address=$thisHostIP" >> "$conf_path"
|
||||||
|
|
||||||
ls -1 "$tmp"/hosts | while read hostYml; do
|
ls -1 "$tmp"/hosts | while read hostYml; do
|
||||||
|
|
||||||
hostName=$(echo "$hostYml" | cut -d. -f1)
|
hostName=$(echo "$hostYml" | cut -d. -f1)
|
||||||
hostIP=$(cat "$tmp"/hosts/"$hostYml" | yq '.nebula.ip')
|
hostIP=$(cat "$tmp"/hosts/"$hostYml" | yq '.nebula.ip')
|
||||||
echo "address=/${hostName}.hosts.cryptic.io/$hostIP" >> "$conf_path"
|
echo "address=/${hostName}.hosts.$domain/$hostIP" >> "$conf_path"
|
||||||
|
|
||||||
done
|
done
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
creationParamsPath = "admin/creation-params.yml"
|
||||||
|
|
||||||
nebulaCertsCACertPath = "nebula/certs/ca.crt"
|
nebulaCertsCACertPath = "nebula/certs/ca.crt"
|
||||||
nebulaCertsCAKeyPath = "nebula/certs/ca.key"
|
nebulaCertsCAKeyPath = "nebula/certs/ca.key"
|
||||||
|
|
||||||
@ -22,8 +24,17 @@ const (
|
|||||||
garageRPCSecretPath = "garage/rpc-secret.txt"
|
garageRPCSecretPath = "garage/rpc-secret.txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CreationParams are general parameters used when creating a new network. These
|
||||||
|
// are available to all hosts within the network via their bootstrap files.
|
||||||
|
type CreationParams struct {
|
||||||
|
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.
|
||||||
type Admin struct {
|
type Admin struct {
|
||||||
|
CreationParams CreationParams
|
||||||
|
|
||||||
NebulaCACert nebula.CACert
|
NebulaCACert nebula.CACert
|
||||||
|
|
||||||
GarageRPCSecret string
|
GarageRPCSecret string
|
||||||
@ -41,6 +52,7 @@ func FromFS(adminFS fs.FS) (Admin, error) {
|
|||||||
into interface{}
|
into interface{}
|
||||||
path string
|
path string
|
||||||
}{
|
}{
|
||||||
|
{&a.CreationParams, creationParamsPath},
|
||||||
{&a.GarageGlobalBucketS3APICredentials, garageGlobalBucketKeyYmlPath},
|
{&a.GarageGlobalBucketS3APICredentials, garageGlobalBucketKeyYmlPath},
|
||||||
{&a.GarageAdminBucketS3APICredentials, garageAdminBucketKeyYmlPath},
|
{&a.GarageAdminBucketS3APICredentials, garageAdminBucketKeyYmlPath},
|
||||||
}
|
}
|
||||||
@ -91,6 +103,7 @@ func (a Admin) WriteTo(into io.Writer) error {
|
|||||||
value interface{}
|
value interface{}
|
||||||
path string
|
path string
|
||||||
}{
|
}{
|
||||||
|
{a.CreationParams, creationParamsPath},
|
||||||
{a.GarageGlobalBucketS3APICredentials, garageGlobalBucketKeyYmlPath},
|
{a.GarageGlobalBucketS3APICredentials, garageGlobalBucketKeyYmlPath},
|
||||||
{a.GarageAdminBucketS3APICredentials, garageAdminBucketKeyYmlPath},
|
{a.GarageAdminBucketS3APICredentials, garageAdminBucketKeyYmlPath},
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cryptic-net/admin"
|
||||||
"cryptic-net/garage"
|
"cryptic-net/garage"
|
||||||
"cryptic-net/nebula"
|
"cryptic-net/nebula"
|
||||||
"cryptic-net/tarutil"
|
"cryptic-net/tarutil"
|
||||||
@ -20,12 +21,15 @@ import (
|
|||||||
|
|
||||||
// Paths within the bootstrap FS which for general data.
|
// Paths within the bootstrap FS which for general data.
|
||||||
const (
|
const (
|
||||||
hostNamePath = "hostname"
|
adminCreationParamsPath = "admin/creation-params.yml"
|
||||||
|
hostNamePath = "hostname"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bootstrap is used for accessing all information contained within a
|
// Bootstrap is used for accessing all information contained within a
|
||||||
// bootstrap.tgz file.
|
// bootstrap.tgz file.
|
||||||
type Bootstrap struct {
|
type Bootstrap struct {
|
||||||
|
AdminCreationParams admin.CreationParams
|
||||||
|
|
||||||
Hosts map[string]Host
|
Hosts map[string]Host
|
||||||
HostName string
|
HostName string
|
||||||
|
|
||||||
@ -48,12 +52,18 @@ func FromFS(bootstrapFS fs.FS) (Bootstrap, error) {
|
|||||||
return Bootstrap{}, fmt.Errorf("loading hosts info from fs: %w", err)
|
return Bootstrap{}, fmt.Errorf("loading hosts info from fs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = yamlutil.LoadYamlFSFile(
|
filesToLoadAsYAML := []struct {
|
||||||
&b.GarageGlobalBucketS3APICredentials,
|
into interface{}
|
||||||
bootstrapFS,
|
path string
|
||||||
garageGlobalBucketKeyYmlPath,
|
}{
|
||||||
); err != nil {
|
{&b.AdminCreationParams, adminCreationParamsPath},
|
||||||
return Bootstrap{}, fmt.Errorf("loading %q from fs: %w", garageGlobalBucketKeyYmlPath, err)
|
{&b.GarageGlobalBucketS3APICredentials, garageGlobalBucketKeyYmlPath},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range filesToLoadAsYAML {
|
||||||
|
if err := yamlutil.LoadYamlFSFile(f.into, bootstrapFS, f.path); err != nil {
|
||||||
|
return Bootstrap{}, fmt.Errorf("loading %q from fs: %w", f.path, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filesToLoadAsString := []struct {
|
filesToLoadAsString := []struct {
|
||||||
@ -106,6 +116,36 @@ func (b Bootstrap) WriteTo(into io.Writer) error {
|
|||||||
|
|
||||||
w := tarutil.NewTGZWriter(into)
|
w := tarutil.NewTGZWriter(into)
|
||||||
|
|
||||||
|
for _, host := range b.Hosts {
|
||||||
|
|
||||||
|
hostB, err := yaml.Marshal(host)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("yaml encoding host %#v: %w", host, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(hostsDirPath, host.Name+".yml")
|
||||||
|
|
||||||
|
w.WriteFileBytes(path, hostB)
|
||||||
|
}
|
||||||
|
|
||||||
|
filesToWriteAsYAML := []struct {
|
||||||
|
value interface{}
|
||||||
|
path string
|
||||||
|
}{
|
||||||
|
{b.AdminCreationParams, adminCreationParamsPath},
|
||||||
|
{b.GarageGlobalBucketS3APICredentials, garageGlobalBucketKeyYmlPath},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range filesToWriteAsYAML {
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(f.value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("yaml encoding data for %q: %w", f.path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteFileBytes(f.path, b)
|
||||||
|
}
|
||||||
|
|
||||||
filesToWriteAsString := []struct {
|
filesToWriteAsString := []struct {
|
||||||
value string
|
value string
|
||||||
path string
|
path string
|
||||||
@ -121,25 +161,6 @@ func (b Bootstrap) WriteTo(into io.Writer) error {
|
|||||||
w.WriteFileBytes(f.path, []byte(f.value))
|
w.WriteFileBytes(f.path, []byte(f.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
garageGlobalBucketKeyB, err := yaml.Marshal(b.GarageGlobalBucketS3APICredentials)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("yaml encoding garage global bucket creds: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteFileBytes(garageGlobalBucketKeyYmlPath, garageGlobalBucketKeyB)
|
|
||||||
|
|
||||||
for _, host := range b.Hosts {
|
|
||||||
|
|
||||||
hostB, err := yaml.Marshal(host)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("yaml encoding host %#v: %w", host, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
path := filepath.Join(hostsDirPath, host.Name+".yml")
|
|
||||||
|
|
||||||
w.WriteFileBytes(path, hostB)
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.Close()
|
return w.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
208
go-workspace/src/cmd/entrypoint/admin.go
Normal file
208
go-workspace/src/cmd/entrypoint/admin.go
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
package entrypoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cryptic-net/admin"
|
||||||
|
"cryptic-net/bootstrap"
|
||||||
|
"cryptic-net/nebula"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readAdmin(path string) (admin.Admin, error) {
|
||||||
|
|
||||||
|
if path == "-" {
|
||||||
|
|
||||||
|
adm, err := admin.FromReader(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return admin.Admin{}, fmt.Errorf("parsing admin.tgz from stdin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return admin.Admin{}, fmt.Errorf("opening file: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
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",
|
||||||
|
checkLock: true,
|
||||||
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
|
||||||
|
flags := subCmdCtx.flagSet(false)
|
||||||
|
|
||||||
|
name := flags.StringP(
|
||||||
|
"name", "n", "",
|
||||||
|
"Name of the host to generate bootstrap.tgz for",
|
||||||
|
)
|
||||||
|
|
||||||
|
adminPath := flags.StringP(
|
||||||
|
"admin-path", "a", "",
|
||||||
|
`Path to admin.tgz file. If the given path is "-" then stdin is used.`,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *name == "" || *adminPath == "" {
|
||||||
|
return errors.New("--name and --admin-path are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
env := subCmdCtx.env
|
||||||
|
|
||||||
|
adm, err := readAdmin(*adminPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading admin.tgz with --admin-path of %q: %w", *adminPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := env.Bootstrap.GlobalBucketS3APIClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating client for global bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE this isn't _technically_ required, but if the `hosts add`
|
||||||
|
// command for this host has been run recently then it might not have
|
||||||
|
// made it into the bootstrap file yet, and so won't be in
|
||||||
|
// `env.Bootstrap`.
|
||||||
|
hosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, client)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("retrieving host info from garage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
host, ok := hosts[*name]
|
||||||
|
if !ok {
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating new nebula host key/cert: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newBootstrap := bootstrap.Bootstrap{
|
||||||
|
AdminCreationParams: adm.CreationParams,
|
||||||
|
|
||||||
|
Hosts: hosts,
|
||||||
|
HostName: *name,
|
||||||
|
|
||||||
|
NebulaHostCert: nebulaHostCert,
|
||||||
|
|
||||||
|
GarageRPCSecret: adm.GarageRPCSecret,
|
||||||
|
GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials,
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBootstrap.WriteTo(os.Stdout)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var subCmdAdmin = subCmd{
|
||||||
|
name: "admin",
|
||||||
|
descr: "Sub-commands which only admins can run",
|
||||||
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
return subCmdCtx.doSubCmd(
|
||||||
|
subCmdAdminMakeBootstrap,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
@ -6,7 +6,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -17,11 +16,8 @@ import (
|
|||||||
crypticnet "cryptic-net"
|
crypticnet "cryptic-net"
|
||||||
"cryptic-net/bootstrap"
|
"cryptic-net/bootstrap"
|
||||||
"cryptic-net/garage"
|
"cryptic-net/garage"
|
||||||
"cryptic-net/yamlutil"
|
|
||||||
|
|
||||||
"github.com/cryptic-io/pmux/pmuxlib"
|
"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
|
// The daemon sub-command deals with starting an actual cryptic-net daemon
|
||||||
@ -46,42 +42,6 @@ import (
|
|||||||
//
|
//
|
||||||
// * (On exit) cleans up the runtime directory.
|
// * (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 {
|
func copyBootstrapToDataDir(env *crypticnet.Env, r io.Reader) error {
|
||||||
|
|
||||||
path := env.DataDirBootstrapPath()
|
path := env.DataDirBootstrapPath()
|
||||||
@ -284,28 +244,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
|
||||||
@ -360,8 +300,8 @@ var subCmdDaemon = subCmd{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeDaemonYml(*daemonYmlPath, builtinDaemonYmlPath, runtimeDirPath); 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -412,6 +352,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
|
||||||
|
|
||||||
|
72
go-workspace/src/cmd/entrypoint/daemon_yml.go
Normal file
72
go-workspace/src/cmd/entrypoint/daemon_yml.go
Normal file
@ -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
|
||||||
|
}
|
@ -1,9 +1,7 @@
|
|||||||
package entrypoint
|
package entrypoint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cryptic-net/admin"
|
|
||||||
"cryptic-net/bootstrap"
|
"cryptic-net/bootstrap"
|
||||||
"cryptic-net/nebula"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -140,107 +138,14 @@ var subCmdHostsDelete = subCmd{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func readAdmin(path string) (admin.Admin, error) {
|
|
||||||
|
|
||||||
if path == "-" {
|
|
||||||
|
|
||||||
adm, err := admin.FromReader(os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
return admin.Admin{}, fmt.Errorf("parsing admin.tgz from stdin: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return adm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return admin.Admin{}, fmt.Errorf("opening file: %w", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return admin.FromReader(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
var subCmdHostsMakeBootstrap = subCmd{
|
|
||||||
name: "make-bootstrap",
|
|
||||||
descr: "Creates a new bootstrap.tgz file for a particular host and writes it to stdout",
|
|
||||||
checkLock: true,
|
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
|
||||||
|
|
||||||
flags := subCmdCtx.flagSet(false)
|
|
||||||
|
|
||||||
name := flags.StringP(
|
|
||||||
"name", "n", "",
|
|
||||||
"Name of the host to generate bootstrap.tgz for",
|
|
||||||
)
|
|
||||||
|
|
||||||
adminPath := flags.StringP(
|
|
||||||
"admin-path", "a", "",
|
|
||||||
`Path to admin.tgz file. If the given path is "-" then stdin is used.`,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := flags.Parse(subCmdCtx.args); err != nil {
|
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *name == "" || *adminPath == "" {
|
|
||||||
return errors.New("--name and --admin-path are required")
|
|
||||||
}
|
|
||||||
|
|
||||||
env := subCmdCtx.env
|
|
||||||
|
|
||||||
adm, err := readAdmin(*adminPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("reading admin.tgz with --admin-path of %q: %w", *adminPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := env.Bootstrap.GlobalBucketS3APIClient()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating client for global bucket: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE this isn't _technically_ required, but if the `hosts add`
|
|
||||||
// command for this host has been run recently then it might not have
|
|
||||||
// made it into the bootstrap file yet, and so won't be in
|
|
||||||
// `env.Bootstrap`.
|
|
||||||
hosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, client)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("retrieving host info from garage: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
host, ok := hosts[*name]
|
|
||||||
if !ok {
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating new nebula host key/cert: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newBootstrap := bootstrap.Bootstrap{
|
|
||||||
Hosts: hosts,
|
|
||||||
HostName: *name,
|
|
||||||
|
|
||||||
NebulaHostCert: nebulaHostCert,
|
|
||||||
|
|
||||||
GarageRPCSecret: adm.GarageRPCSecret,
|
|
||||||
GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials,
|
|
||||||
}
|
|
||||||
|
|
||||||
return newBootstrap.WriteTo(os.Stdout)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var subCmdHosts = subCmd{
|
var subCmdHosts = subCmd{
|
||||||
name: "hosts",
|
name: "hosts",
|
||||||
descr: "Sub-commands having to do with configuration of hosts in the network",
|
descr: "Sub-commands having to do with configuration of hosts in the network",
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
return subCmdCtx.doSubCmd(
|
return subCmdCtx.doSubCmd(
|
||||||
subCmdHostsAdd,
|
subCmdHostsAdd,
|
||||||
subCmdHostsList,
|
|
||||||
subCmdHostsDelete,
|
subCmdHostsDelete,
|
||||||
subCmdHostsMakeBootstrap,
|
subCmdHostsList,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,10 @@ func Main() {
|
|||||||
args: os.Args[1:],
|
args: os.Args[1:],
|
||||||
env: env,
|
env: env,
|
||||||
}.doSubCmd(
|
}.doSubCmd(
|
||||||
|
subCmdAdmin,
|
||||||
subCmdDaemon,
|
subCmdDaemon,
|
||||||
subCmdHosts,
|
|
||||||
subCmdGarage,
|
subCmdGarage,
|
||||||
|
subCmdHosts,
|
||||||
subCmdVersion,
|
subCmdVersion,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
@ -122,7 +113,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, ipCIDRS []*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 +126,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),
|
||||||
|
Ips: ipCIDRS,
|
||||||
NotBefore: now,
|
NotBefore: now,
|
||||||
NotAfter: expireAt,
|
NotAfter: expireAt,
|
||||||
PublicKey: pubKey,
|
PublicKey: pubKey,
|
||||||
|
Loading…
Reference in New Issue
Block a user