Skip to content
Snippets Groups Projects
Commit 146ed59b authored by Florent Gluck's avatar Florent Gluck
Browse files

Ongoing work on security overhaul:

An authentification layer is added on top of spice to authentify whoever is trying to attach to a VM.
Updated server and both nexush and nexus-cli accordingly.
The only visible change for users is that now client credentials to attach to a VM don't include a spice port and password, but only a single password.
Consequently the port column was removed from vmcreds2pdf/vmcreds2csv and the password is now a bit longer.
Server and client versions were bumped to 1.11.0.
WIP: nexus-exam
parent 3856a9dd
No related branches found
No related tags found
No related merge requests found
Showing
with 284 additions and 195 deletions
......@@ -215,9 +215,6 @@ check_bin_var:
check_server_var:
$(call check_defined, SERVER)
check_serverip_var:
$(call check_defined, SERVER_IP)
check_login_var:
$(call check_defined, LOGIN)
......@@ -241,7 +238,7 @@ help_client:
@echo "└──────────────────────────────────────────────────────────────────────────────┘"
@echo " run_nexush Run nexush for Linux/amd64; require LOGIN variable"
@echo " Example: make run_nexush LOGIN=janedoe@nexus.org"
@echo " run_nexus-exam Run nexus-exam; require CERT and SERVER_IP variables"
@echo " run_nexus-exam Run nexus-exam; require CERT and SERVER variables"
@echo ""
@echo "┌──────────────────────────────────────────────────────────────────────────────┐"
@echo "│ VALIDATION tests │"
......@@ -250,10 +247,9 @@ help_client:
@echo " Require LOGIN variable. Example:"
@echo ""
@echo "────────────────────────────────────────────────────────────────────────────────"
@echo " CERT specifies the path to the public CA certificate."
@echo " SERVER specifies the server ip address and port, separated by a colon,"
@echo " CERT: directory where the public CA certificate resides ($(CA_CERT_FILE))."
@echo " SERVER: server ip address and port, separated by a colon,"
@echo " for instance: SERVER=127.0.0.1:1077"
@echo " SERVER_IP specifies the server ip address."
copy_resources_client:
@mkdir -p $(RESOURCES_DIR_CLIENT)
......@@ -306,8 +302,8 @@ clean_client:
run_nexush: check_login_var $(BUILD_DIR_CLIENT)/nexush
$(BUILD_DIR_CLIENT)/nexush $(LOGIN)
run_nexus-exam: check_serverip_var $(BUILD_DIR_CLIENT)/nexus-exam
@NEXUS_SERVER=$(SERVER_IP) NEXUS_CERT=$(CERT)/$(CA_CERT_FILE) $(BUILD_DIR_CLIENT)/nexus-exam
run_nexus-exam: check_server_var $(BUILD_DIR_CLIENT)/nexus-exam
@NEXUS_SERVER=$(SERVER) NEXUS_CERT=$(CERT)/$(CA_CERT_FILE) $(BUILD_DIR_CLIENT)/nexus-exam
tests: check_login_var tests/run_tests build_nexus-cli $(BUILD_DIR_CLIENT)/nexus-cli
@cd tests && ./run_tests $(BUILD_ABS_CLIENT)/nexus-cli $(LOGIN)
......
......@@ -29,31 +29,33 @@
|--- |--- |--- |--- |--- |--- |--- |--- |
| `/vms` | returns VMs that can be listed | GET | - | `VM_LIST_ANY` | OR | `VM_LIST` | [\[\]common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/{id}` | returns the specified VM | GET | - | `VM_LIST_ANY` | OR | `VM_LIST` | [common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/start` | returns VM creds info for VMs that can be started | GET | - | `VM_START_ANY` | OR | `VM_START` | [\[\]common.vm.VMCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/attach` | returns VM creds info for VMs that can be attached to | GET | - | `VM_ATTACH_ANY` | OR | `VM_ATTACH` | [\[\]common.vm.VMCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/{id}/attach` | returns VM creds info for the specified VM | GET | - | `VM_ATTACH_ANY` | OR | `VM_ATTACH` | [common.vm.VMCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/stop` | returns VM creds info for VMs that can be killed/shutdown | GET | - | `VM_STOP_ANY` | OR | `VM_STOP` | [\[\]common.vm.VMCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/reboot` | returns VM creds info for VMs that can be rebooted | GET | - | `VM_REBOOT_ANY` | OR | `VM_REBOOT` | [\[\]common.vm.VMCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/edit` | returns VM creds info for VMs that can be edited | GET | - | `VM_EDIT_ANY` | OR | `VM_EDIT` | [\[\]common.vm.VMCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/editaccess` | returns VM creds info for VMs that can have their access changed | GET | - | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | [\[\]common.vm.VMCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/del` | returns VM creds info for VMs that can be deleted | GET | - | `VM_DESTROY_ANY` | OR | `VM_DESTROY` | [\[\]common.vm.VMCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/exportdir` | returns VM creds info for VMs that can have a dir downloaded | GET | - | `VM_READFS_ANY` | OR | `VM_READFS` | [\[\]common.vm.VMCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/importfiles` | returns VM creds info for VMs allowing files upload | GET | - | `VM_WRITEFS_ANY` | OR | `VM_WRITEFS` | [\[\]common.vm.VMCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/start` | returns VMs that can be started | GET | - | `VM_START_ANY` | OR | `VM_START` | [\[\]common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/attach` | returns "attach creds" for attachable VMs | GET | - | `VM_ATTACH_ANY` | OR | `VM_ATTACH` | [\[\]common.vm.VMAttachCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/{id}/attach` | returns "attach creds" for the specified VM | GET | - | `VM_ATTACH_ANY` | OR | `VM_ATTACH` | [common.vm.VMAttachCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/stop` | returns VMs that can be killed/shutdown | GET | - | `VM_STOP_ANY` | OR | `VM_STOP` | [\[\]common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/reboot` | returns VMs that can be rebooted | GET | - | `VM_REBOOT_ANY` | OR | `VM_REBOOT` | [\[\]common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/edit` | returns VMs that can be edited | GET | - | `VM_EDIT_ANY` | OR | `VM_EDIT` | [\[\]common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/editaccess` | returns VMs that can have their access changed | GET | - | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | [\[\]common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/del` | returns VMs that can be deleted | GET | - | `VM_DESTROY_ANY` | OR | `VM_DESTROY` | [\[\]common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/exportdir` | returns VMs that can have a dir downloaded | GET | - | `VM_READFS_ANY` | OR | `VM_READFS` | [\[\]common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/importfiles` | returns VMs that can have files upload to | GET | - | `VM_WRITEFS_ANY` | OR | `VM_WRITEFS` | [\[\]common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| Route | Description | Method | Input | Req. user cap. | Op. | Req. VM access cap. | Output |
|--- |--- |--- |--- |--- |--- |--- |--- |
| `/vms` | create a VM | POST | [commmon.params.VMCreate](../src/common/params/vms.go) | `VM_CREATE` | - | - | [common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/{id}` | delete a VM | DELETE | - | `VM_DESTROY_ANY` | OR | `VM_DESTROY` | - |
| `/vms/{id}` | edit a VM | PUT | [common.params.VMEdit](../src/common/params/vms.go) | `VM_EDIT_ANY` | OR | `VM_EDIT` | [common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/{id}/start` | start a VM | PUT | - | `VM_START_ANY` | OR | `VM_START` | - |
| `/vms/{id}/startwithcreds` | start a VM with credentials | PUT | [common.params.VMStartWithCreds](../src/common/params/vms.go) | `VM_START_ANY` | OR | `VM_START` | - |
| `/vms/{id}/stop` | kill a VM | PUT | - | `VM_STOP_ANY` | OR | `VM_STOP` | - |
| `/vms/{id}/reboot` | reboot a VM | PUT | - | `VM_REBOOT_ANY` | OR | `VM_REBOOT` | - |
| `/vms/{id}/shutdown` | gracefully shutdown a VM | PUT | - | `VM_STOP_ANY` | OR | `VM_STOP` | - |
| `/vms/{id}/access/{email}` | set VM access for a user | PUT | [common.params.VMAddAccess](../src/common/params/vms.go) | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | - |
| `/vms/{id}/access/{email}` | del VM access for a user | DELETE | - | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | - |
| `/vms/{id}/exportdir` | download a VM's dir | GET | [common.params.VMExportDir](../src/common/params/vms.go) | `VM_READFS_ANY` | OR | `VM_READFS` | tar.gz archive |
| `/vms/{id}/importfiles` | upload files into a VM's dir | POST | multipart form: { "vmDir" (path), "file" (tar.gz archive) } | `VM_WRITEFS_ANY` | OR | `VM_WRITEFS` | |
| `/vms/{id}` | delete the VM | DELETE | - | `VM_DESTROY_ANY` | OR | `VM_DESTROY` | - |
| `/vms/{id}` | edit the VM | PUT | [common.params.VMEdit](../src/common/params/vms.go) | `VM_EDIT_ANY` | OR | `VM_EDIT` | [common.vm.VMNetworkSerialized](../src/common/vm/vm.go) |
| `/vms/{id}/start` | start the VM | PUT | - | `VM_START_ANY` | OR | `VM_START` | - |
| `/vms/{id}/startwithcreds` | start the VM with credentials | PUT | [common.params.VMStartWithCreds](../src/common/params/vms.go) | `VM_START_ANY` | OR | `VM_START` | - |
| `/vms/{id}/spicecreds` | returns Spice creds for the VM | POST | [common.params.VMAttachCreds](../src/common/params/vms.go) | `VM_ATTACH_ANY` | OR | `VM_ATTACH` | [common.vm.VMSpiceCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/spicecreds` | returns Spice creds for a VM | POST | [common.params.VMAttachCreds](../src/common/params/vms.go) | `VM_ATTACH_ANY` | OR | - | [common.vm.VMSpiceCredentialsSerialized](../src/common/vm/vm.go) |
| `/vms/{id}/stop` | kill the VM | PUT | - | `VM_STOP_ANY` | OR | `VM_STOP` | - |
| `/vms/{id}/reboot` | reboot the VM | PUT | - | `VM_REBOOT_ANY` | OR | `VM_REBOOT` | - |
| `/vms/{id}/shutdown` | gracefully shutdown the VM | PUT | - | `VM_STOP_ANY` | OR | `VM_STOP` | - |
| `/vms/{id}/access/{email}` | set the VM's VM access for the user | PUT | [common.params.VMAddAccess](../src/common/params/vms.go) | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | - |
| `/vms/{id}/access/{email}` | del the VM's VM access for the user | DELETE | - | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | - |
| `/vms/{id}/exportdir` | download a dir from the VM | GET | [common.params.VMExportDir](../src/common/params/vms.go) | `VM_READFS_ANY` | OR | `VM_READFS` | tar.gz archive |
| `/vms/{id}/importfiles` | upload files into a dir in the VM | POST | multipart form: { "vmDir" (path), "file" (tar.gz archive) } | `VM_WRITEFS_ANY` | OR | `VM_WRITEFS` | |
### Template management
......
......@@ -69,7 +69,7 @@ func (cmd *UpdatePwd)Run(args []string) int {
return 1
} else {
if resp.IsSuccess() {
u.Println(resp)
u.Println("Password changed successfully.")
return 0
} else {
u.PrintlnErr("Error: "+resp.Status()+": "+resp.String())
......
package cmdVM
import (
"fmt"
"sync"
"errors"
"encoding/json"
"nexus-common/vm"
"nexus-common/params"
"nexus-client/exec"
u "nexus-client/utils"
g "nexus-client/globals"
"github.com/go-playground/validator/v10"
)
// The boolean indicates whether attaching is blocking (synchronous) or non-blocking (asynchronous).
func AttachToVMs(vms []vm.VMAttachCredentialsSerialized, synchronous bool) (int, error) {
statusCode := 0
hostname := g.GetInstance().Hostname
cert := g.GetInstance().PubCert
client := g.GetInstance().Client
host := g.GetInstance().Host
var wg sync.WaitGroup
if synchronous {
// Use wait groups to wait until all viewers threads have completed.
wg.Add(len(vms))
}
for _, v := range(vms) {
uuid := v.ID.String()
p := &params.VMAttachCreds{ Pwd: v.Pwd }
resp, err := client.R().SetBody(p).Post(host+"/vms/"+uuid+"/spicecreds")
if err != nil {
return 1, err
}
if resp.IsSuccess() {
var creds vm.VMSpiceCredentialsSerialized
if err := json.Unmarshal(resp.Body(), &creds); err != nil {
return 1, err
}
if err := validator.New(validator.WithRequiredStructEnabled()).Struct(creds); err != nil {
return 1, err
}
go func(v vm.VMAttachCredentialsSerialized, creds vm.VMSpiceCredentialsSerialized) {
stdoutStderr, err := exec.RunRemoteViewer(hostname, cert, v.Name, creds.SpicePort, creds.SpicePwd, false)
if err != nil {
u.PrintlnErr("Failed attaching to VM ", v.ID, ": ", fmt.Sprintf("%s", stdoutStderr))
statusCode |= 1
}
if synchronous {
wg.Done()
}
} (v, creds)
} else {
return 1, errors.New("Error: "+resp.Status()+": "+resp.String())
}
}
if synchronous {
wg.Wait()
}
return statusCode, nil
}
......@@ -118,7 +118,7 @@ func getFilteredVMs(route string, patterns []string) ([]vm.VMNetworkSerialized,
// Regular expression examples:
// "." -> matches everything
// "bla" -> matches any VM name containing "bla"
func getFilteredVMCredentials(route string, patterns []string) ([]vm.VMCredentialsSerialized, error) {
func getFilteredVMCredentials(route string, patterns []string) ([]vm.VMAttachCredentialsSerialized, error) {
if len(patterns) < 1 {
return nil, errors.New("At least one ID or regex must be specified")
}
......@@ -143,7 +143,7 @@ func getFilteredVMCredentials(route string, patterns []string) ([]vm.VMCredentia
return nil, err
}
vmsList := []vm.VMCredentialsSerialized{}
vmsList := []vm.VMAttachCredentialsSerialized{}
if resp.IsSuccess() {
vms, err := deserializeVMsCredentials(resp)
......@@ -203,8 +203,8 @@ func deserializeVM(resp *resty.Response) (*vm.VMNetworkSerialized, error) {
}
// Deserialize a list of VM credentials from an http response.
func deserializeVMsCredentials(resp *resty.Response) ([]vm.VMCredentialsSerialized, error) {
vmsCreds := []vm.VMCredentialsSerialized{}
func deserializeVMsCredentials(resp *resty.Response) ([]vm.VMAttachCredentialsSerialized, error) {
vmsCreds := []vm.VMAttachCredentialsSerialized{}
if err := json.Unmarshal(resp.Body(), &vmsCreds); err != nil {
return nil, err
}
......@@ -212,8 +212,8 @@ func deserializeVMsCredentials(resp *resty.Response) ([]vm.VMCredentialsSerializ
}
// Deserialize a VM credentials from an http response.
func deserializeVMCredentials(resp *resty.Response) (*vm.VMCredentialsSerialized, error) {
var vmCreds vm.VMCredentialsSerialized
func deserializeVMCredentials(resp *resty.Response) (*vm.VMAttachCredentialsSerialized, error) {
var vmCreds vm.VMAttachCredentialsSerialized
if err := json.Unmarshal(resp.Body(), &vmCreds); err != nil {
return nil, err
}
......
package cmdVM
import (
"fmt"
"nexus-common/vm"
"nexus-client/exec"
u "nexus-client/utils"
g "nexus-client/globals"
)
type AttachAsync struct {
......@@ -33,36 +30,26 @@ func (cmd *AttachAsync)Run(args []string) int {
return 1
}
hostname := g.GetInstance().Hostname
cert := g.GetInstance().PubCert
argc := len(args)
if argc < 1 {
cmd.PrintUsage()
return 1
}
vms, err := getFilteredVMCredentials("/vms/attach", args)
creds, err := getFilteredVMCredentials("/vms/attach", args)
if err != nil {
u.PrintlnErr(err.Error())
return 1
}
if len(vms) == 0 {
if len(creds) == 0 {
u.PrintlnErr("Error: VM(s) not found.")
return 1
}
statusCode := 0
for _, v := range(vms) {
go func(v vm.VMCredentialsSerialized) {
stdoutStderr, err := exec.RunRemoteViewer(hostname, cert, v.Name, v.Port, v.Pwd, false)
statusCode, err := AttachToVMs(creds, true)
if err != nil {
u.PrintlnErr("Failed attaching to VM ", v.ID, ": ", fmt.Sprintf("%s", stdoutStderr))
statusCode |= 1
}
} (v)
u.PrintlnErr(err)
}
return statusCode
......
package cmdVM
import (
"fmt"
"nexus-common/vm"
"nexus-client/exec"
"nexus-common/vm"
u "nexus-client/utils"
g "nexus-client/globals"
)
......@@ -18,7 +17,7 @@ func (cmd *AttachAsyncSingle)GetName() string {
func (cmd *AttachAsyncSingle)GetDesc() []string {
return []string{
"Attach to a VM.",
"Attaches to a VM in order to use its desktop environment.",
"If not the VM's owner: requires VM_ATTACH VM access capability or VM_ATTACH_ANY user capability."}
}
......@@ -37,17 +36,15 @@ func (cmd *AttachAsyncSingle)Run(args []string) int {
return 1
}
hostname := g.GetInstance().Hostname
cert := g.GetInstance().PubCert
client := g.GetInstance().Client
host := g.GetInstance().Host
argc := len(args)
if argc < 1 {
if argc != 1 {
cmd.PrintUsage()
return 1
}
client := g.GetInstance().Client
host := g.GetInstance().Host
vmID := args[0]
resp, err := client.R().Get(host+"/vms/"+vmID+"/attach")
if err != nil {
......@@ -55,18 +52,16 @@ func (cmd *AttachAsyncSingle)Run(args []string) int {
return 1
} else {
if resp.IsSuccess() {
myvm, err := deserializeVMCredentials(resp)
creds, err := deserializeVMCredentials(resp)
if err != nil {
u.PrintlnErr("Failed retrieving server's response: "+err.Error())
return 1
}
go func(v vm.VMCredentialsSerialized) {
stdoutStderr, err := exec.RunRemoteViewer(hostname, cert, v.Name, v.Port, v.Pwd, false)
statusCode, err := AttachToVMs([]vm.VMAttachCredentialsSerialized{*creds}, false)
if err != nil {
u.PrintlnErr("Failed attaching to VM ", v.ID, ": ", fmt.Sprintf("%s", stdoutStderr))
u.PrintlnErr(err)
}
} (*myvm)
return statusCode
} else {
u.PrintlnErr("Failed retrieving VM credentials for VM \""+vmID+"\": "+resp.Status()+": "+resp.String())
return 1
......
package cmdVM
import (
"fmt"
"sync"
"nexus-client/exec"
"nexus-common/vm"
u "nexus-client/utils"
g "nexus-client/globals"
)
type AttachSync struct {
......@@ -20,7 +16,7 @@ func (cmd *AttachSync)GetName() string {
func (cmd *AttachSync)GetDesc() []string {
return []string{
"Attaches to one or more VMs in order to use their desktop environment.",
"If not the VM's owner: requires VM_LIST VM access capability or VM_LIST_ANY user capability."}
"If not the VM's owner: requires VM_ATTACH VM access capability or VM_ATTACH_ANY user capability."}
}
func (cmd *AttachSync)PrintUsage() {
......@@ -34,44 +30,27 @@ func (cmd *AttachSync)Run(args []string) int {
return 1
}
hostname := g.GetInstance().Hostname
cert := g.GetInstance().PubCert
argc := len(args)
if argc < 1 {
cmd.PrintUsage()
return 1
}
vms, err := getFilteredVMCredentials("/vms/attach", args)
creds, err := getFilteredVMCredentials("/vms/attach", args)
if err != nil {
u.PrintlnErr(err.Error())
return 1
}
if len(vms) == 0 {
if len(creds) == 0 {
u.PrintlnErr("Error: VM(s) not found.")
return 1
}
// Use wait groups to wait until all viewers threads have completed.
var wg sync.WaitGroup
wg.Add(len(vms))
statusCode := 0
for _, v := range(vms) {
go func(v vm.VMCredentialsSerialized) {
stdoutStderr, err := exec.RunRemoteViewer(hostname, cert, v.Name, v.Port, v.Pwd, false)
statusCode, err := AttachToVMs(creds, true)
if err != nil {
u.PrintlnErr("Failed attaching to VM ", v.ID, ": ", fmt.Sprintf("%s", stdoutStderr))
statusCode |= 1
u.PrintlnErr(err)
}
wg.Done()
} (v)
}
wg.Wait()
return statusCode
}
......@@ -3,7 +3,6 @@ package cmdVM
import (
"os"
"fmt"
"strconv"
u "nexus-client/utils"
g "nexus-client/globals"
)
......@@ -19,7 +18,7 @@ func (cmd *Creds2csv)GetName() string {
func (cmd *Creds2csv)GetDesc() []string {
return []string{
"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",
"The written CSV file contains 3 columns: VM ID;VM name;password",
"If not the VM's owner: requires VM_ATTACH VM access capability or VM_ATTACH_ANY user capability."}
}
......@@ -42,13 +41,13 @@ func (cmd *Creds2csv)Run(args []string) int {
csvFile := args[argc-1]
vms, err := getFilteredVMCredentials("/vms/attach", args[:argc-1])
credsList, err := getFilteredVMCredentials("/vms/attach", args[:argc-1])
if err != nil {
u.PrintlnErr("Error: "+err.Error())
return 1
}
if len(vms) == 0 {
if len(credsList) == 0 {
u.PrintlnErr("Error: VM(s) not found.")
return 1
}
......@@ -62,9 +61,9 @@ func (cmd *Creds2csv)Run(args []string) int {
defer f.Close()
for _, vm := range vms {
for _, creds := range credsList {
sep := string(g.CsvFieldSeparator)
_, err := fmt.Fprintln(f, vm.ID.String()+sep+vm.Name+sep+strconv.Itoa(vm.Port)+sep+vm.Pwd)
_, err := fmt.Fprintln(f, creds.ID.String()+sep+creds.Name+sep+creds.Pwd)
if err != nil {
u.PrintlnErr("Error: "+err.Error())
return 1
......
package cmdVM
import (
"strconv"
u "nexus-client/utils"
"github.com/go-pdf/fpdf"
)
......@@ -43,14 +42,14 @@ func (cmd *Creds2pdf)Run(args []string) int {
pdfFile := args[argc-1]
vms, err := getFilteredVMCredentials("/vms/attach", args[:argc-1])
credsList, err := getFilteredVMCredentials("/vms/attach", args[:argc-1])
if err != nil {
u.PrintlnErr("Error: "+err.Error())
return 1
}
if len(vms) == 0 {
u.PrintlnErr("Error: VM(s) not found (possible causes: no VM by that ID/regex, VM(s) not started, no right to list requested VM(s)).")
if len(credsList) == 0 {
u.PrintlnErr("Error: VM(s) not found (possible causes: no VM by that ID/regex, VM(s) not started, missing capabilities).")
return 1
}
......@@ -59,7 +58,7 @@ func (cmd *Creds2pdf)Run(args []string) int {
const rightMargin = 0.
// Max number of chars allowed in the VM's name given the used font and size.
const maxChar = 70
columnWidth := [...]float64 {156.,14.,23.}
columnWidth := [...]float64 {156.,30.}
const rowHeight = 15.
const fontSize = 12.
const cellBorder = "1" // full cell border; use "" for no border
......@@ -76,16 +75,15 @@ func (cmd *Creds2pdf)Run(args []string) int {
// Read a specific monospace bold font known to support UTF8 encoding
pdf.AddUTF8FontFromBytes("cmuntb", "b", []byte(embeddedCMUTypewriterBold))
for _, vm := range vms {
if len(vm.Name) > maxChar {
vm.Name = vm.Name[0:maxChar-1]
vm.Name = vm.Name+"…"
for _, creds := range credsList {
if len(creds.Name) > maxChar {
creds.Name = creds.Name[0:maxChar-1]
creds.Name = creds.Name+"…"
}
pdf.SetX(leftMargin)
pdf.SetFont("cmuntb", "b", fontSize) // "b" means bold; use "" for normal
pdf.CellFormat(columnWidth[0], rowHeight, vm.Name, cellBorder, nextPos, align, bkgd, link, url)
pdf.CellFormat(columnWidth[1], rowHeight, strconv.Itoa(vm.Port), cellBorder, nextPos, "C", bkgd, link, url)
pdf.CellFormat(columnWidth[2], rowHeight, vm.Pwd, cellBorder, nextPos, "C", bkgd, link, url)
pdf.CellFormat(columnWidth[0], rowHeight, creds.Name, cellBorder, nextPos, align, bkgd, link, url)
pdf.CellFormat(columnWidth[1], rowHeight, creds.Pwd, cellBorder, nextPos, "C", bkgd, link, url)
pdf.Ln(-1) // line break; -1 means move by the height of the last printed cell
}
......
......@@ -2,7 +2,6 @@ package cmdVM
import (
"errors"
"strconv"
"nexus-common/params"
u "nexus-client/utils"
g "nexus-client/globals"
......@@ -29,32 +28,27 @@ func (cmd *StartWithCreds)PrintUsage() {
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.`
const usage string = `file.csv 3-column CSV file defining the VMs to start and their credentials.
Content of the 3 columns: VM ID;VM name;password.`
u.PrintlnErr(usage)
}
func (cmd *StartWithCreds)parseCSVFile(csvFile string) ([]string, []string, []string, error) {
func (cmd *StartWithCreds)parseCSVFile(csvFile string) ([]string, []string, error) {
// Column 0: VM IDs
vmIDs, err := u.ReadCSVColumn(csvFile, 0)
if err != nil {
return nil, nil, nil, err
return nil, nil, err
}
// Column 2: ports
ports, err := u.ReadCSVColumn(csvFile, 2)
// Column 2: passwords
pwds, 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
return 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!")
if (len(pwds) != count) {
return nil, nil, errors.New("Invalid CSV file: all columns must have the same number of entries!")
}
return vmIDs, ports, pwds, nil
return vmIDs, pwds, nil
}
func (cmd *StartWithCreds)Run(args []string) int {
......@@ -67,7 +61,7 @@ func (cmd *StartWithCreds)Run(args []string) int {
return 1
}
vmIDs, ports, pwds, err := cmd.parseCSVFile(args[0])
vmIDs, pwds, err := cmd.parseCSVFile(args[0])
if err != nil {
u.PrintlnErr(err.Error())
return 1
......@@ -78,11 +72,6 @@ func (cmd *StartWithCreds)Run(args []string) int {
vmArgs := &params.VMStartWithCreds {}
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 {
......
......@@ -2,14 +2,42 @@ module nexus-exam
go 1.18
replace nexus-common/caps => ../../common/caps
replace nexus-common/params => ../../common/params
replace nexus-common/vm => ../../common/vm
replace nexus-common/template => ../../common/template
replace nexus-client/version => ../version
replace nexus-client/globals => ../globals
replace nexus-client/defaults => ../defaults
replace nexus-common/utils => ../../common/utils
replace nexus-client/utils => ../utils
replace nexus-client/globals => ../globals
replace nexus-client/cmd => ../cmd
replace nexus-client/cmdMisc => ../cmdMisc
replace nexus-client/cmdLogin => ../cmdLogin
replace nexus-client/cmdToken => ../cmdToken
replace nexus-client/cmdTemplate => ../cmdTemplate
replace nexus-client/cmdUser => ../cmdUser
replace nexus-client/cmdVM => ../cmdVM
replace nexus-client/exec => ../exec
replace nexus-client/cmdVersion => ../cmdVersion
require fyne.io/fyne/v2 v2.2.1
require (
......@@ -25,7 +53,7 @@ require (
github.com/go-resty/resty/v2 v2.7.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
github.com/google/uuid v1.1.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
......@@ -38,11 +66,19 @@ require (
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect
nexus-client/cmd v0.0.0-00010101000000-000000000000 // indirect
nexus-client/cmdLogin v0.0.0-00010101000000-000000000000 // indirect
nexus-client/cmdVersion v0.0.0-00010101000000-000000000000 // indirect
nexus-client/exec v0.0.0-00010101000000-000000000000 // indirect
nexus-client/globals v0.0.0-00010101000000-000000000000 // indirect
nexus-client/utils v0.0.0-00010101000000-000000000000 // indirect
nexus-client/version v0.0.0-00010101000000-000000000000 // indirect
nexus-common/caps v0.0.0-00010101000000-000000000000 // indirect
nexus-common/params v0.0.0-00010101000000-000000000000 // indirect
nexus-common/utils v0.0.0-00010101000000-000000000000 // indirect
nexus-common/vm v0.0.0-00010101000000-000000000000 // indirect
)
......@@ -163,6 +163,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
......@@ -457,6 +459,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
......
......@@ -2,16 +2,20 @@ package main
import (
"os"
"errors"
"strconv"
// "errors"
"strings"
// "strconv"
"nexus-common/utils"
"nexus-client/exec"
u "nexus-client/utils"
g "nexus-client/globals"
"nexus-client/cmdVersion"
// "nexus-client/cmdLogin"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"fyne.io/fyne/v2/container"
"github.com/go-resty/resty/v2"
)
const (
......@@ -24,11 +28,11 @@ func run() int {
return 1
}
server, found := os.LookupEnv(g.ENV_NEXUS_SERVER)
serverEnvVar, found := os.LookupEnv(g.ENV_NEXUS_SERVER)
if !found {
u.PrintlnErr("Environment variable \""+g.ENV_NEXUS_SERVER+"\" must be set!")
u.PrintlnErr("It defines the nexus server to connect to.")
u.PrintlnErr("Example: export "+g.ENV_NEXUS_SERVER+"=192.168.1.42")
u.PrintlnErr("It defines the nexus server to connect to along the port number.")
u.PrintlnErr("Example: export "+g.ENV_NEXUS_SERVER+"=192.168.1.42:1077")
return 1
}
......@@ -45,36 +49,47 @@ func run() int {
return 1
}
parts := strings.Split(serverEnvVar, ":")
hostname := parts[0]
client := resty.New()
client.SetRootCertificate(certPath)
host := "https://"+serverEnvVar
g.Init(hostname, host, certPath, client)
// Checks the client version is compatible with the server's API.
if !cmdVersion.CheckServerCompatibility("nexus-exam") {
return 1
}
// Logins and obtains a JWT token.
// email := ""
// pwd := ""
// token, err := cmdLogin.GetToken(email, pwd)
// u.PrintlnErr("")
// if err != nil {
// u.PrintlnErr("Error: "+err.Error())
// return 1
// }
app := app.New()
app.Settings().SetTheme(theme.LightTheme())
win := app.NewWindow(windowTitle)
portEntry := widget.NewEntry()
portEntry.Validator = func(val string) error {
port, err := strconv.Atoi(val)
if err != nil {
return errors.New("Please enter an integer")
}
if port < 1024 || port > 65535 {
return errors.New("Please enter a value within [1024,65535]")
}
return nil
}
pwdEntry := widget.NewEntry()
pwdEntry.Password = true
label := widget.NewLabel("Enter port and password to connect to your VM:")
label := widget.NewLabel("Enter password to connect to your VM:")
form := &widget.Form{
Items: []*widget.FormItem {
{Text: "Port", Widget: portEntry},
{Text: "Password", Widget: pwdEntry},
},
OnSubmit: func() {
port, _ := strconv.Atoi(portEntry.Text)
exec.RunRemoteViewer(server, certPath, windowTitle, port, pwdEntry.Text, true)
// exec.RunRemoteViewer(server, certPath, windowTitle, port, pwdEntry.Text, true)
},
SubmitText: "Connect",
}
......
......@@ -6,8 +6,8 @@ import (
const (
major = 1
minor = 10
bugfix = 1
minor = 11
bugfix = 0
)
var version params.Version = params.NewVersion(major, minor, bugfix)
......
......@@ -16,8 +16,11 @@ type VMCreate struct {
}
type VMStartWithCreds struct {
Port int `json:"port" validate:"required,gte=1025,lte=65535"`
Pwd string `json:"pwd" validate:"required,min=8,max=64"`
Pwd string `json:"pwd" validate:"required,min=10,max=64"`
}
type VMAttachCreds struct {
Pwd string `json:"pwd" validate:"required,min=10,max=64"`
}
type VMEdit struct {
......
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
......@@ -2,13 +2,11 @@ package utils
import (
"os"
"errors"
)
// Returns true if the specified file exists, false otherwise.
func FileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
_, err := os.Stat(filename)
return !errors.Is(err, os.ErrNotExist)
}
......@@ -40,12 +40,15 @@ type (
StartTime time.Time `json:"startTime"`
}
// VM id, name and credentials to be serialized over the network.
VMCredentialsSerialized struct {
VMSpiceCredentialsSerialized struct {
SpicePort int `json:"spicePort" validate:"required,gte=1025,lte=65535"`
SpicePwd string `json:"spicePwd" validate:"required,min=16,max=64"`
}
VMAttachCredentialsSerialized struct {
ID uuid.UUID `json:"id" validate:"required"`
Name string `json:"name" validate:"required,min=1,max=256"`
Port int `json:"port" validate:"required,gte=1025,lte=65535"`
Pwd string `json:"pwd" validate:"required,min=1,max=64"`
Pwd string `json:"pwd" validate:"required,min=8,max=64"`
}
VMState string // see stateXXX below
......
......@@ -40,10 +40,15 @@ type Config struct {
// Unix permissions when creating directories: when creating templates and VMs and for tmp directory.
MkDirPerm os.FileMode
VMPwdLength int
VMPwdDigitCount int
VMPwdSymbolCount int
VMPwdRepeatChars bool
VMSpicePwdLength int
VMSpicePwdDigitCount int
VMSpicePwdSymbolCount int
VMSpicePwdRepeatChars bool
VMAttachPwdLength int
VMAttachPwdDigitCount int
VMAttachPwdSymbolCount int
VMAttachPwdRepeatChars bool
UsersFile string
DataDir string
......@@ -78,10 +83,18 @@ func GetInstance() *Config {
sanityChecks(config)
config.MkDirPerm = 0750
config.VMPwdLength = 8
config.VMPwdDigitCount = 4
config.VMPwdSymbolCount = 0
config.VMPwdRepeatChars = false
// Spice password
config.VMSpicePwdLength = 32
config.VMSpicePwdDigitCount = 10
config.VMSpicePwdSymbolCount = 0
config.VMSpicePwdRepeatChars = true
// VM attach password
config.VMAttachPwdLength = 10
config.VMAttachPwdDigitCount = 4
config.VMAttachPwdSymbolCount = 0
config.VMAttachPwdRepeatChars = true
config.UsersFile = filepath.Join(configDir, "/users.json")
config.DataDir = dataDir
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment