From c45125fbe04b3f8f139deae2844d36fa5e7be250 Mon Sep 17 00:00:00 2001 From: Florent Gluck <florent.gluck@hesge.ch> Date: Thu, 18 May 2023 00:50:04 +0200 Subject: [PATCH] Added new command: --------------------- vmstartwithcreds Starts one or more VMs with user-defined credentials. Requires VM_START VM access capability or VM_START_ANY user capability. --------------------- client: fixed ReadCSVColumn which didn't work if separator char was not a comma --- docs/README_client.md | 18 ++++- src/client/cmdVM/vmStartWithCreds.go | 106 +++++++++++++++++++++++++++ src/client/nexus-cli/nexus-cli.go | 1 + src/client/nexush/nexush.go | 1 + src/client/utils/csv.go | 8 +- src/server/router/routerVMs.go | 7 +- src/server/vms/vms.go | 6 ++ 7 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 src/client/cmdVM/vmStartWithCreds.go diff --git a/docs/README_client.md b/docs/README_client.md index 090d001..f732439 100644 --- a/docs/README_client.md +++ b/docs/README_client.md @@ -140,6 +140,14 @@ nexush> help ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ls List files in the specified dir or in the current dir if no argument is specified. ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― +refresh Obtains a new access token. +――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― +version Display nexus server and client's versions. +――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― +whoami Displays the current user's details. +――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― +passwd Updates the current user's password. +――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― userlist Lists users. Requires USER_LIST user capability. ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― @@ -158,12 +166,16 @@ vmlist Lists VMs. vmcred2pdf Creates a PDF with the credentials required to attach to running VMs. Requires VM_LIST VM access capability or VM_LIST_ANY user capability. ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― -vmcred2txt Creates a text file with the credentials required to attach to running VMs. +vmcred2csv Creates a CSV file with the credentials required to attach to running VMs. + The written CSV file contains 4 columns: VM ID;VM name;port;password Requires VM_LIST VM access capability or VM_LIST_ANY user capability. ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― vmstart Starts one or more VMs. Requires VM_START VM access capability or VM_START_ANY user capability. ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― +vmstartwithcreds Starts one or more VMs with user-defined credentials. + Requires VM_START VM access capability or VM_START_ANY user capability. +――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― vmkill Kills one or more VMs. Requires VM_STOP VM access capability or VM_STOP_ANY user capability. ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― @@ -191,13 +203,13 @@ vmaddaccess Adds a user's VM access in one or more VMs. vmdelaccess Removes a user's VM access in one or more VMs. Requires VM_SET_ACCESS user capability and VM_SET_ACCESS VM access capability. ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― -vmexportdir Exports one or more VMs' directory into one or more tar archives. +vmexportdir Exports one or more VMs' directory into one or more compressed archives. Requires VM_READFS VM access capability or VM_READFS_ANY user capability. ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― vmimportdir Copies a local directory (or file) and all its content into one or more VMs. Requires VM_WRITEFS VM access capability or VM_WRITEFS_ANY user capability. ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― -tpllist Lists available templates. +tpllist Lists templates. Requires TPL_LIST or TPL_LIST_ANY user capability. ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― tplcreate Creates a template from an existing VM. diff --git a/src/client/cmdVM/vmStartWithCreds.go b/src/client/cmdVM/vmStartWithCreds.go new file mode 100644 index 0000000..e8bbbef --- /dev/null +++ b/src/client/cmdVM/vmStartWithCreds.go @@ -0,0 +1,106 @@ +package cmdVM + +import ( + "errors" + "strconv" + u "nexus-client/utils" + g "nexus-client/globals" +) + +type StartWithCreds struct { + Name string +} + +func (cmd *StartWithCreds)GetName() string { + return cmd.Name +} + +func (cmd *StartWithCreds)GetDesc() []string { + return []string{ + "Starts one or more VMs with user-defined credentials.", + "Requires VM_START VM access capability or VM_START_ANY user capability."} +} + +func (cmd *StartWithCreds)PrintUsage() { + for _, desc := range cmd.GetDesc() { + u.PrintlnErr(desc) + } + u.PrintlnErr("―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――") + u.PrintlnErr("USAGE: "+cmd.GetName()+" file.csv") + u.PrintlnErr("―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――") + const usage string = `file.csv 4-column CSV file defining the VMs to start and their credentials. + Content of the 4 columns: VM ID;VM name;port;password.` + u.PrintlnErr(usage) +} + +func (cmd *StartWithCreds)parseCSVFile(csvFile string) ([]string, []string, []string, error) { + // Column 0: VM IDs + vmIDs, err := u.ReadCSVColumn(csvFile, 0) + if err != nil { + return nil, nil, nil, err + } + // Column 2: ports + ports, err := u.ReadCSVColumn(csvFile, 2) + if err != nil { + return nil, nil, nil, err + } + // Column 3: passwords + pwds, err := u.ReadCSVColumn(csvFile, 3) + if err != nil { + return nil, nil, nil, err + } + count := len(vmIDs) + if (len(ports) != count) || (len(pwds) != count) { + return nil, nil, nil, errors.New("Invalid CSV file: all columns must have the same number of entries!") + } + return vmIDs, ports, pwds, nil +} + +func (cmd *StartWithCreds)Run(args []string) int { + client := g.GetInstance().Client + host := g.GetInstance().Host + + argc := len(args) + if argc != 1 { + cmd.PrintUsage() + return 1 + } + + vmIDs, ports, pwds, err := cmd.parseCSVFile(args[0]) + if err != nil { + u.PrintlnErr(err.Error()) + return 1 + } + + statusCode := 0 + + type VMArgs struct { + Port int + Pwd string + } + + vmArgs := &VMArgs {} + + for i, vmID := range(vmIDs) { + port, err := strconv.Atoi(ports[i]) + if err != nil { + u.PrintlnErr("Line ",i,": invalid port number") + } + vmArgs.Port = port + vmArgs.Pwd = pwds[i] + resp, err := client.R().SetBody(vmArgs).Put(host+"/vms/"+vmID+"/startwithcreds") + if err != nil { + u.PrintlnErr("Failed starting VM \""+vmID+"\": "+err.Error()) + statusCode = 1 + } else { + if resp.IsSuccess() { + u.Println("Started VM \""+vmID+"\"") + } else { + u.PrintlnErr("Failed starting VM \""+vmID+"\": "+resp.Status()+": "+resp.String()) + statusCode = 1 + } + } + } + + return statusCode +} diff --git a/src/client/nexus-cli/nexus-cli.go b/src/client/nexus-cli/nexus-cli.go index d704775..e8a4732 100644 --- a/src/client/nexus-cli/nexus-cli.go +++ b/src/client/nexus-cli/nexus-cli.go @@ -38,6 +38,7 @@ var cmdList = []cmd.Command { &cmdVM.Cred2pdf{"vmcred2pdf"}, &cmdVM.Cred2csv{"vmcred2csv"}, &cmdVM.Start{"vmstart"}, + &cmdVM.StartWithCreds{"vmstartwithcreds"}, &cmdVM.Stop{"vmkill"}, &cmdVM.Shutdown{"vmshutdown"}, &cmdVM.Reboot{"vmreboot"}, diff --git a/src/client/nexush/nexush.go b/src/client/nexush/nexush.go index e156e7c..152172d 100644 --- a/src/client/nexush/nexush.go +++ b/src/client/nexush/nexush.go @@ -45,6 +45,7 @@ var cmdList = []cmd.Command { &cmdVM.Cred2pdf{"vmcred2pdf"}, &cmdVM.Cred2csv{"vmcred2csv"}, &cmdVM.Start{"vmstart"}, + &cmdVM.StartWithCreds{"vmstartwithcreds"}, &cmdVM.Stop{"vmkill"}, &cmdVM.Shutdown{"vmshutdown"}, &cmdVM.Reboot{"vmreboot"}, diff --git a/src/client/utils/csv.go b/src/client/utils/csv.go index 552f5f3..8441477 100644 --- a/src/client/utils/csv.go +++ b/src/client/utils/csv.go @@ -4,6 +4,7 @@ import ( "os" "io" "errors" + "strconv" "encoding/csv" ) @@ -16,6 +17,8 @@ func ReadCSVColumn(csvFile string, column int) ([]string, error) { } defer file.Close() reader := csv.NewReader(file) + reader.Comma = ';' // specify the column separator + reader.Comment = '#' // lines starting with this symbol are to be ignored var entries []string for { @@ -26,8 +29,9 @@ func ReadCSVColumn(csvFile string, column int) ([]string, error) { if err != nil { return nil, errors.New("Failed reading "+err.Error()) } - if column >= len(record) { - return nil, errors.New("Failed reading \""+csvFile+"\": column out of bound") + colCount := len(record) + if column >= colCount { + return nil, errors.New("Failed reading \""+csvFile+"\": column out of bound (detected "+strconv.Itoa(colCount)+" columns)") } entries = append(entries, record[column]) } diff --git a/src/server/router/routerVMs.go b/src/server/router/routerVMs.go index 6467f70..0664134 100644 --- a/src/server/router/routerVMs.go +++ b/src/server/router/routerVMs.go @@ -244,16 +244,15 @@ func (r *RouterVMs)StartVMWithCreds(c echo.Context) error { // Deserializes and validates client's parameters. type Parameters struct { - port int `json:"port" validate:"required,gte=1100,lte=65535"` - pwd string `json:"pwd" validate:"required,min=8,max=64"` + Port int `json:"port" validate:"required,gte=1100,lte=65535"` + Pwd string `json:"pwd" validate:"required,min=8,max=64"` } p := new(Parameters) if err := decodeJson(c, &p); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } - _, _, err := r.vms.StartVM(vm.GetID()) - if err != nil { + if err := r.vms.StartVMWithCreds(vm.GetID(), p.Port, p.Pwd); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } return c.JSONPretty(http.StatusOK, jsonMsg("OK"), " ") diff --git a/src/server/vms/vms.go b/src/server/vms/vms.go index 6d2edad..15447ad 100644 --- a/src/server/vms/vms.go +++ b/src/server/vms/vms.go @@ -177,6 +177,12 @@ func (vms *VMs)StartVMWithCreds(vmID uuid.UUID, port int, pwd string) error { vms.rwlock.Lock() defer vms.rwlock.Unlock() + if vms.usedPorts[port] || !utils.IsPortAvailable(port) { + return errors.New("Failed starting VM: port already in use") + } else { + vms.usedPorts[port] = true + } + vm, err := vms.getVMUnsafe(vmID) if err != nil { return err -- GitLab