Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ module github.com/brevdev/brev-cli
go 1.25.0

require (
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260820222245-1cfc91443320.1
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.12-20260820222245-1cfc91443320.1
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260827214152-35c65570f2a0.1
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.12-20260827214152-35c65570f2a0.1
connectrpc.com/connect v1.20.0
github.com/NVIDIA/go-nvml v0.13.0-1
github.com/alessio/shellescape v1.4.1
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260820222245-1cfc91443320.1 h1:PKIsaGilewnQUSHNUn+Ir4sagWne713vJS3Ys7h9vAY=
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260820222245-1cfc91443320.1/go.mod h1:r4xfuOy9bpAXm13ugDRO+JNmFVlXecGRuKtn1X7os/k=
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.12-20260820222245-1cfc91443320.1 h1:gmAgE9NC+BAovZIs9CNmjgExqM+Gox8AZ6ud3eVMxfA=
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.12-20260820222245-1cfc91443320.1/go.mod h1:N18pnR0HL6srurI7G19FpSEki71wA1u4e2c5zbfeTV8=
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260827214152-35c65570f2a0.1 h1:xzM4gdexDMGgTwdgrlUFHgedW+CSbdXaQzOimV5PbPU=
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260827214152-35c65570f2a0.1/go.mod h1:qMKDH/phd8XN/OWkJlSVhHJ/8P2w1dfE5zUQprRRp8c=
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.12-20260827214152-35c65570f2a0.1 h1:17qqLaEUl7Biv3eyy9owm5yrS/Zi9Zyxc01XSZIzVbU=
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.12-20260827214152-35c65570f2a0.1/go.mod h1:N18pnR0HL6srurI7G19FpSEki71wA1u4e2c5zbfeTV8=
buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.12-20220906235457-8b4922735da5.1 h1:Qk/4GJyWVWvWsfEFeX4T+k7KouZdRUxxUnIUwJ3hmZg=
buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.12-20220906235457-8b4922735da5.1/go.mod h1:SacJAYqnICCQAsBA46cSA/hxhqhxYkiYzseucf6/fhQ=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/brevdev/brev-cli/pkg/cmd/copy"
"github.com/brevdev/brev-cli/pkg/cmd/delete"
"github.com/brevdev/brev-cli/pkg/cmd/deregister"
"github.com/brevdev/brev-cli/pkg/cmd/disablessh"
"github.com/brevdev/brev-cli/pkg/cmd/enablessh"
"github.com/brevdev/brev-cli/pkg/cmd/envvars"
"github.com/brevdev/brev-cli/pkg/cmd/exec"
Expand Down Expand Up @@ -334,6 +335,7 @@ func createCmdTree(cmd *cobra.Command, t *terminal.Terminal, loginCmdStore *stor
cmd.AddCommand(deregister.NewCmdDeregister(t, externalNodeCmdStore))
cmd.AddCommand(upgrade.NewCmdUpgrade(t, noLoginCmdStore))
cmd.AddCommand(enablessh.NewCmdEnableSSH(t, externalNodeCmdStore))
cmd.AddCommand(disablessh.NewCmdDisableSSH(t, externalNodeCmdStore))
cmd.AddCommand(grantssh.NewCmdGrantSSH(t, externalNodeCmdStore))
cmd.AddCommand(revokessh.NewCmdRevokeSSH(t, externalNodeCmdStore))
cmd.AddCommand(runtasks.NewCmdRunTasks(t, noLoginCmdStore))
Expand Down
88 changes: 65 additions & 23 deletions pkg/cmd/deregister/deregister.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/brevdev/brev-cli/pkg/config"
"github.com/brevdev/brev-cli/pkg/entity"
"github.com/brevdev/brev-cli/pkg/externalnode"
"github.com/brevdev/brev-cli/pkg/sshcert"
"github.com/brevdev/brev-cli/pkg/sudo"
"github.com/brevdev/brev-cli/pkg/terminal"

Expand All @@ -26,18 +27,27 @@ type DeregisterStore interface {
GetAccessToken() (string, error)
}

type SSHKeyRemover interface {
type CertAuthorityRemover interface {
RemoveCertAuthority(u *user.User, nodeID, linuxUser string) (bool, error)
}

// LegacySSHKeyRemover removes Brev-managed per-user SSH keys (legacy nodes).
type LegacySSHKeyRemover interface {
RemoveBrevKeys(u *user.User) ([]string, error)
}

type brevSSHKeyRemover struct{}
type brevCertAuthorityRemover struct{}

func (brevCertAuthorityRemover) RemoveCertAuthority(u *user.User, nodeID, linuxUser string) (bool, error) {
removed, err := sshcert.RemoveCertAuthorityLine(u.HomeDir, nodeID, linuxUser)
return removed, breverrors.WrapAndTrace(err)
}

type legacyKeyRemover struct{}

func (brevSSHKeyRemover) RemoveBrevKeys(u *user.User) ([]string, error) {
func (legacyKeyRemover) RemoveBrevKeys(u *user.User) ([]string, error) {
removed, err := register.RemoveBrevAuthorizedKeys(u)
if err != nil {
return nil, fmt.Errorf("removing brev authorized keys: %w", err)
}
return removed, nil
return removed, breverrors.WrapAndTrace(err)
}

// deregisterDeps bundles the side-effecting dependencies of runDeregister so
Expand All @@ -50,7 +60,10 @@ type deregisterDeps struct {
netbird register.NetBirdManager
nodeClients externalnode.NodeClientFactory
registrationStore register.RegistrationStore
sshKeys SSHKeyRemover
sshKeys CertAuthorityRemover
legacyKeys LegacySSHKeyRemover
// currentUser resolves the OS user for authorized_keys operations.
currentUser func() (*user.User, error)
}

func defaultDeregisterDeps() deregisterDeps {
Expand All @@ -61,8 +74,10 @@ func defaultDeregisterDeps() deregisterDeps {
gater: sudo.Default,
netbird: register.Netbird{},
nodeClients: register.DefaultNodeClientFactory{},
sshKeys: brevCertAuthorityRemover{},
legacyKeys: legacyKeyRemover{},
registrationStore: register.NewFileRegistrationStore(),
sshKeys: brevSSHKeyRemover{},
currentUser: user.Current,
}
}

Expand Down Expand Up @@ -176,7 +191,7 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,
if orgName == "" {
orgName = "(unknown)"
}
osUser, _ := user.Current()
osUser, _ := deps.currentUser()
linuxUser := "(unknown)"
if osUser != nil {
linuxUser = osUser.Username
Expand All @@ -197,7 +212,7 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,
t.Vprint("")
t.Vprint(t.Yellow(" This will:"))
t.Vprint(" 1. Remove this node from Brev")
t.Vprint(" 2. Remove Brev SSH keys from this machine (if any)")
t.Vprint(" 2. Remove any SSH data associated with this node")
t.Vprint(" 3. Uninstall the Brev tunnel")
t.Vprint(" 4. Delete local registration data")
t.Vprint("")
Expand All @@ -213,27 +228,27 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,
}
}

// a Brev cert-authority line for this node means certauth mode, otherwise legacy per-user keys
certAuth := false
if osUser != nil {
certAuth = sshcert.HasCertAuthorityLine(osUser.HomeDir, reg.ExternalNodeID)
}

t.Vprint(t.Yellow("[Step 1/4] Removing node from Brev..."))
if err := removeNodeFromBrev(ctx, t, s, deps, reg); err != nil {
return err
}
t.Vprint("")

t.Vprint(t.Yellow("[Step 2/4] Removing Brev SSH keys..."))
t.Vprint(t.Yellow("[Step 2/4] Removing any SSH data associated with this node..."))
if osUser == nil {
t.Vprintf(" %s\n", t.Yellow("Skipped: could not determine current user"))
} else {
removed, kerr := deps.sshKeys.RemoveBrevKeys(osUser)
switch {
case kerr != nil:
t.Vprintf(" %s\n", t.Yellow(fmt.Sprintf("Warning: failed to remove Brev SSH keys: %v", kerr)))
case len(removed) > 0:
t.Vprintf("%s Brev SSH keys removed from authorized_keys:\n", t.Green(" ✓"))
for _, key := range removed {
t.Vprintf(" - %s\n", key)
}
default:
t.Vprint(" No Brev SSH keys found in authorized_keys.")
linuxUsername := osUser.Username
if certAuth {
removeCertAuthorityStep(t, deps, osUser, reg.ExternalNodeID, linuxUsername)
} else {
removeLegacyKeysStep(t, deps, osUser)
}
}
t.Vprint("")
Expand All @@ -260,3 +275,30 @@ func runDeregister(ctx context.Context, t *terminal.Terminal, s DeregisterStore,

return nil
}

func removeCertAuthorityStep(t *terminal.Terminal, deps deregisterDeps, osUser *user.User, nodeID, linuxUser string) {
removed, cerr := deps.sshKeys.RemoveCertAuthority(osUser, nodeID, linuxUser)
switch {
case cerr != nil:
t.Vprintf(" %s\n", t.Yellow(fmt.Sprintf("Warning: failed to remove cert-authority: %v", cerr)))
case removed:
t.Vprintf("%s Certificate authority removed from authorized_keys.\n", t.Green(" ✓"))
default:
t.Vprint(" No certificate authority line found in authorized_keys.")
}
}

func removeLegacyKeysStep(t *terminal.Terminal, deps deregisterDeps, osUser *user.User) {
removed, kerr := deps.legacyKeys.RemoveBrevKeys(osUser)
switch {
case kerr != nil:
t.Vprintf(" %s\n", t.Yellow(fmt.Sprintf("Warning: failed to remove Brev SSH keys: %v", kerr)))
case len(removed) > 0:
t.Vprintf("%s Brev SSH keys removed from authorized_keys:\n", t.Green(" ✓"))
for _, key := range removed {
t.Vprintf(" - %s\n", key)
}
default:
t.Vprint(" No Brev SSH keys found in authorized_keys.")
}
}
Loading
Loading