Compare commits

..

No commits in common. "9d5c8ea4dbd3e6409fd55d9b7d6df1e7a18605a1" and "56f796e3fbb562ad1fe6c6d6bf8f5f2e8a15eb1b" have entirely different histories.

18 changed files with 169 additions and 224 deletions

View File

@ -4,6 +4,7 @@ package admin
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"isle/garage"
"isle/nebula" "isle/nebula"
) )
@ -22,6 +23,10 @@ type Admin struct {
Nebula struct { Nebula struct {
CACredentials nebula.CACredentials CACredentials nebula.CACredentials
} }
Garage struct {
GlobalBucketS3APICredentials garage.S3APICredentials
}
} }
// FromReader reads an admin.json from the given io.Reader. // FromReader reads an admin.json from the given io.Reader.

View File

@ -6,9 +6,12 @@ import (
"crypto/sha512" "crypto/sha512"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"isle/admin" "isle/admin"
"isle/garage"
"isle/nebula" "isle/nebula"
"net/netip" "net/netip"
"os"
"path/filepath" "path/filepath"
"sort" "sort"
) )
@ -25,11 +28,20 @@ func AppDirPath(appDirPath string) string {
return filepath.Join(appDirPath, "share/bootstrap.json") return filepath.Join(appDirPath, "share/bootstrap.json")
} }
// Bootstrap contains all information which is needed by a host daemon to join a // Garage contains parameters needed to connect to and use the garage cluster.
// network on boot. type Garage struct {
AdminToken string
// TODO this should be part of admin.CreationParams
GlobalBucketS3APICredentials garage.S3APICredentials
}
// Bootstrap is used for accessing all information contained within a
// bootstrap.json file.
type Bootstrap struct { type Bootstrap struct {
AdminCreationParams admin.CreationParams AdminCreationParams admin.CreationParams
CAPublicCredentials nebula.CAPublicCredentials CAPublicCredentials nebula.CAPublicCredentials
Garage Garage
PrivateCredentials nebula.HostPrivateCredentials PrivateCredentials nebula.HostPrivateCredentials
HostAssigned `json:"-"` HostAssigned `json:"-"`
@ -43,6 +55,7 @@ type Bootstrap struct {
func New( func New(
caCreds nebula.CACredentials, caCreds nebula.CACredentials,
adminCreationParams admin.CreationParams, adminCreationParams admin.CreationParams,
garage Garage,
name nebula.HostName, name nebula.HostName,
ip netip.Addr, ip netip.Addr,
) ( ) (
@ -68,6 +81,7 @@ func New(
return Bootstrap{ return Bootstrap{
AdminCreationParams: adminCreationParams, AdminCreationParams: adminCreationParams,
CAPublicCredentials: caCreds.Public, CAPublicCredentials: caCreds.Public,
Garage: garage,
PrivateCredentials: hostPrivCreds, PrivateCredentials: hostPrivCreds,
HostAssigned: assigned, HostAssigned: assigned,
SignedHostAssigned: signedAssigned, SignedHostAssigned: signedAssigned,
@ -75,9 +89,23 @@ func New(
}, nil }, nil
} }
// UnmarshalJSON implements the json.Unmarshaler interface. It will // FromFile reads a bootstrap from a file at the given path. The HostAssigned
// automatically populate the HostAssigned field by unwrapping the // field will automatically be unwrapped.
// SignedHostAssigned field. func FromFile(path string) (Bootstrap, error) {
f, err := os.Open(path)
if err != nil {
return Bootstrap{}, fmt.Errorf("opening file: %w", err)
}
defer f.Close()
var b Bootstrap
if err := json.NewDecoder(f).Decode(&b); err != nil {
return Bootstrap{}, fmt.Errorf("decoding json: %w", err)
}
return b, nil
}
func (b *Bootstrap) UnmarshalJSON(data []byte) error { func (b *Bootstrap) UnmarshalJSON(data []byte) error {
type inner Bootstrap type inner Bootstrap
@ -96,6 +124,11 @@ func (b *Bootstrap) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// WriteTo writes the Bootstrap as a new bootstrap to the given io.Writer.
func (b Bootstrap) WriteTo(into io.Writer) error {
return json.NewEncoder(into).Encode(b)
}
// ThisHost is a shortcut for b.Hosts[b.HostName], but will panic if the // ThisHost is a shortcut for b.Hosts[b.HostName], but will panic if the
// HostName isn't found in the Hosts map. // HostName isn't found in the Hosts map.
func (b Bootstrap) ThisHost() Host { func (b Bootstrap) ThisHost() Host {

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"isle/bootstrap" "isle/bootstrap"
@ -68,7 +67,7 @@ var subCmdHostsCreate = subCmd{
return fmt.Errorf("calling CreateHost: %w", err) return fmt.Errorf("calling CreateHost: %w", err)
} }
return json.NewEncoder(os.Stdout).Encode(res.JoiningBootstrap) return res.HostBootstrap.WriteTo(os.Stdout)
}, },
} }

View File

@ -4,8 +4,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"isle/admin" "isle/admin"
"isle/bootstrap"
"isle/daemon" "isle/daemon"
"isle/jsonutil"
"os" "os"
) )
@ -88,8 +88,8 @@ var subCmdNetworkJoin = subCmd{
return errors.New("--bootstrap-path is required") return errors.New("--bootstrap-path is required")
} }
var newBootstrap daemon.JoiningBootstrap newBootstrap, err := bootstrap.FromFile(*bootstrapPath)
if err := jsonutil.LoadFile(&newBootstrap, *bootstrapPath); err != nil { if err != nil {
return fmt.Errorf( return fmt.Errorf(
"loading bootstrap from %q: %w", *bootstrapPath, err, "loading bootstrap from %q: %w", *bootstrapPath, err,
) )

View File

@ -1,24 +1,14 @@
package daemon package daemon
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"isle/bootstrap" "isle/bootstrap"
"isle/garage/garagesrv" "isle/garage/garagesrv"
"isle/jsonutil"
"isle/secrets"
) )
// JoiningBootstrap wraps a normal Bootstrap to include extra data which a host
// might need while joining a network.
type JoiningBootstrap struct {
Bootstrap bootstrap.Bootstrap
Secrets map[secrets.ID]json.RawMessage
}
func writeBootstrapToStateDir( func writeBootstrapToStateDir(
stateDirPath string, hostBootstrap bootstrap.Bootstrap, stateDirPath string, hostBootstrap bootstrap.Bootstrap,
) error { ) error {
@ -31,11 +21,14 @@ func writeBootstrapToStateDir(
return fmt.Errorf("creating directory %q: %w", dirPath, err) return fmt.Errorf("creating directory %q: %w", dirPath, err)
} }
if err := jsonutil.WriteFile(hostBootstrap, path, 0700); err != nil { f, err := os.Create(path)
return fmt.Errorf("writing bootstrap to %q: %w", path, err) if err != nil {
return fmt.Errorf("creating file %q: %w", path, err)
} }
return nil defer f.Close()
return hostBootstrap.WriteTo(f)
} }
func coalesceDaemonConfigAndBootstrap( func coalesceDaemonConfigAndBootstrap(

View File

@ -19,12 +19,11 @@ func garageAdminClientLogger(logger *mlog.Logger) *mlog.Logger {
return logger.WithNamespace("garageAdminClient") return logger.WithNamespace("garageAdminClient")
} }
// newGarageAdminClient will return an AdminClient for a local garage instance, // NewGarageAdminClient will return an AdminClient for a local garage instance,
// or it will _panic_ if there is no local instance configured. // or it will _panic_ if there is no local instance configured.
func newGarageAdminClient( func NewGarageAdminClient(
logger *mlog.Logger, logger *mlog.Logger,
daemonConfig Config, daemonConfig Config,
adminToken string,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) *garage.AdminClient { ) *garage.AdminClient {
@ -36,7 +35,7 @@ func newGarageAdminClient(
thisHost.IP().String(), thisHost.IP().String(),
strconv.Itoa(daemonConfig.Storage.Allocations[0].AdminPort), strconv.Itoa(daemonConfig.Storage.Allocations[0].AdminPort),
), ),
adminToken, hostBootstrap.Garage.AdminToken,
) )
} }
@ -44,7 +43,6 @@ func waitForGarage(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
daemonConfig Config, daemonConfig Config,
adminToken string,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) error { ) error {
@ -66,7 +64,9 @@ func waitForGarage(
) )
adminClient := garage.NewAdminClient( adminClient := garage.NewAdminClient(
adminClientLogger, adminAddr, adminToken, adminClientLogger,
adminAddr,
hostBootstrap.Garage.AdminToken,
) )
ctx := mctx.Annotate(ctx, "garageAdminAddr", adminAddr) ctx := mctx.Annotate(ctx, "garageAdminAddr", adminAddr)
@ -101,7 +101,7 @@ func bootstrapGarageHostForAlloc(
} }
func garageWriteChildConfig( func garageWriteChildConfig(
rpcSecret, runtimeDirPath, adminToken string, rpcSecret, runtimeDirPath string,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
alloc ConfigStorageAllocation, alloc ConfigStorageAllocation,
) ( ) (
@ -130,7 +130,7 @@ func garageWriteChildConfig(
DataPath: alloc.DataPath, DataPath: alloc.DataPath,
RPCSecret: rpcSecret, RPCSecret: rpcSecret,
AdminToken: adminToken, AdminToken: hostBootstrap.Garage.AdminToken,
LocalPeer: peer, LocalPeer: peer,
BootstrapPeers: hostBootstrap.GaragePeers(), BootstrapPeers: hostBootstrap.GaragePeers(),
@ -148,7 +148,6 @@ func garagePmuxProcConfigs(
logger *mlog.Logger, logger *mlog.Logger,
rpcSecret, runtimeDirPath, binDirPath string, rpcSecret, runtimeDirPath, binDirPath string,
daemonConfig Config, daemonConfig Config,
adminToken string,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) ( ) (
[]pmuxlib.ProcessConfig, error, []pmuxlib.ProcessConfig, error,
@ -166,7 +165,7 @@ func garagePmuxProcConfigs(
for _, alloc := range allocs { for _, alloc := range allocs {
childConfigPath, err := garageWriteChildConfig( childConfigPath, err := garageWriteChildConfig(
rpcSecret, runtimeDirPath, adminToken, hostBootstrap, alloc, rpcSecret, runtimeDirPath, hostBootstrap, alloc,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err) return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
@ -189,14 +188,11 @@ func garageApplyLayout(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
daemonConfig Config, daemonConfig Config,
adminToken string,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) error { ) error {
var ( var (
adminClient = newGarageAdminClient( adminClient = NewGarageAdminClient(logger, daemonConfig, hostBootstrap)
logger, daemonConfig, adminToken, hostBootstrap,
)
thisHost = hostBootstrap.ThisHost() thisHost = hostBootstrap.ThisHost()
hostName = thisHost.Name hostName = thisHost.Name
allocs = daemonConfig.Storage.Allocations allocs = daemonConfig.Storage.Allocations

View File

@ -12,7 +12,6 @@ func (c *Children) newPmuxConfig(
ctx context.Context, ctx context.Context,
garageRPCSecret, binDirPath string, garageRPCSecret, binDirPath string,
daemonConfig Config, daemonConfig Config,
garageAdminToken string,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) ( ) (
pmuxlib.Config, error, pmuxlib.Config, error,
@ -46,7 +45,6 @@ func (c *Children) newPmuxConfig(
c.opts.EnvVars.RuntimeDirPath, c.opts.EnvVars.RuntimeDirPath,
binDirPath, binDirPath,
daemonConfig, daemonConfig,
garageAdminToken,
hostBootstrap, hostBootstrap,
) )
if err != nil { if err != nil {
@ -69,7 +67,6 @@ func (c *Children) newPmuxConfig(
func (c *Children) postPmuxInit( func (c *Children) postPmuxInit(
ctx context.Context, ctx context.Context,
daemonConfig Config, daemonConfig Config,
garageAdminToken string,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) error { ) error {
c.logger.Info(ctx, "Waiting for nebula VPN to come online") c.logger.Info(ctx, "Waiting for nebula VPN to come online")
@ -78,9 +75,7 @@ func (c *Children) postPmuxInit(
} }
c.logger.Info(ctx, "Waiting for garage instances to come online") c.logger.Info(ctx, "Waiting for garage instances to come online")
err := waitForGarage( err := waitForGarage(ctx, c.logger, daemonConfig, hostBootstrap)
ctx, c.logger, daemonConfig, garageAdminToken, hostBootstrap,
)
if err != nil { if err != nil {
return fmt.Errorf("waiting for garage to start: %w", err) return fmt.Errorf("waiting for garage to start: %w", err)
} }

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"isle/bootstrap" "isle/bootstrap"
"isle/garage"
"isle/secrets" "isle/secrets"
"code.betamike.com/micropelago/pmux/pmuxlib" "code.betamike.com/micropelago/pmux/pmuxlib"
@ -32,7 +33,6 @@ func NewChildren(
binDirPath string, binDirPath string,
secretsStore secrets.Store, secretsStore secrets.Store,
daemonConfig Config, daemonConfig Config,
garageAdminToken string,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
opts *Opts, opts *Opts,
) ( ) (
@ -41,7 +41,7 @@ func NewChildren(
opts = opts.withDefaults() opts = opts.withDefaults()
logger.Info(ctx, "Loading secrets") logger.Info(ctx, "Loading secrets")
garageRPCSecret, err := getGarageRPCSecret(ctx, secretsStore) garageRPCSecret, err := garage.GetRPCSecret(ctx, secretsStore)
if err != nil && !errors.Is(err, secrets.ErrNotFound) { if err != nil && !errors.Is(err, secrets.ErrNotFound) {
return nil, fmt.Errorf("loading garage RPC secret: %w", err) return nil, fmt.Errorf("loading garage RPC secret: %w", err)
} }
@ -56,12 +56,7 @@ func NewChildren(
} }
pmuxConfig, err := c.newPmuxConfig( pmuxConfig, err := c.newPmuxConfig(
ctx, ctx, garageRPCSecret, binDirPath, daemonConfig, hostBootstrap,
garageRPCSecret,
binDirPath,
daemonConfig,
garageAdminToken,
hostBootstrap,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("generating pmux config: %w", err) return nil, fmt.Errorf("generating pmux config: %w", err)
@ -73,9 +68,7 @@ func NewChildren(
c.logger.Debug(pmuxCtx, "pmux stopped") c.logger.Debug(pmuxCtx, "pmux stopped")
}() }()
initErr := c.postPmuxInit( initErr := c.postPmuxInit(ctx, daemonConfig, hostBootstrap)
ctx, daemonConfig, garageAdminToken, hostBootstrap,
)
if initErr != nil { if initErr != nil {
logger.Warn(ctx, "failed to initialize Children, shutting down child processes", err) logger.Warn(ctx, "failed to initialize Children, shutting down child processes", err)
if err := c.Shutdown(); err != nil { if err := c.Shutdown(); err != nil {

View File

@ -11,7 +11,7 @@ import (
"io/fs" "io/fs"
"isle/admin" "isle/admin"
"isle/bootstrap" "isle/bootstrap"
"isle/jsonutil" "isle/garage"
"isle/nebula" "isle/nebula"
"isle/secrets" "isle/secrets"
"net/netip" "net/netip"
@ -50,7 +50,7 @@ type Daemon interface {
// //
// Errors: // Errors:
// - ErrAlreadyJoined // - ErrAlreadyJoined
JoinNetwork(context.Context, JoiningBootstrap) error JoinNetwork(context.Context, bootstrap.Bootstrap) error
// GetBootstraps returns the currently active Bootstrap. // GetBootstraps returns the currently active Bootstrap.
GetBootstrap(context.Context) (bootstrap.Bootstrap, error) GetBootstrap(context.Context) (bootstrap.Bootstrap, error)
@ -70,7 +70,7 @@ type Daemon interface {
hostName nebula.HostName, hostName nebula.HostName,
ip netip.Addr, // TODO automatically choose IP address ip netip.Addr, // TODO automatically choose IP address
) ( ) (
JoiningBootstrap, error, bootstrap.Bootstrap, error,
) )
// CreateNebulaCertificate creates and signs a new nebula certficate for an // CreateNebulaCertificate creates and signs a new nebula certficate for an
@ -141,7 +141,6 @@ type daemon struct {
opts *Opts opts *Opts
secretsStore secrets.Store secretsStore secrets.Store
garageAdminToken string
l sync.RWMutex l sync.RWMutex
state int state int
@ -181,7 +180,6 @@ func NewDaemon(
daemonConfig: daemonConfig, daemonConfig: daemonConfig,
envBinDirPath: envBinDirPath, envBinDirPath: envBinDirPath,
opts: opts.withDefaults(), opts: opts.withDefaults(),
garageAdminToken: randStr(32),
shutdownCh: make(chan struct{}), shutdownCh: make(chan struct{}),
} }
bootstrapFilePath = bootstrap.StateDirPath(d.opts.EnvVars.StateDirPath) bootstrapFilePath = bootstrap.StateDirPath(d.opts.EnvVars.StateDirPath)
@ -202,8 +200,7 @@ func NewDaemon(
) )
} }
var currBootstrap bootstrap.Bootstrap currBootstrap, err := bootstrap.FromFile(bootstrapFilePath)
err = jsonutil.LoadFile(&currBootstrap, bootstrapFilePath)
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) {
// daemon has never had a network created or joined // daemon has never had a network created or joined
} else if err != nil { } else if err != nil {
@ -371,11 +368,7 @@ func (d *daemon) postInit(ctx context.Context) bool {
"Applying garage layout", "Applying garage layout",
func(ctx context.Context) error { func(ctx context.Context) error {
return garageApplyLayout( return garageApplyLayout(
ctx, ctx, d.logger, d.daemonConfig, d.currBootstrap,
d.logger,
d.daemonConfig,
d.garageAdminToken,
d.currBootstrap,
) )
}, },
) { ) {
@ -388,29 +381,28 @@ func (d *daemon) postInit(ctx context.Context) bool {
// //
// TODO this is pretty hacky, but there doesn't seem to be a better way to // TODO this is pretty hacky, but there doesn't seem to be a better way to
// manage it at the moment. // manage it at the moment.
_, err := getGarageS3APIGlobalBucketCredentials(ctx, d.secretsStore) if d.currBootstrap.Garage.GlobalBucketS3APICredentials == (garage.S3APICredentials{}) {
if errors.Is(err, secrets.ErrNotFound) { currBootstrap := d.currBootstrap
if !until( if !until(
ctx, ctx,
d.logger, d.logger,
"Initializing garage shared global bucket", "Initializing garage shared global bucket",
func(ctx context.Context) error { func(ctx context.Context) error {
garageGlobalBucketCreds, err := garageInitializeGlobalBucket( garageGlobalBucketCreds, err := garageInitializeGlobalBucket(
ctx, ctx, d.logger, d.daemonConfig, d.currBootstrap,
d.logger,
d.daemonConfig,
d.garageAdminToken,
d.currBootstrap,
) )
if err != nil { if err != nil {
return fmt.Errorf("initializing global bucket: %w", err) return fmt.Errorf("initializing global bucket: %w", err)
} }
err = setGarageS3APIGlobalBucketCredentials( currBootstrap.Garage.GlobalBucketS3APICredentials = garageGlobalBucketCreds
ctx, d.secretsStore, garageGlobalBucketCreds,
d.logger.Info(ctx, "Writing bootstrap to state directory")
err = writeBootstrapToStateDir(
d.opts.EnvVars.StateDirPath, currBootstrap,
) )
if err != nil { if err != nil {
return fmt.Errorf("storing global bucket creds: %w", err) return fmt.Errorf("writing bootstrap to state dir: %w", err)
} }
return nil return nil
@ -418,6 +410,10 @@ func (d *daemon) postInit(ctx context.Context) bool {
) { ) {
return false return false
} }
d.l.Lock()
d.currBootstrap = currBootstrap
d.l.Unlock()
} }
if !until( if !until(
@ -463,7 +459,6 @@ func (d *daemon) restartLoop(ctx context.Context, readyCh chan<- struct{}) {
d.envBinDirPath, d.envBinDirPath,
d.secretsStore, d.secretsStore,
d.daemonConfig, d.daemonConfig,
d.garageAdminToken,
d.currBootstrap, d.currBootstrap,
d.opts, d.opts,
) )
@ -536,10 +531,14 @@ func (d *daemon) CreateNetwork(
}, },
} }
garageRPCSecret = randStr(32) garageBootstrap = bootstrap.Garage{
AdminToken: randStr(32),
}
) )
err = setGarageRPCSecret(ctx, d.secretsStore, garageRPCSecret) garageRPCSecret := randStr(32)
err = garage.SetRPCSecret(ctx, d.secretsStore, garageRPCSecret)
if err != nil { if err != nil {
return admin.Admin{}, fmt.Errorf("storing garage RPC secret: %w", err) return admin.Admin{}, fmt.Errorf("storing garage RPC secret: %w", err)
} }
@ -547,6 +546,7 @@ func (d *daemon) CreateNetwork(
hostBootstrap, err := bootstrap.New( hostBootstrap, err := bootstrap.New(
nebulaCACreds, nebulaCACreds,
adm.CreationParams, adm.CreationParams,
garageBootstrap,
hostName, hostName,
ipNet.FirstAddr(), ipNet.FirstAddr(),
) )
@ -582,13 +582,22 @@ func (d *daemon) CreateNetwork(
return adm, ctx.Err() return adm, ctx.Err()
} }
// As part of postInit, which is called prior to ready(), the restartLoop
// will check if the global bucket creds have been created yet or not, and
// create them if so. So once ready() is called we can get the created creds
// from the currBootstrap
d.l.RLock()
garageGlobalBucketCreds := d.currBootstrap.Garage.GlobalBucketS3APICredentials
d.l.RUnlock()
adm.Nebula.CACredentials = nebulaCACreds adm.Nebula.CACredentials = nebulaCACreds
adm.Garage.GlobalBucketS3APICredentials = garageGlobalBucketCreds
return adm, nil return adm, nil
} }
func (d *daemon) JoinNetwork( func (d *daemon) JoinNetwork(
ctx context.Context, newBootstrap JoiningBootstrap, ctx context.Context, newBootstrap bootstrap.Bootstrap,
) error { ) error {
d.l.Lock() d.l.Lock()
@ -599,13 +608,7 @@ func (d *daemon) JoinNetwork(
readyCh := make(chan struct{}, 1) readyCh := make(chan struct{}, 1)
err := secrets.Import(ctx, d.secretsStore, newBootstrap.Secrets) err := d.initialize(newBootstrap, readyCh)
if err != nil {
d.l.Unlock()
return fmt.Errorf("importing secrets: %w", err)
}
err = d.initialize(newBootstrap.Bootstrap, readyCh)
d.l.Unlock() d.l.Unlock()
if err != nil { if err != nil {
return fmt.Errorf("initializing daemon: %w", err) return fmt.Errorf("initializing daemon: %w", err)
@ -678,45 +681,39 @@ func (d *daemon) CreateHost(
hostName nebula.HostName, hostName nebula.HostName,
ip netip.Addr, ip netip.Addr,
) ( ) (
JoiningBootstrap, error, bootstrap.Bootstrap, error,
) { ) {
return withCurrBootstrap(d, func( return withCurrBootstrap(d, func(
currBootstrap bootstrap.Bootstrap, currBootstrap bootstrap.Bootstrap,
) ( ) (
JoiningBootstrap, error, bootstrap.Bootstrap, error,
) { ) {
var ( garageGlobalBucketS3APICreds := currBootstrap.Garage.GlobalBucketS3APICredentials
joiningBootstrap JoiningBootstrap
err error
)
joiningBootstrap.Bootstrap, err = bootstrap.New( garageBootstrap := bootstrap.Garage{
AdminToken: randStr(32),
GlobalBucketS3APICredentials: garageGlobalBucketS3APICreds,
}
newHostBootstrap, err := bootstrap.New(
makeCACreds(currBootstrap, caSigningPrivateKey), makeCACreds(currBootstrap, caSigningPrivateKey),
currBootstrap.AdminCreationParams, currBootstrap.AdminCreationParams,
garageBootstrap,
hostName, hostName,
ip, ip,
) )
if err != nil { if err != nil {
return JoiningBootstrap{}, fmt.Errorf( return bootstrap.Bootstrap{}, fmt.Errorf(
"initializing bootstrap data: %w", err, "initializing bootstrap data: %w", err,
) )
} }
joiningBootstrap.Bootstrap.Hosts = currBootstrap.Hosts newHostBootstrap.Hosts = currBootstrap.Hosts
if joiningBootstrap.Secrets, err = secrets.Export(
ctx, d.secretsStore, []secrets.ID{
garageRPCSecretSecretID,
garageS3APIGlobalBucketCredentialsSecretID,
},
); err != nil {
return JoiningBootstrap{}, fmt.Errorf("exporting secrets: %w", err)
}
// TODO persist new bootstrap to garage. Requires making the daemon // TODO persist new bootstrap to garage. Requires making the daemon
// config change watching logic smarter, so only dnsmasq gets restarted. // config change watching logic smarter, so only dnsmasq gets restarted.
return joiningBootstrap, nil return newHostBootstrap, nil
}) })
} }

View File

@ -24,19 +24,14 @@ func (d *daemon) getGarageClientParams(
) ( ) (
GarageClientParams, error, GarageClientParams, error,
) { ) {
creds, err := getGarageS3APIGlobalBucketCredentials(ctx, d.secretsStore) rpcSecret, err := garage.GetRPCSecret(ctx, d.secretsStore)
if err != nil {
return GarageClientParams{}, fmt.Errorf("getting garage global bucket creds: %w", err)
}
rpcSecret, err := getGarageRPCSecret(ctx, d.secretsStore)
if err != nil && !errors.Is(err, secrets.ErrNotFound) { if err != nil && !errors.Is(err, secrets.ErrNotFound) {
return GarageClientParams{}, fmt.Errorf("getting garage rpc secret: %w", err) return GarageClientParams{}, fmt.Errorf("getting garage rpc secret: %w", err)
} }
return GarageClientParams{ return GarageClientParams{
Peer: currBootstrap.ChooseGaragePeer(), Peer: currBootstrap.ChooseGaragePeer(),
GlobalBucketS3APICredentials: creds, GlobalBucketS3APICredentials: currBootstrap.Garage.GlobalBucketS3APICredentials,
RPCSecret: rpcSecret, RPCSecret: rpcSecret,
}, nil }, nil
} }

View File

@ -24,13 +24,12 @@ func garageInitializeGlobalBucket(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
daemonConfig Config, daemonConfig Config,
adminToken string,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) ( ) (
garage.S3APICredentials, error, garage.S3APICredentials, error,
) { ) {
adminClient := newGarageAdminClient( adminClient := NewGarageAdminClient(
logger, daemonConfig, adminToken, hostBootstrap, logger, daemonConfig, hostBootstrap,
) )
creds, err := adminClient.CreateS3APICredentials( creds, err := adminClient.CreateS3APICredentials(

View File

@ -56,7 +56,7 @@ func (r *RPC) CreateNetwork(
// JoinNetwork passes through to the Daemon method of the same name. // JoinNetwork passes through to the Daemon method of the same name.
func (r *RPC) JoinNetwork( func (r *RPC) JoinNetwork(
ctx context.Context, req JoiningBootstrap, ctx context.Context, req bootstrap.Bootstrap,
) ( ) (
struct{}, error, struct{}, error,
) { ) {
@ -137,7 +137,7 @@ type CreateHostRequest struct {
// CreateHostResult wraps the results from the CreateHost RPC method. // CreateHostResult wraps the results from the CreateHost RPC method.
type CreateHostResult struct { type CreateHostResult struct {
JoiningBootstrap JoiningBootstrap HostBootstrap bootstrap.Bootstrap
} }
// CreateHost passes the call through to the Daemon method of the // CreateHost passes the call through to the Daemon method of the
@ -147,14 +147,14 @@ func (r *RPC) CreateHost(
) ( ) (
CreateHostResult, error, CreateHostResult, error,
) { ) {
joiningBootstrap, err := r.daemon.CreateHost( hostBootstrap, err := r.daemon.CreateHost(
ctx, req.CASigningPrivateKey, req.HostName, req.IP, ctx, req.CASigningPrivateKey, req.HostName, req.IP,
) )
if err != nil { if err != nil {
return CreateHostResult{}, err return CreateHostResult{}, err
} }
return CreateHostResult{JoiningBootstrap: joiningBootstrap}, nil return CreateHostResult{HostBootstrap: hostBootstrap}, nil
} }
// CreateNebulaCertificateRequest contains the arguments to the // CreateNebulaCertificateRequest contains the arguments to the

View File

@ -1,39 +0,0 @@
package daemon
import (
"fmt"
"isle/garage"
"isle/secrets"
)
const (
secretsNSGarage = "garage"
)
////////////////////////////////////////////////////////////////////////////////
// Garage-related secrets
func garageS3APIBucketCredentialsSecretID(credsName string) secrets.ID {
return secrets.NewID(
secretsNSGarage, fmt.Sprintf("s3-api-bucket-credentials-%s", credsName),
)
}
var (
garageRPCSecretSecretID = secrets.NewID(secretsNSGarage, "rpc-secret")
garageS3APIGlobalBucketCredentialsSecretID = garageS3APIBucketCredentialsSecretID(
garage.GlobalBucketS3APICredentialsName,
)
)
// Get/Set functions for garage-related secrets.
var (
getGarageRPCSecret, setGarageRPCSecret = secrets.GetSetFunctions[string](
garageRPCSecretSecretID,
)
getGarageS3APIGlobalBucketCredentials,
setGarageS3APIGlobalBucketCredentials = secrets.GetSetFunctions[garage.S3APICredentials](
garageS3APIGlobalBucketCredentialsSecretID,
)
)

20
go/garage/secrets.go Normal file
View File

@ -0,0 +1,20 @@
package garage
import "isle/secrets"
var (
rpcSecretSecretID = secrets.NewID("garage", "rpc-secret")
globalBucketS3APICredentialsSecretID = secrets.NewID("garage", "global-bucket-s3-api-credentials")
)
// Get/Set functions for garage-related secrets.
var (
GetRPCSecret, SetRPCSecret = secrets.GetSetFunctions[string](
rpcSecretSecretID,
)
GetGlobalBucketS3APICredentials,
SetGlobalBucketS3APICredentials = secrets.GetSetFunctions[S3APICredentials](
globalBucketS3APICredentialsSecretID,
)
)

View File

@ -2,7 +2,6 @@ package secrets
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
) )
@ -65,41 +64,3 @@ func MultiGet(ctx context.Context, s Store, m map[ID]any) error {
} }
return errors.Join(errs...) return errors.Join(errs...)
} }
// Export returns a map of ID to raw payload for each ID given. An error is
// returned for _each_ ID which could not be exported, wrapped using
// `errors.Join`, alongside whatever keys could be exported.
func Export(
ctx context.Context, s Store, ids []ID,
) (
map[ID]json.RawMessage, error,
) {
var (
m = map[ID]json.RawMessage{}
errs []error
)
for _, id := range ids {
var into json.RawMessage
if err := s.Get(ctx, &into, id); err != nil {
errs = append(errs, fmt.Errorf("exporting %q: %w", id, err))
continue
}
m[id] = into
}
return m, errors.Join(errs...)
}
// Import sets all given ID/payload pairs into the Store.
func Import(
ctx context.Context, s Store, m map[ID]json.RawMessage,
) error {
var errs []error
for id, payload := range m {
if err := s.Set(ctx, id, payload); err != nil {
errs = append(errs, fmt.Errorf("importing %q: %w", id, err))
}
}
return errors.Join(errs...)
}

View File

@ -0,0 +1,14 @@
# shellcheck source=../../utils/with-1-data-1-empty-node-network.sh
source "$UTILS"/with-1-data-1-empty-node-network.sh
adminBS="$XDG_STATE_HOME"/isle/bootstrap.json
bs="$secondus_bootstrap" # set in with-1-data-1-empty-node-network.sh
[ "$(jq -r <"$bs" '.AdminCreationParams')" = "$(jq -r <admin.json '.CreationParams')" ]
[ "$(jq -r <"$bs" '.SignedHostAssigned.Body.Name')" = "secondus" ]
[ "$(jq -r <"$bs" '.Hosts.primus.PublicCredentials')" \
= "$(jq -r <"$adminBS" '.SignedHostAssigned.Body.PublicCredentials')" ]
[ "$(jq <"$bs" '.Hosts.primus.Garage.Instances|length')" = "3" ]

View File

@ -1,16 +0,0 @@
# shellcheck source=../../utils/with-1-data-1-empty-node-network.sh
source "$UTILS"/with-1-data-1-empty-node-network.sh
adminBS="$XDG_STATE_HOME"/isle/bootstrap.json
bs="$secondus_bootstrap" # set in with-1-data-1-empty-node-network.sh
[ "$(jq -r <"$bs" '.Bootstrap.AdminCreationParams')" = "$(jq -r <admin.json '.CreationParams')" ]
[ "$(jq -r <"$bs" '.Bootstrap.SignedHostAssigned.Body.Name')" = "secondus" ]
[ "$(jq -r <"$bs" '.Bootstrap.Hosts.primus.PublicCredentials')" \
= "$(jq -r <"$adminBS" '.SignedHostAssigned.Body.PublicCredentials')" ]
[ "$(jq <"$bs" '.Bootstrap.Hosts.primus.Garage.Instances|length')" = "3" ]
[ "$(jq <"$bs" '.Secrets["garage-rpc-secret"]')" != "null" ]