isle/go/bootstrap/hosts.go

135 lines
3.6 KiB
Go

package bootstrap
import (
"fmt"
"isle/garage"
"isle/nebula"
"net/netip"
)
// NebulaHost describes the nebula configuration of a Host which is relevant for
// other hosts to know.
type NebulaHost struct {
PublicAddr string
}
// GarageHost describes a single garage instance in the GarageHost.
type GarageHostInstance struct {
ID string
RPCPort int
S3APIPort int
}
// GarageHost describes the garage configuration of a Host which is relevant for
// other hosts to know.
type GarageHost struct {
Instances []GarageHostInstance
}
// HostAssigned are all fields related to a host which were assigned to it by an
// admin.
type HostAssigned struct {
Name nebula.HostName
PublicCredentials nebula.HostPublicCredentials
}
// HostConfigured are all the fields a host can configure for itself.
type HostConfigured struct {
Nebula NebulaHost `json:",omitempty"`
Garage GarageHost `json:",omitempty"`
}
// AuthenticatedHost wraps all the data about a host which other hosts may know
// about it, such that those hosts can authenticate that the data is valid and
// approved by an admin.
type AuthenticatedHost struct {
Assigned nebula.Signed[HostAssigned] // signed by CA
Configured nebula.Signed[HostConfigured] // signed by host
}
// Unwrap attempts to authenticate and unwrap the Host embedded in this
// instance. nebula.ErrInvalidSignature is returned if any signatures are
// invalid.
func (ah AuthenticatedHost) Unwrap(caCreds nebula.CAPublicCredentials) (Host, error) {
assigned, err := ah.Assigned.Unwrap(caCreds.SigningKey)
if err != nil {
return Host{}, fmt.Errorf("unwrapping assigned fields using CA public key: %w", err)
}
configured, err := ah.Configured.Unwrap(assigned.PublicCredentials.SigningKey)
if err != nil {
return Host{}, fmt.Errorf("unwrapping configured fields using host public key: %w", err)
}
return Host{assigned, configured}, nil
}
// Host contains all data bout a host which other hosts may know about it.
//
// A Host should only be obtained over the network as an AuthenticatedHost, and
// subsequently Unwrapped.
type Host struct {
HostAssigned
HostConfigured
}
// NewHost creates a Host instance using the given assigned fields, along with
// the HostPrivateCredentials which its PublicCredentials field.
func NewHost(
caCreds nebula.CACredentials, name nebula.HostName, ip netip.Addr,
) (
host Host, hostPrivCreds nebula.HostPrivateCredentials, err error,
) {
hostPubCreds, hostPrivCreds, err := nebula.NewHostCredentials(
caCreds, name, ip,
)
if err != nil {
err = fmt.Errorf("generating host credentials: %w", err)
return
}
host = Host{
HostAssigned: HostAssigned{
Name: name,
PublicCredentials: hostPubCreds,
},
}
return
}
// IP returns the IP address encoded in the Host's nebula certificate, or panics
// if there is an error.
//
// This assumes that the Host and its data has already been verified against the
// CA signing key.
func (h Host) IP() netip.Addr {
cert := h.PublicCredentials.Cert.Unwrap()
if len(cert.Details.Ips) == 0 {
panic(fmt.Sprintf("host %q not configured with any ips: %+v", h.Name, h))
}
ip := cert.Details.Ips[0].IP
addr, ok := netip.AddrFromSlice(ip)
if !ok {
panic(fmt.Sprintf("ip %q (%#v) is not valid, somehow", ip, ip))
}
return addr
}
// GarageNodes returns a RemoteNode for each garage instance advertised by this
// Host.
func (h Host) GarageNodes() []garage.RemoteNode {
var nodes []garage.RemoteNode
for _, instance := range h.Garage.Instances {
nodes = append(nodes, garage.RemoteNode{
ID: instance.ID,
IP: h.IP().String(),
RPCPort: instance.RPCPort,
S3APIPort: instance.S3APIPort,
})
}
return nodes
}