types/authkey: include user object in response (#2542)

* types/authkey: include user object, not string

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* make preauthkeys use id

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* integration: wire up user id for auth keys

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby 2025-04-30 12:45:08 +03:00 committed by GitHub
parent f1206328dc
commit 8f9fbf16f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 454 additions and 779 deletions

View file

@ -3,9 +3,12 @@ package integration
import (
"fmt"
"net/netip"
"strconv"
"testing"
"time"
"slices"
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/tsic"
"github.com/samber/lo"
@ -84,8 +87,11 @@ func TestAuthKeyLogoutAndReloginSameUser(t *testing.T) {
time.Sleep(5 * time.Minute)
}
userMap, err := headscale.MapUsers()
assertNoErr(t, err)
for _, userName := range spec.Users {
key, err := scenario.CreatePreAuthKey(userName, true, false)
key, err := scenario.CreatePreAuthKey(userMap[userName].GetId(), true, false)
if err != nil {
t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err)
}
@ -121,16 +127,7 @@ func TestAuthKeyLogoutAndReloginSameUser(t *testing.T) {
}
for _, ip := range ips {
found := false
for _, oldIP := range clientIPs[client] {
if ip == oldIP {
found = true
break
}
}
if !found {
if !slices.Contains(clientIPs[client], ip) {
t.Fatalf(
"IPs changed for client %s. Used to be %v now %v",
client.Hostname(),
@ -195,8 +192,11 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) {
t.Logf("all clients logged out")
userMap, err := headscale.MapUsers()
assertNoErr(t, err)
// Create a new authkey for user1, to be used for all clients
key, err := scenario.CreatePreAuthKey("user1", true, false)
key, err := scenario.CreatePreAuthKey(userMap["user1"].GetId(), true, false)
if err != nil {
t.Fatalf("failed to create pre-auth key for user1: %s", err)
}
@ -300,8 +300,11 @@ func TestAuthKeyLogoutAndReloginSameUserExpiredKey(t *testing.T) {
time.Sleep(5 * time.Minute)
}
userMap, err := headscale.MapUsers()
assertNoErr(t, err)
for _, userName := range spec.Users {
key, err := scenario.CreatePreAuthKey(userName, true, false)
key, err := scenario.CreatePreAuthKey(userMap[userName].GetId(), true, false)
if err != nil {
t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err)
}
@ -312,7 +315,7 @@ func TestAuthKeyLogoutAndReloginSameUserExpiredKey(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
userName,
strconv.FormatUint(userMap[userName].GetId(), 10),
"expire",
key.Key,
})

View file

@ -4,6 +4,7 @@ import (
"cmp"
"encoding/json"
"fmt"
"strconv"
"strings"
"testing"
"time"
@ -271,7 +272,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
user,
"1",
"create",
"--reusable",
"--expiration",
@ -297,7 +298,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
user,
"1",
"list",
"--output",
"json",
@ -311,8 +312,8 @@ func TestPreAuthKeyCommand(t *testing.T) {
assert.Equal(
t,
[]string{keys[0].GetId(), keys[1].GetId(), keys[2].GetId()},
[]string{
[]uint64{keys[0].GetId(), keys[1].GetId(), keys[2].GetId()},
[]uint64{
listedPreAuthKeys[1].GetId(),
listedPreAuthKeys[2].GetId(),
listedPreAuthKeys[3].GetId(),
@ -354,7 +355,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
user,
"1",
"expire",
listedPreAuthKeys[1].GetKey(),
},
@ -368,7 +369,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
user,
"1",
"list",
"--output",
"json",
@ -408,7 +409,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
user,
"1",
"create",
"--reusable",
"--output",
@ -425,7 +426,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
user,
"1",
"list",
"--output",
"json",
@ -470,7 +471,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
user,
"1",
"create",
"--reusable=true",
"--output",
@ -487,7 +488,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
user,
"1",
"create",
"--ephemeral=true",
"--output",
@ -507,7 +508,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
user,
"1",
"list",
"--output",
"json",
@ -547,7 +548,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
headscale, err := scenario.Headscale()
assertNoErr(t, err)
err = headscale.CreateUser(user2)
u2, err := headscale.CreateUser(user2)
assertNoErr(t, err)
var user2Key v1.PreAuthKey
@ -558,7 +559,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
user2,
strconv.FormatUint(u2.GetId(), 10),
"create",
"--reusable",
"--expiration",

View file

@ -18,10 +18,11 @@ type ControlServer interface {
GetHealthEndpoint() string
GetEndpoint() string
WaitForRunning() error
CreateUser(user string) error
CreateAuthKey(user string, reusable bool, ephemeral bool) (*v1.PreAuthKey, error)
CreateUser(user string) (*v1.User, error)
CreateAuthKey(user uint64, reusable bool, ephemeral bool) (*v1.PreAuthKey, error)
ListNodes(users ...string) ([]*v1.Node, error)
ListUsers() ([]*v1.User, error)
MapUsers() (map[string]*v1.User, error)
ApproveRoutes(uint64, []netip.Prefix) (*v1.Node, error)
GetCert() []byte
GetHostname() string

View file

@ -133,7 +133,7 @@ func testEphemeralWithOptions(t *testing.T, opts ...hsic.Option) {
assertNoErrHeadscaleEnv(t, err)
for _, userName := range spec.Users {
err = scenario.CreateUser(userName)
user, err := scenario.CreateUser(userName)
if err != nil {
t.Fatalf("failed to create user %s: %s", userName, err)
}
@ -143,7 +143,7 @@ func testEphemeralWithOptions(t *testing.T, opts ...hsic.Option) {
t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err)
}
key, err := scenario.CreatePreAuthKey(userName, true, true)
key, err := scenario.CreatePreAuthKey(user.GetId(), true, true)
if err != nil {
t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err)
}
@ -211,7 +211,7 @@ func TestEphemeral2006DeletedTooQuickly(t *testing.T) {
assertNoErrHeadscaleEnv(t, err)
for _, userName := range spec.Users {
err = scenario.CreateUser(userName)
user, err := scenario.CreateUser(userName)
if err != nil {
t.Fatalf("failed to create user %s: %s", userName, err)
}
@ -221,7 +221,7 @@ func TestEphemeral2006DeletedTooQuickly(t *testing.T) {
t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err)
}
key, err := scenario.CreatePreAuthKey(userName, true, true)
key, err := scenario.CreatePreAuthKey(user.GetId(), true, true)
if err != nil {
t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err)
}

View file

@ -28,6 +28,7 @@ import (
"github.com/ory/dockertest/v3/docker"
"gopkg.in/yaml.v3"
"tailscale.com/tailcfg"
"tailscale.com/util/mak"
)
const (
@ -703,32 +704,38 @@ func (t *HeadscaleInContainer) WaitForRunning() error {
// CreateUser adds a new user to the Headscale instance.
func (t *HeadscaleInContainer) CreateUser(
user string,
) error {
command := []string{"headscale", "users", "create", user, fmt.Sprintf("--email=%s@test.no", user)}
) (*v1.User, error) {
command := []string{"headscale", "users", "create", user, fmt.Sprintf("--email=%s@test.no", user), "--output", "json"}
_, _, err := dockertestutil.ExecuteCommand(
result, _, err := dockertestutil.ExecuteCommand(
t.container,
command,
[]string{},
)
if err != nil {
return err
return nil, err
}
return nil
var u v1.User
err = json.Unmarshal([]byte(result), &u)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal user: %w", err)
}
return &u, nil
}
// CreateAuthKey creates a new "authorisation key" for a User that can be used
// to authorise a TailscaleClient with the Headscale instance.
func (t *HeadscaleInContainer) CreateAuthKey(
user string,
user uint64,
reusable bool,
ephemeral bool,
) (*v1.PreAuthKey, error) {
command := []string{
"headscale",
"--user",
user,
strconv.FormatUint(user, 10),
"preauthkeys",
"create",
"--expiration",
@ -834,6 +841,22 @@ func (t *HeadscaleInContainer) ListUsers() ([]*v1.User, error) {
return users, nil
}
// MapUsers returns a map of users from Headscale. It is keyed by the
// user name.
func (t *HeadscaleInContainer) MapUsers() (map[string]*v1.User, error) {
users, err := t.ListUsers()
if err != nil {
return nil, err
}
var userMap map[string]*v1.User
for _, user := range users {
mak.Set(&userMap, user.Name, user)
}
return userMap, nil
}
func (h *HeadscaleInContainer) SetPolicy(pol *policyv1.ACLPolicy) error {
err := h.writePolicy(pol)
if err != nil {

View file

@ -1680,7 +1680,10 @@ func TestAutoApproveMultiNetwork(t *testing.T) {
scenario.runHeadscaleRegister("user1", body)
} else {
pak, err := scenario.CreatePreAuthKey("user1", false, false)
userMap, err := headscale.MapUsers()
assertNoErr(t, err)
pak, err := scenario.CreatePreAuthKey(userMap["user1"].GetId(), false, false)
assertNoErr(t, err)
err = routerUsernet1.Login(headscale.GetEndpoint(), pak.Key)

View file

@ -436,7 +436,7 @@ func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error) {
// CreatePreAuthKey creates a "pre authentorised key" to be created in the
// Headscale instance on behalf of the Scenario.
func (s *Scenario) CreatePreAuthKey(
user string,
user uint64,
reusable bool,
ephemeral bool,
) (*v1.PreAuthKey, error) {
@ -454,21 +454,21 @@ func (s *Scenario) CreatePreAuthKey(
// CreateUser creates a User to be created in the
// Headscale instance on behalf of the Scenario.
func (s *Scenario) CreateUser(user string) error {
func (s *Scenario) CreateUser(user string) (*v1.User, error) {
if headscale, err := s.Headscale(); err == nil {
err := headscale.CreateUser(user)
u, err := headscale.CreateUser(user)
if err != nil {
return fmt.Errorf("failed to create user: %w", err)
return nil, fmt.Errorf("failed to create user: %w", err)
}
s.users[user] = &User{
Clients: make(map[string]TailscaleClient),
}
return nil
return u, nil
}
return fmt.Errorf("failed to create user: %w", errNoHeadscaleAvailable)
return nil, fmt.Errorf("failed to create user: %w", errNoHeadscaleAvailable)
}
/// Client related stuff
@ -703,7 +703,7 @@ func (s *Scenario) createHeadscaleEnv(
sort.Strings(s.spec.Users)
for _, user := range s.spec.Users {
err = s.CreateUser(user)
u, err := s.CreateUser(user)
if err != nil {
return err
}
@ -726,7 +726,7 @@ func (s *Scenario) createHeadscaleEnv(
return err
}
} else {
key, err := s.CreatePreAuthKey(user, true, false)
key, err := s.CreatePreAuthKey(u.GetId(), true, false)
if err != nil {
return err
}

View file

@ -51,7 +51,7 @@ func TestHeadscale(t *testing.T) {
})
t.Run("create-user", func(t *testing.T) {
err := scenario.CreateUser(user)
_, err := scenario.CreateUser(user)
if err != nil {
t.Fatalf("failed to create user: %s", err)
}
@ -62,7 +62,7 @@ func TestHeadscale(t *testing.T) {
})
t.Run("create-auth-key", func(t *testing.T) {
_, err := scenario.CreatePreAuthKey(user, true, false)
_, err := scenario.CreatePreAuthKey(1, true, false)
if err != nil {
t.Fatalf("failed to create preauthkey: %s", err)
}
@ -100,7 +100,7 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) {
})
t.Run("create-user", func(t *testing.T) {
err := scenario.CreateUser(user)
_, err := scenario.CreateUser(user)
if err != nil {
t.Fatalf("failed to create user: %s", err)
}
@ -122,7 +122,7 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) {
})
t.Run("join-headscale", func(t *testing.T) {
key, err := scenario.CreatePreAuthKey(user, true, false)
key, err := scenario.CreatePreAuthKey(1, true, false)
if err != nil {
t.Fatalf("failed to create preauthkey: %s", err)
}