diff --git a/server/internal/services/console.go b/server/internal/services/console.go index 0e5e440..5c37960 100644 --- a/server/internal/services/console.go +++ b/server/internal/services/console.go @@ -8,6 +8,8 @@ import ( "strconv" "strings" "time" + + "github.com/mrhid6/vantage/server/internal/models" ) func sessionHMACKey() ([]byte, error) { @@ -67,3 +69,50 @@ func VerifySessionToken(token string) (string, error) { } return string(sid), nil } + +type GuacParams struct { + Protocol string + Params map[string]string +} + +func portOr(v, def int) string { + if v == 0 { + v = def + } + return strconv.Itoa(v) +} + +// BuildGuacParams assembles the guacd connection parameter map for a protocol. +// privateKey is the decrypted SSH private key (ssh only); rdpUser/rdpPass are +// used for rdp. None of these values are persisted or logged by the caller. +func BuildGuacParams(srv *models.Server, protocol, privateKey, rdpUser, rdpPass string) (*GuacParams, error) { + host := srv.IPAddress + switch protocol { + case "ssh": + p := map[string]string{ + "hostname": host, + "port": portOr(srv.SSHPort, 22), + } + if privateKey != "" { + p["private-key"] = privateKey + } + return &GuacParams{Protocol: "ssh", Params: p}, nil + case "rdp": + return &GuacParams{Protocol: "rdp", Params: map[string]string{ + "hostname": host, + "port": portOr(srv.RDPPort, 3389), + "username": rdpUser, + "password": rdpPass, + "security": "any", + "ignore-cert": "true", + }}, nil + case "vnc": + return &GuacParams{Protocol: "vnc", Params: map[string]string{ + "hostname": host, + "port": "5900", + "password": rdpPass, + }}, nil + default: + return nil, fmt.Errorf("unsupported protocol %q", protocol) + } +} diff --git a/server/internal/services/console_test.go b/server/internal/services/console_test.go index 0d25da8..25f598b 100644 --- a/server/internal/services/console_test.go +++ b/server/internal/services/console_test.go @@ -3,6 +3,8 @@ package services import ( "testing" "time" + + "github.com/mrhid6/vantage/server/internal/models" ) func TestSessionTokenRoundTrip(t *testing.T) { @@ -41,3 +43,41 @@ func TestSessionTokenTampered(t *testing.T) { t.Fatalf("expected signature error, got nil") } } + +func TestBuildGuacParamsSSH(t *testing.T) { + srv := &models.Server{IPAddress: "10.0.0.5", SSHPort: 22} + p, err := BuildGuacParams(srv, "ssh", "PRIVATE-KEY-DATA", "", "") + if err != nil { + t.Fatalf("err: %v", err) + } + if p.Protocol != "ssh" { + t.Fatalf("protocol %q", p.Protocol) + } + if p.Params["hostname"] != "10.0.0.5" || p.Params["port"] != "22" { + t.Fatalf("bad host/port: %+v", p.Params) + } + if p.Params["private-key"] != "PRIVATE-KEY-DATA" { + t.Fatalf("missing private-key") + } +} + +func TestBuildGuacParamsRDP(t *testing.T) { + srv := &models.Server{IPAddress: "10.0.0.9", RDPPort: 3389} + p, err := BuildGuacParams(srv, "rdp", "", "administrator", "s3cret") + if err != nil { + t.Fatalf("err: %v", err) + } + if p.Params["port"] != "3389" || p.Params["username"] != "administrator" || p.Params["password"] != "s3cret" { + t.Fatalf("bad rdp params: %+v", p.Params) + } + if p.Params["ignore-cert"] != "true" { + t.Fatalf("expected ignore-cert=true") + } +} + +func TestBuildGuacParamsUnknownProtocol(t *testing.T) { + srv := &models.Server{IPAddress: "10.0.0.9"} + if _, err := BuildGuacParams(srv, "telnet", "", "", ""); err == nil { + t.Fatalf("expected error for unknown protocol") + } +}