isle/go/bootstrap/bootstrap.go
Brian Picciano 68f417b5ba Upgrade garage to v1.0.0
This required switching all garage admin API calls to the new v1
versions, and redoing how the global bucket key is created so it is
created via the "create key" API call.
2024-06-11 16:57:31 +02:00

160 lines
4.0 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"
"io"
"isle/admin"
"isle/garage"
"isle/nebula"
"net"
"os"
"path/filepath"
"sort"
)
// DataDirPath returns the path within the user's data directory where the
// bootstrap file is stored.
func DataDirPath(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")
}
// Garage contains parameters needed to connect to and use the garage cluster.
type Garage struct {
// TODO this should be part of some new configuration section related to
// secrets which may or may not be granted to this host
RPCSecret string
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 {
AdminCreationParams admin.CreationParams
CAPublicCredentials nebula.CAPublicCredentials
Garage Garage
PrivateCredentials nebula.HostPrivateCredentials
HostAssigned `json:"-"`
SignedHostAssigned nebula.Signed[HostAssigned] // signed by CA
Hosts map[string]Host
}
// New initializes and returns a new Bootstrap file for a new host. This
// function assigns Hosts an empty map.
func New(
caCreds nebula.CACredentials,
adminCreationParams admin.CreationParams,
garage Garage,
name string,
ip net.IP,
) (
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)
}
return Bootstrap{
AdminCreationParams: adminCreationParams,
CAPublicCredentials: caCreds.Public,
Garage: garage,
PrivateCredentials: hostPrivCreds,
HostAssigned: assigned,
SignedHostAssigned: signedAssigned,
Hosts: map[string]Host{},
}, nil
}
// FromReader reads a bootstrap file from the given io.Reader.
func FromReader(r io.Reader) (Bootstrap, error) {
var b Bootstrap
err := json.NewDecoder(r).Decode(&b)
if err != nil {
return Bootstrap{}, fmt.Errorf("decoding json: %w", err)
}
if b.HostAssigned, err = b.SignedHostAssigned.UnwrapUnsafe(); err != nil {
return Bootstrap{}, fmt.Errorf("unwrapping host assigned: %w", err)
}
return b, err
}
// FromFile reads a bootstrap from a file at the given path.
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()
return FromReader(f)
}
// 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
// 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[string]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
}