mirror of
https://github.com/juanfont/headscale.git
synced 2024-12-22 07:57:34 +00:00
1848 lines
37 KiB
Go
1848 lines
37 KiB
Go
package integration
|
|
|
|
import (
|
|
"cmp"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
tcmp "github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
|
"github.com/juanfont/headscale/hscontrol/policy"
|
|
"github.com/juanfont/headscale/integration/hsic"
|
|
"github.com/juanfont/headscale/integration/tsic"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
func executeAndUnmarshal[T any](headscale ControlServer, command []string, result T) error {
|
|
str, err := headscale.Execute(command)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = json.Unmarshal([]byte(str), result)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unmarshal: %s\n command err: %s", err, str)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Interface ensuring that we can sort structs from gRPC that
|
|
// have an ID field.
|
|
type GRPCSortable interface {
|
|
GetId() uint64
|
|
}
|
|
|
|
func sortWithID[T GRPCSortable](a, b T) int {
|
|
return cmp.Compare(a.GetId(), b.GetId())
|
|
}
|
|
|
|
func TestUserCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
"user1": 0,
|
|
"user2": 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
var listUsers []*v1.User
|
|
err = executeAndUnmarshal(headscale,
|
|
[]string{
|
|
"headscale",
|
|
"users",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listUsers,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
slices.SortFunc(listUsers, sortWithID)
|
|
result := []string{listUsers[0].GetName(), listUsers[1].GetName()}
|
|
|
|
assert.Equal(
|
|
t,
|
|
[]string{"user1", "user2"},
|
|
result,
|
|
)
|
|
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"users",
|
|
"rename",
|
|
"--output=json",
|
|
fmt.Sprintf("--identifier=%d", listUsers[1].GetId()),
|
|
"--new-name=newname",
|
|
},
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
var listAfterRenameUsers []*v1.User
|
|
err = executeAndUnmarshal(headscale,
|
|
[]string{
|
|
"headscale",
|
|
"users",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listAfterRenameUsers,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
slices.SortFunc(listUsers, sortWithID)
|
|
result = []string{listAfterRenameUsers[0].GetName(), listAfterRenameUsers[1].GetName()}
|
|
|
|
assert.Equal(
|
|
t,
|
|
[]string{"user1", "newname"},
|
|
result,
|
|
)
|
|
|
|
var listByUsername []*v1.User
|
|
err = executeAndUnmarshal(headscale,
|
|
[]string{
|
|
"headscale",
|
|
"users",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
"--name=user1",
|
|
},
|
|
&listByUsername,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
slices.SortFunc(listByUsername, sortWithID)
|
|
want := []*v1.User{
|
|
{
|
|
Id: 1,
|
|
Name: "user1",
|
|
},
|
|
}
|
|
|
|
if diff := tcmp.Diff(want, listByUsername, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
|
|
t.Errorf("unexpected users (-want +got):\n%s", diff)
|
|
}
|
|
|
|
var listByID []*v1.User
|
|
err = executeAndUnmarshal(headscale,
|
|
[]string{
|
|
"headscale",
|
|
"users",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
"--identifier=1",
|
|
},
|
|
&listByID,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
slices.SortFunc(listByID, sortWithID)
|
|
want = []*v1.User{
|
|
{
|
|
Id: 1,
|
|
Name: "user1",
|
|
},
|
|
}
|
|
|
|
if diff := tcmp.Diff(want, listByID, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
|
|
t.Errorf("unexpected users (-want +got):\n%s", diff)
|
|
}
|
|
|
|
deleteResult, err := headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"users",
|
|
"destroy",
|
|
"--force",
|
|
// Delete "user1"
|
|
"--identifier=1",
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
assert.Contains(t, deleteResult, "User destroyed")
|
|
|
|
var listAfterIDDelete []*v1.User
|
|
err = executeAndUnmarshal(headscale,
|
|
[]string{
|
|
"headscale",
|
|
"users",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listAfterIDDelete,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
slices.SortFunc(listAfterIDDelete, sortWithID)
|
|
want = []*v1.User{
|
|
{
|
|
Id: 2,
|
|
Name: "newname",
|
|
},
|
|
}
|
|
|
|
if diff := tcmp.Diff(want, listAfterIDDelete, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
|
|
t.Errorf("unexpected users (-want +got):\n%s", diff)
|
|
}
|
|
|
|
deleteResult, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"users",
|
|
"destroy",
|
|
"--force",
|
|
"--name=newname",
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
assert.Contains(t, deleteResult, "User destroyed")
|
|
|
|
var listAfterNameDelete []v1.User
|
|
err = executeAndUnmarshal(headscale,
|
|
[]string{
|
|
"headscale",
|
|
"users",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listAfterNameDelete,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
require.Len(t, listAfterNameDelete, 0)
|
|
}
|
|
|
|
func TestPreAuthKeyCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
user := "preauthkeyspace"
|
|
count := 3
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
user: 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipak"))
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
keys := make([]*v1.PreAuthKey, count)
|
|
assertNoErr(t, err)
|
|
|
|
for index := 0; index < count; index++ {
|
|
var preAuthKey v1.PreAuthKey
|
|
err := executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"preauthkeys",
|
|
"--user",
|
|
user,
|
|
"create",
|
|
"--reusable",
|
|
"--expiration",
|
|
"24h",
|
|
"--output",
|
|
"json",
|
|
"--tags",
|
|
"tag:test1,tag:test2",
|
|
},
|
|
&preAuthKey,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
keys[index] = &preAuthKey
|
|
}
|
|
|
|
assert.Len(t, keys, 3)
|
|
|
|
var listedPreAuthKeys []v1.PreAuthKey
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"preauthkeys",
|
|
"--user",
|
|
user,
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listedPreAuthKeys,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
// There is one key created by "scenario.CreateHeadscaleEnv"
|
|
assert.Len(t, listedPreAuthKeys, 4)
|
|
|
|
assert.Equal(
|
|
t,
|
|
[]string{keys[0].GetId(), keys[1].GetId(), keys[2].GetId()},
|
|
[]string{
|
|
listedPreAuthKeys[1].GetId(),
|
|
listedPreAuthKeys[2].GetId(),
|
|
listedPreAuthKeys[3].GetId(),
|
|
},
|
|
)
|
|
|
|
assert.NotEmpty(t, listedPreAuthKeys[1].GetKey())
|
|
assert.NotEmpty(t, listedPreAuthKeys[2].GetKey())
|
|
assert.NotEmpty(t, listedPreAuthKeys[3].GetKey())
|
|
|
|
assert.True(t, listedPreAuthKeys[1].GetExpiration().AsTime().After(time.Now()))
|
|
assert.True(t, listedPreAuthKeys[2].GetExpiration().AsTime().After(time.Now()))
|
|
assert.True(t, listedPreAuthKeys[3].GetExpiration().AsTime().After(time.Now()))
|
|
|
|
assert.True(
|
|
t,
|
|
listedPreAuthKeys[1].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
|
|
)
|
|
assert.True(
|
|
t,
|
|
listedPreAuthKeys[2].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
|
|
)
|
|
assert.True(
|
|
t,
|
|
listedPreAuthKeys[3].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
|
|
)
|
|
|
|
for index := range listedPreAuthKeys {
|
|
if index == 0 {
|
|
continue
|
|
}
|
|
|
|
assert.Equal(t, listedPreAuthKeys[index].GetAclTags(), []string{"tag:test1", "tag:test2"})
|
|
}
|
|
|
|
// Test key expiry
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"preauthkeys",
|
|
"--user",
|
|
user,
|
|
"expire",
|
|
listedPreAuthKeys[1].GetKey(),
|
|
},
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
var listedPreAuthKeysAfterExpire []v1.PreAuthKey
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"preauthkeys",
|
|
"--user",
|
|
user,
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listedPreAuthKeysAfterExpire,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
assert.True(t, listedPreAuthKeysAfterExpire[1].GetExpiration().AsTime().Before(time.Now()))
|
|
assert.True(t, listedPreAuthKeysAfterExpire[2].GetExpiration().AsTime().After(time.Now()))
|
|
assert.True(t, listedPreAuthKeysAfterExpire[3].GetExpiration().AsTime().After(time.Now()))
|
|
}
|
|
|
|
func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
user := "pre-auth-key-without-exp-user"
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
user: 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipaknaexp"))
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
var preAuthKey v1.PreAuthKey
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"preauthkeys",
|
|
"--user",
|
|
user,
|
|
"create",
|
|
"--reusable",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&preAuthKey,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
var listedPreAuthKeys []v1.PreAuthKey
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"preauthkeys",
|
|
"--user",
|
|
user,
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listedPreAuthKeys,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
// There is one key created by "scenario.CreateHeadscaleEnv"
|
|
assert.Len(t, listedPreAuthKeys, 2)
|
|
|
|
assert.True(t, listedPreAuthKeys[1].GetExpiration().AsTime().After(time.Now()))
|
|
assert.True(
|
|
t,
|
|
listedPreAuthKeys[1].GetExpiration().AsTime().Before(time.Now().Add(time.Minute*70)),
|
|
)
|
|
}
|
|
|
|
func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
user := "pre-auth-key-reus-ephm-user"
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
user: 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipakresueeph"))
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
var preAuthReusableKey v1.PreAuthKey
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"preauthkeys",
|
|
"--user",
|
|
user,
|
|
"create",
|
|
"--reusable=true",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&preAuthReusableKey,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
var preAuthEphemeralKey v1.PreAuthKey
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"preauthkeys",
|
|
"--user",
|
|
user,
|
|
"create",
|
|
"--ephemeral=true",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&preAuthEphemeralKey,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
assert.True(t, preAuthEphemeralKey.GetEphemeral())
|
|
assert.False(t, preAuthEphemeralKey.GetReusable())
|
|
|
|
var listedPreAuthKeys []v1.PreAuthKey
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"preauthkeys",
|
|
"--user",
|
|
user,
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listedPreAuthKeys,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
// There is one key created by "scenario.CreateHeadscaleEnv"
|
|
assert.Len(t, listedPreAuthKeys, 3)
|
|
}
|
|
|
|
func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
user1 := "user1"
|
|
user2 := "user2"
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
user1: 1,
|
|
user2: 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(
|
|
spec,
|
|
[]tsic.Option{},
|
|
hsic.WithTestName("clipak"),
|
|
hsic.WithEmbeddedDERPServerOnly(),
|
|
hsic.WithTLS(),
|
|
hsic.WithHostnameAsServerURL(),
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
var user2Key v1.PreAuthKey
|
|
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"preauthkeys",
|
|
"--user",
|
|
user2,
|
|
"create",
|
|
"--reusable",
|
|
"--expiration",
|
|
"24h",
|
|
"--output",
|
|
"json",
|
|
"--tags",
|
|
"tag:test1,tag:test2",
|
|
},
|
|
&user2Key,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
allClients, err := scenario.ListTailscaleClients()
|
|
assertNoErrListClients(t, err)
|
|
|
|
assert.Len(t, allClients, 1)
|
|
|
|
client := allClients[0]
|
|
|
|
// Log out from user1
|
|
err = client.Logout()
|
|
assertNoErr(t, err)
|
|
|
|
err = scenario.WaitForTailscaleLogout()
|
|
assertNoErr(t, err)
|
|
|
|
status, err := client.Status()
|
|
assertNoErr(t, err)
|
|
if status.BackendState == "Starting" || status.BackendState == "Running" {
|
|
t.Fatalf("expected node to be logged out, backend state: %s", status.BackendState)
|
|
}
|
|
|
|
err = client.Login(headscale.GetEndpoint(), user2Key.GetKey())
|
|
assertNoErr(t, err)
|
|
|
|
status, err = client.Status()
|
|
assertNoErr(t, err)
|
|
if status.BackendState != "Running" {
|
|
t.Fatalf("expected node to be logged in, backend state: %s", status.BackendState)
|
|
}
|
|
|
|
if status.Self.UserID.String() != "userid:2" {
|
|
t.Fatalf("expected node to be logged in as userid:2, got: %s", status.Self.UserID.String())
|
|
}
|
|
|
|
var listNodes []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listNodes,
|
|
)
|
|
assert.Nil(t, err)
|
|
assert.Len(t, listNodes, 1)
|
|
|
|
assert.Equal(t, "user2", listNodes[0].GetUser().GetName())
|
|
}
|
|
|
|
func TestApiKeyCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
count := 5
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
"user1": 0,
|
|
"user2": 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
keys := make([]string, count)
|
|
|
|
for idx := 0; idx < count; idx++ {
|
|
apiResult, err := headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"apikeys",
|
|
"create",
|
|
"--expiration",
|
|
"24h",
|
|
"--output",
|
|
"json",
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
assert.NotEmpty(t, apiResult)
|
|
|
|
keys[idx] = apiResult
|
|
}
|
|
|
|
assert.Len(t, keys, 5)
|
|
|
|
var listedAPIKeys []v1.ApiKey
|
|
err = executeAndUnmarshal(headscale,
|
|
[]string{
|
|
"headscale",
|
|
"apikeys",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listedAPIKeys,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, listedAPIKeys, 5)
|
|
|
|
assert.Equal(t, uint64(1), listedAPIKeys[0].GetId())
|
|
assert.Equal(t, uint64(2), listedAPIKeys[1].GetId())
|
|
assert.Equal(t, uint64(3), listedAPIKeys[2].GetId())
|
|
assert.Equal(t, uint64(4), listedAPIKeys[3].GetId())
|
|
assert.Equal(t, uint64(5), listedAPIKeys[4].GetId())
|
|
|
|
assert.NotEmpty(t, listedAPIKeys[0].GetPrefix())
|
|
assert.NotEmpty(t, listedAPIKeys[1].GetPrefix())
|
|
assert.NotEmpty(t, listedAPIKeys[2].GetPrefix())
|
|
assert.NotEmpty(t, listedAPIKeys[3].GetPrefix())
|
|
assert.NotEmpty(t, listedAPIKeys[4].GetPrefix())
|
|
|
|
assert.True(t, listedAPIKeys[0].GetExpiration().AsTime().After(time.Now()))
|
|
assert.True(t, listedAPIKeys[1].GetExpiration().AsTime().After(time.Now()))
|
|
assert.True(t, listedAPIKeys[2].GetExpiration().AsTime().After(time.Now()))
|
|
assert.True(t, listedAPIKeys[3].GetExpiration().AsTime().After(time.Now()))
|
|
assert.True(t, listedAPIKeys[4].GetExpiration().AsTime().After(time.Now()))
|
|
|
|
assert.True(
|
|
t,
|
|
listedAPIKeys[0].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
|
|
)
|
|
assert.True(
|
|
t,
|
|
listedAPIKeys[1].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
|
|
)
|
|
assert.True(
|
|
t,
|
|
listedAPIKeys[2].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
|
|
)
|
|
assert.True(
|
|
t,
|
|
listedAPIKeys[3].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
|
|
)
|
|
assert.True(
|
|
t,
|
|
listedAPIKeys[4].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
|
|
)
|
|
|
|
expiredPrefixes := make(map[string]bool)
|
|
|
|
// Expire three keys
|
|
for idx := 0; idx < 3; idx++ {
|
|
_, err := headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"apikeys",
|
|
"expire",
|
|
"--prefix",
|
|
listedAPIKeys[idx].GetPrefix(),
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
expiredPrefixes[listedAPIKeys[idx].GetPrefix()] = true
|
|
}
|
|
|
|
var listedAfterExpireAPIKeys []v1.ApiKey
|
|
err = executeAndUnmarshal(headscale,
|
|
[]string{
|
|
"headscale",
|
|
"apikeys",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listedAfterExpireAPIKeys,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
for index := range listedAfterExpireAPIKeys {
|
|
if _, ok := expiredPrefixes[listedAfterExpireAPIKeys[index].GetPrefix()]; ok {
|
|
// Expired
|
|
assert.True(
|
|
t,
|
|
listedAfterExpireAPIKeys[index].GetExpiration().AsTime().Before(time.Now()),
|
|
)
|
|
} else {
|
|
// Not expired
|
|
assert.False(
|
|
t,
|
|
listedAfterExpireAPIKeys[index].GetExpiration().AsTime().Before(time.Now()),
|
|
)
|
|
}
|
|
}
|
|
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"apikeys",
|
|
"delete",
|
|
"--prefix",
|
|
listedAPIKeys[0].GetPrefix(),
|
|
})
|
|
assert.Nil(t, err)
|
|
|
|
var listedAPIKeysAfterDelete []v1.ApiKey
|
|
err = executeAndUnmarshal(headscale,
|
|
[]string{
|
|
"headscale",
|
|
"apikeys",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listedAPIKeysAfterDelete,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, listedAPIKeysAfterDelete, 4)
|
|
}
|
|
|
|
func TestNodeTagCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
"user1": 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
machineKeys := []string{
|
|
"mkey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
|
"mkey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c",
|
|
}
|
|
nodes := make([]*v1.Node, len(machineKeys))
|
|
assert.Nil(t, err)
|
|
|
|
for index, machineKey := range machineKeys {
|
|
_, err := headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"debug",
|
|
"create-node",
|
|
"--name",
|
|
fmt.Sprintf("node-%d", index+1),
|
|
"--user",
|
|
"user1",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
var node v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"--user",
|
|
"user1",
|
|
"register",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
&node,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
nodes[index] = &node
|
|
}
|
|
assert.Len(t, nodes, len(machineKeys))
|
|
|
|
var node v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"tag",
|
|
"-i", "1",
|
|
"-t", "tag:test",
|
|
"--output", "json",
|
|
},
|
|
&node,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, []string{"tag:test"}, node.GetForcedTags())
|
|
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"tag",
|
|
"-i", "2",
|
|
"-t", "wrong-tag",
|
|
"--output", "json",
|
|
},
|
|
)
|
|
assert.ErrorContains(t, err, "tag must start with the string 'tag:'")
|
|
|
|
// Test list all nodes after added seconds
|
|
resultMachines := make([]*v1.Node, len(machineKeys))
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--output", "json",
|
|
},
|
|
&resultMachines,
|
|
)
|
|
assert.Nil(t, err)
|
|
found := false
|
|
for _, node := range resultMachines {
|
|
if node.GetForcedTags() != nil {
|
|
for _, tag := range node.GetForcedTags() {
|
|
if tag == "tag:test" {
|
|
found = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert.Equal(
|
|
t,
|
|
true,
|
|
found,
|
|
"should find a node with the tag 'tag:test' in the list of nodes",
|
|
)
|
|
}
|
|
|
|
func TestNodeAdvertiseTagCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
policy *policy.ACLPolicy
|
|
wantTag bool
|
|
}{
|
|
{
|
|
name: "no-policy",
|
|
wantTag: false,
|
|
},
|
|
{
|
|
name: "with-policy",
|
|
policy: &policy.ACLPolicy{
|
|
ACLs: []policy.ACL{
|
|
{
|
|
Action: "accept",
|
|
Sources: []string{"*"},
|
|
Destinations: []string{"*:*"},
|
|
},
|
|
},
|
|
TagOwners: map[string][]string{
|
|
"tag:test": {"user1"},
|
|
},
|
|
},
|
|
wantTag: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
// defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
"user1": 1,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec,
|
|
[]tsic.Option{tsic.WithTags([]string{"tag:test"})},
|
|
hsic.WithTestName("cliadvtags"),
|
|
hsic.WithACLPolicy(tt.policy),
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
// Test list all nodes after added seconds
|
|
resultMachines := make([]*v1.Node, spec["user1"])
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--tags",
|
|
"--output", "json",
|
|
},
|
|
&resultMachines,
|
|
)
|
|
assert.Nil(t, err)
|
|
found := false
|
|
for _, node := range resultMachines {
|
|
if tags := node.GetValidTags(); tags != nil {
|
|
found = slices.Contains(tags, "tag:test")
|
|
}
|
|
}
|
|
assert.Equalf(
|
|
t,
|
|
tt.wantTag,
|
|
found,
|
|
"'tag:test' found(%t) is the list of nodes, expected %t", found, tt.wantTag,
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNodeCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
"node-user": 0,
|
|
"other-user": 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
// Pregenerated machine keys
|
|
machineKeys := []string{
|
|
"mkey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
|
"mkey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c",
|
|
"mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
|
|
"mkey:8bc13285cee598acf76b1824a6f4490f7f2e3751b201e28aeb3b07fe81d5b4a1",
|
|
"mkey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
|
|
}
|
|
nodes := make([]*v1.Node, len(machineKeys))
|
|
assert.Nil(t, err)
|
|
|
|
for index, machineKey := range machineKeys {
|
|
_, err := headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"debug",
|
|
"create-node",
|
|
"--name",
|
|
fmt.Sprintf("node-%d", index+1),
|
|
"--user",
|
|
"node-user",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
var node v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"--user",
|
|
"node-user",
|
|
"register",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
&node,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
nodes[index] = &node
|
|
}
|
|
|
|
assert.Len(t, nodes, len(machineKeys))
|
|
|
|
// Test list all nodes after added seconds
|
|
var listAll []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listAll,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, listAll, 5)
|
|
|
|
assert.Equal(t, uint64(1), listAll[0].GetId())
|
|
assert.Equal(t, uint64(2), listAll[1].GetId())
|
|
assert.Equal(t, uint64(3), listAll[2].GetId())
|
|
assert.Equal(t, uint64(4), listAll[3].GetId())
|
|
assert.Equal(t, uint64(5), listAll[4].GetId())
|
|
|
|
assert.Equal(t, "node-1", listAll[0].GetName())
|
|
assert.Equal(t, "node-2", listAll[1].GetName())
|
|
assert.Equal(t, "node-3", listAll[2].GetName())
|
|
assert.Equal(t, "node-4", listAll[3].GetName())
|
|
assert.Equal(t, "node-5", listAll[4].GetName())
|
|
|
|
otherUserMachineKeys := []string{
|
|
"mkey:b5b444774186d4217adcec407563a1223929465ee2c68a4da13af0d0185b4f8e",
|
|
"mkey:dc721977ac7415aafa87f7d4574cbe07c6b171834a6d37375782bdc1fb6b3584",
|
|
}
|
|
otherUserMachines := make([]*v1.Node, len(otherUserMachineKeys))
|
|
assert.Nil(t, err)
|
|
|
|
for index, machineKey := range otherUserMachineKeys {
|
|
_, err := headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"debug",
|
|
"create-node",
|
|
"--name",
|
|
fmt.Sprintf("otherUser-node-%d", index+1),
|
|
"--user",
|
|
"other-user",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
var node v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"--user",
|
|
"other-user",
|
|
"register",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
&node,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
otherUserMachines[index] = &node
|
|
}
|
|
|
|
assert.Len(t, otherUserMachines, len(otherUserMachineKeys))
|
|
|
|
// Test list all nodes after added otherUser
|
|
var listAllWithotherUser []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listAllWithotherUser,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
// All nodes, nodes + otherUser
|
|
assert.Len(t, listAllWithotherUser, 7)
|
|
|
|
assert.Equal(t, uint64(6), listAllWithotherUser[5].GetId())
|
|
assert.Equal(t, uint64(7), listAllWithotherUser[6].GetId())
|
|
|
|
assert.Equal(t, "otherUser-node-1", listAllWithotherUser[5].GetName())
|
|
assert.Equal(t, "otherUser-node-2", listAllWithotherUser[6].GetName())
|
|
|
|
// Test list all nodes after added otherUser
|
|
var listOnlyotherUserMachineUser []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--user",
|
|
"other-user",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listOnlyotherUserMachineUser,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, listOnlyotherUserMachineUser, 2)
|
|
|
|
assert.Equal(t, uint64(6), listOnlyotherUserMachineUser[0].GetId())
|
|
assert.Equal(t, uint64(7), listOnlyotherUserMachineUser[1].GetId())
|
|
|
|
assert.Equal(
|
|
t,
|
|
"otherUser-node-1",
|
|
listOnlyotherUserMachineUser[0].GetName(),
|
|
)
|
|
assert.Equal(
|
|
t,
|
|
"otherUser-node-2",
|
|
listOnlyotherUserMachineUser[1].GetName(),
|
|
)
|
|
|
|
// Delete a nodes
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"delete",
|
|
"--identifier",
|
|
// Delete the last added machine
|
|
"4",
|
|
"--output",
|
|
"json",
|
|
"--force",
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
// Test: list main user after node is deleted
|
|
var listOnlyMachineUserAfterDelete []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--user",
|
|
"node-user",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listOnlyMachineUserAfterDelete,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, listOnlyMachineUserAfterDelete, 4)
|
|
}
|
|
|
|
func TestNodeExpireCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
"node-expire-user": 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
// Pregenerated machine keys
|
|
machineKeys := []string{
|
|
"mkey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
|
"mkey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c",
|
|
"mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
|
|
"mkey:8bc13285cee598acf76b1824a6f4490f7f2e3751b201e28aeb3b07fe81d5b4a1",
|
|
"mkey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
|
|
}
|
|
nodes := make([]*v1.Node, len(machineKeys))
|
|
|
|
for index, machineKey := range machineKeys {
|
|
_, err := headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"debug",
|
|
"create-node",
|
|
"--name",
|
|
fmt.Sprintf("node-%d", index+1),
|
|
"--user",
|
|
"node-expire-user",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
var node v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"--user",
|
|
"node-expire-user",
|
|
"register",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
&node,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
nodes[index] = &node
|
|
}
|
|
|
|
assert.Len(t, nodes, len(machineKeys))
|
|
|
|
var listAll []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listAll,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, listAll, 5)
|
|
|
|
assert.True(t, listAll[0].GetExpiry().AsTime().IsZero())
|
|
assert.True(t, listAll[1].GetExpiry().AsTime().IsZero())
|
|
assert.True(t, listAll[2].GetExpiry().AsTime().IsZero())
|
|
assert.True(t, listAll[3].GetExpiry().AsTime().IsZero())
|
|
assert.True(t, listAll[4].GetExpiry().AsTime().IsZero())
|
|
|
|
for idx := 0; idx < 3; idx++ {
|
|
_, err := headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"expire",
|
|
"--identifier",
|
|
fmt.Sprintf("%d", listAll[idx].GetId()),
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
var listAllAfterExpiry []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listAllAfterExpiry,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, listAllAfterExpiry, 5)
|
|
|
|
assert.True(t, listAllAfterExpiry[0].GetExpiry().AsTime().Before(time.Now()))
|
|
assert.True(t, listAllAfterExpiry[1].GetExpiry().AsTime().Before(time.Now()))
|
|
assert.True(t, listAllAfterExpiry[2].GetExpiry().AsTime().Before(time.Now()))
|
|
assert.True(t, listAllAfterExpiry[3].GetExpiry().AsTime().IsZero())
|
|
assert.True(t, listAllAfterExpiry[4].GetExpiry().AsTime().IsZero())
|
|
}
|
|
|
|
func TestNodeRenameCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
"node-rename-command": 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
// Pregenerated machine keys
|
|
machineKeys := []string{
|
|
"mkey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
|
|
"mkey:8bc13285cee598acf76b1824a6f4490f7f2e3751b201e28aeb3b07fe81d5b4a1",
|
|
"mkey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507",
|
|
"mkey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c",
|
|
"mkey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
|
|
}
|
|
nodes := make([]*v1.Node, len(machineKeys))
|
|
assert.Nil(t, err)
|
|
|
|
for index, machineKey := range machineKeys {
|
|
_, err := headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"debug",
|
|
"create-node",
|
|
"--name",
|
|
fmt.Sprintf("node-%d", index+1),
|
|
"--user",
|
|
"node-rename-command",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
var node v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"--user",
|
|
"node-rename-command",
|
|
"register",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
&node,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
nodes[index] = &node
|
|
}
|
|
|
|
assert.Len(t, nodes, len(machineKeys))
|
|
|
|
var listAll []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listAll,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, listAll, 5)
|
|
|
|
assert.Contains(t, listAll[0].GetGivenName(), "node-1")
|
|
assert.Contains(t, listAll[1].GetGivenName(), "node-2")
|
|
assert.Contains(t, listAll[2].GetGivenName(), "node-3")
|
|
assert.Contains(t, listAll[3].GetGivenName(), "node-4")
|
|
assert.Contains(t, listAll[4].GetGivenName(), "node-5")
|
|
|
|
for idx := 0; idx < 3; idx++ {
|
|
res, err := headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"rename",
|
|
"--identifier",
|
|
fmt.Sprintf("%d", listAll[idx].GetId()),
|
|
fmt.Sprintf("newnode-%d", idx+1),
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Contains(t, res, "Node renamed")
|
|
}
|
|
|
|
var listAllAfterRename []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listAllAfterRename,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, listAllAfterRename, 5)
|
|
|
|
assert.Equal(t, "newnode-1", listAllAfterRename[0].GetGivenName())
|
|
assert.Equal(t, "newnode-2", listAllAfterRename[1].GetGivenName())
|
|
assert.Equal(t, "newnode-3", listAllAfterRename[2].GetGivenName())
|
|
assert.Contains(t, listAllAfterRename[3].GetGivenName(), "node-4")
|
|
assert.Contains(t, listAllAfterRename[4].GetGivenName(), "node-5")
|
|
|
|
// Test failure for too long names
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"rename",
|
|
"--identifier",
|
|
fmt.Sprintf("%d", listAll[4].GetId()),
|
|
strings.Repeat("t", 64),
|
|
},
|
|
)
|
|
assert.ErrorContains(t, err, "not be over 63 chars")
|
|
|
|
var listAllAfterRenameAttempt []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&listAllAfterRenameAttempt,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, listAllAfterRenameAttempt, 5)
|
|
|
|
assert.Equal(t, "newnode-1", listAllAfterRenameAttempt[0].GetGivenName())
|
|
assert.Equal(t, "newnode-2", listAllAfterRenameAttempt[1].GetGivenName())
|
|
assert.Equal(t, "newnode-3", listAllAfterRenameAttempt[2].GetGivenName())
|
|
assert.Contains(t, listAllAfterRenameAttempt[3].GetGivenName(), "node-4")
|
|
assert.Contains(t, listAllAfterRenameAttempt[4].GetGivenName(), "node-5")
|
|
}
|
|
|
|
func TestNodeMoveCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
"old-user": 0,
|
|
"new-user": 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
// Randomly generated node key
|
|
machineKey := "mkey:688411b767663479632d44140f08a9fde87383adc7cdeb518f62ce28a17ef0aa"
|
|
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"debug",
|
|
"create-node",
|
|
"--name",
|
|
"nomad-node",
|
|
"--user",
|
|
"old-user",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
var node v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"--user",
|
|
"old-user",
|
|
"register",
|
|
"--key",
|
|
machineKey,
|
|
"--output",
|
|
"json",
|
|
},
|
|
&node,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, uint64(1), node.GetId())
|
|
assert.Equal(t, "nomad-node", node.GetName())
|
|
assert.Equal(t, node.GetUser().GetName(), "old-user")
|
|
|
|
nodeID := fmt.Sprintf("%d", node.GetId())
|
|
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"move",
|
|
"--identifier",
|
|
nodeID,
|
|
"--user",
|
|
"new-user",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&node,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, node.GetUser().GetName(), "new-user")
|
|
|
|
var allNodes []v1.Node
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"list",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&allNodes,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Len(t, allNodes, 1)
|
|
|
|
assert.Equal(t, allNodes[0].GetId(), node.GetId())
|
|
assert.Equal(t, allNodes[0].GetUser(), node.GetUser())
|
|
assert.Equal(t, allNodes[0].GetUser().GetName(), "new-user")
|
|
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"move",
|
|
"--identifier",
|
|
nodeID,
|
|
"--user",
|
|
"non-existing-user",
|
|
"--output",
|
|
"json",
|
|
},
|
|
)
|
|
assert.ErrorContains(
|
|
t,
|
|
err,
|
|
"user not found",
|
|
)
|
|
assert.Equal(t, node.GetUser().GetName(), "new-user")
|
|
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"move",
|
|
"--identifier",
|
|
nodeID,
|
|
"--user",
|
|
"old-user",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&node,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, node.GetUser().GetName(), "old-user")
|
|
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"nodes",
|
|
"move",
|
|
"--identifier",
|
|
nodeID,
|
|
"--user",
|
|
"old-user",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&node,
|
|
)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, node.GetUser().GetName(), "old-user")
|
|
}
|
|
|
|
func TestPolicyCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
"policy-user": 0,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(
|
|
spec,
|
|
[]tsic.Option{},
|
|
hsic.WithTestName("clins"),
|
|
hsic.WithConfigEnv(map[string]string{
|
|
"HEADSCALE_POLICY_MODE": "database",
|
|
}),
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
p := policy.ACLPolicy{
|
|
ACLs: []policy.ACL{
|
|
{
|
|
Action: "accept",
|
|
Sources: []string{"*"},
|
|
Destinations: []string{"*:*"},
|
|
},
|
|
},
|
|
TagOwners: map[string][]string{
|
|
"tag:exists": {"policy-user"},
|
|
},
|
|
}
|
|
|
|
pBytes, _ := json.Marshal(p)
|
|
|
|
policyFilePath := "/etc/headscale/policy.json"
|
|
|
|
err = headscale.WriteFile(policyFilePath, pBytes)
|
|
assertNoErr(t, err)
|
|
|
|
// No policy is present at this time.
|
|
// Add a new policy from a file.
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"policy",
|
|
"set",
|
|
"-f",
|
|
policyFilePath,
|
|
},
|
|
)
|
|
|
|
assertNoErr(t, err)
|
|
|
|
// Get the current policy and check
|
|
// if it is the same as the one we set.
|
|
var output *policy.ACLPolicy
|
|
err = executeAndUnmarshal(
|
|
headscale,
|
|
[]string{
|
|
"headscale",
|
|
"policy",
|
|
"get",
|
|
"--output",
|
|
"json",
|
|
},
|
|
&output,
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
assert.Len(t, output.TagOwners, 1)
|
|
assert.Len(t, output.ACLs, 1)
|
|
assert.Equal(t, output.TagOwners["tag:exists"], []string{"policy-user"})
|
|
}
|
|
|
|
func TestPolicyBrokenConfigCommand(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
t.Parallel()
|
|
|
|
scenario, err := NewScenario(dockertestMaxWait())
|
|
assertNoErr(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
spec := map[string]int{
|
|
"policy-user": 1,
|
|
}
|
|
|
|
err = scenario.CreateHeadscaleEnv(
|
|
spec,
|
|
[]tsic.Option{},
|
|
hsic.WithTestName("clins"),
|
|
hsic.WithConfigEnv(map[string]string{
|
|
"HEADSCALE_POLICY_MODE": "database",
|
|
}),
|
|
)
|
|
assertNoErr(t, err)
|
|
|
|
headscale, err := scenario.Headscale()
|
|
assertNoErr(t, err)
|
|
|
|
p := policy.ACLPolicy{
|
|
ACLs: []policy.ACL{
|
|
{
|
|
// This is an unknown action, so it will return an error
|
|
// and the config will not be applied.
|
|
Action: "acccept",
|
|
Sources: []string{"*"},
|
|
Destinations: []string{"*:*"},
|
|
},
|
|
},
|
|
TagOwners: map[string][]string{
|
|
"tag:exists": {"policy-user"},
|
|
},
|
|
}
|
|
|
|
pBytes, _ := json.Marshal(p)
|
|
|
|
policyFilePath := "/etc/headscale/policy.json"
|
|
|
|
err = headscale.WriteFile(policyFilePath, pBytes)
|
|
assertNoErr(t, err)
|
|
|
|
// No policy is present at this time.
|
|
// Add a new policy from a file.
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"policy",
|
|
"set",
|
|
"-f",
|
|
policyFilePath,
|
|
},
|
|
)
|
|
assert.ErrorContains(t, err, "compiling filter rules: invalid action")
|
|
|
|
// The new policy was invalid, the old one should still be in place, which
|
|
// is none.
|
|
_, err = headscale.Execute(
|
|
[]string{
|
|
"headscale",
|
|
"policy",
|
|
"get",
|
|
"--output",
|
|
"json",
|
|
},
|
|
)
|
|
assert.ErrorContains(t, err, "acl policy not found")
|
|
}
|