Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove ephemeral on logout #1098

Merged
merged 5 commits into from
Jan 5, 2023
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Report if a machine is online in CLI more accurately [#1062](https://github.com/juanfont/headscale/pull/1062)
- Added config option for custom DNS records [#1035](https://github.com/juanfont/headscale/pull/1035)
- Expire nodes based on OIDC token expiry [#1067](https://github.com/juanfont/headscale/pull/1067)
- Remove ephemeral nodes on logout [#1098](https://github.com/juanfont/headscale/pull/1098)

## 0.17.1 (2022-12-05)

Expand Down
3 changes: 1 addition & 2 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,7 @@ func (h *Headscale) expireEphemeralNodesWorker() {

expiredFound := false
for _, machine := range machines {
if machine.AuthKey != nil && machine.LastSeen != nil &&
machine.AuthKey.Ephemeral &&
if machine.isEphemeral() && machine.LastSeen != nil &&
time.Now().
After(machine.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) {
expiredFound = true
Expand Down
2 changes: 1 addition & 1 deletion integration/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type ControlServer interface {
GetEndpoint() string
WaitForReady() error
CreateNamespace(namespace string) error
CreateAuthKey(namespace string) (*v1.PreAuthKey, error)
CreateAuthKey(namespace string, reusable bool, ephemeral bool) (*v1.PreAuthKey, error)
ListMachinesInNamespace(namespace string) ([]*v1.Machine, error)
GetCert() []byte
GetHostname() string
Expand Down
105 changes: 104 additions & 1 deletion integration/general_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) {
}

for namespaceName := range spec {
key, err := scenario.CreatePreAuthKey(namespaceName)
key, err := scenario.CreatePreAuthKey(namespaceName, true, false)
if err != nil {
t.Errorf("failed to create pre-auth key for namespace %s: %s", namespaceName, err)
}
Expand Down Expand Up @@ -197,6 +197,109 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) {
}
}

func TestEphemeral(t *testing.T) {
IntegrationSkip(t)
t.Parallel()

scenario, err := NewScenario()
if err != nil {
t.Errorf("failed to create scenario: %s", err)
}

spec := map[string]int{
"namespace1": len(TailscaleVersions),
"namespace2": len(TailscaleVersions),
}

headscale, err := scenario.Headscale(hsic.WithTestName("ephemeral"))
if err != nil {
t.Errorf("failed to create headscale environment: %s", err)
}

for namespaceName, clientCount := range spec {
err = scenario.CreateNamespace(namespaceName)
if err != nil {
t.Errorf("failed to create namespace %s: %s", namespaceName, err)
}

err = scenario.CreateTailscaleNodesInNamespace(namespaceName, "all", clientCount, []tsic.Option{}...)
if err != nil {
t.Errorf("failed to create tailscale nodes in namespace %s: %s", namespaceName, err)
}

key, err := scenario.CreatePreAuthKey(namespaceName, true, true)
if err != nil {
t.Errorf("failed to create pre-auth key for namespace %s: %s", namespaceName, err)
}

err = scenario.RunTailscaleUp(namespaceName, headscale.GetEndpoint(), key.GetKey())
if err != nil {
t.Errorf("failed to run tailscale up for namespace %s: %s", namespaceName, err)
}
}

err = scenario.WaitForTailscaleSync()
if err != nil {
t.Errorf("failed wait for tailscale clients to be in sync: %s", err)
}

allClients, err := scenario.ListTailscaleClients()
if err != nil {
t.Errorf("failed to get clients: %s", err)
}

allIps, err := scenario.ListTailscaleClientsIPs()
if err != nil {
t.Errorf("failed to get clients: %s", err)
}

success := 0
for _, client := range allClients {
for _, ip := range allIps {
err := client.Ping(ip.String())
if err != nil {
t.Errorf("failed to ping %s from %s: %s", ip, client.Hostname(), err)
} else {
success++
}
}
}

t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))

for _, client := range allClients {
err := client.Logout()
if err != nil {
t.Errorf("failed to logout client %s: %s", client.Hostname(), err)
}
}

scenario.WaitForTailscaleLogout()

t.Logf("all clients logged out")

for namespaceName := range spec {
machines, err := headscale.ListMachinesInNamespace(namespaceName)
if err != nil {
log.Error().
Err(err).
Str("namespace", namespaceName).
Msg("Error listing machines in namespace")

return
}

if len(machines) != 0 {
t.Errorf("expected no machines, got %d in namespace %s", len(machines), namespaceName)
}
}

err = scenario.Shutdown()
if err != nil {
t.Errorf("failed to tear down scenario: %s", err)
}
}

func TestPingAllByHostname(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
Expand Down
11 changes: 10 additions & 1 deletion integration/hsic/hsic.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,20 +316,29 @@ func (t *HeadscaleInContainer) CreateNamespace(

func (t *HeadscaleInContainer) CreateAuthKey(
namespace string,
reusable bool,
ephemeral bool,
) (*v1.PreAuthKey, error) {
command := []string{
"headscale",
"--namespace",
namespace,
"preauthkeys",
"create",
"--reusable",
"--expiration",
"24h",
"--output",
"json",
}

if reusable {
command = append(command, "--reusable")
}

if ephemeral {
command = append(command, "--ephemeral")
}

result, _, err := dockertestutil.ExecuteCommand(
t.container,
command,
Expand Down
6 changes: 3 additions & 3 deletions integration/scenario.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,9 @@ func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error) {
return headscale, nil
}

func (s *Scenario) CreatePreAuthKey(namespace string) (*v1.PreAuthKey, error) {
func (s *Scenario) CreatePreAuthKey(namespace string, reusable bool, ephemeral bool) (*v1.PreAuthKey, error) {
if headscale, err := s.Headscale(); err == nil {
key, err := headscale.CreateAuthKey(namespace)
key, err := headscale.CreateAuthKey(namespace, reusable, ephemeral)
if err != nil {
return nil, fmt.Errorf("failed to create namespace: %w", err)
}
Expand Down Expand Up @@ -368,7 +368,7 @@ func (s *Scenario) CreateHeadscaleEnv(
return err
}

key, err := s.CreatePreAuthKey(namespaceName)
key, err := s.CreatePreAuthKey(namespaceName, true, false)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions integration/scenario_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestHeadscale(t *testing.T) {
})

t.Run("create-auth-key", func(t *testing.T) {
_, err := scenario.CreatePreAuthKey(namespace)
_, err := scenario.CreatePreAuthKey(namespace, true, false)
if err != nil {
t.Errorf("failed to create preauthkey: %s", err)
}
Expand Down Expand Up @@ -166,7 +166,7 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) {
})

t.Run("join-headscale", func(t *testing.T) {
key, err := scenario.CreatePreAuthKey(namespace)
key, err := scenario.CreatePreAuthKey(namespace, true, false)
if err != nil {
t.Errorf("failed to create preauthkey: %s", err)
}
Expand Down
14 changes: 10 additions & 4 deletions machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ func (machine *Machine) isOnline() bool {
return machine.LastSeen.After(time.Now().Add(-keepAliveInterval))
}

// isEphemeral returns if the machine is registered as an Ephemeral node.
// https://tailscale.com/kb/1111/ephemeral-nodes/
func (machine *Machine) isEphemeral() bool {
return machine.AuthKey != nil && machine.AuthKey.Ephemeral
}

func containsAddresses(inputs []string, addrs []string) bool {
for _, addr := range addrs {
if containsStr(inputs, addr) {
Expand Down Expand Up @@ -386,7 +392,7 @@ func (h *Headscale) GetMachineByGivenName(namespace string, givenName string) (*
// GetMachineByID finds a Machine by ID and returns the Machine struct.
func (h *Headscale) GetMachineByID(id uint64) (*Machine, error) {
m := Machine{}
if result := h.db.Preload("Namespace").Find(&Machine{ID: id}).First(&m); result.Error != nil {
if result := h.db.Preload("AuthKey").Preload("Namespace").Find(&Machine{ID: id}).First(&m); result.Error != nil {
return nil, result.Error
}

Expand All @@ -398,7 +404,7 @@ func (h *Headscale) GetMachineByMachineKey(
machineKey key.MachinePublic,
) (*Machine, error) {
m := Machine{}
if result := h.db.Preload("Namespace").First(&m, "machine_key = ?", MachinePublicKeyStripPrefix(machineKey)); result.Error != nil {
if result := h.db.Preload("AuthKey").Preload("Namespace").First(&m, "machine_key = ?", MachinePublicKeyStripPrefix(machineKey)); result.Error != nil {
return nil, result.Error
}

Expand All @@ -410,7 +416,7 @@ func (h *Headscale) GetMachineByNodeKey(
nodeKey key.NodePublic,
) (*Machine, error) {
machine := Machine{}
if result := h.db.Preload("Namespace").First(&machine, "node_key = ?",
if result := h.db.Preload("AuthKey").Preload("Namespace").First(&machine, "node_key = ?",
NodePublicKeyStripPrefix(nodeKey)); result.Error != nil {
return nil, result.Error
}
Expand All @@ -423,7 +429,7 @@ func (h *Headscale) GetMachineByAnyKey(
machineKey key.MachinePublic, nodeKey key.NodePublic, oldNodeKey key.NodePublic,
) (*Machine, error) {
machine := Machine{}
if result := h.db.Preload("Namespace").First(&machine, "machine_key = ? OR node_key = ? OR node_key = ?",
if result := h.db.Preload("AuthKey").Preload("Namespace").First(&machine, "machine_key = ? OR node_key = ? OR node_key = ?",
MachinePublicKeyStripPrefix(machineKey),
NodePublicKeyStripPrefix(nodeKey),
NodePublicKeyStripPrefix(oldNodeKey)); result.Error != nil {
Expand Down
14 changes: 14 additions & 0 deletions protocol_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,20 @@ func (h *Headscale) handleMachineLogOutCommon(
Caller().
Err(err).
Msg("Failed to write response")

return
}

if machine.isEphemeral() {
err = h.HardDeleteMachine(&machine)
if err != nil {
log.Error().
Err(err).
Str("machine", machine.Hostname).
Msg("Cannot delete ephemeral machine from the database")
}

return
}

log.Info().
Expand Down