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
Branches
No related tags found
No related merge requests found
Showing
with 284 additions and 195 deletions
...@@ -215,9 +215,6 @@ check_bin_var: ...@@ -215,9 +215,6 @@ check_bin_var:
check_server_var: check_server_var:
$(call check_defined, SERVER) $(call check_defined, SERVER)
check_serverip_var:
$(call check_defined, SERVER_IP)
check_login_var: check_login_var:
$(call check_defined, LOGIN) $(call check_defined, LOGIN)
...@@ -241,7 +238,7 @@ help_client: ...@@ -241,7 +238,7 @@ help_client:
@echo "└──────────────────────────────────────────────────────────────────────────────┘" @echo "└──────────────────────────────────────────────────────────────────────────────┘"
@echo " run_nexush Run nexush for Linux/amd64; require LOGIN variable" @echo " run_nexush Run nexush for Linux/amd64; require LOGIN variable"
@echo " Example: make run_nexush LOGIN=janedoe@nexus.org" @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 "┌──────────────────────────────────────────────────────────────────────────────┐" @echo "┌──────────────────────────────────────────────────────────────────────────────┐"
@echo "│ VALIDATION tests │" @echo "│ VALIDATION tests │"
...@@ -250,10 +247,9 @@ help_client: ...@@ -250,10 +247,9 @@ help_client:
@echo " Require LOGIN variable. Example:" @echo " Require LOGIN variable. Example:"
@echo "" @echo ""
@echo "────────────────────────────────────────────────────────────────────────────────" @echo "────────────────────────────────────────────────────────────────────────────────"
@echo " CERT specifies the path to the public CA certificate." @echo " CERT: directory where the public CA certificate resides ($(CA_CERT_FILE))."
@echo " SERVER specifies the server ip address and port, separated by a colon," @echo " SERVER: server ip address and port, separated by a colon,"
@echo " for instance: SERVER=127.0.0.1:1077" @echo " for instance: SERVER=127.0.0.1:1077"
@echo " SERVER_IP specifies the server ip address."
copy_resources_client: copy_resources_client:
@mkdir -p $(RESOURCES_DIR_CLIENT) @mkdir -p $(RESOURCES_DIR_CLIENT)
...@@ -306,8 +302,8 @@ clean_client: ...@@ -306,8 +302,8 @@ clean_client:
run_nexush: check_login_var $(BUILD_DIR_CLIENT)/nexush run_nexush: check_login_var $(BUILD_DIR_CLIENT)/nexush
$(BUILD_DIR_CLIENT)/nexush $(LOGIN) $(BUILD_DIR_CLIENT)/nexush $(LOGIN)
run_nexus-exam: check_serverip_var $(BUILD_DIR_CLIENT)/nexus-exam run_nexus-exam: check_server_var $(BUILD_DIR_CLIENT)/nexus-exam
@NEXUS_SERVER=$(SERVER_IP) NEXUS_CERT=$(CERT)/$(CA_CERT_FILE) $(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 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) @cd tests && ./run_tests $(BUILD_ABS_CLIENT)/nexus-cli $(LOGIN)
......
...@@ -29,31 +29,33 @@ ...@@ -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` | 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/{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/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 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/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 VM creds info for the specified VM | GET | - | `VM_ATTACH_ANY` | OR | `VM_ATTACH` | [common.vm.VMCredentialsSerialized](../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 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/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 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/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 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/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 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/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 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/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 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/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 VM creds info for VMs allowing files upload | GET | - | `VM_WRITEFS_ANY` | OR | `VM_WRITEFS` | [\[\]common.vm.VMCredentialsSerialized](../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 | | 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` | 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}` | delete the 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}` | 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 a VM | PUT | - | `VM_START_ANY` | OR | `VM_START` | - | | `/vms/{id}/start` | start the 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}/startwithcreds` | start the 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}/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/{id}/reboot` | reboot a VM | PUT | - | `VM_REBOOT_ANY` | OR | `VM_REBOOT` | - | | `/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}/shutdown` | gracefully shutdown a VM | PUT | - | `VM_STOP_ANY` | OR | `VM_STOP` | - | | `/vms/{id}/stop` | kill the 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}/reboot` | reboot the VM | PUT | - | `VM_REBOOT_ANY` | OR | `VM_REBOOT` | - |
| `/vms/{id}/access/{email}` | del VM access for a user | DELETE | - | `VM_SET_ACCESS` | AND | `VM_SET_ACCESS` | - | | `/vms/{id}/shutdown` | gracefully shutdown the VM | PUT | - | `VM_STOP_ANY` | OR | `VM_STOP` | - |
| `/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}/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}/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}/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 ### Template management
......
...@@ -69,7 +69,7 @@ func (cmd *UpdatePwd)Run(args []string) int { ...@@ -69,7 +69,7 @@ func (cmd *UpdatePwd)Run(args []string) int {
return 1 return 1
} else { } else {
if resp.IsSuccess() { if resp.IsSuccess() {
u.Println(resp) u.Println("Password changed successfully.")
return 0 return 0
} else { } else {
u.PrintlnErr("Error: "+resp.Status()+": "+resp.String()) 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, ...@@ -118,7 +118,7 @@ func getFilteredVMs(route string, patterns []string) ([]vm.VMNetworkSerialized,
// Regular expression examples: // Regular expression examples:
// "." -> matches everything // "." -> matches everything
// "bla" -> matches any VM name containing "bla" // "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 { if len(patterns) < 1 {
return nil, errors.New("At least one ID or regex must be specified") 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 ...@@ -143,7 +143,7 @@ func getFilteredVMCredentials(route string, patterns []string) ([]vm.VMCredentia
return nil, err return nil, err
} }
vmsList := []vm.VMCredentialsSerialized{} vmsList := []vm.VMAttachCredentialsSerialized{}
if resp.IsSuccess() { if resp.IsSuccess() {
vms, err := deserializeVMsCredentials(resp) vms, err := deserializeVMsCredentials(resp)
...@@ -203,8 +203,8 @@ func deserializeVM(resp *resty.Response) (*vm.VMNetworkSerialized, error) { ...@@ -203,8 +203,8 @@ func deserializeVM(resp *resty.Response) (*vm.VMNetworkSerialized, error) {
} }
// Deserialize a list of VM credentials from an http response. // Deserialize a list of VM credentials from an http response.
func deserializeVMsCredentials(resp *resty.Response) ([]vm.VMCredentialsSerialized, error) { func deserializeVMsCredentials(resp *resty.Response) ([]vm.VMAttachCredentialsSerialized, error) {
vmsCreds := []vm.VMCredentialsSerialized{} vmsCreds := []vm.VMAttachCredentialsSerialized{}
if err := json.Unmarshal(resp.Body(), &vmsCreds); err != nil { if err := json.Unmarshal(resp.Body(), &vmsCreds); err != nil {
return nil, err return nil, err
} }
...@@ -212,8 +212,8 @@ func deserializeVMsCredentials(resp *resty.Response) ([]vm.VMCredentialsSerializ ...@@ -212,8 +212,8 @@ func deserializeVMsCredentials(resp *resty.Response) ([]vm.VMCredentialsSerializ
} }
// Deserialize a VM credentials from an http response. // Deserialize a VM credentials from an http response.
func deserializeVMCredentials(resp *resty.Response) (*vm.VMCredentialsSerialized, error) { func deserializeVMCredentials(resp *resty.Response) (*vm.VMAttachCredentialsSerialized, error) {
var vmCreds vm.VMCredentialsSerialized var vmCreds vm.VMAttachCredentialsSerialized
if err := json.Unmarshal(resp.Body(), &vmCreds); err != nil { if err := json.Unmarshal(resp.Body(), &vmCreds); err != nil {
return nil, err return nil, err
} }
......
package cmdVM package cmdVM
import ( import (
"fmt"
"nexus-common/vm"
"nexus-client/exec" "nexus-client/exec"
u "nexus-client/utils" u "nexus-client/utils"
g "nexus-client/globals"
) )
type AttachAsync struct { type AttachAsync struct {
...@@ -33,36 +30,26 @@ func (cmd *AttachAsync)Run(args []string) int { ...@@ -33,36 +30,26 @@ func (cmd *AttachAsync)Run(args []string) int {
return 1 return 1
} }
hostname := g.GetInstance().Hostname
cert := g.GetInstance().PubCert
argc := len(args) argc := len(args)
if argc < 1 { if argc < 1 {
cmd.PrintUsage() cmd.PrintUsage()
return 1 return 1
} }
vms, err := getFilteredVMCredentials("/vms/attach", args) creds, err := getFilteredVMCredentials("/vms/attach", args)
if err != nil { if err != nil {
u.PrintlnErr(err.Error()) u.PrintlnErr(err.Error())
return 1 return 1
} }
if len(vms) == 0 { if len(creds) == 0 {
u.PrintlnErr("Error: VM(s) not found.") u.PrintlnErr("Error: VM(s) not found.")
return 1 return 1
} }
statusCode := 0 statusCode, err := AttachToVMs(creds, true)
for _, v := range(vms) {
go func(v vm.VMCredentialsSerialized) {
stdoutStderr, err := exec.RunRemoteViewer(hostname, cert, v.Name, v.Port, v.Pwd, false)
if err != nil { if err != nil {
u.PrintlnErr("Failed attaching to VM ", v.ID, ": ", fmt.Sprintf("%s", stdoutStderr)) u.PrintlnErr(err)
statusCode |= 1
}
} (v)
} }
return statusCode return statusCode
......
package cmdVM package cmdVM
import ( import (
"fmt"
"nexus-common/vm"
"nexus-client/exec" "nexus-client/exec"
"nexus-common/vm"
u "nexus-client/utils" u "nexus-client/utils"
g "nexus-client/globals" g "nexus-client/globals"
) )
...@@ -18,7 +17,7 @@ func (cmd *AttachAsyncSingle)GetName() string { ...@@ -18,7 +17,7 @@ func (cmd *AttachAsyncSingle)GetName() string {
func (cmd *AttachAsyncSingle)GetDesc() []string { func (cmd *AttachAsyncSingle)GetDesc() []string {
return []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."} "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 { ...@@ -37,17 +36,15 @@ func (cmd *AttachAsyncSingle)Run(args []string) int {
return 1 return 1
} }
hostname := g.GetInstance().Hostname
cert := g.GetInstance().PubCert
client := g.GetInstance().Client
host := g.GetInstance().Host
argc := len(args) argc := len(args)
if argc < 1 { if argc != 1 {
cmd.PrintUsage() cmd.PrintUsage()
return 1 return 1
} }
client := g.GetInstance().Client
host := g.GetInstance().Host
vmID := args[0] vmID := args[0]
resp, err := client.R().Get(host+"/vms/"+vmID+"/attach") resp, err := client.R().Get(host+"/vms/"+vmID+"/attach")
if err != nil { if err != nil {
...@@ -55,18 +52,16 @@ func (cmd *AttachAsyncSingle)Run(args []string) int { ...@@ -55,18 +52,16 @@ func (cmd *AttachAsyncSingle)Run(args []string) int {
return 1 return 1
} else { } else {
if resp.IsSuccess() { if resp.IsSuccess() {
myvm, err := deserializeVMCredentials(resp) creds, err := deserializeVMCredentials(resp)
if err != nil { if err != nil {
u.PrintlnErr("Failed retrieving server's response: "+err.Error()) u.PrintlnErr("Failed retrieving server's response: "+err.Error())
return 1 return 1
} }
statusCode, err := AttachToVMs([]vm.VMAttachCredentialsSerialized{*creds}, false)
go func(v vm.VMCredentialsSerialized) {
stdoutStderr, err := exec.RunRemoteViewer(hostname, cert, v.Name, v.Port, v.Pwd, false)
if err != nil { if err != nil {
u.PrintlnErr("Failed attaching to VM ", v.ID, ": ", fmt.Sprintf("%s", stdoutStderr)) u.PrintlnErr(err)
} }
} (*myvm) return statusCode
} else { } else {
u.PrintlnErr("Failed retrieving VM credentials for VM \""+vmID+"\": "+resp.Status()+": "+resp.String()) u.PrintlnErr("Failed retrieving VM credentials for VM \""+vmID+"\": "+resp.Status()+": "+resp.String())
return 1 return 1
......
package cmdVM package cmdVM
import ( import (
"fmt"
"sync"
"nexus-client/exec" "nexus-client/exec"
"nexus-common/vm"
u "nexus-client/utils" u "nexus-client/utils"
g "nexus-client/globals"
) )
type AttachSync struct { type AttachSync struct {
...@@ -20,7 +16,7 @@ func (cmd *AttachSync)GetName() string { ...@@ -20,7 +16,7 @@ func (cmd *AttachSync)GetName() string {
func (cmd *AttachSync)GetDesc() []string { func (cmd *AttachSync)GetDesc() []string {
return []string{ return []string{
"Attaches to one or more VMs in order to use their desktop environment.", "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() { func (cmd *AttachSync)PrintUsage() {
...@@ -34,44 +30,27 @@ func (cmd *AttachSync)Run(args []string) int { ...@@ -34,44 +30,27 @@ func (cmd *AttachSync)Run(args []string) int {
return 1 return 1
} }
hostname := g.GetInstance().Hostname
cert := g.GetInstance().PubCert
argc := len(args) argc := len(args)
if argc < 1 { if argc < 1 {
cmd.PrintUsage() cmd.PrintUsage()
return 1 return 1
} }
vms, err := getFilteredVMCredentials("/vms/attach", args) creds, err := getFilteredVMCredentials("/vms/attach", args)
if err != nil { if err != nil {
u.PrintlnErr(err.Error()) u.PrintlnErr(err.Error())
return 1 return 1
} }
if len(vms) == 0 { if len(creds) == 0 {
u.PrintlnErr("Error: VM(s) not found.") u.PrintlnErr("Error: VM(s) not found.")
return 1 return 1
} }
// Use wait groups to wait until all viewers threads have completed. statusCode, err := AttachToVMs(creds, true)
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)
if err != nil { if err != nil {
u.PrintlnErr("Failed attaching to VM ", v.ID, ": ", fmt.Sprintf("%s", stdoutStderr)) u.PrintlnErr(err)
statusCode |= 1
} }
wg.Done()
} (v)
}
wg.Wait()
return statusCode return statusCode
} }
...@@ -3,7 +3,6 @@ package cmdVM ...@@ -3,7 +3,6 @@ package cmdVM
import ( import (
"os" "os"
"fmt" "fmt"
"strconv"
u "nexus-client/utils" u "nexus-client/utils"
g "nexus-client/globals" g "nexus-client/globals"
) )
...@@ -19,7 +18,7 @@ func (cmd *Creds2csv)GetName() string { ...@@ -19,7 +18,7 @@ func (cmd *Creds2csv)GetName() string {
func (cmd *Creds2csv)GetDesc() []string { func (cmd *Creds2csv)GetDesc() []string {
return []string{ return []string{
"Creates a CSV file with the credentials required to attach to running VMs.", "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."} "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 { ...@@ -42,13 +41,13 @@ func (cmd *Creds2csv)Run(args []string) int {
csvFile := args[argc-1] csvFile := args[argc-1]
vms, err := getFilteredVMCredentials("/vms/attach", args[:argc-1]) credsList, err := getFilteredVMCredentials("/vms/attach", args[:argc-1])
if err != nil { if err != nil {
u.PrintlnErr("Error: "+err.Error()) u.PrintlnErr("Error: "+err.Error())
return 1 return 1
} }
if len(vms) == 0 { if len(credsList) == 0 {
u.PrintlnErr("Error: VM(s) not found.") u.PrintlnErr("Error: VM(s) not found.")
return 1 return 1
} }
...@@ -62,9 +61,9 @@ func (cmd *Creds2csv)Run(args []string) int { ...@@ -62,9 +61,9 @@ func (cmd *Creds2csv)Run(args []string) int {
defer f.Close() defer f.Close()
for _, vm := range vms { for _, creds := range credsList {
sep := string(g.CsvFieldSeparator) 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 { if err != nil {
u.PrintlnErr("Error: "+err.Error()) u.PrintlnErr("Error: "+err.Error())
return 1 return 1
......
package cmdVM package cmdVM
import ( import (
"strconv"
u "nexus-client/utils" u "nexus-client/utils"
"github.com/go-pdf/fpdf" "github.com/go-pdf/fpdf"
) )
...@@ -43,14 +42,14 @@ func (cmd *Creds2pdf)Run(args []string) int { ...@@ -43,14 +42,14 @@ func (cmd *Creds2pdf)Run(args []string) int {
pdfFile := args[argc-1] pdfFile := args[argc-1]
vms, err := getFilteredVMCredentials("/vms/attach", args[:argc-1]) credsList, err := getFilteredVMCredentials("/vms/attach", args[:argc-1])
if err != nil { if err != nil {
u.PrintlnErr("Error: "+err.Error()) u.PrintlnErr("Error: "+err.Error())
return 1 return 1
} }
if len(vms) == 0 { if len(credsList) == 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)).") u.PrintlnErr("Error: VM(s) not found (possible causes: no VM by that ID/regex, VM(s) not started, missing capabilities).")
return 1 return 1
} }
...@@ -59,7 +58,7 @@ func (cmd *Creds2pdf)Run(args []string) int { ...@@ -59,7 +58,7 @@ func (cmd *Creds2pdf)Run(args []string) int {
const rightMargin = 0. const rightMargin = 0.
// Max number of chars allowed in the VM's name given the used font and size. // Max number of chars allowed in the VM's name given the used font and size.
const maxChar = 70 const maxChar = 70
columnWidth := [...]float64 {156.,14.,23.} columnWidth := [...]float64 {156.,30.}
const rowHeight = 15. const rowHeight = 15.
const fontSize = 12. const fontSize = 12.
const cellBorder = "1" // full cell border; use "" for no border const cellBorder = "1" // full cell border; use "" for no border
...@@ -76,16 +75,15 @@ func (cmd *Creds2pdf)Run(args []string) int { ...@@ -76,16 +75,15 @@ func (cmd *Creds2pdf)Run(args []string) int {
// Read a specific monospace bold font known to support UTF8 encoding // Read a specific monospace bold font known to support UTF8 encoding
pdf.AddUTF8FontFromBytes("cmuntb", "b", []byte(embeddedCMUTypewriterBold)) pdf.AddUTF8FontFromBytes("cmuntb", "b", []byte(embeddedCMUTypewriterBold))
for _, vm := range vms { for _, creds := range credsList {
if len(vm.Name) > maxChar { if len(creds.Name) > maxChar {
vm.Name = vm.Name[0:maxChar-1] creds.Name = creds.Name[0:maxChar-1]
vm.Name = vm.Name+"…" creds.Name = creds.Name+"…"
} }
pdf.SetX(leftMargin) pdf.SetX(leftMargin)
pdf.SetFont("cmuntb", "b", fontSize) // "b" means bold; use "" for normal 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[0], rowHeight, creds.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[1], rowHeight, creds.Pwd, cellBorder, nextPos, "C", bkgd, link, url)
pdf.CellFormat(columnWidth[2], rowHeight, vm.Pwd, cellBorder, nextPos, "C", bkgd, link, url)
pdf.Ln(-1) // line break; -1 means move by the height of the last printed cell pdf.Ln(-1) // line break; -1 means move by the height of the last printed cell
} }
......
...@@ -2,7 +2,6 @@ package cmdVM ...@@ -2,7 +2,6 @@ package cmdVM
import ( import (
"errors" "errors"
"strconv"
"nexus-common/params" "nexus-common/params"
u "nexus-client/utils" u "nexus-client/utils"
g "nexus-client/globals" g "nexus-client/globals"
...@@ -29,32 +28,27 @@ func (cmd *StartWithCreds)PrintUsage() { ...@@ -29,32 +28,27 @@ func (cmd *StartWithCreds)PrintUsage() {
u.PrintlnErr("―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――") u.PrintlnErr("―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――")
u.PrintlnErr("USAGE: "+cmd.GetName()+" file.csv") u.PrintlnErr("USAGE: "+cmd.GetName()+" file.csv")
u.PrintlnErr("―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――") u.PrintlnErr("―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――")
const usage string = `file.csv 4-column CSV file defining the VMs to start and their credentials. const usage string = `file.csv 3-column CSV file defining the VMs to start and their credentials.
Content of the 4 columns: VM ID;VM name;port;password.` Content of the 3 columns: VM ID;VM name;password.`
u.PrintlnErr(usage) 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 // Column 0: VM IDs
vmIDs, err := u.ReadCSVColumn(csvFile, 0) vmIDs, err := u.ReadCSVColumn(csvFile, 0)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, err
} }
// Column 2: ports // Column 2: passwords
ports, err := u.ReadCSVColumn(csvFile, 2) pwds, err := u.ReadCSVColumn(csvFile, 2)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, err
}
// Column 3: passwords
pwds, err := u.ReadCSVColumn(csvFile, 3)
if err != nil {
return nil, nil, nil, err
} }
count := len(vmIDs) count := len(vmIDs)
if (len(ports) != count) || (len(pwds) != count) { if (len(pwds) != count) {
return nil, nil, nil, errors.New("Invalid CSV file: all columns must have the same number of entries!") 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 { func (cmd *StartWithCreds)Run(args []string) int {
...@@ -67,7 +61,7 @@ func (cmd *StartWithCreds)Run(args []string) int { ...@@ -67,7 +61,7 @@ func (cmd *StartWithCreds)Run(args []string) int {
return 1 return 1
} }
vmIDs, ports, pwds, err := cmd.parseCSVFile(args[0]) vmIDs, pwds, err := cmd.parseCSVFile(args[0])
if err != nil { if err != nil {
u.PrintlnErr(err.Error()) u.PrintlnErr(err.Error())
return 1 return 1
...@@ -78,11 +72,6 @@ func (cmd *StartWithCreds)Run(args []string) int { ...@@ -78,11 +72,6 @@ func (cmd *StartWithCreds)Run(args []string) int {
vmArgs := &params.VMStartWithCreds {} vmArgs := &params.VMStartWithCreds {}
for i, vmID := range(vmIDs) { 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] vmArgs.Pwd = pwds[i]
resp, err := client.R().SetBody(vmArgs).Put(host+"/vms/"+vmID+"/startwithcreds") resp, err := client.R().SetBody(vmArgs).Put(host+"/vms/"+vmID+"/startwithcreds")
if err != nil { if err != nil {
......
...@@ -2,14 +2,42 @@ module nexus-exam ...@@ -2,14 +2,42 @@ module nexus-exam
go 1.18 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-common/utils => ../../common/utils
replace nexus-client/utils => ../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/exec => ../exec
replace nexus-client/cmdVersion => ../cmdVersion
require fyne.io/fyne/v2 v2.2.1 require fyne.io/fyne/v2 v2.2.1
require ( require (
...@@ -25,7 +53,7 @@ require ( ...@@ -25,7 +53,7 @@ require (
github.com/go-resty/resty/v2 v2.7.0 // indirect github.com/go-resty/resty/v2 v2.7.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // 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/gopherjs/gopherjs v1.17.2 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
...@@ -38,11 +66,19 @@ require ( ...@@ -38,11 +66,19 @@ require (
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // 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 golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // 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/exec v0.0.0-00010101000000-000000000000 // indirect
nexus-client/globals 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/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/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 ...@@ -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/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 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 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= 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 ...@@ -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-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 h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/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 ...@@ -2,16 +2,20 @@ package main
import ( import (
"os" "os"
"errors" // "errors"
"strconv" "strings"
// "strconv"
"nexus-common/utils" "nexus-common/utils"
"nexus-client/exec" "nexus-client/exec"
u "nexus-client/utils" u "nexus-client/utils"
g "nexus-client/globals" g "nexus-client/globals"
"nexus-client/cmdVersion"
// "nexus-client/cmdLogin"
"fyne.io/fyne/v2/app" "fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"github.com/go-resty/resty/v2"
) )
const ( const (
...@@ -24,11 +28,11 @@ func run() int { ...@@ -24,11 +28,11 @@ func run() int {
return 1 return 1
} }
server, found := os.LookupEnv(g.ENV_NEXUS_SERVER) serverEnvVar, found := os.LookupEnv(g.ENV_NEXUS_SERVER)
if !found { if !found {
u.PrintlnErr("Environment variable \""+g.ENV_NEXUS_SERVER+"\" must be set!") u.PrintlnErr("Environment variable \""+g.ENV_NEXUS_SERVER+"\" must be set!")
u.PrintlnErr("It defines the nexus server to connect to.") 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") u.PrintlnErr("Example: export "+g.ENV_NEXUS_SERVER+"=192.168.1.42:1077")
return 1 return 1
} }
...@@ -45,36 +49,47 @@ func run() int { ...@@ -45,36 +49,47 @@ func run() int {
return 1 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 := app.New()
app.Settings().SetTheme(theme.LightTheme()) app.Settings().SetTheme(theme.LightTheme())
win := app.NewWindow(windowTitle) 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 := widget.NewEntry()
pwdEntry.Password = true 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{ form := &widget.Form{
Items: []*widget.FormItem { Items: []*widget.FormItem {
{Text: "Port", Widget: portEntry},
{Text: "Password", Widget: pwdEntry}, {Text: "Password", Widget: pwdEntry},
}, },
OnSubmit: func() { 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", SubmitText: "Connect",
} }
......
...@@ -6,8 +6,8 @@ import ( ...@@ -6,8 +6,8 @@ import (
const ( const (
major = 1 major = 1
minor = 10 minor = 11
bugfix = 1 bugfix = 0
) )
var version params.Version = params.NewVersion(major, minor, bugfix) var version params.Version = params.NewVersion(major, minor, bugfix)
......
...@@ -16,8 +16,11 @@ type VMCreate struct { ...@@ -16,8 +16,11 @@ type VMCreate struct {
} }
type VMStartWithCreds struct { type VMStartWithCreds struct {
Port int `json:"port" validate:"required,gte=1025,lte=65535"` Pwd string `json:"pwd" validate:"required,min=10,max=64"`
Pwd string `json:"pwd" validate:"required,min=8,max=64"` }
type VMAttachCreds struct {
Pwd string `json:"pwd" validate:"required,min=10,max=64"`
} }
type VMEdit struct { 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 ...@@ -2,13 +2,11 @@ package utils
import ( import (
"os" "os"
"errors"
) )
// Returns true if the specified file exists, false otherwise. // Returns true if the specified file exists, false otherwise.
func FileExists(filename string) bool { func FileExists(filename string) bool {
info, err := os.Stat(filename) _, err := os.Stat(filename)
if os.IsNotExist(err) { return !errors.Is(err, os.ErrNotExist)
return false
}
return !info.IsDir()
} }
...@@ -40,12 +40,15 @@ type ( ...@@ -40,12 +40,15 @@ type (
StartTime time.Time `json:"startTime"` StartTime time.Time `json:"startTime"`
} }
// VM id, name and credentials to be serialized over the network. VMSpiceCredentialsSerialized struct {
VMCredentialsSerialized 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"` ID uuid.UUID `json:"id" validate:"required"`
Name string `json:"name" validate:"required,min=1,max=256"` 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=8,max=64"`
Pwd string `json:"pwd" validate:"required,min=1,max=64"`
} }
VMState string // see stateXXX below VMState string // see stateXXX below
......
...@@ -40,10 +40,15 @@ type Config struct { ...@@ -40,10 +40,15 @@ type Config struct {
// Unix permissions when creating directories: when creating templates and VMs and for tmp directory. // Unix permissions when creating directories: when creating templates and VMs and for tmp directory.
MkDirPerm os.FileMode MkDirPerm os.FileMode
VMPwdLength int VMSpicePwdLength int
VMPwdDigitCount int VMSpicePwdDigitCount int
VMPwdSymbolCount int VMSpicePwdSymbolCount int
VMPwdRepeatChars bool VMSpicePwdRepeatChars bool
VMAttachPwdLength int
VMAttachPwdDigitCount int
VMAttachPwdSymbolCount int
VMAttachPwdRepeatChars bool
UsersFile string UsersFile string
DataDir string DataDir string
...@@ -78,10 +83,18 @@ func GetInstance() *Config { ...@@ -78,10 +83,18 @@ func GetInstance() *Config {
sanityChecks(config) sanityChecks(config)
config.MkDirPerm = 0750 config.MkDirPerm = 0750
config.VMPwdLength = 8
config.VMPwdDigitCount = 4 // Spice password
config.VMPwdSymbolCount = 0 config.VMSpicePwdLength = 32
config.VMPwdRepeatChars = false 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.UsersFile = filepath.Join(configDir, "/users.json")
config.DataDir = dataDir config.DataDir = dataDir
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment