fix: send ssh username to guacd (default root) for console SSH

This commit is contained in:
2026-07-17 11:54:33 +01:00
parent 50907448d2
commit 1ad5b5d6db
6 changed files with 43 additions and 5 deletions
+9 -1
View File
@@ -21,6 +21,7 @@ func consoleConnect(c *gin.Context) {
KeyID string `json:"key_id"`
RDPUsername string `json:"rdp_username"`
RDPPassword string `json:"rdp_password"`
SSHUsername string `json:"ssh_username"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -51,6 +52,13 @@ func consoleConnect(c *gin.Context) {
}
}
if body.Protocol == "ssh" {
if err := services.SetConsoleSSHUser(sess.SessionID, body.SSHUsername); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
services.LogEvent("console.opened", actorFromCtx(c), srv.ServerID, "",
"console session opened ("+body.Protocol+")")
@@ -98,7 +106,7 @@ func consoleTunnel(c *gin.Context) {
return
}
}
gp, err := services.BuildGuacParams(srv, sess.Protocol, privKey, rdpUser, rdpPass)
gp, err := services.BuildGuacParams(srv, sess.Protocol, sess.SSHUsername, privKey, rdpUser, rdpPass)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
@@ -17,6 +17,8 @@ type ConsoleSession struct {
EndedAt *time.Time `bson:"ended_at,omitempty" json:"ended_at,omitempty"`
ClientIP string `bson:"client_ip,omitempty" json:"client_ip,omitempty"`
SSHUsername string `bson:"ssh_username,omitempty" json:"ssh_username,omitempty"`
RDPUserEnc string `bson:"rdp_user_enc,omitempty" json:"-"`
RDPPassEnc string `bson:"rdp_pass_enc,omitempty" json:"-"`
}
+15 -1
View File
@@ -89,7 +89,7 @@ func portOr(v, def int) string {
// 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) {
func BuildGuacParams(srv *models.Server, protocol, sshUser, privateKey, rdpUser, rdpPass string) (*GuacParams, error) {
host := srv.IPAddress
switch protocol {
case "ssh":
@@ -97,6 +97,10 @@ func BuildGuacParams(srv *models.Server, protocol, privateKey, rdpUser, rdpPass
"hostname": host,
"port": portOr(srv.SSHPort, 22),
}
if sshUser == "" {
sshUser = "root"
}
p["username"] = sshUser
if privateKey != "" {
p["private-key"] = privateKey
}
@@ -200,6 +204,16 @@ func ConsumeConsoleRDPCreds(sessionID string) (username, password string, err er
return username, password, nil
}
// SetConsoleSSHUser persists the SSH username to use on the session doc.
func SetConsoleSSHUser(sessionID, username string) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := db.Col("console_sessions").UpdateOne(ctx,
bson.M{"session_id": sessionID},
bson.M{"$set": bson.M{"ssh_username": username}})
return err
}
func EndConsoleSession(sessionID string) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
+6 -3
View File
@@ -46,7 +46,7 @@ func TestSessionTokenTampered(t *testing.T) {
func TestBuildGuacParamsSSH(t *testing.T) {
srv := &models.Server{IPAddress: "10.0.0.5", SSHPort: 22}
p, err := BuildGuacParams(srv, "ssh", "PRIVATE-KEY-DATA", "", "")
p, err := BuildGuacParams(srv, "ssh", "", "PRIVATE-KEY-DATA", "", "")
if err != nil {
t.Fatalf("err: %v", err)
}
@@ -59,11 +59,14 @@ func TestBuildGuacParamsSSH(t *testing.T) {
if p.Params["private-key"] != "PRIVATE-KEY-DATA" {
t.Fatalf("missing private-key")
}
if p.Params["username"] != "root" {
t.Fatalf("expected default username root, got %q", p.Params["username"])
}
}
func TestBuildGuacParamsRDP(t *testing.T) {
srv := &models.Server{IPAddress: "10.0.0.9", RDPPort: 3389}
p, err := BuildGuacParams(srv, "rdp", "", "administrator", "s3cret")
p, err := BuildGuacParams(srv, "rdp", "", "", "administrator", "s3cret")
if err != nil {
t.Fatalf("err: %v", err)
}
@@ -77,7 +80,7 @@ func TestBuildGuacParamsRDP(t *testing.T) {
func TestBuildGuacParamsUnknownProtocol(t *testing.T) {
srv := &models.Server{IPAddress: "10.0.0.9"}
if _, err := BuildGuacParams(srv, "telnet", "", "", ""); err == nil {
if _, err := BuildGuacParams(srv, "telnet", "", "", "", ""); err == nil {
t.Fatalf("expected error for unknown protocol")
}
}
+10
View File
@@ -19,6 +19,7 @@ export default function ServerConsolePage() {
const [protocol, setProtocol] = useState<string>(searchParams.get("protocol") || "");
const [keyId, setKeyId] = useState<string>("");
const [sshUsername, setSshUsername] = useState<string>("root");
const [rdpUsername, setRdpUsername] = useState("");
const [rdpPassword, setRdpPassword] = useState("");
const [connecting, setConnecting] = useState(false);
@@ -72,6 +73,7 @@ export default function ServerConsolePage() {
};
if (protocol === "ssh") {
body.key_id = keyId || undefined;
body.ssh_username = sshUsername || undefined;
} else if (protocol === "rdp") {
body.rdp_username = rdpUsername || undefined;
body.rdp_password = rdpPassword || undefined;
@@ -152,6 +154,14 @@ export default function ServerConsolePage() {
{protocol === "ssh" && (
<div>
<label className="mb-1.5 block text-sm font-medium text-text-secondary">SSH Username</label>
<input
type="text"
value={sshUsername}
onChange={(e) => setSshUsername(e.target.value)}
placeholder="root"
className="mb-3 w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary focus:border-accent/50 focus:outline-none focus:ring-1 focus:ring-accent/30"
/>
<label className="mb-1.5 block text-sm font-medium text-text-secondary">SSH Key</label>
<select
value={keyId}
+1
View File
@@ -28,6 +28,7 @@ export interface ConsoleConnectRequest {
key_id?: string;
rdp_username?: string;
rdp_password?: string;
ssh_username?: string;
}
export interface ConsoleConnectResponse {