Brian Picciano
8c3e6a2845
In a world where the daemon can manage more than one network, the Daemon is really responsible only for knowing which networks are currently joined, creating/joining/leaving networks, and routing incoming RPC requests to the correct network handler as needed. The new network package, with its Network interface, inherits most of the logic that Daemon used to have, leaving Daemon only the parts needed for the functionality just described. There's a lot of cleanup done here in order to really nail down the separation of concerns between the two, especially around directory creation.
165 lines
4.2 KiB
Go
165 lines
4.2 KiB
Go
// Package bootstrap deals with the parsing and creation of bootstrap.json
|
|
// files. It also contains some helpers which rely on bootstrap data.
|
|
package bootstrap
|
|
|
|
import (
|
|
"crypto/sha512"
|
|
"encoding/json"
|
|
"fmt"
|
|
"isle/nebula"
|
|
"isle/toolkit"
|
|
"maps"
|
|
"net/netip"
|
|
"path/filepath"
|
|
"sort"
|
|
|
|
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
|
|
)
|
|
|
|
// StateDirPath returns the path within the user's state directory where the
|
|
// bootstrap file is stored.
|
|
func StateDirPath(dataDirPath string) string {
|
|
return filepath.Join(dataDirPath, "bootstrap.json")
|
|
}
|
|
|
|
// AppDirPath returns the path within the AppDir where an embedded bootstrap
|
|
// file might be found.
|
|
func AppDirPath(appDirPath string) string {
|
|
return filepath.Join(appDirPath, "share/bootstrap.json")
|
|
}
|
|
|
|
// 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 {
|
|
ID string
|
|
Name string
|
|
Domain string
|
|
}
|
|
|
|
// NewCreationParams instantiates and returns a CreationParams.
|
|
func NewCreationParams(name, domain string) CreationParams {
|
|
return CreationParams{
|
|
ID: toolkit.RandStr(32),
|
|
Name: name,
|
|
Domain: domain,
|
|
}
|
|
}
|
|
|
|
// Annotate implements the mctx.Annotator interface.
|
|
func (p CreationParams) Annotate(aa mctx.Annotations) {
|
|
aa["networkID"] = p.ID
|
|
aa["networkName"] = p.Name
|
|
aa["networkDomain"] = p.Domain
|
|
}
|
|
|
|
// Bootstrap contains all information which is needed by a host daemon to join a
|
|
// network on boot.
|
|
type Bootstrap struct {
|
|
NetworkCreationParams CreationParams
|
|
CAPublicCredentials nebula.CAPublicCredentials
|
|
|
|
PrivateCredentials nebula.HostPrivateCredentials
|
|
HostAssigned `json:"-"`
|
|
SignedHostAssigned nebula.Signed[HostAssigned] // signed by CA
|
|
|
|
Hosts map[nebula.HostName]Host
|
|
}
|
|
|
|
// New initializes and returns a new Bootstrap file for a new host.
|
|
//
|
|
// TODO in the resulting bootstrap only include this host and hosts which are
|
|
// necessary for connecting to nebula/garage. Remember to immediately re-poll
|
|
// garage for the full hosts list during network joining.
|
|
func New(
|
|
caCreds nebula.CACredentials,
|
|
adminCreationParams CreationParams,
|
|
existingHosts map[nebula.HostName]Host,
|
|
name nebula.HostName,
|
|
ip netip.Addr,
|
|
) (
|
|
Bootstrap, error,
|
|
) {
|
|
hostPubCreds, hostPrivCreds, err := nebula.NewHostCredentials(
|
|
caCreds, name, ip,
|
|
)
|
|
if err != nil {
|
|
return Bootstrap{}, fmt.Errorf("generating host credentials: %w", err)
|
|
}
|
|
|
|
assigned := HostAssigned{
|
|
Name: name,
|
|
PublicCredentials: hostPubCreds,
|
|
}
|
|
|
|
signedAssigned, err := nebula.Sign(assigned, caCreds.SigningPrivateKey)
|
|
if err != nil {
|
|
return Bootstrap{}, fmt.Errorf("signing assigned fields: %w", err)
|
|
}
|
|
|
|
existingHosts = maps.Clone(existingHosts)
|
|
existingHosts[name] = Host{
|
|
HostAssigned: assigned,
|
|
}
|
|
|
|
return Bootstrap{
|
|
NetworkCreationParams: adminCreationParams,
|
|
CAPublicCredentials: caCreds.Public,
|
|
PrivateCredentials: hostPrivCreds,
|
|
HostAssigned: assigned,
|
|
SignedHostAssigned: signedAssigned,
|
|
Hosts: existingHosts,
|
|
}, nil
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaler interface. It will
|
|
// automatically populate the HostAssigned field by unwrapping the
|
|
// SignedHostAssigned field.
|
|
func (b *Bootstrap) UnmarshalJSON(data []byte) error {
|
|
type inner Bootstrap
|
|
|
|
err := json.Unmarshal(data, (*inner)(b))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.HostAssigned, err = b.SignedHostAssigned.Unwrap(
|
|
b.CAPublicCredentials.SigningKey,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("unwrapping HostAssigned: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ThisHost is a shortcut for b.Hosts[b.HostName], but will panic if the
|
|
// HostName isn't found in the Hosts map.
|
|
func (b Bootstrap) ThisHost() Host {
|
|
host, ok := b.Hosts[b.Name]
|
|
if !ok {
|
|
panic(fmt.Sprintf("hostname %q not defined in bootstrap's hosts", b.Name))
|
|
}
|
|
|
|
return host
|
|
}
|
|
|
|
// Hash returns a deterministic hash of the given hosts map.
|
|
func HostsHash(hostsMap map[nebula.HostName]Host) ([]byte, error) {
|
|
|
|
hosts := make([]Host, 0, len(hostsMap))
|
|
for _, host := range hostsMap {
|
|
hosts = append(hosts, host)
|
|
}
|
|
|
|
sort.Slice(hosts, func(i, j int) bool {
|
|
return hosts[i].Name < hosts[j].Name
|
|
})
|
|
|
|
h := sha512.New()
|
|
if err := json.NewEncoder(h).Encode(hosts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return h.Sum(nil), nil
|
|
}
|