Skip to content

Commit b92cb09

Browse files
committed
tests
1 parent f41d40a commit b92cb09

5 files changed

Lines changed: 388 additions & 6 deletions

File tree

pkg/cmd/deregister/deregister.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ package deregister
44
import (
55
"context"
66
"fmt"
7+
"os/user"
78
"runtime"
89

910
nodev1connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/devplaneapi/v1/devplaneapiv1connect"
1011
nodev1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1"
1112
"connectrpc.com/connect"
1213

14+
"github.com/brevdev/brev-cli/pkg/cmd/enablessh"
1315
"github.com/brevdev/brev-cli/pkg/cmd/register"
1416
"github.com/brevdev/brev-cli/pkg/config"
1517
"github.com/brevdev/brev-cli/pkg/entity"
@@ -34,6 +36,7 @@ type deregisterDeps struct {
3436
uninstallNetbird func() error
3537
newNodeClient func(provider register.TokenProvider, baseURL string) nodev1connect.ExternalNodeServiceClient
3638
registrationStore register.RegistrationStore
39+
removeBrevKeys func(*user.User) error
3740
}
3841

3942
func prodDeregisterDeps(brevHome string) deregisterDeps {
@@ -48,6 +51,7 @@ func prodDeregisterDeps(brevHome string) deregisterDeps {
4851
uninstallNetbird: register.UninstallNetbird,
4952
newNodeClient: register.NewNodeServiceClient,
5053
registrationStore: register.NewFileRegistrationStore(brevHome),
54+
removeBrevKeys: enablessh.RemoveBrevAuthorizedKeys,
5155
}
5256
}
5357

@@ -125,6 +129,19 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,
125129
t.Vprint(t.Green(" Node removed from Brev."))
126130
t.Vprint("")
127131

132+
// Remove Brev SSH keys from authorized_keys.
133+
u, err := user.Current()
134+
if err != nil {
135+
t.Vprintf(" Warning: could not determine current user for SSH key cleanup: %v\n", err)
136+
} else {
137+
if err := deps.removeBrevKeys(u); err != nil {
138+
t.Vprintf(" Warning: failed to remove Brev SSH keys: %v\n", err)
139+
} else {
140+
t.Vprint(t.Green(" Brev SSH keys removed from authorized_keys."))
141+
}
142+
}
143+
t.Vprint("")
144+
128145
removeNetbird := deps.promptSelect(
129146
"Would you also like to uninstall NetBird?",
130147
[]string{"Yes, uninstall NetBird", "No, keep NetBird installed"},

pkg/cmd/deregister/deregister_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"net/http/httptest"
7+
"os/user"
78
"testing"
89

910
nodev1connect "buf.build/gen/go/brevdev/devplane/connectrpc/go/devplaneapi/v1/devplaneapiv1connect"
@@ -94,6 +95,7 @@ func testDeregisterDeps(t *testing.T, svc *fakeNodeService, regStore register.Re
9495
return register.NewNodeServiceClient(provider, server.URL)
9596
},
9697
registrationStore: regStore,
98+
removeBrevKeys: func(_ *user.User) error { return nil },
9799
}, server
98100
}
99101

@@ -295,3 +297,86 @@ func Test_runDeregister_SkipsNetbirdUninstall(t *testing.T) {
295297
t.Error("NetBird uninstall should not be called when user declines")
296298
}
297299
}
300+
301+
func Test_runDeregister_CallsRemoveBrevKeys(t *testing.T) {
302+
regStore := &mockRegistrationStore{
303+
reg: &register.DeviceRegistration{
304+
ExternalNodeID: "unode_abc",
305+
DisplayName: "My Spark",
306+
OrgID: "org_123",
307+
},
308+
}
309+
310+
store := &mockDeregisterStore{
311+
user: &entity.User{ID: "user_1"},
312+
home: "/home/testuser/.brev",
313+
token: "tok",
314+
}
315+
316+
svc := &fakeNodeService{
317+
removeNodeFn: func(_ *nodev1.RemoveNodeRequest) (*nodev1.RemoveNodeResponse, error) {
318+
return &nodev1.RemoveNodeResponse{}, nil
319+
},
320+
}
321+
322+
removeBrevKeysCalled := false
323+
deps, server := testDeregisterDeps(t, svc, regStore)
324+
defer server.Close()
325+
deps.removeBrevKeys = func(_ *user.User) error {
326+
removeBrevKeysCalled = true
327+
return nil
328+
}
329+
330+
term := terminal.New()
331+
err := runDeregister(context.Background(), term, store, deps)
332+
if err != nil {
333+
t.Fatalf("runDeregister failed: %v", err)
334+
}
335+
336+
if !removeBrevKeysCalled {
337+
t.Error("expected removeBrevKeys to be called during deregistration")
338+
}
339+
}
340+
341+
func Test_runDeregister_RemoveBrevKeysFailureIsNonFatal(t *testing.T) {
342+
regStore := &mockRegistrationStore{
343+
reg: &register.DeviceRegistration{
344+
ExternalNodeID: "unode_abc",
345+
DisplayName: "My Spark",
346+
OrgID: "org_123",
347+
},
348+
}
349+
350+
store := &mockDeregisterStore{
351+
user: &entity.User{ID: "user_1"},
352+
home: "/home/testuser/.brev",
353+
token: "tok",
354+
}
355+
356+
svc := &fakeNodeService{
357+
removeNodeFn: func(_ *nodev1.RemoveNodeRequest) (*nodev1.RemoveNodeResponse, error) {
358+
return &nodev1.RemoveNodeResponse{}, nil
359+
},
360+
}
361+
362+
deps, server := testDeregisterDeps(t, svc, regStore)
363+
defer server.Close()
364+
deps.removeBrevKeys = func(_ *user.User) error {
365+
return fmt.Errorf("permission denied")
366+
}
367+
368+
term := terminal.New()
369+
err := runDeregister(context.Background(), term, store, deps)
370+
if err != nil {
371+
t.Fatalf("expected deregister to succeed despite removeBrevKeys failure, got: %v", err)
372+
}
373+
374+
// Registration should still be cleaned up.
375+
exists, err := regStore.Exists()
376+
if err != nil {
377+
t.Fatalf("Exists error: %v", err)
378+
}
379+
if exists {
380+
t.Error("expected registration to be deleted even when SSH key cleanup fails")
381+
}
382+
}

pkg/cmd/enablessh/enablessh.go

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,13 @@ func EnableSSH(
142142
return nil
143143
}
144144

145+
// BrevKeyComment is the marker appended to every SSH key that Brev installs.
146+
// It allows RemoveBrevAuthorizedKeys to identify and remove exactly those keys.
147+
const BrevKeyComment = "# brev-cli"
148+
145149
// InstallAuthorizedKey appends the given public key to the user's
146-
// ~/.ssh/authorized_keys if it isn't already present.
150+
// ~/.ssh/authorized_keys if it isn't already present. The key is tagged with
151+
// a brev-cli comment so it can be removed later by RemoveBrevAuthorizedKeys.
147152
func InstallAuthorizedKey(u *user.User, pubKey string) error {
148153
pubKey = strings.TrimSpace(pubKey)
149154
if pubKey == "" {
@@ -163,15 +168,17 @@ func InstallAuthorizedKey(u *user.User, pubKey string) error {
163168
}
164169

165170
if strings.Contains(string(existing), pubKey) {
166-
return nil // already present
171+
return nil // already present (tagged or not)
167172
}
168173

174+
taggedKey := pubKey + " " + BrevKeyComment
175+
169176
// Ensure existing content ends with a newline before appending.
170177
content := string(existing)
171178
if len(content) > 0 && !strings.HasSuffix(content, "\n") {
172179
content += "\n"
173180
}
174-
content += pubKey + "\n"
181+
content += taggedKey + "\n"
175182

176183
if err := os.WriteFile(authKeysPath, []byte(content), 0o600); err != nil {
177184
return fmt.Errorf("writing authorized_keys: %w", err)
@@ -180,6 +187,34 @@ func InstallAuthorizedKey(u *user.User, pubKey string) error {
180187
return nil
181188
}
182189

190+
// RemoveBrevAuthorizedKeys removes all SSH keys tagged with the brev-cli
191+
// comment from the user's ~/.ssh/authorized_keys.
192+
func RemoveBrevAuthorizedKeys(u *user.User) error {
193+
authKeysPath := filepath.Join(u.HomeDir, ".ssh", "authorized_keys")
194+
195+
existing, err := os.ReadFile(authKeysPath) // #nosec G304
196+
if err != nil {
197+
if os.IsNotExist(err) {
198+
return nil
199+
}
200+
return fmt.Errorf("reading authorized_keys: %w", err)
201+
}
202+
203+
var kept []string
204+
for _, line := range strings.Split(string(existing), "\n") {
205+
if strings.Contains(line, BrevKeyComment) {
206+
continue
207+
}
208+
kept = append(kept, line)
209+
}
210+
211+
result := strings.Join(kept, "\n")
212+
if err := os.WriteFile(authKeysPath, []byte(result), 0o600); err != nil {
213+
return fmt.Errorf("writing authorized_keys: %w", err)
214+
}
215+
return nil
216+
}
217+
183218
// checkSSHDaemon prints a warning if neither "ssh" nor "sshd" systemd services
184219
// appear to be active. It never returns an error — it is best-effort.
185220
func checkSSHDaemon(t *terminal.Terminal) {

0 commit comments

Comments
 (0)