102 lines
3 KiB
Go
102 lines
3 KiB
Go
package remote
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
|
|
"git.faercol.me/faercol/http-boot-config/config/logger"
|
|
"git.faercol.me/faercol/http-boot-config/config/prober"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const enrollURL = "/enroll"
|
|
|
|
type enrollEFIOption struct {
|
|
Name string `json:"name"`
|
|
DevicePath string `json:"device_path"`
|
|
}
|
|
|
|
type enrollPayload struct {
|
|
Name string `json:"name"`
|
|
Options map[string]enrollEFIOption `json:"options"`
|
|
SelectedOption string `json:"selected_option"`
|
|
}
|
|
|
|
type enrollRespPayloadJSON struct {
|
|
ID string `json:"ID"`
|
|
MulticastGroup string `json:"multicast_group"`
|
|
MulticastPort int `json:"multicast_port"`
|
|
}
|
|
|
|
type EnrollConfig struct {
|
|
ID uuid.UUID
|
|
MulticastGroup net.IP
|
|
MulticastPort int
|
|
}
|
|
|
|
func enrollToServer(ctx context.Context, host string, name string, apps []prober.EfiApp) (EnrollConfig, error) {
|
|
payload := enrollPayload{
|
|
Name: name,
|
|
Options: make(map[string]enrollEFIOption),
|
|
}
|
|
for _, a := range apps {
|
|
appID := uuid.New()
|
|
payload.Options[appID.String()] = enrollEFIOption{Name: a.Name, DevicePath: a.DevicePath}
|
|
if a.Active {
|
|
payload.SelectedOption = appID.String()
|
|
}
|
|
}
|
|
|
|
dat, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return EnrollConfig{}, fmt.Errorf("failed to serialize payload: %w", err)
|
|
}
|
|
|
|
subCtx, cancel := context.WithTimeout(ctx, reqTimeout)
|
|
defer cancel()
|
|
req, err := http.NewRequestWithContext(subCtx, http.MethodPost, host+enrollURL, bytes.NewBuffer(dat))
|
|
if err != nil {
|
|
return EnrollConfig{}, fmt.Errorf("failed to initialize request: %w", err)
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return EnrollConfig{}, fmt.Errorf("failed to do query to remote boot server: %w", err)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return EnrollConfig{}, fmt.Errorf("unexpected returncode %d", resp.StatusCode)
|
|
}
|
|
|
|
var respPayload enrollRespPayloadJSON
|
|
respDat, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return EnrollConfig{}, fmt.Errorf("failed to read response from server: %w", err)
|
|
}
|
|
if err := json.Unmarshal(respDat, &respPayload); err != nil {
|
|
return EnrollConfig{}, fmt.Errorf("failed to deserialize data from server: %w", err)
|
|
}
|
|
newID, err := uuid.Parse(respPayload.ID)
|
|
if err != nil {
|
|
return EnrollConfig{}, fmt.Errorf("invalid UUID %q from server: %w", respPayload.ID, err)
|
|
}
|
|
return EnrollConfig{ID: newID, MulticastGroup: net.ParseIP(respPayload.MulticastGroup), MulticastPort: respPayload.MulticastPort}, nil
|
|
}
|
|
|
|
func Enroll(ctx context.Context, host, name string, l *logger.SimpleLogger) (EnrollConfig, error) {
|
|
apps, err := prober.GetEFIApps()
|
|
if err != nil {
|
|
return EnrollConfig{}, fmt.Errorf("failed to get list of available EFI applications: %w", err)
|
|
}
|
|
|
|
newConf, err := enrollToServer(ctx, host, name, apps)
|
|
if err != nil {
|
|
return EnrollConfig{}, fmt.Errorf("failed to enroll client to remote host: %w", err)
|
|
}
|
|
l.Infof("Successfully enrolled new client, ID is %q", newConf.ID.String())
|
|
return newConf, nil
|
|
}
|