Skip to content

Commit a66fe34

Browse files
committed
sshport
1 parent 8bdb6f9 commit a66fe34

8 files changed

Lines changed: 83 additions & 9 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.24.0
44

55
require (
66
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.19.1-20260228021043-887d38e1b474.2
7-
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260228021043-887d38e1b474.1
7+
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260305005117-3cacb6388cd4.1
88
connectrpc.com/connect v1.19.1
99
github.com/alessio/shellescape v1.4.1
1010
github.com/brevdev/parse v0.0.11

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.19.1-20260228021043-887d38e1b474.2 h1:Sq0kIa/xKzScbJcqB5EbPVhOL0QYHPr3araQaupL2lk=
22
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.19.1-20260228021043-887d38e1b474.2/go.mod h1:Yh34p9aADmWsKv2umYlMpnCZuBmNBE9N+HImgRriJXM=
3-
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260228021043-887d38e1b474.1 h1:WlSch6mGiV/gO+vq6y0Ut+HO2ffFHsLhTI3lVWdO0bI=
4-
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260228021043-887d38e1b474.1/go.mod h1:V/y7Wxg0QvU4XPVwqErF5NHLobUT1QEyfgrGuQIxdPo=
3+
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260305005117-3cacb6388cd4.1 h1:3Y3FI5kbM4uacawy5dySjVTPSbu2BJMO42eQHf2wz+g=
4+
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260305005117-3cacb6388cd4.1/go.mod h1:V/y7Wxg0QvU4XPVwqErF5NHLobUT1QEyfgrGuQIxdPo=
55
buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1 h1:6amhprQmCKJ4wgJ6ngkh32d9V+dQcOLUZ/SfHdOnYgo=
66
buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1/go.mod h1:O+pnSHMru/naTMrm4tmpBoH3wz6PHa+R75HR7Mv8X2g=
77
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=

pkg/cmd/enablessh/enablessh.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@ func runEnableSSH(ctx context.Context, t *terminal.Terminal, s EnableSSHStore, d
7070
return breverrors.WrapAndTrace(err)
7171
}
7272

73-
return enableSSH(ctx, t, deps.nodeClients, s, reg, brevUser)
73+
return enableSSH(ctx, t, deps, s, reg, brevUser)
7474
}
7575

7676
// enableSSH grants SSH access to the given node for the current Brev user.
7777
// This is the "reflexive grant" — granting yourself SSH access to the device.
7878
func enableSSH(
7979
ctx context.Context,
8080
t *terminal.Terminal,
81-
nodeClients externalnode.NodeClientFactory,
81+
deps enableSSHDeps,
8282
tokenProvider externalnode.TokenProvider,
8383
reg *register.DeviceRegistration,
8484
brevUser *entity.User,
@@ -98,7 +98,12 @@ func enableSSH(
9898
t.Vprintf(" Linux user: %s\n", u.Username)
9999
t.Vprint("")
100100

101-
if err := register.GrantSSHAccessToNode(ctx, t, nodeClients, tokenProvider, reg, brevUser, u); err != nil {
101+
port, err := register.PromptSSHPort(t)
102+
if err != nil {
103+
return fmt.Errorf("SSH port: %w", err)
104+
}
105+
106+
if err := register.GrantSSHAccessToNode(ctx, t, deps.nodeClients, tokenProvider, reg, brevUser, u, port); err != nil {
102107
return fmt.Errorf("enable SSH failed: %w", err)
103108
}
104109

pkg/cmd/grantssh/grantssh.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,12 @@ func runGrantSSH(ctx context.Context, t *terminal.Terminal, s GrantSSHStore, dep
131131
t.Vprintf(" Linux user: %s\n", linuxUser)
132132
t.Vprint("")
133133

134-
if err := register.GrantSSHAccessToNode(ctx, t, deps.nodeClients, s, reg, selectedUser, osUser); err != nil {
134+
port, err := register.PromptSSHPort(t)
135+
if err != nil {
136+
return fmt.Errorf("SSH port: %w", err)
137+
}
138+
139+
if err := register.GrantSSHAccessToNode(ctx, t, deps.nodeClients, s, reg, selectedUser, osUser, port); err != nil {
135140
return fmt.Errorf("grant SSH failed: %w", err)
136141
}
137142

pkg/cmd/grantssh/grantssh_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ func Test_runGrantSSH_HappyPath(t *testing.T) {
271271
deps, server := testGrantSSHDeps(t, svc, regStore)
272272
defer server.Close()
273273

274+
register.SetTestSSHPort(22)
275+
defer register.ClearTestSSHPort()
276+
274277
term := terminal.New()
275278
err := runGrantSSH(context.Background(), term, store, deps)
276279
if err != nil {
@@ -323,6 +326,9 @@ func Test_runGrantSSH_RPCFailure(t *testing.T) {
323326
deps, server := testGrantSSHDeps(t, svc, regStore)
324327
defer server.Close()
325328

329+
register.SetTestSSHPort(22)
330+
defer register.ClearTestSSHPort()
331+
326332
term := terminal.New()
327333
err := runGrantSSH(context.Background(), term, store, deps)
328334
if err == nil {

pkg/cmd/register/register.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,12 @@ func grantSSHAccess(ctx context.Context, t *terminal.Terminal, deps registerDeps
353353
t.Vprintf(" Linux user: %s\n", osUser.Username)
354354
t.Vprint("")
355355

356-
err := GrantSSHAccessToNode(ctx, t, deps.nodeClients, tokenProvider, reg, brevUser, osUser)
356+
port, err := PromptSSHPort(t)
357+
if err != nil {
358+
return fmt.Errorf("SSH port: %w", err)
359+
}
360+
361+
err = GrantSSHAccessToNode(ctx, t, deps.nodeClients, tokenProvider, reg, brevUser, osUser, port)
357362
if err != nil {
358363
return fmt.Errorf("grant SSH failed: %w", err)
359364
}

pkg/cmd/register/register_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ func Test_runRegister_HappyPath(t *testing.T) {
168168

169169
deps.setupRunner = setupRunner
170170

171+
SetTestSSHPort(22)
172+
defer ClearTestSSHPort()
173+
171174
term := terminal.New()
172175
err := runRegister(context.Background(), term, store, "My Spark", deps)
173176
if err != nil {
@@ -408,6 +411,9 @@ func Test_runRegister_NoSetupCommand(t *testing.T) {
408411

409412
deps.setupRunner = setupRunner
410413

414+
SetTestSSHPort(22)
415+
defer ClearTestSSHPort()
416+
411417
term := terminal.New()
412418
err := runRegister(context.Background(), term, store, "My Spark", deps)
413419
if err != nil {
@@ -538,6 +544,9 @@ func Test_runRegister_GrantSSH_retries_on_connection_error_then_succeeds(t *test
538544

539545
deps.prompter = mockConfirmer{confirm: true}
540546

547+
SetTestSSHPort(22)
548+
defer ClearTestSSHPort()
549+
541550
term := terminal.New()
542551
err := runRegister(context.Background(), term, store, "My Spark", deps)
543552
if err != nil {
@@ -584,6 +593,9 @@ func Test_runRegister_GrantSSH_no_retry_on_permanent_error(t *testing.T) {
584593

585594
deps.prompter = mockConfirmer{confirm: true}
586595

596+
SetTestSSHPort(22)
597+
defer ClearTestSSHPort()
598+
587599
term := terminal.New()
588600
err := runRegister(context.Background(), term, store, "My Spark", deps)
589601
if err != nil {

pkg/cmd/register/sshkeys.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"os/user"
99
"path/filepath"
10+
"strconv"
1011
"strings"
1112
"time"
1213

@@ -132,7 +133,7 @@ func RemoveAuthorizedKeyLine(u *user.User, line string) error {
132133

133134
// GrantSSHAccessToNode installs the user's public key in authorized_keys and
134135
// calls GrantNodeSSHAccess to record access server-side. If the RPC fails,
135-
// the installed key is rolled back.
136+
// the installed key is rolled back. port is the target SSH port (e.g. 22).
136137
func GrantSSHAccessToNode(
137138
ctx context.Context,
138139
t *terminal.Terminal,
@@ -141,6 +142,7 @@ func GrantSSHAccessToNode(
141142
reg *DeviceRegistration,
142143
targetUser *entity.User,
143144
osUser *user.User,
145+
port uint32,
144146
) error {
145147
if targetUser.PublicKey != "" {
146148
if added, err := InstallAuthorizedKey(osUser, targetUser.PublicKey, targetUser.ID); err != nil {
@@ -163,6 +165,7 @@ func GrantSSHAccessToNode(
163165
ExternalNodeId: reg.ExternalNodeID,
164166
UserId: targetUser.ID,
165167
LinuxUser: osUser.Username,
168+
Port: int32(port),
166169
}))
167170
if err != nil {
168171
// Retryable error
@@ -192,6 +195,44 @@ func GrantSSHAccessToNode(
192195
return nil
193196
}
194197

198+
const defaultSSHPort = 22
199+
200+
// testSSHPort is set by tests to avoid blocking on stdin. When non-nil,
201+
// PromptSSHPort returns this value without prompting.
202+
var testSSHPort *uint32
203+
204+
// SetTestSSHPort sets the port returned by PromptSSHPort without prompting.
205+
// Only for use in tests; call ClearTestSSHPort when done.
206+
func SetTestSSHPort(port uint32) { testSSHPort = &port }
207+
208+
// ClearTestSSHPort clears the test port override.
209+
func ClearTestSSHPort() { testSSHPort = nil }
210+
211+
// PromptSSHPort prompts the user for the target SSH port, defaulting to 22 if
212+
// they press Enter or leave it empty. Returns an error for invalid port numbers.
213+
func PromptSSHPort(t *terminal.Terminal) (uint32, error) {
214+
if testSSHPort != nil {
215+
return *testSSHPort, nil
216+
}
217+
portStr := terminal.PromptGetInput(terminal.PromptContent{
218+
Label: " SSH port (default 22): ",
219+
Default: "22",
220+
AllowEmpty: true,
221+
})
222+
portStr = strings.TrimSpace(portStr)
223+
if portStr == "" {
224+
return defaultSSHPort, nil
225+
}
226+
n, err := strconv.ParseUint(portStr, 10, 16)
227+
if err != nil {
228+
return 0, fmt.Errorf("invalid port %q: %w", portStr, err)
229+
}
230+
if n < 1 || n > 65535 {
231+
return 0, fmt.Errorf("port must be between 1 and 65535, got %d", n)
232+
}
233+
return uint32(n), nil
234+
}
235+
195236
// InstallAuthorizedKey appends the given public key to the user's
196237
// ~/.ssh/authorized_keys if it isn't already present. The key is tagged with
197238
// a brev-cli comment (including the user ID) so it can be identified and

0 commit comments

Comments
 (0)