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 }