diff --git a/bootserver/bootoption/bootoption.go b/bootserver/bootoption/bootoption.go index e90546c..227c367 100644 --- a/bootserver/bootoption/bootoption.go +++ b/bootserver/bootoption/bootoption.go @@ -3,9 +3,8 @@ package bootoption import "github.com/google/uuid" type EFIApp struct { - Name string `json:"name"` - Path string `json:"path"` - DiskID string `json:"disk_id"` + Name string `json:"name"` + DevicePath string `json:"device_path"` } type Client struct { diff --git a/bootserver/bootprotocol/bootprotocol.go b/bootserver/bootprotocol/bootprotocol.go index ea028a1..571040a 100644 --- a/bootserver/bootprotocol/bootprotocol.go +++ b/bootserver/bootprotocol/bootprotocol.go @@ -6,6 +6,7 @@ import ( "encoding" "errors" "fmt" + "strings" "github.com/google/uuid" ) @@ -20,7 +21,7 @@ const ( ) var spaceByte = []byte(" ") -var commaByte = []byte(",") +var commaByte = []byte(";") const ( keyID = "id" @@ -140,9 +141,13 @@ func (am *acceptMessage) UnmarshalBinary(data []byte) error { func (am *acceptMessage) MarshalBinary() (data []byte, err error) { action := []byte(am.Action().String()) + // efiApp := strings.ReplaceAll(am.efiApp, `\`, `\\`) + // efiApp = strings.ReplaceAll(efiApp, "File(", "") + efiApp := strings.ReplaceAll(am.efiApp, "File(", "") + efiApp, _ = strings.CutSuffix(efiApp, ")") params := [][]byte{ []byte(fmt.Sprintf("%s=%s", keyID, am.id.String())), - []byte(fmt.Sprintf("%s=%s", keyEfiApp, am.efiApp)), + []byte(fmt.Sprintf("%s=%s", keyEfiApp, efiApp)), } param_bytes := bytes.Join(params, commaByte) return bytes.Join([][]byte{action, param_bytes}, spaceByte), nil diff --git a/bootserver/config/config.go b/bootserver/config/config.go index 9aabc58..8db8fbb 100644 --- a/bootserver/config/config.go +++ b/bootserver/config/config.go @@ -55,6 +55,7 @@ type jsonConf struct { Iface string `json:"interface"` Port int `json:"port"` McastGroup string `json:"multicast_group"` + SrcAddr string `json:"src_addr"` } `json:"boot_provider"` } @@ -68,6 +69,7 @@ type AppConfig struct { UPDMcastGroup string UDPPort int UDPIface string + UDPSrcAddr string } func parseLevel(lvlStr string) logrus.Level { @@ -97,6 +99,7 @@ func (ac *AppConfig) UnmarshalJSON(data []byte) error { ac.UPDMcastGroup = jsonConf.BootProvider.McastGroup ac.UDPIface = jsonConf.BootProvider.Iface ac.UDPPort = jsonConf.BootProvider.Port + ac.UDPSrcAddr = jsonConf.BootProvider.SrcAddr ac.DataFilepath = jsonConf.Storage.Path return nil } diff --git a/bootserver/controllers/client/client.go b/bootserver/controllers/client/client.go index 97a30b9..019a409 100644 --- a/bootserver/controllers/client/client.go +++ b/bootserver/controllers/client/client.go @@ -64,15 +64,47 @@ func (bc *BootController) setBootOption(w http.ResponseWriter, r *http.Request) return http.StatusAccepted, nil, nil } +func (bc *BootController) deleteClient(w http.ResponseWriter, r *http.Request) (int, []byte, error) { + dat, err := io.ReadAll(r.Body) + if err != nil { + return http.StatusInternalServerError, nil, fmt.Errorf("failed to read body: %w", err) + } + var payload setBootOptionPayload + if err := json.Unmarshal(dat, &payload); err != nil { + return http.StatusBadRequest, nil, fmt.Errorf("failed to parse body: %w", err) + } + + clientID, err := uuid.Parse(payload.ClientID) + if err != nil { + return http.StatusBadRequest, []byte("bad client ID"), fmt.Errorf("invalid format for client ID: %w", err) + } + if err := bc.clientService.DeleteClient(clientID); err != nil { + return http.StatusInternalServerError, []byte("failed to delete client"), fmt.Errorf("failed to delete client: %w", err) + } + + return http.StatusOK, nil, nil +} + func (bc *BootController) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPut { + var returncode int + var content []byte + var err error + + switch r.Method { + case http.MethodPut: + returncode, content, err = bc.setBootOption(w, r) + if err != nil { + bc.l.Errorf("Error setting boot option for client: %s", err.Error()) + } + case http.MethodDelete: + returncode, content, err = bc.deleteClient(w, r) + if err != nil { + bc.l.Errorf("Error setting boot option for client: %s", err.Error()) + } + default: helpers.HandleResponse(w, r, http.StatusMethodNotAllowed, nil, bc.l) return } - returncode, content, err := bc.setBootOption(w, r) - if err != nil { - bc.l.Errorf("Error setting boot option for client: %s", err.Error()) - } helpers.HandleResponse(w, r, returncode, content, bc.l) } diff --git a/bootserver/controllers/client/enroll.go b/bootserver/controllers/client/enroll.go index 069a44a..74356ac 100644 --- a/bootserver/controllers/client/enroll.go +++ b/bootserver/controllers/client/enroll.go @@ -15,18 +15,25 @@ import ( const EnrollRoute = "/enroll" type newClientPayload struct { - ID string `json:"ID"` + ID string `json:"ID"` + MulticastGroup string `json:"multicast_group"` + MulticastPort int `json:"multicast_port"` } type EnrollController struct { - clientService *services.ClientHandlerService - l *logrus.Logger + clientService *services.ClientHandlerService + l *logrus.Logger + multicastPort int + multicastGroup string } -func NewEnrollController(l *logrus.Logger, service *services.ClientHandlerService) *EnrollController { +func NewEnrollController(l *logrus.Logger, service *services.ClientHandlerService, mcastPort int, mcastGroup string) *EnrollController { return &EnrollController{ - clientService: service, - l: l, + + clientService: service, + l: l, + multicastPort: mcastPort, + multicastGroup: mcastGroup, } } @@ -50,12 +57,13 @@ func (ec *EnrollController) enrollMachine(w http.ResponseWriter, r *http.Request return http.StatusInternalServerError, nil, fmt.Errorf("failed to create client %w", err) } - payload, err := json.Marshal(newClientPayload{ID: cltID.String()}) + payload, err := json.Marshal(newClientPayload{ID: cltID.String(), MulticastGroup: ec.multicastGroup, MulticastPort: ec.multicastPort}) if err != nil { return http.StatusInternalServerError, nil, fmt.Errorf("failed to serialize payload: %w", err) } ec.l.Infof("Added client") + w.Header().Add("Content-Type", "application/json") return http.StatusOK, payload, nil } diff --git a/bootserver/controllers/ui/ui.go b/bootserver/controllers/ui/ui.go index a9a2507..e4c881f 100644 --- a/bootserver/controllers/ui/ui.go +++ b/bootserver/controllers/ui/ui.go @@ -20,7 +20,6 @@ type templateBootOption struct { ID string Name string Path string - DiskID string Selected bool } @@ -65,9 +64,8 @@ func (uc *UIController) serveUI(w http.ResponseWriter, r *http.Request) (int, in for id, bo := range clt.Options { tplBO = append(tplBO, templateBootOption{ Name: bo.Name, - Path: bo.Path, + Path: bo.DevicePath, ID: id, - DiskID: bo.DiskID, Selected: id == clt.SelectedOption, }) sort.Slice(tplBO, func(i, j int) bool { diff --git a/bootserver/server/server.go b/bootserver/server/server.go index 27b275e..7806905 100644 --- a/bootserver/server/server.go +++ b/bootserver/server/server.go @@ -67,7 +67,7 @@ func New(appConf *config.AppConfig, logger *logrus.Logger) (*Server, error) { } service := services.NewClientHandlerService(appConf.DataFilepath, logger) controllers := map[string]http.Handler{ - client.EnrollRoute: middlewares.WithLogger(client.NewEnrollController(logger, service), logger), + client.EnrollRoute: middlewares.WithLogger(client.NewEnrollController(logger, service, appConf.UDPPort, appConf.UPDMcastGroup), logger), client.ConfigRoute: middlewares.WithLogger(client.NewGetConfigController(logger, service, appConf), logger), client.SetBootRoute: middlewares.WithLogger(client.NewBootController(logger, service), logger), ui.StaticRoute: &ui.StaticController{}, diff --git a/bootserver/services/services.go b/bootserver/services/services.go index 2293cc4..1669f22 100644 --- a/bootserver/services/services.go +++ b/bootserver/services/services.go @@ -156,6 +156,20 @@ func (chs *ClientHandlerService) GetClientSelectedBootOption(client uuid.UUID) ( } } +func (chs *ClientHandlerService) DeleteClient(client uuid.UUID) error { + var err error + + clients, err := chs.load() + if err != nil { + return fmt.Errorf("failed to load current config: %w", err) + } + delete(clients, client) + if err := chs.unload(clients); err != nil { + return fmt.Errorf("failed to save current config: %w", err) + } + return nil +} + func (chs *ClientHandlerService) SetClientBootOption(client uuid.UUID, option string) error { var err error diff --git a/bootserver/udplistener/udplistener.go b/bootserver/udplistener/udplistener.go index a583b27..e167c75 100644 --- a/bootserver/udplistener/udplistener.go +++ b/bootserver/udplistener/udplistener.go @@ -22,6 +22,7 @@ type udpMessage struct { type UDPListener struct { addr *net.UDPAddr + laddr *net.UDPAddr iface *net.Interface l *net.UDPConn log *logrus.Logger @@ -41,9 +42,15 @@ func New(conf *config.AppConfig, log *logrus.Logger) (*UDPListener, error) { return nil, fmt.Errorf("failed to resolve interface name %s: %w", conf.UDPIface, err) } + laddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("[%s%%%s]:0", conf.UDPSrcAddr, conf.UDPIface)) + if err != nil { + return nil, fmt.Errorf("failed to resolve UDP source address: %w", err) + } + return &UDPListener{ addr: addr, iface: iface, + laddr: laddr, ctx: context.TODO(), service: services.NewClientHandlerService(conf.DataFilepath, log), log: log, @@ -78,7 +85,7 @@ func (l *UDPListener) handleBootRequest(msg bootprotocol.Message, subLogger logr requestLogger.Errorf("Failed to get config for client: %s", err.Error()) return bootprotocol.Deny(msg.ID(), "unknown server error") } - return bootprotocol.Accept(msg.ID(), bootOption.Path) + return bootprotocol.Accept(msg.ID(), bootOption.DevicePath) } func (l *UDPListener) handleClient(msg *udpMessage) error { @@ -88,7 +95,7 @@ func (l *UDPListener) handleClient(msg *udpMessage) error { response := l.handleBootRequest(msg.message, clientLogger) clientLogger.Debug("Dialing client for answer") - con, err := net.DialUDP("udp", nil, msg.sourceAddr) + con, err := net.DialUDP("udp", l.laddr, msg.sourceAddr) if err != nil { return fmt.Errorf("failed to dialed client: %w", err) }