From a66fe347cce40556c569113082418994fc84e2dd Mon Sep 17 00:00:00 2001 From: Drew Malin Date: Wed, 4 Mar 2026 17:19:27 -0800 Subject: [PATCH 1/3] sshport --- go.mod | 2 +- go.sum | 4 +-- pkg/cmd/enablessh/enablessh.go | 11 +++++--- pkg/cmd/grantssh/grantssh.go | 7 ++++- pkg/cmd/grantssh/grantssh_test.go | 6 +++++ pkg/cmd/register/register.go | 7 ++++- pkg/cmd/register/register_test.go | 12 +++++++++ pkg/cmd/register/sshkeys.go | 43 ++++++++++++++++++++++++++++++- 8 files changed, 83 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 8b44aa62..ec29314f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.0 require ( buf.build/gen/go/brevdev/devplane/connectrpc/go v1.19.1-20260228021043-887d38e1b474.2 - buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260228021043-887d38e1b474.1 + buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260305005117-3cacb6388cd4.1 connectrpc.com/connect v1.19.1 github.com/alessio/shellescape v1.4.1 github.com/brevdev/parse v0.0.11 diff --git a/go.sum b/go.sum index d9bb3161..468ebc6f 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ buf.build/gen/go/brevdev/devplane/connectrpc/go v1.19.1-20260228021043-887d38e1b474.2 h1:Sq0kIa/xKzScbJcqB5EbPVhOL0QYHPr3araQaupL2lk= buf.build/gen/go/brevdev/devplane/connectrpc/go v1.19.1-20260228021043-887d38e1b474.2/go.mod h1:Yh34p9aADmWsKv2umYlMpnCZuBmNBE9N+HImgRriJXM= -buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260228021043-887d38e1b474.1 h1:WlSch6mGiV/gO+vq6y0Ut+HO2ffFHsLhTI3lVWdO0bI= -buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260228021043-887d38e1b474.1/go.mod h1:V/y7Wxg0QvU4XPVwqErF5NHLobUT1QEyfgrGuQIxdPo= +buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260305005117-3cacb6388cd4.1 h1:3Y3FI5kbM4uacawy5dySjVTPSbu2BJMO42eQHf2wz+g= +buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260305005117-3cacb6388cd4.1/go.mod h1:V/y7Wxg0QvU4XPVwqErF5NHLobUT1QEyfgrGuQIxdPo= buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1 h1:6amhprQmCKJ4wgJ6ngkh32d9V+dQcOLUZ/SfHdOnYgo= buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1/go.mod h1:O+pnSHMru/naTMrm4tmpBoH3wz6PHa+R75HR7Mv8X2g= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= diff --git a/pkg/cmd/enablessh/enablessh.go b/pkg/cmd/enablessh/enablessh.go index 7b8c5f5e..1eefc6f9 100644 --- a/pkg/cmd/enablessh/enablessh.go +++ b/pkg/cmd/enablessh/enablessh.go @@ -70,7 +70,7 @@ func runEnableSSH(ctx context.Context, t *terminal.Terminal, s EnableSSHStore, d return breverrors.WrapAndTrace(err) } - return enableSSH(ctx, t, deps.nodeClients, s, reg, brevUser) + return enableSSH(ctx, t, deps, s, reg, brevUser) } // enableSSH grants SSH access to the given node for the current Brev user. @@ -78,7 +78,7 @@ func runEnableSSH(ctx context.Context, t *terminal.Terminal, s EnableSSHStore, d func enableSSH( ctx context.Context, t *terminal.Terminal, - nodeClients externalnode.NodeClientFactory, + deps enableSSHDeps, tokenProvider externalnode.TokenProvider, reg *register.DeviceRegistration, brevUser *entity.User, @@ -98,7 +98,12 @@ func enableSSH( t.Vprintf(" Linux user: %s\n", u.Username) t.Vprint("") - if err := register.GrantSSHAccessToNode(ctx, t, nodeClients, tokenProvider, reg, brevUser, u); err != nil { + port, err := register.PromptSSHPort(t) + if err != nil { + return fmt.Errorf("SSH port: %w", err) + } + + if err := register.GrantSSHAccessToNode(ctx, t, deps.nodeClients, tokenProvider, reg, brevUser, u, port); err != nil { return fmt.Errorf("enable SSH failed: %w", err) } diff --git a/pkg/cmd/grantssh/grantssh.go b/pkg/cmd/grantssh/grantssh.go index d4c9b6b0..985ef7c7 100644 --- a/pkg/cmd/grantssh/grantssh.go +++ b/pkg/cmd/grantssh/grantssh.go @@ -131,7 +131,12 @@ func runGrantSSH(ctx context.Context, t *terminal.Terminal, s GrantSSHStore, dep t.Vprintf(" Linux user: %s\n", linuxUser) t.Vprint("") - if err := register.GrantSSHAccessToNode(ctx, t, deps.nodeClients, s, reg, selectedUser, osUser); err != nil { + port, err := register.PromptSSHPort(t) + if err != nil { + return fmt.Errorf("SSH port: %w", err) + } + + if err := register.GrantSSHAccessToNode(ctx, t, deps.nodeClients, s, reg, selectedUser, osUser, port); err != nil { return fmt.Errorf("grant SSH failed: %w", err) } diff --git a/pkg/cmd/grantssh/grantssh_test.go b/pkg/cmd/grantssh/grantssh_test.go index 0dc3084e..22461be0 100644 --- a/pkg/cmd/grantssh/grantssh_test.go +++ b/pkg/cmd/grantssh/grantssh_test.go @@ -271,6 +271,9 @@ func Test_runGrantSSH_HappyPath(t *testing.T) { deps, server := testGrantSSHDeps(t, svc, regStore) defer server.Close() + register.SetTestSSHPort(22) + defer register.ClearTestSSHPort() + term := terminal.New() err := runGrantSSH(context.Background(), term, store, deps) if err != nil { @@ -323,6 +326,9 @@ func Test_runGrantSSH_RPCFailure(t *testing.T) { deps, server := testGrantSSHDeps(t, svc, regStore) defer server.Close() + register.SetTestSSHPort(22) + defer register.ClearTestSSHPort() + term := terminal.New() err := runGrantSSH(context.Background(), term, store, deps) if err == nil { diff --git a/pkg/cmd/register/register.go b/pkg/cmd/register/register.go index 63cc9550..4e4cf0db 100644 --- a/pkg/cmd/register/register.go +++ b/pkg/cmd/register/register.go @@ -353,7 +353,12 @@ func grantSSHAccess(ctx context.Context, t *terminal.Terminal, deps registerDeps t.Vprintf(" Linux user: %s\n", osUser.Username) t.Vprint("") - err := GrantSSHAccessToNode(ctx, t, deps.nodeClients, tokenProvider, reg, brevUser, osUser) + port, err := PromptSSHPort(t) + if err != nil { + return fmt.Errorf("SSH port: %w", err) + } + + err = GrantSSHAccessToNode(ctx, t, deps.nodeClients, tokenProvider, reg, brevUser, osUser, port) if err != nil { return fmt.Errorf("grant SSH failed: %w", err) } diff --git a/pkg/cmd/register/register_test.go b/pkg/cmd/register/register_test.go index 8eb25776..b031fea4 100644 --- a/pkg/cmd/register/register_test.go +++ b/pkg/cmd/register/register_test.go @@ -168,6 +168,9 @@ func Test_runRegister_HappyPath(t *testing.T) { deps.setupRunner = setupRunner + SetTestSSHPort(22) + defer ClearTestSSHPort() + term := terminal.New() err := runRegister(context.Background(), term, store, "My Spark", deps) if err != nil { @@ -408,6 +411,9 @@ func Test_runRegister_NoSetupCommand(t *testing.T) { deps.setupRunner = setupRunner + SetTestSSHPort(22) + defer ClearTestSSHPort() + term := terminal.New() err := runRegister(context.Background(), term, store, "My Spark", deps) if err != nil { @@ -538,6 +544,9 @@ func Test_runRegister_GrantSSH_retries_on_connection_error_then_succeeds(t *test deps.prompter = mockConfirmer{confirm: true} + SetTestSSHPort(22) + defer ClearTestSSHPort() + term := terminal.New() err := runRegister(context.Background(), term, store, "My Spark", deps) if err != nil { @@ -584,6 +593,9 @@ func Test_runRegister_GrantSSH_no_retry_on_permanent_error(t *testing.T) { deps.prompter = mockConfirmer{confirm: true} + SetTestSSHPort(22) + defer ClearTestSSHPort() + term := terminal.New() err := runRegister(context.Background(), term, store, "My Spark", deps) if err != nil { diff --git a/pkg/cmd/register/sshkeys.go b/pkg/cmd/register/sshkeys.go index 4414cad9..1e8661fe 100644 --- a/pkg/cmd/register/sshkeys.go +++ b/pkg/cmd/register/sshkeys.go @@ -7,6 +7,7 @@ import ( "os" "os/user" "path/filepath" + "strconv" "strings" "time" @@ -132,7 +133,7 @@ func RemoveAuthorizedKeyLine(u *user.User, line string) error { // GrantSSHAccessToNode installs the user's public key in authorized_keys and // calls GrantNodeSSHAccess to record access server-side. If the RPC fails, -// the installed key is rolled back. +// the installed key is rolled back. port is the target SSH port (e.g. 22). func GrantSSHAccessToNode( ctx context.Context, t *terminal.Terminal, @@ -141,6 +142,7 @@ func GrantSSHAccessToNode( reg *DeviceRegistration, targetUser *entity.User, osUser *user.User, + port uint32, ) error { if targetUser.PublicKey != "" { if added, err := InstallAuthorizedKey(osUser, targetUser.PublicKey, targetUser.ID); err != nil { @@ -163,6 +165,7 @@ func GrantSSHAccessToNode( ExternalNodeId: reg.ExternalNodeID, UserId: targetUser.ID, LinuxUser: osUser.Username, + Port: int32(port), })) if err != nil { // Retryable error @@ -192,6 +195,44 @@ func GrantSSHAccessToNode( return nil } +const defaultSSHPort = 22 + +// testSSHPort is set by tests to avoid blocking on stdin. When non-nil, +// PromptSSHPort returns this value without prompting. +var testSSHPort *uint32 + +// SetTestSSHPort sets the port returned by PromptSSHPort without prompting. +// Only for use in tests; call ClearTestSSHPort when done. +func SetTestSSHPort(port uint32) { testSSHPort = &port } + +// ClearTestSSHPort clears the test port override. +func ClearTestSSHPort() { testSSHPort = nil } + +// PromptSSHPort prompts the user for the target SSH port, defaulting to 22 if +// they press Enter or leave it empty. Returns an error for invalid port numbers. +func PromptSSHPort(t *terminal.Terminal) (uint32, error) { + if testSSHPort != nil { + return *testSSHPort, nil + } + portStr := terminal.PromptGetInput(terminal.PromptContent{ + Label: " SSH port (default 22): ", + Default: "22", + AllowEmpty: true, + }) + portStr = strings.TrimSpace(portStr) + if portStr == "" { + return defaultSSHPort, nil + } + n, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return 0, fmt.Errorf("invalid port %q: %w", portStr, err) + } + if n < 1 || n > 65535 { + return 0, fmt.Errorf("port must be between 1 and 65535, got %d", n) + } + return uint32(n), nil +} + // InstallAuthorizedKey appends the given public key to the user's // ~/.ssh/authorized_keys if it isn't already present. The key is tagged with // a brev-cli comment (including the user ID) so it can be identified and From 9dbb1f533815662052dfe7f69e2429ce15e3b9e8 Mon Sep 17 00:00:00 2001 From: Drew Malin Date: Wed, 4 Mar 2026 17:22:40 -0800 Subject: [PATCH 2/3] int32 --- pkg/cmd/register/sshkeys.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/cmd/register/sshkeys.go b/pkg/cmd/register/sshkeys.go index 1e8661fe..7e235ef6 100644 --- a/pkg/cmd/register/sshkeys.go +++ b/pkg/cmd/register/sshkeys.go @@ -142,7 +142,7 @@ func GrantSSHAccessToNode( reg *DeviceRegistration, targetUser *entity.User, osUser *user.User, - port uint32, + port int32, ) error { if targetUser.PublicKey != "" { if added, err := InstallAuthorizedKey(osUser, targetUser.PublicKey, targetUser.ID); err != nil { @@ -165,7 +165,7 @@ func GrantSSHAccessToNode( ExternalNodeId: reg.ExternalNodeID, UserId: targetUser.ID, LinuxUser: osUser.Username, - Port: int32(port), + Port: port, })) if err != nil { // Retryable error @@ -199,18 +199,18 @@ const defaultSSHPort = 22 // testSSHPort is set by tests to avoid blocking on stdin. When non-nil, // PromptSSHPort returns this value without prompting. -var testSSHPort *uint32 +var testSSHPort *int32 // SetTestSSHPort sets the port returned by PromptSSHPort without prompting. // Only for use in tests; call ClearTestSSHPort when done. -func SetTestSSHPort(port uint32) { testSSHPort = &port } +func SetTestSSHPort(port int32) { testSSHPort = &port } // ClearTestSSHPort clears the test port override. func ClearTestSSHPort() { testSSHPort = nil } // PromptSSHPort prompts the user for the target SSH port, defaulting to 22 if // they press Enter or leave it empty. Returns an error for invalid port numbers. -func PromptSSHPort(t *terminal.Terminal) (uint32, error) { +func PromptSSHPort(t *terminal.Terminal) (int32, error) { if testSSHPort != nil { return *testSSHPort, nil } @@ -230,7 +230,7 @@ func PromptSSHPort(t *terminal.Terminal) (uint32, error) { if n < 1 || n > 65535 { return 0, fmt.Errorf("port must be between 1 and 65535, got %d", n) } - return uint32(n), nil + return int32(n), nil } // InstallAuthorizedKey appends the given public key to the user's From 4fe85c8b10ec54a0326a0a67eafdeca5ceed139c Mon Sep 17 00:00:00 2001 From: Drew Malin Date: Wed, 4 Mar 2026 18:04:34 -0800 Subject: [PATCH 3/3] fix --- pkg/cmd/register/sshkeys.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/cmd/register/sshkeys.go b/pkg/cmd/register/sshkeys.go index 7e235ef6..28918052 100644 --- a/pkg/cmd/register/sshkeys.go +++ b/pkg/cmd/register/sshkeys.go @@ -216,7 +216,6 @@ func PromptSSHPort(t *terminal.Terminal) (int32, error) { } portStr := terminal.PromptGetInput(terminal.PromptContent{ Label: " SSH port (default 22): ", - Default: "22", AllowEmpty: true, }) portStr = strings.TrimSpace(portStr)