2024-06-10 16:56:36 +00:00
|
|
|
// Package bootstrap deals with the parsing and creation of bootstrap.json
|
|
|
|
// files. It also contains some helpers which rely on bootstrap data.
|
2021-04-20 21:31:37 +00:00
|
|
|
package bootstrap
|
|
|
|
|
|
|
|
import (
|
2022-10-15 16:41:07 +00:00
|
|
|
"crypto/sha512"
|
2024-06-10 16:56:36 +00:00
|
|
|
"encoding/json"
|
2021-04-20 21:31:37 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2024-06-10 16:56:36 +00:00
|
|
|
"isle/admin"
|
|
|
|
"isle/garage"
|
|
|
|
"isle/nebula"
|
2024-06-10 20:31:29 +00:00
|
|
|
"net"
|
2022-10-15 14:28:03 +00:00
|
|
|
"os"
|
2022-10-15 16:41:07 +00:00
|
|
|
"path/filepath"
|
|
|
|
"sort"
|
2021-04-20 21:31:37 +00:00
|
|
|
)
|
|
|
|
|
2022-10-26 22:23:39 +00:00
|
|
|
// DataDirPath returns the path within the user's data directory where the
|
|
|
|
// bootstrap file is stored.
|
|
|
|
func DataDirPath(dataDirPath string) string {
|
2024-06-10 16:56:36 +00:00
|
|
|
return filepath.Join(dataDirPath, "bootstrap.json")
|
2022-10-26 22:23:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AppDirPath returns the path within the AppDir where an embedded bootstrap
|
|
|
|
// file might be found.
|
|
|
|
func AppDirPath(appDirPath string) string {
|
2024-06-10 16:56:36 +00:00
|
|
|
return filepath.Join(appDirPath, "share/bootstrap.json")
|
2022-10-26 22:23:39 +00:00
|
|
|
}
|
|
|
|
|
2024-06-10 20:31:29 +00:00
|
|
|
// Garage contains parameters needed to connect to and use the garage cluster.
|
|
|
|
type Garage struct {
|
2024-06-11 12:54:26 +00:00
|
|
|
// 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
|
2024-06-10 20:31:29 +00:00
|
|
|
GlobalBucketS3APICredentials garage.S3APICredentials
|
|
|
|
}
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
// Bootstrap is used for accessing all information contained within a
|
2024-06-10 16:56:36 +00:00
|
|
|
// bootstrap.json file.
|
2022-10-15 14:28:03 +00:00
|
|
|
type Bootstrap struct {
|
2024-06-10 16:56:36 +00:00
|
|
|
AdminCreationParams admin.CreationParams
|
2024-06-10 20:31:29 +00:00
|
|
|
CAPublicCredentials nebula.CAPublicCredentials
|
|
|
|
Garage Garage
|
|
|
|
|
|
|
|
PrivateCredentials nebula.HostPrivateCredentials
|
|
|
|
HostAssigned `json:"-"`
|
|
|
|
SignedHostAssigned nebula.Signed[HostAssigned] // signed by CA
|
|
|
|
|
|
|
|
Hosts map[string]Host
|
|
|
|
}
|
2022-10-16 15:05:05 +00:00
|
|
|
|
2024-06-10 20:31:29 +00:00
|
|
|
// 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)
|
|
|
|
}
|
2022-10-15 14:28:03 +00:00
|
|
|
|
2024-06-10 20:31:29 +00:00
|
|
|
assigned := HostAssigned{
|
|
|
|
Name: name,
|
|
|
|
PublicCredentials: hostPubCreds,
|
2024-06-10 16:56:36 +00:00
|
|
|
}
|
2022-10-15 14:28:03 +00:00
|
|
|
|
2024-06-10 20:31:29 +00:00
|
|
|
signedAssigned, err := nebula.Sign(assigned, caCreds.SigningPrivateKey)
|
|
|
|
if err != nil {
|
|
|
|
return Bootstrap{}, fmt.Errorf("signing assigned fields: %w", err)
|
2024-06-10 16:56:36 +00:00
|
|
|
}
|
2024-06-10 20:31:29 +00:00
|
|
|
|
|
|
|
return Bootstrap{
|
|
|
|
AdminCreationParams: adminCreationParams,
|
|
|
|
CAPublicCredentials: caCreds.Public,
|
|
|
|
Garage: garage,
|
|
|
|
PrivateCredentials: hostPrivCreds,
|
|
|
|
HostAssigned: assigned,
|
|
|
|
SignedHostAssigned: signedAssigned,
|
|
|
|
Hosts: map[string]Host{},
|
|
|
|
}, nil
|
2022-10-15 14:28:03 +00:00
|
|
|
}
|
2021-04-20 21:31:37 +00:00
|
|
|
|
2024-06-10 16:56:36 +00:00
|
|
|
// FromReader reads a bootstrap file from the given io.Reader.
|
2022-10-15 14:28:03 +00:00
|
|
|
func FromReader(r io.Reader) (Bootstrap, error) {
|
2022-11-02 13:34:40 +00:00
|
|
|
var b Bootstrap
|
2024-06-10 20:31:29 +00:00
|
|
|
|
2024-06-10 16:56:36 +00:00
|
|
|
err := json.NewDecoder(r).Decode(&b)
|
2024-06-10 20:31:29 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2022-11-02 13:34:40 +00:00
|
|
|
return b, err
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
|
|
|
|
2024-06-10 16:56:36 +00:00
|
|
|
// FromFile reads a bootstrap from a file at the given path.
|
2022-10-15 14:28:03 +00:00
|
|
|
func FromFile(path string) (Bootstrap, error) {
|
2022-10-11 19:24:53 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
f, err := os.Open(path)
|
2022-10-11 19:24:53 +00:00
|
|
|
if err != nil {
|
2022-10-15 14:28:03 +00:00
|
|
|
return Bootstrap{}, fmt.Errorf("opening file: %w", err)
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
2022-10-15 14:28:03 +00:00
|
|
|
defer f.Close()
|
2022-10-11 19:24:53 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
return FromReader(f)
|
|
|
|
}
|
2022-10-11 19:24:53 +00:00
|
|
|
|
2024-06-10 16:56:36 +00:00
|
|
|
// WriteTo writes the Bootstrap as a new bootstrap to the given io.Writer.
|
2022-10-15 16:41:07 +00:00
|
|
|
func (b Bootstrap) WriteTo(into io.Writer) error {
|
2024-06-10 16:56:36 +00:00
|
|
|
return json.NewEncoder(into).Encode(b)
|
2022-10-15 16:41:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
// 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 {
|
|
|
|
|
2024-06-10 20:31:29 +00:00
|
|
|
host, ok := b.Hosts[b.Name]
|
2022-10-15 14:28:03 +00:00
|
|
|
if !ok {
|
2024-06-10 20:31:29 +00:00
|
|
|
panic(fmt.Sprintf("hostname %q not defined in bootstrap's hosts", b.Name))
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
return host
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
2022-10-15 16:41:07 +00:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2024-06-10 16:56:36 +00:00
|
|
|
sort.Slice(hosts, func(i, j int) bool {
|
|
|
|
return hosts[i].Name < hosts[j].Name
|
|
|
|
})
|
2022-10-15 16:41:07 +00:00
|
|
|
|
|
|
|
h := sha512.New()
|
2024-06-10 16:56:36 +00:00
|
|
|
if err := json.NewEncoder(h).Encode(hosts); err != nil {
|
2022-10-15 16:41:07 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return h.Sum(nil), nil
|
|
|
|
}
|