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

ongoing work on clean solution for CA certificate

parent a4431b9c
Branches
No related tags found
No related merge requests found
......@@ -33,16 +33,23 @@ var (
//go:embed nexus_exam_pwd.val
nexus_exam_pwd string
client *nc.NexusClient
client *nc.NexusClient
exitFn func()
isAuthenticated bool
isServerUp bool
statusLabel *widget.Label
credentialsInputBox *widget.Form
)
func attachVM(parent fyne.Window, pwd string) {
err := client.VMAttachFromPwd(pwd, true)
func exit(code int) {
if exitFn != nil {
exitFn()
}
os.Exit(code)
}
func attachVM(parent fyne.Window, certificateFile string, pwd string) {
err := client.VMAttachFromPwd(pwd, certificateFile, true)
if err != nil {
errorPopup(parent, "Failed attaching to VM: "+err.Error())
return
......@@ -55,12 +62,12 @@ func abortWindow(msg string) {
win := a.NewWindow(windowTitle)
win.SetOnClosed(func() {
win.Close()
os.Exit(1)
exit(1)
})
label := widget.NewLabel("FATAL: " + msg)
label := widget.NewLabel("FATAL ERROR: " + msg)
button := widget.NewButton("Quit", func() {
win.Close()
os.Exit(1)
exit(1)
})
content := container.NewPadded(container.NewVBox(label, button))
win.SetContent(content)
......@@ -151,8 +158,25 @@ func checkServerUp(parent fyne.Window) {
func run() int {
hypervisorCheck()
client = nc.New(defaults.NexusServer)
client.SetTimeout(4 * time.Second)
var err error
client, err = nc.New(defaults.NexusServer)
if err != nil {
abortWindow(err.Error())
}
certFile, err := client.GetVMAttachCertificate()
if err != nil {
abortWindow(err.Error())
}
exitFn = func() { os.Remove(certFile) }
// This thread acts as a signal handler for SIGINT or SIGTERM.
// When one of these signals is received, the temporary certificate file is deleted.
// Without this "handler", the temporary certificate file wouldn't be deleted.
go func() {
u.WaitForSignals()
exit(1)
}()
isServerUp = false
isAuthenticated = false
......@@ -180,7 +204,7 @@ func run() int {
return nil
}
statusLabel = widget.NewLabel("")
statusLabel = widget.NewLabel("\n\n\n")
statusLabel.Show()
credentialsInputBox = &widget.Form{
......@@ -188,7 +212,7 @@ func run() int {
{Text: "Password", Widget: pwdEntry},
},
OnSubmit: func() {
attachVM(win, pwdEntry.Text)
attachVM(win, certFile, pwdEntry.Text)
},
SubmitText: "Connect",
}
......@@ -205,5 +229,5 @@ func run() int {
}
func main() {
os.Exit(run())
exit(run())
}
......@@ -102,14 +102,11 @@ func run() int {
savedTermState, _ = term.GetState(int(os.Stdin.Fd()))
// This thread acts as a signal handler for SIGINT or SIGTERM.
go func() {
u.WaitForSignals()
restoreTerm()
os.Exit(1)
}()
nc := nc.New(serverEnvVar)
nc, err := nc.New(serverEnvVar)
if err != nil {
u.PrintlnErr("Error: " + err.Error())
return 1
}
// Checks the client version is compatible with the server's API.
if !cmdVersion.CheckServerCompatibility(nc, appname) {
......@@ -133,6 +130,22 @@ func run() int {
return 1
}
certFile, err := nc.GetVMAttachCertificate()
if err != nil {
u.PrintlnErr("Error: " + err.Error())
return 1
}
// This thread acts as a signal handler for SIGINT or SIGTERM.
// When one of these signals is received, the temporary certificate file is deleted.
// Without this "handler", the temporary certificate file wouldn't be deleted.
go func() {
u.WaitForSignals()
os.Remove(certFile)
restoreTerm()
os.Exit(1)
}()
shell(nc)
return 0
}
......
......@@ -9,30 +9,37 @@ import (
)
type NexusClient struct {
client *resty.Client
client *resty.Client
hostname string // host, e.g. "127.0.0.1" or "my.domain.net"
}
// host has the following format:
// ip:port, e.g. 127.0.0.1:1077
func New(host string) *NexusClient {
nc := &NexusClient{resty.New()}
// The host argument has the following format:
// "ip:port" or "domain:port", e.g. "127.0.0.1:1077" or "my.domain.net:80"
func New(host string) (*NexusClient, error) {
nc := &NexusClient{}
nc.client = resty.New()
// To disable the check of the CA signed certificate
nc.client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
nc.client.SetBaseURL("https://" + host)
nc.client.SetAllowGetMethodPayload(true)
// Change the default timeout to 10 seconds
nc.client.SetTimeout(10 * time.Second)
return nc
}
func (nc *NexusClient) GetBaseURL() (string, error) {
url, err := url.Parse(nc.client.BaseURL)
if err != nil {
return "", err
return nil, err
}
return url.Hostname(), nil
nc.hostname = url.Hostname()
return nc, nil
}
// Returns the hostname, e.g. "127.0.0.1" or "my.domain.net".
func (nc *NexusClient) GetHostname() string {
return nc.hostname
}
// Change the default timeout.
func (nc *NexusClient) SetTimeout(timeout time.Duration) {
nc.client.SetTimeout(timeout)
}
......@@ -2,9 +2,9 @@ package nexusclient
import (
"errors"
"fmt"
"os/exec"
"runtime"
"strconv"
"strings"
)
......@@ -45,20 +45,26 @@ func checkRemoteViewer() error {
// name is the VM's name.
// port is the port the VM spice server is listening to.
// pwd is the spice password to connect to the running VM.
func runRemoteViewer(hostname string, cert string, name string, port int, pwd string, fullscreen bool) ([]byte, error) {
portStr := strconv.Itoa(port)
spice := "spice://" + hostname + "?tls-port=" + portStr + "&password=" + pwd
spiceCert := "--spice-ca-file=" + cert
spiceSecure := "--spice-secure-channels=all"
func runRemoteViewer(hostname string, cert string, name string, port int, pwd string, fullscreen bool) error {
// portStr := strconv.Itoa(port)
// spice := "spice://" + hostname + "?tls-port=" + portStr + "&password=" + pwd
// spiceCert := "--spice-ca-file=" + cert
// spiceSecure := "--spice-secure-channels=all"
// To exit fullscreen mode, either:
// - move the mouse to the middle of the top of the screen.
// - use ctrl+F12 as specified below
// To activate kiosk mode, add these options: "-k --kiosk-quit=on-disconnect"
var c *exec.Cmd
if fullscreen {
c = exec.Command("remote-viewer", spice, spiceCert, spiceSecure, "-t", name, "--hotkeys=toggle-fullscreen=ctrl+f12", "-f")
} else {
c = exec.Command("remote-viewer", spice, spiceCert, spiceSecure, "-t", name, "--hotkeys=toggle-fullscreen=ctrl+f12")
// if fullscreen {
// c = exec.Command("remote-viewer", spice, spiceCert, spiceSecure, "-t", name, "--hotkeys=toggle-fullscreen=ctrl+f12", "-f")
// } else {
// c = exec.Command("remote-viewer", spice, spiceCert, spiceSecure, "-t", name, "--hotkeys=toggle-fullscreen=ctrl+f12")
// }
c = exec.Command("remote-viewer", "spice://crap")
stdoutStderr, err := c.CombinedOutput()
if err != nil {
return errors.New("err: remote-viewer failed: " + string(stdoutStderr[:]))
}
return c.CombinedOutput()
fmt.Println("err: remote-viewer failed: " + string(stdoutStderr[:]))
return nil
}
......@@ -182,35 +182,24 @@ func (nc *NexusClient) VMStop(vmID string) error {
}
}
func (nc *NexusClient) VMAttachFromID(vmID string, fullscreen bool) error {
func (nc *NexusClient) VMAttachFromID(vmID string, certificateFile string, fullscreen bool) error {
creds, err := nc.vmIdToAttachCreds(vmID)
if err != nil {
return err
}
err = nc.VMAttachFromCreds(*creds, fullscreen)
err = nc.VMAttachFromCreds(*creds, certificateFile, fullscreen)
if err != nil {
return err
}
return nil
}
func (nc *NexusClient) VMAttachFromCreds(v vm.VMAttachCredentialsSerialized, fullscreen bool) error {
func (nc *NexusClient) VMAttachFromCreds(v vm.VMAttachCredentialsSerialized, certificateFile string, fullscreen bool) error {
if err := checkRemoteViewer(); err != nil {
return err
}
hostname, err := nc.GetBaseURL()
if err != nil {
return err
}
certFile, err := nc.getSpiceCertificate()
if err != nil {
return err
}
defer os.Remove(certFile)
p := params.VMAttachCreds{Pwd: v.Pwd}
creds, err := nc.vmGetSpiceCreds(v.ID.String(), p)
if err != nil {
......@@ -218,7 +207,7 @@ func (nc *NexusClient) VMAttachFromCreds(v vm.VMAttachCredentialsSerialized, ful
}
// First return value (ignored) is of type: stdoutStderr
_, err = runRemoteViewer(hostname, certFile, creds.Name, creds.SpicePort, creds.SpicePwd, fullscreen)
err = runRemoteViewer(nc.hostname, certificateFile, creds.Name, creds.SpicePort, creds.SpicePwd, fullscreen)
if err != nil {
return err
}
......@@ -226,28 +215,17 @@ func (nc *NexusClient) VMAttachFromCreds(v vm.VMAttachCredentialsSerialized, ful
return nil
}
func (nc *NexusClient) VMAttachFromPwd(pwd string, fullscreen bool) error {
func (nc *NexusClient) VMAttachFromPwd(pwd string, certificateFile string, fullscreen bool) error {
if err := checkRemoteViewer(); err != nil {
return err
}
hostname, err := nc.GetBaseURL()
if err != nil {
return err
}
certFile, err := nc.getSpiceCertificate()
if err != nil {
return err
}
defer os.Remove(certFile)
p := params.VMAttachCreds{Pwd: pwd}
creds, err := nc.vmGetAnySpiceCreds(p)
if err != nil {
return err
} else {
_, err := runRemoteViewer(hostname, certFile, creds.Name, creds.SpicePort, creds.SpicePwd, fullscreen)
err := runRemoteViewer(nc.hostname, certificateFile, creds.Name, creds.SpicePort, creds.SpicePwd, fullscreen)
if err != nil {
return err
}
......@@ -256,6 +234,31 @@ func (nc *NexusClient) VMAttachFromPwd(pwd string, fullscreen bool) error {
return nil
}
// Obtain the certificate required by the various VMAttachXXX methods.
// This public CA certificate is required by Spice remote viewers.
// The certificate file is saved in the OS' temporary directory.
// Its name is randomly generated and garanteed to be unique.
// It is the caller's responsability to delete it before exiting.
func (nc *NexusClient) GetVMAttachCertificate() (string, error) {
uniqPostfix, err := uuid.NewRandom()
if err != nil {
return "", errors.New("Failed saving CA certificate, code 1:\n" + err.Error())
}
outputFile := filepath.Join(os.TempDir(), "cert_"+uniqPostfix.String()+".pem")
resp, err := nc.client.R().SetOutput(outputFile).Get("/misc/cert")
if err != nil {
return "", errors.New("Failed saving CA certificate, code 2:\n" + err.Error())
}
if resp.IsSuccess() {
return outputFile, nil
} else {
errorMsg, _ := u.FileToString(outputFile)
return "", errors.New("Failed saving CA certificate, code 3\n" + resp.Status() + ": " + errorMsg)
}
}
func (nc *NexusClient) VMAddAccess(vmID, vmName, email string, vmAccessCaps *params.VMAddAccess) error {
resp, err := nc.client.R().SetBody(vmAccessCaps).Put("/vms/" + vmID + "/access/" + email)
if err != nil {
......@@ -343,30 +346,6 @@ func (nc *NexusClient) GetListVM(vmID string) (*vm.VMNetworkSerialized, error) {
// Private functions
//========================================================================================
// Obtain a public CA certificate for use with a Spice remote viewer.
// The certificate file is saved in the OS' temporary directory.
// Its name is randomly generated and garanteed to be unique.
// It is the caller's responsability to delete it before exiting.
func (nc *NexusClient) getSpiceCertificate() (string, error) {
uniqPostfix, err := uuid.NewRandom()
if err != nil {
return "", errors.New("Failed saving CA certificate: " + err.Error())
}
outputFile := filepath.Join(os.TempDir(), "cert_"+uniqPostfix.String()+".pem")
resp, err := nc.client.R().SetOutput(outputFile).Get("/misc/cert")
if err != nil {
return "", errors.New("Failed saving CA certificate: " + err.Error())
}
if resp.IsSuccess() {
return outputFile, nil
} else {
errorMsg, _ := u.FileToString(outputFile)
return "", errors.New("Failed saving CA certificate" + resp.Status() + ": " + errorMsg)
}
}
func (nc *NexusClient) vmGetSpiceCreds(vmID string, p params.VMAttachCreds) (*vm.VMSpiceCredentialsSerialized, error) {
resp, err := nc.client.R().SetBody(p).Post("/vms/" + vmID + "/spicecreds")
if err != nil {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment