diff --git a/cmd/boot-script-service/default_api.go b/cmd/boot-script-service/default_api.go index c9fa71e..24d2a13 100644 --- a/cmd/boot-script-service/default_api.go +++ b/cmd/boot-script-service/default_api.go @@ -450,6 +450,16 @@ func BootparametersPost(w http.ResponseWriter, r *http.Request) { fmt.Sprintf("Bad Request: %s", err)) return } + // Check that the xnames are valid + err = args.CheckXnames() + if err != nil { + // Invalid xname format (if included), invalid request + LogBootParameters(fmt.Sprintf("/bootparameters POST FAILED: %s", err.Error()), args) + base.SendProblemDetailsGeneric(w, http.StatusBadRequest, + fmt.Sprintf("Bad Request: %s", err)) + return + } + // Fields appear to be correct. Continue with processing. debugf("Received boot parameters: %v\n", args) err, referralToken := StoreNew(args) if err == nil { diff --git a/cmd/boot-script-service/default_api_test.go b/cmd/boot-script-service/default_api_test.go index 7e9151e..bbdd97d 100644 --- a/cmd/boot-script-service/default_api_test.go +++ b/cmd/boot-script-service/default_api_test.go @@ -23,9 +23,15 @@ package main import ( + "bytes" + "encoding/json" "fmt" + "net/http" + "net/http/httptest" "regexp" "testing" + + "github.com/OpenCHAMI/bss/pkg/bssTypes" ) func mockGetSignedS3Url(s3Url string) (string, error) { @@ -179,3 +185,129 @@ func TestReplaceS3Params_error(t *testing.T) { t.Errorf("replaceS3Params failed.\n expected: %s\n actual: %s\n", expected_params, newParams) } } + +func TestBootparametersPost_Success(t *testing.T) { + + args := bssTypes.BootParams{ + Macs: []string{"00:11:22:33:44:55"}, + Hosts: []string{"x1c1s1b1n1"}, + Nids: []int32{1}, + Kernel: "kernel", + Initrd: "initrd", + Params: "params", + } + + body, _ := json.Marshal(args) + req, err := http.NewRequest("POST", "/bootparameters", bytes.NewBuffer(body)) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(BootparametersPost) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusCreated { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusCreated) + } + +} + +func TestBootparametersPost_BadRequest(t *testing.T) { + + req, err := http.NewRequest("POST", "/bootparameters", bytes.NewBuffer([]byte("invalid body"))) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(BootparametersPost) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusBadRequest) + } +} + +func TestBootparametersPost_InvalidMac(t *testing.T) { + + args := bssTypes.BootParams{ + Macs: []string{"invalid-mac"}, + Hosts: []string{"x1c1s1b1n1"}, + Nids: []int32{1}, + Kernel: "kernel", + Initrd: "initrd", + Params: "params", + } + + body, _ := json.Marshal(args) + req, err := http.NewRequest("POST", "/bootparameters", bytes.NewBuffer(body)) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(BootparametersPost) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusBadRequest) + } +} + +func TestBootparametersPost_InvalidXname(t *testing.T) { + + args := bssTypes.BootParams{ + Macs: []string{"00:11:22:33:44:55"}, + Hosts: []string{"invalid-xname"}, + Nids: []int32{1}, + Kernel: "kernel", + Initrd: "initrd", + Params: "params", + } + + body, _ := json.Marshal(args) + req, err := http.NewRequest("POST", "/bootparameters", bytes.NewBuffer(body)) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(BootparametersPost) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusBadRequest) + } +} + +func TestBootparametersPost_StoreError(t *testing.T) { + + args := bssTypes.BootParams{ + Macs: []string{"00:11:22:33:44:55"}, + Hosts: []string{"xname1"}, + Nids: []int32{1}, + Kernel: "kernel", + Initrd: "initrd", + Params: "params", + } + + body, _ := json.Marshal(args) + req, err := http.NewRequest("POST", "/bootparameters", bytes.NewBuffer(body)) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(BootparametersPost) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusBadRequest) + } +} diff --git a/pkg/bssTypes/types.go b/pkg/bssTypes/types.go index b9ae716..fdfba08 100644 --- a/pkg/bssTypes/types.go +++ b/pkg/bssTypes/types.go @@ -25,6 +25,8 @@ package bssTypes import ( "fmt" "regexp" + + "github.com/Cray-HPE/hms-xname/xnames" ) type PhoneHome struct { @@ -56,7 +58,7 @@ type CloudInit struct { // provide a "default" selection which provides a way to supply default // parameters for any node which is not explicitly configured. type BootParams struct { - Hosts []string `json:"hosts,omitempty"` + Hosts []string `json:"hosts,omitempty"` // This list of hosts must be xnames Macs []string `json:"macs,omitempty"` Nids []int32 `json:"nids,omitempty"` Params string `json:"params,omitempty"` @@ -65,6 +67,7 @@ type BootParams struct { CloudInit CloudInit `json:"cloud-init,omitempty"` } +// Validate the MACs in the boot parameters func (bp BootParams) CheckMacs() (err error) { if len(bp.Macs) > 0 { re := regexp.MustCompile(`^([0-9A-Fa-f]{2}:){5}[0-9a-fA-F]{2}$`) @@ -80,6 +83,20 @@ func (bp BootParams) CheckMacs() (err error) { return } +// Validate the xnames in the boot parameters. They must be of type "Node" +func (bp BootParams) CheckXnames() (err error) { + for _, xname := range bp.Hosts { + myXname := xnames.FromString(xname) + if myXname == nil { + return fmt.Errorf("invalid xname: %s", xname) + } + if myXname.Type() != "Node" { + return fmt.Errorf("invalid xname type: %s", myXname.Type()) + } + } + return nil +} + // The following structures and types all related to the last access information for bootscripts and cloud-init data. type EndpointType string