create DB struct
This is step one in detaching the Database layer from Headscale (h). The ultimate goal is to have all function that does database operations in its own package, and keep the business logic and writing separate. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
b01f1f1867
commit
14e29a7bee
48 changed files with 1731 additions and 1572 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -41,3 +41,5 @@ integration_test/etc/config.dump.yaml
|
|||
# MkDocs
|
||||
.cache
|
||||
/site
|
||||
|
||||
__debug_bin
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
@ -83,7 +83,7 @@ var listAPIKeys = &cobra.Command{
|
|||
}
|
||||
|
||||
tableData = append(tableData, []string{
|
||||
strconv.FormatUint(key.GetId(), hscontrol.Base10),
|
||||
strconv.FormatUint(key.GetId(), util.Base10),
|
||||
key.GetPrefix(),
|
||||
expiration,
|
||||
key.GetCreatedAt().AsTime().Format(HeadscaleDateTimeFormat),
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc/status"
|
||||
|
@ -93,7 +93,7 @@ var createNodeCmd = &cobra.Command{
|
|||
|
||||
return
|
||||
}
|
||||
if !hscontrol.NodePublicKeyRegex.Match([]byte(machineKey)) {
|
||||
if !util.NodePublicKeyRegex.Match([]byte(machineKey)) {
|
||||
err = errPreAuthKeyMalformed
|
||||
ErrorOutput(
|
||||
err,
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
survey "github.com/AlecAivazis/survey/v2"
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc/status"
|
||||
|
@ -529,7 +529,7 @@ func nodesToPtables(
|
|||
|
||||
var machineKey key.MachinePublic
|
||||
err := machineKey.UnmarshalText(
|
||||
[]byte(hscontrol.MachinePublicKeyEnsurePrefix(machine.MachineKey)),
|
||||
[]byte(util.MachinePublicKeyEnsurePrefix(machine.MachineKey)),
|
||||
)
|
||||
if err != nil {
|
||||
machineKey = key.MachinePublic{}
|
||||
|
@ -537,7 +537,7 @@ func nodesToPtables(
|
|||
|
||||
var nodeKey key.NodePublic
|
||||
err = nodeKey.UnmarshalText(
|
||||
[]byte(hscontrol.NodePublicKeyEnsurePrefix(machine.NodeKey)),
|
||||
[]byte(util.NodePublicKeyEnsurePrefix(machine.NodeKey)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -596,7 +596,7 @@ func nodesToPtables(
|
|||
}
|
||||
|
||||
nodeData := []string{
|
||||
strconv.FormatUint(machine.Id, hscontrol.Base10),
|
||||
strconv.FormatUint(machine.Id, util.Base10),
|
||||
machine.Name,
|
||||
machine.GetGivenName(),
|
||||
machineKey.ShortString(),
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
survey "github.com/AlecAivazis/survey/v2"
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -20,9 +20,7 @@ func init() {
|
|||
userCmd.AddCommand(renameUserCmd)
|
||||
}
|
||||
|
||||
const (
|
||||
errMissingParameter = hscontrol.Error("missing parameters")
|
||||
)
|
||||
var errMissingParameter = errors.New("missing parameters")
|
||||
|
||||
var userCmd = &cobra.Command{
|
||||
Use: "users",
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
@ -39,7 +40,7 @@ func getHeadscaleApp() (*hscontrol.Headscale, error) {
|
|||
// We are doing this here, as in the future could be cool to have it also hot-reload
|
||||
|
||||
if cfg.ACL.PolicyPath != "" {
|
||||
aclPath := hscontrol.AbsolutePathFromConfigPath(cfg.ACL.PolicyPath)
|
||||
aclPath := util.AbsolutePathFromConfigPath(cfg.ACL.PolicyPath)
|
||||
err = app.LoadACLPolicyFromPath(aclPath)
|
||||
if err != nil {
|
||||
log.Fatal().
|
||||
|
@ -98,7 +99,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.
|
|||
grpcOptions = append(
|
||||
grpcOptions,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithContextDialer(hscontrol.GrpcSocketDialer),
|
||||
grpc.WithContextDialer(util.GrpcSocketDialer),
|
||||
)
|
||||
} else {
|
||||
// If we are not connecting to a local server, require an API key for authentication
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/check.v1"
|
||||
)
|
||||
|
@ -64,7 +65,7 @@ func (*Suite) TestConfigFileLoading(c *check.C) {
|
|||
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
|
||||
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
|
||||
c.Assert(
|
||||
hscontrol.GetFileMode("unix_socket_permission"),
|
||||
util.GetFileMode("unix_socket_permission"),
|
||||
check.Equals,
|
||||
fs.FileMode(0o770),
|
||||
)
|
||||
|
@ -107,7 +108,7 @@ func (*Suite) TestConfigLoading(c *check.C) {
|
|||
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
|
||||
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
|
||||
c.Assert(
|
||||
hscontrol.GetFileMode("unix_socket_permission"),
|
||||
util.GetFileMode("unix_socket_permission"),
|
||||
check.Equals,
|
||||
fs.FileMode(0o770),
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/tailscale/hujson"
|
||||
"go4.org/netipx"
|
||||
|
@ -20,21 +21,16 @@ import (
|
|||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
const (
|
||||
errEmptyPolicy = Error("empty policy")
|
||||
errInvalidAction = Error("invalid action")
|
||||
errInvalidGroup = Error("invalid group")
|
||||
errInvalidTag = Error("invalid tag")
|
||||
errInvalidPortFormat = Error("invalid port format")
|
||||
errWildcardIsNeeded = Error("wildcard as port is required for the protocol")
|
||||
var (
|
||||
errEmptyPolicy = errors.New("empty policy")
|
||||
errInvalidAction = errors.New("invalid action")
|
||||
errInvalidGroup = errors.New("invalid group")
|
||||
errInvalidTag = errors.New("invalid tag")
|
||||
errInvalidPortFormat = errors.New("invalid port format")
|
||||
errWildcardIsNeeded = errors.New("wildcard as port is required for the protocol")
|
||||
)
|
||||
|
||||
const (
|
||||
Base8 = 8
|
||||
Base10 = 10
|
||||
BitSize16 = 16
|
||||
BitSize32 = 32
|
||||
BitSize64 = 64
|
||||
portRangeBegin = 0
|
||||
portRangeEnd = 65535
|
||||
expectedTokenItems = 2
|
||||
|
@ -123,7 +119,7 @@ func (h *Headscale) LoadACLPolicyFromBytes(acl []byte, format string) error {
|
|||
}
|
||||
|
||||
func (h *Headscale) UpdateACLRules() error {
|
||||
machines, err := h.ListMachines()
|
||||
machines, err := h.db.ListMachines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -230,7 +226,7 @@ func (h *Headscale) generateSSHRules() ([]*tailcfg.SSHRule, error) {
|
|||
return nil, errEmptyPolicy
|
||||
}
|
||||
|
||||
machines, err := h.ListMachines()
|
||||
machines, err := h.db.ListMachines()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -570,7 +566,7 @@ func excludeCorrectlyTaggedNodes(
|
|||
for tag := range aclPolicy.TagOwners {
|
||||
owners, _ := getTagOwners(aclPolicy, user, stripEmailDomain)
|
||||
ns := append(owners, user)
|
||||
if contains(ns, user) {
|
||||
if util.StringOrPrefixListContains(ns, user) {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
|
@ -580,7 +576,7 @@ func excludeCorrectlyTaggedNodes(
|
|||
|
||||
found := false
|
||||
for _, t := range hi.RequestTags {
|
||||
if contains(tags, t) {
|
||||
if util.StringOrPrefixListContains(tags, t) {
|
||||
found = true
|
||||
|
||||
break
|
||||
|
@ -614,7 +610,7 @@ func expandPorts(portsStr string, needsWildcard bool) (*[]tailcfg.PortRange, err
|
|||
rang := strings.Split(portStr, "-")
|
||||
switch len(rang) {
|
||||
case 1:
|
||||
port, err := strconv.ParseUint(rang[0], Base10, BitSize16)
|
||||
port, err := strconv.ParseUint(rang[0], util.Base10, util.BitSize16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -624,11 +620,11 @@ func expandPorts(portsStr string, needsWildcard bool) (*[]tailcfg.PortRange, err
|
|||
})
|
||||
|
||||
case expectedTokenItems:
|
||||
start, err := strconv.ParseUint(rang[0], Base10, BitSize16)
|
||||
start, err := strconv.ParseUint(rang[0], util.Base10, util.BitSize16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
last, err := strconv.ParseUint(rang[1], Base10, BitSize16)
|
||||
last, err := strconv.ParseUint(rang[1], util.Base10, util.BitSize16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -754,7 +750,7 @@ func (pol *ACLPolicy) getIPsFromTag(
|
|||
|
||||
// check for forced tags
|
||||
for _, machine := range machines {
|
||||
if contains(machine.ForcedTags, alias) {
|
||||
if util.StringOrPrefixListContains(machine.ForcedTags, alias) {
|
||||
machine.IPAddresses.AppendToIPSet(&build)
|
||||
}
|
||||
}
|
||||
|
@ -783,7 +779,7 @@ func (pol *ACLPolicy) getIPsFromTag(
|
|||
machines := filterMachinesByUser(machines, user)
|
||||
for _, machine := range machines {
|
||||
hi := machine.GetHostInfo()
|
||||
if contains(hi.RequestTags, alias) {
|
||||
if util.StringOrPrefixListContains(hi.RequestTags, alias) {
|
||||
machine.IPAddresses.AppendToIPSet(&build)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -238,13 +238,13 @@ func (s *Suite) TestInvalidAction(c *check.C) {
|
|||
func (s *Suite) TestSshRules(c *check.C) {
|
||||
envknob.Setenv("HEADSCALE_EXPERIMENTAL_FEATURE_SSH", "1")
|
||||
|
||||
user, err := app.CreateUser("user1")
|
||||
user, err := app.db.CreateUser("user1")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("user1", "testmachine")
|
||||
_, err = app.db.GetMachine("user1", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
hostInfo := tailcfg.Hostinfo{
|
||||
OS: "centos",
|
||||
|
@ -264,7 +264,7 @@ func (s *Suite) TestSshRules(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: HostInfo(hostInfo),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
app.aclPolicy = &ACLPolicy{
|
||||
Groups: Groups{
|
||||
|
@ -348,13 +348,13 @@ func (s *Suite) TestInvalidTagOwners(c *check.C) {
|
|||
// match properly the IP's of the related hosts. The owner is valid and the tag is also valid.
|
||||
// the tag is matched in the Sources section.
|
||||
func (s *Suite) TestValidExpandTagOwnersInSources(c *check.C) {
|
||||
user, err := app.CreateUser("user1")
|
||||
user, err := app.db.CreateUser("user1")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("user1", "testmachine")
|
||||
_, err = app.db.GetMachine("user1", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
hostInfo := tailcfg.Hostinfo{
|
||||
OS: "centos",
|
||||
|
@ -374,7 +374,7 @@ func (s *Suite) TestValidExpandTagOwnersInSources(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: HostInfo(hostInfo),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
app.aclPolicy = &ACLPolicy{
|
||||
Groups: Groups{"group:test": []string{"user1", "user2"}},
|
||||
|
@ -398,13 +398,13 @@ func (s *Suite) TestValidExpandTagOwnersInSources(c *check.C) {
|
|||
// match properly the IP's of the related hosts. The owner is valid and the tag is also valid.
|
||||
// the tag is matched in the Destinations section.
|
||||
func (s *Suite) TestValidExpandTagOwnersInDestinations(c *check.C) {
|
||||
user, err := app.CreateUser("user1")
|
||||
user, err := app.db.CreateUser("user1")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("user1", "testmachine")
|
||||
_, err = app.db.GetMachine("user1", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
hostInfo := tailcfg.Hostinfo{
|
||||
OS: "centos",
|
||||
|
@ -424,7 +424,7 @@ func (s *Suite) TestValidExpandTagOwnersInDestinations(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: HostInfo(hostInfo),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
app.aclPolicy = &ACLPolicy{
|
||||
Groups: Groups{"group:test": []string{"user1", "user2"}},
|
||||
|
@ -448,13 +448,13 @@ func (s *Suite) TestValidExpandTagOwnersInDestinations(c *check.C) {
|
|||
// tag on a host that isn't owned by a tag owners. So the user
|
||||
// of the host should be valid.
|
||||
func (s *Suite) TestInvalidTagValidUser(c *check.C) {
|
||||
user, err := app.CreateUser("user1")
|
||||
user, err := app.db.CreateUser("user1")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("user1", "testmachine")
|
||||
_, err = app.db.GetMachine("user1", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
hostInfo := tailcfg.Hostinfo{
|
||||
OS: "centos",
|
||||
|
@ -474,7 +474,7 @@ func (s *Suite) TestInvalidTagValidUser(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: HostInfo(hostInfo),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
app.aclPolicy = &ACLPolicy{
|
||||
TagOwners: TagOwners{"tag:test": []string{"user1"}},
|
||||
|
@ -497,13 +497,13 @@ func (s *Suite) TestInvalidTagValidUser(c *check.C) {
|
|||
// an ACL rule is matching the tag to a user. It should not be valid since the
|
||||
// host should be tied to the tag now.
|
||||
func (s *Suite) TestValidTagInvalidUser(c *check.C) {
|
||||
user, err := app.CreateUser("user1")
|
||||
user, err := app.db.CreateUser("user1")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("user1", "webserver")
|
||||
_, err = app.db.GetMachine("user1", "webserver")
|
||||
c.Assert(err, check.NotNil)
|
||||
hostInfo := tailcfg.Hostinfo{
|
||||
OS: "centos",
|
||||
|
@ -523,8 +523,8 @@ func (s *Suite) TestValidTagInvalidUser(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: HostInfo(hostInfo),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
_, err = app.GetMachine("user1", "user")
|
||||
app.db.db.Save(&machine)
|
||||
_, err = app.db.GetMachine("user1", "user")
|
||||
hostInfo2 := tailcfg.Hostinfo{
|
||||
OS: "debian",
|
||||
Hostname: "Hostname",
|
||||
|
@ -542,7 +542,7 @@ func (s *Suite) TestValidTagInvalidUser(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: HostInfo(hostInfo2),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
app.aclPolicy = &ACLPolicy{
|
||||
TagOwners: TagOwners{"tag:webapp": []string{"user1"}},
|
||||
|
@ -694,8 +694,7 @@ func (s *Suite) TestPortWildcard(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *Suite) TestPortWildcardYAML(c *check.C) {
|
||||
acl := []byte(`
|
||||
---
|
||||
acl := []byte(`---
|
||||
hosts:
|
||||
host-1: 100.100.100.100/32
|
||||
subnet-1: 100.100.101.100/24
|
||||
|
@ -704,8 +703,7 @@ acls:
|
|||
src:
|
||||
- "*"
|
||||
dst:
|
||||
- host-1:*
|
||||
`)
|
||||
- host-1:*`)
|
||||
err := app.LoadACLPolicyFromBytes(acl, "yaml")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
|
@ -722,15 +720,15 @@ acls:
|
|||
}
|
||||
|
||||
func (s *Suite) TestPortUser(c *check.C) {
|
||||
user, err := app.CreateUser("testuser")
|
||||
user, err := app.db.CreateUser("testuser")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("testuser", "testmachine")
|
||||
_, err = app.db.GetMachine("testuser", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
ips, _ := app.getAvailableIPs()
|
||||
ips, _ := app.db.getAvailableIPs()
|
||||
machine := Machine{
|
||||
ID: 0,
|
||||
MachineKey: "12345",
|
||||
|
@ -742,7 +740,7 @@ func (s *Suite) TestPortUser(c *check.C) {
|
|||
IPAddresses: ips,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
acl := []byte(`
|
||||
{
|
||||
|
@ -767,7 +765,7 @@ func (s *Suite) TestPortUser(c *check.C) {
|
|||
err = app.LoadACLPolicyFromBytes(acl, "hujson")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machines, err := app.ListMachines()
|
||||
machines, err := app.db.ListMachines()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
rules, err := app.aclPolicy.generateFilterRules(machines, false)
|
||||
|
@ -785,15 +783,15 @@ func (s *Suite) TestPortUser(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *Suite) TestPortGroup(c *check.C) {
|
||||
user, err := app.CreateUser("testuser")
|
||||
user, err := app.db.CreateUser("testuser")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("testuser", "testmachine")
|
||||
_, err = app.db.GetMachine("testuser", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
ips, _ := app.getAvailableIPs()
|
||||
ips, _ := app.db.getAvailableIPs()
|
||||
machine := Machine{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
|
@ -805,7 +803,7 @@ func (s *Suite) TestPortGroup(c *check.C) {
|
|||
IPAddresses: ips,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
acl := []byte(`
|
||||
{
|
||||
|
@ -836,7 +834,7 @@ func (s *Suite) TestPortGroup(c *check.C) {
|
|||
err = app.LoadACLPolicyFromBytes(acl, "hujson")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machines, err := app.ListMachines()
|
||||
machines, err := app.db.ListMachines()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
rules, err := app.aclPolicy.generateFilterRules(machines, false)
|
||||
|
|
98
hscontrol/addresses.go
Normal file
98
hscontrol/addresses.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Codehere is mostly taken from github.com/tailscale/tailscale
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package hscontrol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
var ErrCouldNotAllocateIP = errors.New("could not find any suitable IP")
|
||||
|
||||
func (hsdb *HSDatabase) getAvailableIPs() (MachineAddresses, error) {
|
||||
var ips MachineAddresses
|
||||
var err error
|
||||
for _, ipPrefix := range hsdb.ipPrefixes {
|
||||
var ip *netip.Addr
|
||||
ip, err = hsdb.getAvailableIP(ipPrefix)
|
||||
if err != nil {
|
||||
return ips, err
|
||||
}
|
||||
ips = append(ips, *ip)
|
||||
}
|
||||
|
||||
return ips, err
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) getAvailableIP(ipPrefix netip.Prefix) (*netip.Addr, error) {
|
||||
usedIps, err := hsdb.getUsedIPs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipPrefixNetworkAddress, ipPrefixBroadcastAddress := util.GetIPPrefixEndpoints(ipPrefix)
|
||||
|
||||
// Get the first IP in our prefix
|
||||
ip := ipPrefixNetworkAddress.Next()
|
||||
|
||||
for {
|
||||
if !ipPrefix.Contains(ip) {
|
||||
return nil, ErrCouldNotAllocateIP
|
||||
}
|
||||
|
||||
switch {
|
||||
case ip.Compare(ipPrefixBroadcastAddress) == 0:
|
||||
fallthrough
|
||||
case usedIps.Contains(ip):
|
||||
fallthrough
|
||||
case ip == netip.Addr{} || ip.IsLoopback():
|
||||
ip = ip.Next()
|
||||
|
||||
continue
|
||||
|
||||
default:
|
||||
return &ip, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) getUsedIPs() (*netipx.IPSet, error) {
|
||||
// FIXME: This really deserves a better data model,
|
||||
// but this was quick to get running and it should be enough
|
||||
// to begin experimenting with a dual stack tailnet.
|
||||
var addressesSlices []string
|
||||
hsdb.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices)
|
||||
|
||||
var ips netipx.IPSetBuilder
|
||||
for _, slice := range addressesSlices {
|
||||
var machineAddresses MachineAddresses
|
||||
err := machineAddresses.Scan(slice)
|
||||
if err != nil {
|
||||
return &netipx.IPSet{}, fmt.Errorf(
|
||||
"failed to read ip from database: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
for _, ip := range machineAddresses {
|
||||
ips.Add(ip)
|
||||
}
|
||||
}
|
||||
|
||||
ipSet, err := ips.IPSet()
|
||||
if err != nil {
|
||||
return &netipx.IPSet{}, fmt.Errorf(
|
||||
"failed to build IP Set: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return ipSet, nil
|
||||
}
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
func (s *Suite) TestGetAvailableIp(c *check.C) {
|
||||
ips, err := app.getAvailableIPs()
|
||||
ips, err := app.db.getAvailableIPs()
|
||||
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
|
@ -19,16 +19,16 @@ func (s *Suite) TestGetAvailableIp(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *Suite) TestGetUsedIps(c *check.C) {
|
||||
ips, err := app.getAvailableIPs()
|
||||
ips, err := app.db.getAvailableIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
user, err := app.CreateUser("test-ip")
|
||||
user, err := app.db.CreateUser("test-ip")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "testmachine")
|
||||
_, err = app.db.GetMachine("test", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := Machine{
|
||||
|
@ -42,9 +42,9 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
IPAddresses: ips,
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
usedIps, err := app.getUsedIPs()
|
||||
usedIps, err := app.db.getUsedIPs()
|
||||
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
|
@ -56,7 +56,7 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
|
|||
c.Assert(usedIps.Equal(expectedIPSet), check.Equals, true)
|
||||
c.Assert(usedIps.Contains(expected), check.Equals, true)
|
||||
|
||||
machine1, err := app.GetMachineByID(0)
|
||||
machine1, err := app.db.GetMachineByID(0)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(len(machine1.IPAddresses), check.Equals, 1)
|
||||
|
@ -64,19 +64,19 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *Suite) TestGetMultiIp(c *check.C) {
|
||||
user, err := app.CreateUser("test-ip-multi")
|
||||
user, err := app.db.CreateUser("test-ip-multi")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
for index := 1; index <= 350; index++ {
|
||||
app.ipAllocationMutex.Lock()
|
||||
app.db.ipAllocationMutex.Lock()
|
||||
|
||||
ips, err := app.getAvailableIPs()
|
||||
ips, err := app.db.getAvailableIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "testmachine")
|
||||
_, err = app.db.GetMachine("test", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := Machine{
|
||||
|
@ -90,12 +90,12 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
IPAddresses: ips,
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
app.ipAllocationMutex.Unlock()
|
||||
app.db.ipAllocationMutex.Unlock()
|
||||
}
|
||||
|
||||
usedIps, err := app.getUsedIPs()
|
||||
usedIps, err := app.db.getUsedIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
expected0 := netip.MustParseAddr("10.27.0.1")
|
||||
|
@ -117,7 +117,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
|||
c.Assert(usedIps.Contains(expected300), check.Equals, true)
|
||||
|
||||
// Check that we can read back the IPs
|
||||
machine1, err := app.GetMachineByID(1)
|
||||
machine1, err := app.db.GetMachineByID(1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(machine1.IPAddresses), check.Equals, 1)
|
||||
c.Assert(
|
||||
|
@ -126,7 +126,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
|||
netip.MustParseAddr("10.27.0.1"),
|
||||
)
|
||||
|
||||
machine50, err := app.GetMachineByID(50)
|
||||
machine50, err := app.db.GetMachineByID(50)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(machine50.IPAddresses), check.Equals, 1)
|
||||
c.Assert(
|
||||
|
@ -136,7 +136,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
|||
)
|
||||
|
||||
expectedNextIP := netip.MustParseAddr("10.27.1.95")
|
||||
nextIP, err := app.getAvailableIPs()
|
||||
nextIP, err := app.db.getAvailableIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(len(nextIP), check.Equals, 1)
|
||||
|
@ -144,7 +144,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
|||
|
||||
// If we call get Available again, we should receive
|
||||
// the same IP, as it has not been reserved.
|
||||
nextIP2, err := app.getAvailableIPs()
|
||||
nextIP2, err := app.db.getAvailableIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(len(nextIP2), check.Equals, 1)
|
||||
|
@ -152,7 +152,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
|
||||
ips, err := app.getAvailableIPs()
|
||||
ips, err := app.db.getAvailableIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
expected := netip.MustParseAddr("10.27.0.1")
|
||||
|
@ -160,13 +160,13 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
|
|||
c.Assert(len(ips), check.Equals, 1)
|
||||
c.Assert(ips[0].String(), check.Equals, expected.String())
|
||||
|
||||
user, err := app.CreateUser("test-ip")
|
||||
user, err := app.db.CreateUser("test-ip")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "testmachine")
|
||||
_, err = app.db.GetMachine("test", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := Machine{
|
||||
|
@ -179,23 +179,11 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
ips2, err := app.getAvailableIPs()
|
||||
ips2, err := app.db.getAvailableIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(len(ips2), check.Equals, 1)
|
||||
c.Assert(ips2[0].String(), check.Equals, expected.String())
|
||||
}
|
||||
|
||||
func (s *Suite) TestGenerateRandomStringDNSSafe(c *check.C) {
|
||||
for i := 0; i < 100000; i++ {
|
||||
str, err := GenerateRandomStringDNSSafe(8)
|
||||
if err != nil {
|
||||
c.Error(err)
|
||||
}
|
||||
if len(str) != 8 {
|
||||
c.Error("invalid length", len(str), str)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,11 +3,13 @@ package hscontrol
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
@ -19,9 +21,10 @@ const (
|
|||
RegisterMethodAuthKey = "authkey"
|
||||
RegisterMethodOIDC = "oidc"
|
||||
RegisterMethodCLI = "cli"
|
||||
ErrRegisterMethodCLIDoesNotSupportExpire = Error(
|
||||
)
|
||||
|
||||
var ErrRegisterMethodCLIDoesNotSupportExpire = errors.New(
|
||||
"machines registered with CLI does not support expire",
|
||||
)
|
||||
)
|
||||
|
||||
func (h *Headscale) HealthHandler(
|
||||
|
@ -53,7 +56,7 @@ func (h *Headscale) HealthHandler(
|
|||
}
|
||||
}
|
||||
|
||||
if err := h.pingDB(req.Context()); err != nil {
|
||||
if err := h.db.pingDB(req.Context()); err != nil {
|
||||
respond(err)
|
||||
|
||||
return
|
||||
|
@ -95,7 +98,7 @@ func (h *Headscale) RegisterWebAPI(
|
|||
vars := mux.Vars(req)
|
||||
nodeKeyStr, ok := vars["nkey"]
|
||||
|
||||
if !NodePublicKeyRegex.Match([]byte(nodeKeyStr)) {
|
||||
if !util.NodePublicKeyRegex.Match([]byte(nodeKeyStr)) {
|
||||
log.Warn().Str("node_key", nodeKeyStr).Msg("Invalid node key passed to registration url")
|
||||
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
|
@ -116,7 +119,7 @@ func (h *Headscale) RegisterWebAPI(
|
|||
// the template and log an error.
|
||||
var nodeKey key.NodePublic
|
||||
err := nodeKey.UnmarshalText(
|
||||
[]byte(NodePublicKeyEnsurePrefix(nodeKeyStr)),
|
||||
[]byte(util.NodePublicKeyEnsurePrefix(nodeKeyStr)),
|
||||
)
|
||||
|
||||
if !ok || nodeKeyStr == "" || err != nil {
|
||||
|
|
|
@ -3,6 +3,7 @@ package hscontrol
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
@ -15,7 +16,7 @@ func (h *Headscale) generateMapResponse(
|
|||
Str("func", "generateMapResponse").
|
||||
Str("machine", mapRequest.Hostinfo.Hostname).
|
||||
Msg("Creating Map response")
|
||||
node, err := h.toNode(*machine, h.cfg.BaseDomain, h.cfg.DNSConfig)
|
||||
node, err := h.db.toNode(*machine, h.aclPolicy, h.cfg.BaseDomain, h.cfg.DNSConfig)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -26,7 +27,7 @@ func (h *Headscale) generateMapResponse(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
peers, err := h.getValidPeers(machine)
|
||||
peers, err := h.db.getValidPeers(h.aclPolicy, h.aclRules, machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -37,9 +38,9 @@ func (h *Headscale) generateMapResponse(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
profiles := h.getMapResponseUserProfiles(*machine, peers)
|
||||
profiles := h.db.getMapResponseUserProfiles(*machine, peers)
|
||||
|
||||
nodePeers, err := h.toNodes(peers, h.cfg.BaseDomain, h.cfg.DNSConfig)
|
||||
nodePeers, err := h.db.toNodes(peers, h.aclPolicy, h.cfg.BaseDomain, h.cfg.DNSConfig)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -107,7 +108,7 @@ func (h *Headscale) generateMapResponse(
|
|||
Str("func", "generateMapResponse").
|
||||
Str("machine", mapRequest.Hostinfo.Hostname).
|
||||
// Interface("payload", resp).
|
||||
Msgf("Generated map response: %s", tailMapResponseToString(resp))
|
||||
Msgf("Generated map response: %s", util.TailMapResponseToString(resp))
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package hscontrol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
@ -13,10 +15,10 @@ import (
|
|||
const (
|
||||
apiPrefixLength = 7
|
||||
apiKeyLength = 32
|
||||
|
||||
ErrAPIKeyFailedToParse = Error("Failed to parse ApiKey")
|
||||
)
|
||||
|
||||
var ErrAPIKeyFailedToParse = errors.New("failed to parse ApiKey")
|
||||
|
||||
// APIKey describes the datamodel for API keys used to remotely authenticate with
|
||||
// headscale.
|
||||
type APIKey struct {
|
||||
|
@ -30,15 +32,15 @@ type APIKey struct {
|
|||
}
|
||||
|
||||
// CreateAPIKey creates a new ApiKey in a user, and returns it.
|
||||
func (h *Headscale) CreateAPIKey(
|
||||
func (hsdb *HSDatabase) CreateAPIKey(
|
||||
expiration *time.Time,
|
||||
) (string, *APIKey, error) {
|
||||
prefix, err := GenerateRandomStringURLSafe(apiPrefixLength)
|
||||
prefix, err := util.GenerateRandomStringURLSafe(apiPrefixLength)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
toBeHashed, err := GenerateRandomStringURLSafe(apiKeyLength)
|
||||
toBeHashed, err := util.GenerateRandomStringURLSafe(apiKeyLength)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
@ -57,7 +59,7 @@ func (h *Headscale) CreateAPIKey(
|
|||
Expiration: expiration,
|
||||
}
|
||||
|
||||
if err := h.db.Save(&key).Error; err != nil {
|
||||
if err := hsdb.db.Save(&key).Error; err != nil {
|
||||
return "", nil, fmt.Errorf("failed to save API key to database: %w", err)
|
||||
}
|
||||
|
||||
|
@ -65,9 +67,9 @@ func (h *Headscale) CreateAPIKey(
|
|||
}
|
||||
|
||||
// ListAPIKeys returns the list of ApiKeys for a user.
|
||||
func (h *Headscale) ListAPIKeys() ([]APIKey, error) {
|
||||
func (hsdb *HSDatabase) ListAPIKeys() ([]APIKey, error) {
|
||||
keys := []APIKey{}
|
||||
if err := h.db.Find(&keys).Error; err != nil {
|
||||
if err := hsdb.db.Find(&keys).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -75,9 +77,9 @@ func (h *Headscale) ListAPIKeys() ([]APIKey, error) {
|
|||
}
|
||||
|
||||
// GetAPIKey returns a ApiKey for a given key.
|
||||
func (h *Headscale) GetAPIKey(prefix string) (*APIKey, error) {
|
||||
func (hsdb *HSDatabase) GetAPIKey(prefix string) (*APIKey, error) {
|
||||
key := APIKey{}
|
||||
if result := h.db.First(&key, "prefix = ?", prefix); result.Error != nil {
|
||||
if result := hsdb.db.First(&key, "prefix = ?", prefix); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
|
@ -85,9 +87,9 @@ func (h *Headscale) GetAPIKey(prefix string) (*APIKey, error) {
|
|||
}
|
||||
|
||||
// GetAPIKeyByID returns a ApiKey for a given id.
|
||||
func (h *Headscale) GetAPIKeyByID(id uint64) (*APIKey, error) {
|
||||
func (hsdb *HSDatabase) GetAPIKeyByID(id uint64) (*APIKey, error) {
|
||||
key := APIKey{}
|
||||
if result := h.db.Find(&APIKey{ID: id}).First(&key); result.Error != nil {
|
||||
if result := hsdb.db.Find(&APIKey{ID: id}).First(&key); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
|
@ -96,8 +98,8 @@ func (h *Headscale) GetAPIKeyByID(id uint64) (*APIKey, error) {
|
|||
|
||||
// DestroyAPIKey destroys a ApiKey. Returns error if the ApiKey
|
||||
// does not exist.
|
||||
func (h *Headscale) DestroyAPIKey(key APIKey) error {
|
||||
if result := h.db.Unscoped().Delete(key); result.Error != nil {
|
||||
func (hsdb *HSDatabase) DestroyAPIKey(key APIKey) error {
|
||||
if result := hsdb.db.Unscoped().Delete(key); result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
|
@ -105,21 +107,21 @@ func (h *Headscale) DestroyAPIKey(key APIKey) error {
|
|||
}
|
||||
|
||||
// ExpireAPIKey marks a ApiKey as expired.
|
||||
func (h *Headscale) ExpireAPIKey(key *APIKey) error {
|
||||
if err := h.db.Model(&key).Update("Expiration", time.Now()).Error; err != nil {
|
||||
func (hsdb *HSDatabase) ExpireAPIKey(key *APIKey) error {
|
||||
if err := hsdb.db.Model(&key).Update("Expiration", time.Now()).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Headscale) ValidateAPIKey(keyStr string) (bool, error) {
|
||||
func (hsdb *HSDatabase) ValidateAPIKey(keyStr string) (bool, error) {
|
||||
prefix, hash, found := strings.Cut(keyStr, ".")
|
||||
if !found {
|
||||
return false, ErrAPIKeyFailedToParse
|
||||
}
|
||||
|
||||
key, err := h.GetAPIKey(prefix)
|
||||
key, err := hsdb.GetAPIKey(prefix)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to validate api key: %w", err)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
func (*Suite) TestCreateAPIKey(c *check.C) {
|
||||
apiKeyStr, apiKey, err := app.CreateAPIKey(nil)
|
||||
apiKeyStr, apiKey, err := app.db.CreateAPIKey(nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(apiKey, check.NotNil)
|
||||
|
||||
|
@ -16,74 +16,74 @@ func (*Suite) TestCreateAPIKey(c *check.C) {
|
|||
c.Assert(apiKey.Hash, check.NotNil)
|
||||
c.Assert(apiKeyStr, check.Not(check.Equals), "")
|
||||
|
||||
_, err = app.ListAPIKeys()
|
||||
_, err = app.db.ListAPIKeys()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
keys, err := app.ListAPIKeys()
|
||||
keys, err := app.db.ListAPIKeys()
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(keys), check.Equals, 1)
|
||||
}
|
||||
|
||||
func (*Suite) TestAPIKeyDoesNotExist(c *check.C) {
|
||||
key, err := app.GetAPIKey("does-not-exist")
|
||||
key, err := app.db.GetAPIKey("does-not-exist")
|
||||
c.Assert(err, check.NotNil)
|
||||
c.Assert(key, check.IsNil)
|
||||
}
|
||||
|
||||
func (*Suite) TestValidateAPIKeyOk(c *check.C) {
|
||||
nowPlus2 := time.Now().Add(2 * time.Hour)
|
||||
apiKeyStr, apiKey, err := app.CreateAPIKey(&nowPlus2)
|
||||
apiKeyStr, apiKey, err := app.db.CreateAPIKey(&nowPlus2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(apiKey, check.NotNil)
|
||||
|
||||
valid, err := app.ValidateAPIKey(apiKeyStr)
|
||||
valid, err := app.db.ValidateAPIKey(apiKeyStr)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(valid, check.Equals, true)
|
||||
}
|
||||
|
||||
func (*Suite) TestValidateAPIKeyNotOk(c *check.C) {
|
||||
nowMinus2 := time.Now().Add(time.Duration(-2) * time.Hour)
|
||||
apiKeyStr, apiKey, err := app.CreateAPIKey(&nowMinus2)
|
||||
apiKeyStr, apiKey, err := app.db.CreateAPIKey(&nowMinus2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(apiKey, check.NotNil)
|
||||
|
||||
valid, err := app.ValidateAPIKey(apiKeyStr)
|
||||
valid, err := app.db.ValidateAPIKey(apiKeyStr)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(valid, check.Equals, false)
|
||||
|
||||
now := time.Now()
|
||||
apiKeyStrNow, apiKey, err := app.CreateAPIKey(&now)
|
||||
apiKeyStrNow, apiKey, err := app.db.CreateAPIKey(&now)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(apiKey, check.NotNil)
|
||||
|
||||
validNow, err := app.ValidateAPIKey(apiKeyStrNow)
|
||||
validNow, err := app.db.ValidateAPIKey(apiKeyStrNow)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(validNow, check.Equals, false)
|
||||
|
||||
validSilly, err := app.ValidateAPIKey("nota.validkey")
|
||||
validSilly, err := app.db.ValidateAPIKey("nota.validkey")
|
||||
c.Assert(err, check.NotNil)
|
||||
c.Assert(validSilly, check.Equals, false)
|
||||
|
||||
validWithErr, err := app.ValidateAPIKey("produceerrorkey")
|
||||
validWithErr, err := app.db.ValidateAPIKey("produceerrorkey")
|
||||
c.Assert(err, check.NotNil)
|
||||
c.Assert(validWithErr, check.Equals, false)
|
||||
}
|
||||
|
||||
func (*Suite) TestExpireAPIKey(c *check.C) {
|
||||
nowPlus2 := time.Now().Add(2 * time.Hour)
|
||||
apiKeyStr, apiKey, err := app.CreateAPIKey(&nowPlus2)
|
||||
apiKeyStr, apiKey, err := app.db.CreateAPIKey(&nowPlus2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(apiKey, check.NotNil)
|
||||
|
||||
valid, err := app.ValidateAPIKey(apiKeyStr)
|
||||
valid, err := app.db.ValidateAPIKey(apiKeyStr)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(valid, check.Equals, true)
|
||||
|
||||
err = app.ExpireAPIKey(apiKey)
|
||||
err = app.db.ExpireAPIKey(apiKey)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(apiKey.Expiration, check.NotNil)
|
||||
|
||||
notValid, err := app.ValidateAPIKey(apiKeyStr)
|
||||
notValid, err := app.db.ValidateAPIKey(apiKeyStr)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(notValid, check.Equals, false)
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/juanfont/headscale"
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/patrickmn/go-cache"
|
||||
zerolog "github.com/philip-bui/grpc-zerolog"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
@ -41,24 +42,21 @@ import (
|
|||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/grpc/status"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
const (
|
||||
errSTUNAddressNotSet = Error("STUN address not set")
|
||||
errUnsupportedDatabase = Error("unsupported DB")
|
||||
errUnsupportedLetsEncryptChallengeType = Error(
|
||||
var (
|
||||
errSTUNAddressNotSet = errors.New("STUN address not set")
|
||||
errUnsupportedDatabase = errors.New("unsupported DB")
|
||||
errUnsupportedLetsEncryptChallengeType = errors.New(
|
||||
"unknown value for Lets Encrypt challenge type",
|
||||
)
|
||||
)
|
||||
|
||||
const (
|
||||
AuthPrefix = "Bearer "
|
||||
Postgres = "postgres"
|
||||
Sqlite = "sqlite3"
|
||||
updateInterval = 5000
|
||||
HTTPReadTimeout = 30 * time.Second
|
||||
HTTPShutdownTimeout = 3 * time.Second
|
||||
|
@ -75,7 +73,7 @@ const (
|
|||
// Headscale represents the base app of the service.
|
||||
type Headscale struct {
|
||||
cfg *Config
|
||||
db *gorm.DB
|
||||
db *HSDatabase
|
||||
dbString string
|
||||
dbType string
|
||||
dbDebug bool
|
||||
|
@ -96,10 +94,11 @@ type Headscale struct {
|
|||
|
||||
registrationCache *cache.Cache
|
||||
|
||||
ipAllocationMutex sync.Mutex
|
||||
|
||||
shutdownChan chan struct{}
|
||||
pollNetMapStreamWG sync.WaitGroup
|
||||
|
||||
stateUpdateChan chan struct{}
|
||||
cancelStateUpdateChan chan struct{}
|
||||
}
|
||||
|
||||
func NewHeadscale(cfg *Config) (*Headscale, error) {
|
||||
|
@ -164,13 +163,27 @@ func NewHeadscale(cfg *Config) (*Headscale, error) {
|
|||
registrationCache: registrationCache,
|
||||
pollNetMapStreamWG: sync.WaitGroup{},
|
||||
lastStateChange: xsync.NewMapOf[time.Time](),
|
||||
|
||||
stateUpdateChan: make(chan struct{}),
|
||||
cancelStateUpdateChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
err = app.initDB()
|
||||
go app.watchStateChannel()
|
||||
|
||||
db, err := NewHeadscaleDatabase(
|
||||
cfg.DBtype,
|
||||
dbString,
|
||||
cfg.OIDC.StripEmaildomain,
|
||||
app.dbDebug,
|
||||
app.stateUpdateChan,
|
||||
cfg.IPPrefixes,
|
||||
cfg.BaseDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
app.db = db
|
||||
|
||||
if cfg.OIDC.Issuer != "" {
|
||||
err = app.initOIDC()
|
||||
if err != nil {
|
||||
|
@ -231,7 +244,7 @@ func (h *Headscale) expireExpiredMachines(milliSeconds int64) {
|
|||
func (h *Headscale) failoverSubnetRoutes(milliSeconds int64) {
|
||||
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
|
||||
for range ticker.C {
|
||||
err := h.handlePrimarySubnetFailover()
|
||||
err := h.db.handlePrimarySubnetFailover()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to handle primary subnet failover")
|
||||
}
|
||||
|
@ -239,7 +252,7 @@ func (h *Headscale) failoverSubnetRoutes(milliSeconds int64) {
|
|||
}
|
||||
|
||||
func (h *Headscale) expireEphemeralNodesWorker() {
|
||||
users, err := h.ListUsers()
|
||||
users, err := h.db.ListUsers()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error listing users")
|
||||
|
||||
|
@ -247,7 +260,7 @@ func (h *Headscale) expireEphemeralNodesWorker() {
|
|||
}
|
||||
|
||||
for _, user := range users {
|
||||
machines, err := h.ListMachinesByUser(user.Name)
|
||||
machines, err := h.db.ListMachinesByUser(user.Name)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
|
@ -267,7 +280,7 @@ func (h *Headscale) expireEphemeralNodesWorker() {
|
|||
Str("machine", machine.Hostname).
|
||||
Msg("Ephemeral client removed from database")
|
||||
|
||||
err = h.db.Unscoped().Delete(machine).Error
|
||||
err = h.db.db.Unscoped().Delete(machine).Error
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
|
@ -284,7 +297,7 @@ func (h *Headscale) expireEphemeralNodesWorker() {
|
|||
}
|
||||
|
||||
func (h *Headscale) expireExpiredMachinesWorker() {
|
||||
users, err := h.ListUsers()
|
||||
users, err := h.db.ListUsers()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error listing users")
|
||||
|
||||
|
@ -292,7 +305,7 @@ func (h *Headscale) expireExpiredMachinesWorker() {
|
|||
}
|
||||
|
||||
for _, user := range users {
|
||||
machines, err := h.ListMachinesByUser(user.Name)
|
||||
machines, err := h.db.ListMachinesByUser(user.Name)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
|
@ -308,7 +321,7 @@ func (h *Headscale) expireExpiredMachinesWorker() {
|
|||
machine.Expiry.After(h.getLastStateChange(user)) {
|
||||
expiredFound = true
|
||||
|
||||
err := h.ExpireMachine(&machines[index])
|
||||
err := h.db.ExpireMachine(&machines[index])
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
|
@ -387,7 +400,7 @@ func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
|
|||
)
|
||||
}
|
||||
|
||||
valid, err := h.ValidateAPIKey(strings.TrimPrefix(token, AuthPrefix))
|
||||
valid, err := h.db.ValidateAPIKey(strings.TrimPrefix(token, AuthPrefix))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -438,7 +451,7 @@ func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler
|
|||
return
|
||||
}
|
||||
|
||||
valid, err := h.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix))
|
||||
valid, err := h.db.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -597,7 +610,7 @@ func (h *Headscale) Serve() error {
|
|||
h.cfg.UnixSocket,
|
||||
[]grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithContextDialer(GrpcSocketDialer),
|
||||
grpc.WithContextDialer(util.GrpcSocketDialer),
|
||||
}...,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -760,7 +773,7 @@ func (h *Headscale) Serve() error {
|
|||
// TODO(kradalby): Reload config on SIGHUP
|
||||
|
||||
if h.cfg.ACL.PolicyPath != "" {
|
||||
aclPath := AbsolutePathFromConfigPath(h.cfg.ACL.PolicyPath)
|
||||
aclPath := util.AbsolutePathFromConfigPath(h.cfg.ACL.PolicyPath)
|
||||
err := h.LoadACLPolicyFromPath(aclPath)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to reload ACL policy")
|
||||
|
@ -778,6 +791,7 @@ func (h *Headscale) Serve() error {
|
|||
Msg("Received signal to stop, shutting down gracefully")
|
||||
|
||||
close(h.shutdownChan)
|
||||
|
||||
h.pollNetMapStreamWG.Wait()
|
||||
|
||||
// Gracefully shut down servers
|
||||
|
@ -806,8 +820,12 @@ func (h *Headscale) Serve() error {
|
|||
// Stop listening (and unlink the socket if unix type):
|
||||
socketListener.Close()
|
||||
|
||||
<-h.cancelStateUpdateChan
|
||||
close(h.stateUpdateChan)
|
||||
close(h.cancelStateUpdateChan)
|
||||
|
||||
// Close db connections
|
||||
db, err := h.db.DB()
|
||||
db, err := h.db.db.DB()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get db handle")
|
||||
}
|
||||
|
@ -905,12 +923,25 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(kradalby): baby steps, make this more robust.
|
||||
func (h *Headscale) watchStateChannel() {
|
||||
for {
|
||||
select {
|
||||
case <-h.stateUpdateChan:
|
||||
h.setLastStateChangeToNow()
|
||||
|
||||
case <-h.cancelStateUpdateChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Headscale) setLastStateChangeToNow() {
|
||||
var err error
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
users, err := h.ListUsers()
|
||||
users, err := h.db.ListUsers()
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -1002,7 +1033,7 @@ func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) {
|
|||
}
|
||||
|
||||
trimmedPrivateKey := strings.TrimSpace(string(privateKey))
|
||||
privateKeyEnsurePrefix := PrivateKeyEnsurePrefix(trimmedPrivateKey)
|
||||
privateKeyEnsurePrefix := util.PrivateKeyEnsurePrefix(trimmedPrivateKey)
|
||||
|
||||
var machineKey key.MachinePrivate
|
||||
if err = machineKey.UnmarshalText([]byte(privateKeyEnsurePrefix)); err != nil {
|
||||
|
|
|
@ -42,18 +42,32 @@ func (s *Suite) ResetDB(c *check.C) {
|
|||
IPPrefixes: []netip.Prefix{
|
||||
netip.MustParsePrefix("10.27.0.0/23"),
|
||||
},
|
||||
OIDC: OIDCConfig{
|
||||
StripEmaildomain: false,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(kradalby): make this use NewHeadscale properly so it doesnt drift
|
||||
app = Headscale{
|
||||
cfg: &cfg,
|
||||
dbType: "sqlite3",
|
||||
dbString: tmpDir + "/headscale_test.db",
|
||||
|
||||
stateUpdateChan: make(chan struct{}),
|
||||
cancelStateUpdateChan: make(chan struct{}),
|
||||
}
|
||||
err = app.initDB()
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
db, err := app.openDB()
|
||||
|
||||
go app.watchStateChannel()
|
||||
|
||||
db, err := NewHeadscaleDatabase(
|
||||
app.dbType,
|
||||
app.dbString,
|
||||
cfg.OIDC.StripEmaildomain,
|
||||
false,
|
||||
app.stateUpdateChan,
|
||||
cfg.IPPrefixes,
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
c.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
@ -271,15 +272,15 @@ func GetTLSConfig() TLSConfig {
|
|||
LetsEncrypt: LetsEncryptConfig{
|
||||
Hostname: viper.GetString("tls_letsencrypt_hostname"),
|
||||
Listen: viper.GetString("tls_letsencrypt_listen"),
|
||||
CacheDir: AbsolutePathFromConfigPath(
|
||||
CacheDir: util.AbsolutePathFromConfigPath(
|
||||
viper.GetString("tls_letsencrypt_cache_dir"),
|
||||
),
|
||||
ChallengeType: viper.GetString("tls_letsencrypt_challenge_type"),
|
||||
},
|
||||
CertPath: AbsolutePathFromConfigPath(
|
||||
CertPath: util.AbsolutePathFromConfigPath(
|
||||
viper.GetString("tls_cert_path"),
|
||||
),
|
||||
KeyPath: AbsolutePathFromConfigPath(
|
||||
KeyPath: util.AbsolutePathFromConfigPath(
|
||||
viper.GetString("tls_key_path"),
|
||||
),
|
||||
}
|
||||
|
@ -585,10 +586,10 @@ func GetHeadscaleConfig() (*Config, error) {
|
|||
DisableUpdateCheck: viper.GetBool("disable_check_updates"),
|
||||
|
||||
IPPrefixes: prefixes,
|
||||
PrivateKeyPath: AbsolutePathFromConfigPath(
|
||||
PrivateKeyPath: util.AbsolutePathFromConfigPath(
|
||||
viper.GetString("private_key_path"),
|
||||
),
|
||||
NoisePrivateKeyPath: AbsolutePathFromConfigPath(
|
||||
NoisePrivateKeyPath: util.AbsolutePathFromConfigPath(
|
||||
viper.GetString("noise.private_key_path"),
|
||||
),
|
||||
BaseDomain: baseDomain,
|
||||
|
@ -604,7 +605,7 @@ func GetHeadscaleConfig() (*Config, error) {
|
|||
),
|
||||
|
||||
DBtype: viper.GetString("db_type"),
|
||||
DBpath: AbsolutePathFromConfigPath(viper.GetString("db_path")),
|
||||
DBpath: util.AbsolutePathFromConfigPath(viper.GetString("db_path")),
|
||||
DBhost: viper.GetString("db_host"),
|
||||
DBport: viper.GetInt("db_port"),
|
||||
DBname: viper.GetString("db_name"),
|
||||
|
@ -620,7 +621,7 @@ func GetHeadscaleConfig() (*Config, error) {
|
|||
ACMEURL: viper.GetString("acme_url"),
|
||||
|
||||
UnixSocket: viper.GetString("unix_socket"),
|
||||
UnixSocketPermission: GetFileMode("unix_socket_permission"),
|
||||
UnixSocketPermission: util.GetFileMode("unix_socket_permission"),
|
||||
|
||||
OIDC: OIDCConfig{
|
||||
OnlyStartIfOIDCIsAvailable: viper.GetBool(
|
||||
|
|
186
hscontrol/db.go
186
hscontrol/db.go
|
@ -7,6 +7,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
|
@ -19,55 +20,90 @@ import (
|
|||
|
||||
const (
|
||||
dbVersion = "1"
|
||||
Postgres = "postgres"
|
||||
Sqlite = "sqlite3"
|
||||
)
|
||||
|
||||
errValueNotFound = Error("not found")
|
||||
ErrCannotParsePrefix = Error("cannot parse prefix")
|
||||
var (
|
||||
errValueNotFound = errors.New("not found")
|
||||
ErrCannotParsePrefix = errors.New("cannot parse prefix")
|
||||
errDatabaseNotSupported = errors.New("database type not supported")
|
||||
)
|
||||
|
||||
// KV is a key-value store in a psql table. For future use...
|
||||
// TODO(kradalby): Is this used for anything?
|
||||
type KV struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (h *Headscale) initDB() error {
|
||||
db, err := h.openDB()
|
||||
type HSDatabase struct {
|
||||
db *gorm.DB
|
||||
notifyStateChan chan<- struct{}
|
||||
|
||||
ipAllocationMutex sync.Mutex
|
||||
|
||||
ipPrefixes []netip.Prefix
|
||||
baseDomain string
|
||||
stripEmailDomain bool
|
||||
}
|
||||
|
||||
// TODO(kradalby): assemble this struct from toptions or something typed
|
||||
// rather than arguments.
|
||||
func NewHeadscaleDatabase(
|
||||
dbType, connectionAddr string,
|
||||
stripEmailDomain, debug bool,
|
||||
notifyStateChan chan<- struct{},
|
||||
ipPrefixes []netip.Prefix,
|
||||
baseDomain string,
|
||||
) (*HSDatabase, error) {
|
||||
dbConn, err := openDB(dbType, connectionAddr, debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.db = db
|
||||
|
||||
if h.dbType == Postgres {
|
||||
db.Exec(`create extension if not exists "uuid-ossp";`)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = db.Migrator().RenameTable("namespaces", "users")
|
||||
db := HSDatabase{
|
||||
db: dbConn,
|
||||
notifyStateChan: notifyStateChan,
|
||||
|
||||
err = db.AutoMigrate(&User{})
|
||||
ipPrefixes: ipPrefixes,
|
||||
baseDomain: baseDomain,
|
||||
stripEmailDomain: stripEmailDomain,
|
||||
}
|
||||
|
||||
log.Debug().Msgf("database %#v", dbConn)
|
||||
|
||||
if dbType == Postgres {
|
||||
dbConn.Exec(`create extension if not exists "uuid-ossp";`)
|
||||
}
|
||||
|
||||
_ = dbConn.Migrator().RenameTable("namespaces", "users")
|
||||
|
||||
err = dbConn.AutoMigrate(User{})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = db.Migrator().RenameColumn(&Machine{}, "namespace_id", "user_id")
|
||||
_ = db.Migrator().RenameColumn(&PreAuthKey{}, "namespace_id", "user_id")
|
||||
_ = dbConn.Migrator().RenameColumn(&Machine{}, "namespace_id", "user_id")
|
||||
_ = dbConn.Migrator().RenameColumn(&PreAuthKey{}, "namespace_id", "user_id")
|
||||
|
||||
_ = db.Migrator().RenameColumn(&Machine{}, "ip_address", "ip_addresses")
|
||||
_ = db.Migrator().RenameColumn(&Machine{}, "name", "hostname")
|
||||
_ = dbConn.Migrator().RenameColumn(&Machine{}, "ip_address", "ip_addresses")
|
||||
_ = dbConn.Migrator().RenameColumn(&Machine{}, "name", "hostname")
|
||||
|
||||
// GivenName is used as the primary source of DNS names, make sure
|
||||
// the field is populated and normalized if it was not when the
|
||||
// machine was registered.
|
||||
_ = db.Migrator().RenameColumn(&Machine{}, "nickname", "given_name")
|
||||
_ = dbConn.Migrator().RenameColumn(&Machine{}, "nickname", "given_name")
|
||||
|
||||
// If the Machine table has a column for registered,
|
||||
// find all occourences of "false" and drop them. Then
|
||||
// remove the column.
|
||||
if db.Migrator().HasColumn(&Machine{}, "registered") {
|
||||
if dbConn.Migrator().HasColumn(&Machine{}, "registered") {
|
||||
log.Info().
|
||||
Msg(`Database has legacy "registered" column in machine, removing...`)
|
||||
|
||||
machines := Machines{}
|
||||
if err := h.db.Not("registered").Find(&machines).Error; err != nil {
|
||||
if err := dbConn.Not("registered").Find(&machines).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Error accessing db")
|
||||
}
|
||||
|
||||
|
@ -76,7 +112,7 @@ func (h *Headscale) initDB() error {
|
|||
Str("machine", machine.Hostname).
|
||||
Str("machine_key", machine.MachineKey).
|
||||
Msg("Deleting unregistered machine")
|
||||
if err := h.db.Delete(&Machine{}, machine.ID).Error; err != nil {
|
||||
if err := dbConn.Delete(&Machine{}, machine.ID).Error; err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("machine", machine.Hostname).
|
||||
|
@ -85,18 +121,18 @@ func (h *Headscale) initDB() error {
|
|||
}
|
||||
}
|
||||
|
||||
err := db.Migrator().DropColumn(&Machine{}, "registered")
|
||||
err := dbConn.Migrator().DropColumn(&Machine{}, "registered")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error dropping registered column")
|
||||
}
|
||||
}
|
||||
|
||||
err = db.AutoMigrate(&Route{})
|
||||
err = dbConn.AutoMigrate(&Route{})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if db.Migrator().HasColumn(&Machine{}, "enabled_routes") {
|
||||
if dbConn.Migrator().HasColumn(&Machine{}, "enabled_routes") {
|
||||
log.Info().Msgf("Database has legacy enabled_routes column in machine, migrating...")
|
||||
|
||||
type MachineAux struct {
|
||||
|
@ -105,7 +141,7 @@ func (h *Headscale) initDB() error {
|
|||
}
|
||||
|
||||
machinesAux := []MachineAux{}
|
||||
err := db.Table("machines").Select("id, enabled_routes").Scan(&machinesAux).Error
|
||||
err := dbConn.Table("machines").Select("id, enabled_routes").Scan(&machinesAux).Error
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Error accessing db")
|
||||
}
|
||||
|
@ -120,7 +156,7 @@ func (h *Headscale) initDB() error {
|
|||
continue
|
||||
}
|
||||
|
||||
err = db.Preload("Machine").
|
||||
err = dbConn.Preload("Machine").
|
||||
Where("machine_id = ? AND prefix = ?", machine.ID, IPPrefix(prefix)).
|
||||
First(&Route{}).
|
||||
Error
|
||||
|
@ -138,7 +174,7 @@ func (h *Headscale) initDB() error {
|
|||
Enabled: true,
|
||||
Prefix: IPPrefix(prefix),
|
||||
}
|
||||
if err := h.db.Create(&route).Error; err != nil {
|
||||
if err := dbConn.Create(&route).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Error creating route")
|
||||
} else {
|
||||
log.Info().
|
||||
|
@ -149,20 +185,20 @@ func (h *Headscale) initDB() error {
|
|||
}
|
||||
}
|
||||
|
||||
err = db.Migrator().DropColumn(&Machine{}, "enabled_routes")
|
||||
err = dbConn.Migrator().DropColumn(&Machine{}, "enabled_routes")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error dropping enabled_routes column")
|
||||
}
|
||||
}
|
||||
|
||||
err = db.AutoMigrate(&Machine{})
|
||||
err = dbConn.AutoMigrate(&Machine{})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if db.Migrator().HasColumn(&Machine{}, "given_name") {
|
||||
if dbConn.Migrator().HasColumn(&Machine{}, "given_name") {
|
||||
machines := Machines{}
|
||||
if err := h.db.Find(&machines).Error; err != nil {
|
||||
if err := dbConn.Find(&machines).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Error accessing db")
|
||||
}
|
||||
|
||||
|
@ -170,7 +206,7 @@ func (h *Headscale) initDB() error {
|
|||
if machine.GivenName == "" {
|
||||
normalizedHostname, err := NormalizeToFQDNRules(
|
||||
machine.Hostname,
|
||||
h.cfg.OIDC.StripEmaildomain,
|
||||
stripEmailDomain,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
|
@ -180,7 +216,7 @@ func (h *Headscale) initDB() error {
|
|||
Msg("Failed to normalize machine hostname in DB migration")
|
||||
}
|
||||
|
||||
err = h.RenameMachine(&machines[item], normalizedHostname)
|
||||
err = db.RenameMachine(&machines[item], normalizedHostname)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -192,51 +228,51 @@ func (h *Headscale) initDB() error {
|
|||
}
|
||||
}
|
||||
|
||||
err = db.AutoMigrate(&KV{})
|
||||
err = dbConn.AutoMigrate(&KV{})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.AutoMigrate(&PreAuthKey{})
|
||||
err = dbConn.AutoMigrate(&PreAuthKey{})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.AutoMigrate(&PreAuthKeyACLTag{})
|
||||
err = dbConn.AutoMigrate(&PreAuthKeyACLTag{})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = db.Migrator().DropTable("shared_machines")
|
||||
_ = dbConn.Migrator().DropTable("shared_machines")
|
||||
|
||||
err = db.AutoMigrate(&APIKey{})
|
||||
err = dbConn.AutoMigrate(&APIKey{})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = h.setValue("db_version", dbVersion)
|
||||
// TODO(kradalby): is this needed?
|
||||
err = db.setValue("db_version", dbVersion)
|
||||
|
||||
return err
|
||||
return &db, err
|
||||
}
|
||||
|
||||
func (h *Headscale) openDB() (*gorm.DB, error) {
|
||||
var db *gorm.DB
|
||||
var err error
|
||||
func openDB(dbType, connectionAddr string, debug bool) (*gorm.DB, error) {
|
||||
log.Debug().Str("type", dbType).Str("connection", connectionAddr).Msg("opening database")
|
||||
|
||||
var log logger.Interface
|
||||
if h.dbDebug {
|
||||
log = logger.Default
|
||||
var dbLogger logger.Interface
|
||||
if debug {
|
||||
dbLogger = logger.Default
|
||||
} else {
|
||||
log = logger.Default.LogMode(logger.Silent)
|
||||
dbLogger = logger.Default.LogMode(logger.Silent)
|
||||
}
|
||||
|
||||
switch h.dbType {
|
||||
switch dbType {
|
||||
case Sqlite:
|
||||
db, err = gorm.Open(
|
||||
sqlite.Open(h.dbString+"?_synchronous=1&_journal_mode=WAL"),
|
||||
db, err := gorm.Open(
|
||||
sqlite.Open(connectionAddr+"?_synchronous=1&_journal_mode=WAL"),
|
||||
&gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
Logger: log,
|
||||
Logger: dbLogger,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -250,24 +286,30 @@ func (h *Headscale) openDB() (*gorm.DB, error) {
|
|||
sqlDB.SetMaxOpenConns(1)
|
||||
sqlDB.SetConnMaxIdleTime(time.Hour)
|
||||
|
||||
return db, err
|
||||
|
||||
case Postgres:
|
||||
db, err = gorm.Open(postgres.Open(h.dbString), &gorm.Config{
|
||||
return gorm.Open(postgres.Open(connectionAddr), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
Logger: log,
|
||||
Logger: dbLogger,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf(
|
||||
"database of type %s is not supported: %w",
|
||||
dbType,
|
||||
errDatabaseNotSupported,
|
||||
)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
func (hsdb *HSDatabase) notifyStateChange() {
|
||||
hsdb.notifyStateChan <- struct{}{}
|
||||
}
|
||||
|
||||
// getValue returns the value for the given key in KV.
|
||||
func (h *Headscale) getValue(key string) (string, error) {
|
||||
func (hsdb *HSDatabase) getValue(key string) (string, error) {
|
||||
var row KV
|
||||
if result := h.db.First(&row, "key = ?", key); errors.Is(
|
||||
if result := hsdb.db.First(&row, "key = ?", key); errors.Is(
|
||||
result.Error,
|
||||
gorm.ErrRecordNotFound,
|
||||
) {
|
||||
|
@ -278,34 +320,34 @@ func (h *Headscale) getValue(key string) (string, error) {
|
|||
}
|
||||
|
||||
// setValue sets value for the given key in KV.
|
||||
func (h *Headscale) setValue(key string, value string) error {
|
||||
func (hsdb *HSDatabase) setValue(key string, value string) error {
|
||||
keyValue := KV{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
if _, err := h.getValue(key); err == nil {
|
||||
h.db.Model(&keyValue).Where("key = ?", key).Update("value", value)
|
||||
if _, err := hsdb.getValue(key); err == nil {
|
||||
hsdb.db.Model(&keyValue).Where("key = ?", key).Update("value", value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := h.db.Create(keyValue).Error; err != nil {
|
||||
if err := hsdb.db.Create(keyValue).Error; err != nil {
|
||||
return fmt.Errorf("failed to create key value pair in the database: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Headscale) pingDB(ctx context.Context) error {
|
||||
func (hsdb *HSDatabase) pingDB(ctx context.Context) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second)
|
||||
defer cancel()
|
||||
db, err := h.db.DB()
|
||||
sqlDB, err := hsdb.db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.PingContext(ctx)
|
||||
return sqlDB.PingContext(ctx)
|
||||
}
|
||||
|
||||
// This is a "wrapper" type around tailscales
|
||||
|
|
|
@ -112,16 +112,16 @@ func (s *Suite) TestMagicDNSRootDomainsIPv6SingleMultiple(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||
userShared1, err := app.CreateUser("shared1")
|
||||
userShared1, err := app.db.CreateUser("shared1")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
userShared2, err := app.CreateUser("shared2")
|
||||
userShared2, err := app.db.CreateUser("shared2")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
userShared3, err := app.CreateUser("shared3")
|
||||
userShared3, err := app.db.CreateUser("shared3")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKeyInShared1, err := app.CreatePreAuthKey(
|
||||
preAuthKeyInShared1, err := app.db.CreatePreAuthKey(
|
||||
userShared1.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -130,7 +130,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKeyInShared2, err := app.CreatePreAuthKey(
|
||||
preAuthKeyInShared2, err := app.db.CreatePreAuthKey(
|
||||
userShared2.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -139,7 +139,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKeyInShared3, err := app.CreatePreAuthKey(
|
||||
preAuthKeyInShared3, err := app.db.CreatePreAuthKey(
|
||||
userShared3.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -148,7 +148,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
PreAuthKey2InShared1, err := app.CreatePreAuthKey(
|
||||
PreAuthKey2InShared1, err := app.db.CreatePreAuthKey(
|
||||
userShared1.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -157,7 +157,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine(userShared1.Name, "test_get_shared_nodes_1")
|
||||
_, err = app.db.GetMachine(userShared1.Name, "test_get_shared_nodes_1")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machineInShared1 := &Machine{
|
||||
|
@ -172,9 +172,9 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")},
|
||||
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
||||
}
|
||||
app.db.Save(machineInShared1)
|
||||
app.db.db.Save(machineInShared1)
|
||||
|
||||
_, err = app.GetMachine(userShared1.Name, machineInShared1.Hostname)
|
||||
_, err = app.db.GetMachine(userShared1.Name, machineInShared1.Hostname)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machineInShared2 := &Machine{
|
||||
|
@ -189,9 +189,9 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")},
|
||||
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
||||
}
|
||||
app.db.Save(machineInShared2)
|
||||
app.db.db.Save(machineInShared2)
|
||||
|
||||
_, err = app.GetMachine(userShared2.Name, machineInShared2.Hostname)
|
||||
_, err = app.db.GetMachine(userShared2.Name, machineInShared2.Hostname)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machineInShared3 := &Machine{
|
||||
|
@ -206,9 +206,9 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")},
|
||||
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
||||
}
|
||||
app.db.Save(machineInShared3)
|
||||
app.db.db.Save(machineInShared3)
|
||||
|
||||
_, err = app.GetMachine(userShared3.Name, machineInShared3.Hostname)
|
||||
_, err = app.db.GetMachine(userShared3.Name, machineInShared3.Hostname)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine2InShared1 := &Machine{
|
||||
|
@ -223,7 +223,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.4")},
|
||||
AuthKeyID: uint(PreAuthKey2InShared1.ID),
|
||||
}
|
||||
app.db.Save(machine2InShared1)
|
||||
app.db.db.Save(machine2InShared1)
|
||||
|
||||
baseDomain := "foobar.headscale.net"
|
||||
dnsConfigOrig := tailcfg.DNSConfig{
|
||||
|
@ -232,7 +232,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||
Proxied: true,
|
||||
}
|
||||
|
||||
peersOfMachineInShared1, err := app.getPeers(machineInShared1)
|
||||
peersOfMachineInShared1, err := app.db.getPeers(app.aclPolicy, app.aclRules, machineInShared1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
dnsConfig := getMapResponseDNSConfig(
|
||||
|
@ -259,16 +259,16 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
||||
userShared1, err := app.CreateUser("shared1")
|
||||
userShared1, err := app.db.CreateUser("shared1")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
userShared2, err := app.CreateUser("shared2")
|
||||
userShared2, err := app.db.CreateUser("shared2")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
userShared3, err := app.CreateUser("shared3")
|
||||
userShared3, err := app.db.CreateUser("shared3")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKeyInShared1, err := app.CreatePreAuthKey(
|
||||
preAuthKeyInShared1, err := app.db.CreatePreAuthKey(
|
||||
userShared1.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -277,7 +277,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKeyInShared2, err := app.CreatePreAuthKey(
|
||||
preAuthKeyInShared2, err := app.db.CreatePreAuthKey(
|
||||
userShared2.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -286,7 +286,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKeyInShared3, err := app.CreatePreAuthKey(
|
||||
preAuthKeyInShared3, err := app.db.CreatePreAuthKey(
|
||||
userShared3.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -295,7 +295,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKey2InShared1, err := app.CreatePreAuthKey(
|
||||
preAuthKey2InShared1, err := app.db.CreatePreAuthKey(
|
||||
userShared1.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -304,7 +304,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine(userShared1.Name, "test_get_shared_nodes_1")
|
||||
_, err = app.db.GetMachine(userShared1.Name, "test_get_shared_nodes_1")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machineInShared1 := &Machine{
|
||||
|
@ -319,9 +319,9 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")},
|
||||
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
||||
}
|
||||
app.db.Save(machineInShared1)
|
||||
app.db.db.Save(machineInShared1)
|
||||
|
||||
_, err = app.GetMachine(userShared1.Name, machineInShared1.Hostname)
|
||||
_, err = app.db.GetMachine(userShared1.Name, machineInShared1.Hostname)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machineInShared2 := &Machine{
|
||||
|
@ -336,9 +336,9 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")},
|
||||
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
||||
}
|
||||
app.db.Save(machineInShared2)
|
||||
app.db.db.Save(machineInShared2)
|
||||
|
||||
_, err = app.GetMachine(userShared2.Name, machineInShared2.Hostname)
|
||||
_, err = app.db.GetMachine(userShared2.Name, machineInShared2.Hostname)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machineInShared3 := &Machine{
|
||||
|
@ -353,9 +353,9 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")},
|
||||
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
||||
}
|
||||
app.db.Save(machineInShared3)
|
||||
app.db.db.Save(machineInShared3)
|
||||
|
||||
_, err = app.GetMachine(userShared3.Name, machineInShared3.Hostname)
|
||||
_, err = app.db.GetMachine(userShared3.Name, machineInShared3.Hostname)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine2InShared1 := &Machine{
|
||||
|
@ -370,7 +370,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.4")},
|
||||
AuthKeyID: uint(preAuthKey2InShared1.ID),
|
||||
}
|
||||
app.db.Save(machine2InShared1)
|
||||
app.db.db.Save(machine2InShared1)
|
||||
|
||||
baseDomain := "foobar.headscale.net"
|
||||
dnsConfigOrig := tailcfg.DNSConfig{
|
||||
|
@ -379,7 +379,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||
Proxied: false,
|
||||
}
|
||||
|
||||
peersOfMachine1Shared1, err := app.getPeers(machineInShared1)
|
||||
peersOfMachine1Shared1, err := app.db.getPeers(app.aclPolicy, app.aclRules, machineInShared1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
dnsConfig := getMapResponseDNSConfig(
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
@ -30,7 +31,7 @@ func (api headscaleV1APIServer) GetUser(
|
|||
ctx context.Context,
|
||||
request *v1.GetUserRequest,
|
||||
) (*v1.GetUserResponse, error) {
|
||||
user, err := api.h.GetUser(request.GetName())
|
||||
user, err := api.h.db.GetUser(request.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -42,7 +43,7 @@ func (api headscaleV1APIServer) CreateUser(
|
|||
ctx context.Context,
|
||||
request *v1.CreateUserRequest,
|
||||
) (*v1.CreateUserResponse, error) {
|
||||
user, err := api.h.CreateUser(request.GetName())
|
||||
user, err := api.h.db.CreateUser(request.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -54,12 +55,12 @@ func (api headscaleV1APIServer) RenameUser(
|
|||
ctx context.Context,
|
||||
request *v1.RenameUserRequest,
|
||||
) (*v1.RenameUserResponse, error) {
|
||||
err := api.h.RenameUser(request.GetOldName(), request.GetNewName())
|
||||
err := api.h.db.RenameUser(request.GetOldName(), request.GetNewName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := api.h.GetUser(request.GetNewName())
|
||||
user, err := api.h.db.GetUser(request.GetNewName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -71,7 +72,7 @@ func (api headscaleV1APIServer) DeleteUser(
|
|||
ctx context.Context,
|
||||
request *v1.DeleteUserRequest,
|
||||
) (*v1.DeleteUserResponse, error) {
|
||||
err := api.h.DestroyUser(request.GetName())
|
||||
err := api.h.db.DestroyUser(request.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -83,7 +84,7 @@ func (api headscaleV1APIServer) ListUsers(
|
|||
ctx context.Context,
|
||||
request *v1.ListUsersRequest,
|
||||
) (*v1.ListUsersResponse, error) {
|
||||
users, err := api.h.ListUsers()
|
||||
users, err := api.h.db.ListUsers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -116,7 +117,7 @@ func (api headscaleV1APIServer) CreatePreAuthKey(
|
|||
}
|
||||
}
|
||||
|
||||
preAuthKey, err := api.h.CreatePreAuthKey(
|
||||
preAuthKey, err := api.h.db.CreatePreAuthKey(
|
||||
request.GetUser(),
|
||||
request.GetReusable(),
|
||||
request.GetEphemeral(),
|
||||
|
@ -134,12 +135,12 @@ func (api headscaleV1APIServer) ExpirePreAuthKey(
|
|||
ctx context.Context,
|
||||
request *v1.ExpirePreAuthKeyRequest,
|
||||
) (*v1.ExpirePreAuthKeyResponse, error) {
|
||||
preAuthKey, err := api.h.GetPreAuthKey(request.GetUser(), request.Key)
|
||||
preAuthKey, err := api.h.db.GetPreAuthKey(request.GetUser(), request.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = api.h.ExpirePreAuthKey(preAuthKey)
|
||||
err = api.h.db.ExpirePreAuthKey(preAuthKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -151,7 +152,7 @@ func (api headscaleV1APIServer) ListPreAuthKeys(
|
|||
ctx context.Context,
|
||||
request *v1.ListPreAuthKeysRequest,
|
||||
) (*v1.ListPreAuthKeysResponse, error) {
|
||||
preAuthKeys, err := api.h.ListPreAuthKeys(request.GetUser())
|
||||
preAuthKeys, err := api.h.db.ListPreAuthKeys(request.GetUser())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -173,7 +174,8 @@ func (api headscaleV1APIServer) RegisterMachine(
|
|||
Str("node_key", request.GetKey()).
|
||||
Msg("Registering machine")
|
||||
|
||||
machine, err := api.h.RegisterMachineFromAuthCallback(
|
||||
machine, err := api.h.db.RegisterMachineFromAuthCallback(
|
||||
api.h.registrationCache,
|
||||
request.GetKey(),
|
||||
request.GetUser(),
|
||||
nil,
|
||||
|
@ -190,7 +192,7 @@ func (api headscaleV1APIServer) GetMachine(
|
|||
ctx context.Context,
|
||||
request *v1.GetMachineRequest,
|
||||
) (*v1.GetMachineResponse, error) {
|
||||
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -202,7 +204,7 @@ func (api headscaleV1APIServer) SetTags(
|
|||
ctx context.Context,
|
||||
request *v1.SetTagsRequest,
|
||||
) (*v1.SetTagsResponse, error) {
|
||||
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -216,7 +218,7 @@ func (api headscaleV1APIServer) SetTags(
|
|||
}
|
||||
}
|
||||
|
||||
err = api.h.SetTags(machine, request.GetTags())
|
||||
err = api.h.db.SetTags(machine, request.GetTags(), api.h.UpdateACLRules)
|
||||
if err != nil {
|
||||
return &v1.SetTagsResponse{
|
||||
Machine: nil,
|
||||
|
@ -248,12 +250,12 @@ func (api headscaleV1APIServer) DeleteMachine(
|
|||
ctx context.Context,
|
||||
request *v1.DeleteMachineRequest,
|
||||
) (*v1.DeleteMachineResponse, error) {
|
||||
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = api.h.DeleteMachine(
|
||||
err = api.h.db.DeleteMachine(
|
||||
machine,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -267,12 +269,12 @@ func (api headscaleV1APIServer) ExpireMachine(
|
|||
ctx context.Context,
|
||||
request *v1.ExpireMachineRequest,
|
||||
) (*v1.ExpireMachineResponse, error) {
|
||||
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
api.h.ExpireMachine(
|
||||
api.h.db.ExpireMachine(
|
||||
machine,
|
||||
)
|
||||
|
||||
|
@ -288,12 +290,12 @@ func (api headscaleV1APIServer) RenameMachine(
|
|||
ctx context.Context,
|
||||
request *v1.RenameMachineRequest,
|
||||
) (*v1.RenameMachineResponse, error) {
|
||||
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = api.h.RenameMachine(
|
||||
err = api.h.db.RenameMachine(
|
||||
machine,
|
||||
request.GetNewName(),
|
||||
)
|
||||
|
@ -314,7 +316,7 @@ func (api headscaleV1APIServer) ListMachines(
|
|||
request *v1.ListMachinesRequest,
|
||||
) (*v1.ListMachinesResponse, error) {
|
||||
if request.GetUser() != "" {
|
||||
machines, err := api.h.ListMachinesByUser(request.GetUser())
|
||||
machines, err := api.h.db.ListMachinesByUser(request.GetUser())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -327,7 +329,7 @@ func (api headscaleV1APIServer) ListMachines(
|
|||
return &v1.ListMachinesResponse{Machines: response}, nil
|
||||
}
|
||||
|
||||
machines, err := api.h.ListMachines()
|
||||
machines, err := api.h.db.ListMachines()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -352,12 +354,12 @@ func (api headscaleV1APIServer) MoveMachine(
|
|||
ctx context.Context,
|
||||
request *v1.MoveMachineRequest,
|
||||
) (*v1.MoveMachineResponse, error) {
|
||||
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = api.h.SetMachineUser(machine, request.GetUser())
|
||||
err = api.h.db.SetMachineUser(machine, request.GetUser())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -369,7 +371,7 @@ func (api headscaleV1APIServer) GetRoutes(
|
|||
ctx context.Context,
|
||||
request *v1.GetRoutesRequest,
|
||||
) (*v1.GetRoutesResponse, error) {
|
||||
routes, err := api.h.GetRoutes()
|
||||
routes, err := api.h.db.GetRoutes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -383,7 +385,7 @@ func (api headscaleV1APIServer) EnableRoute(
|
|||
ctx context.Context,
|
||||
request *v1.EnableRouteRequest,
|
||||
) (*v1.EnableRouteResponse, error) {
|
||||
err := api.h.EnableRoute(request.GetRouteId())
|
||||
err := api.h.db.EnableRoute(request.GetRouteId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -395,7 +397,7 @@ func (api headscaleV1APIServer) DisableRoute(
|
|||
ctx context.Context,
|
||||
request *v1.DisableRouteRequest,
|
||||
) (*v1.DisableRouteResponse, error) {
|
||||
err := api.h.DisableRoute(request.GetRouteId())
|
||||
err := api.h.db.DisableRoute(request.GetRouteId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -407,12 +409,12 @@ func (api headscaleV1APIServer) GetMachineRoutes(
|
|||
ctx context.Context,
|
||||
request *v1.GetMachineRoutesRequest,
|
||||
) (*v1.GetMachineRoutesResponse, error) {
|
||||
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||
machine, err := api.h.db.GetMachineByID(request.GetMachineId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routes, err := api.h.GetMachineRoutes(machine)
|
||||
routes, err := api.h.db.GetMachineRoutes(machine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -426,7 +428,7 @@ func (api headscaleV1APIServer) DeleteRoute(
|
|||
ctx context.Context,
|
||||
request *v1.DeleteRouteRequest,
|
||||
) (*v1.DeleteRouteResponse, error) {
|
||||
err := api.h.DeleteRoute(request.GetRouteId())
|
||||
err := api.h.db.DeleteRoute(request.GetRouteId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -443,7 +445,7 @@ func (api headscaleV1APIServer) CreateApiKey(
|
|||
expiration = request.GetExpiration().AsTime()
|
||||
}
|
||||
|
||||
apiKey, _, err := api.h.CreateAPIKey(
|
||||
apiKey, _, err := api.h.db.CreateAPIKey(
|
||||
&expiration,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -460,12 +462,12 @@ func (api headscaleV1APIServer) ExpireApiKey(
|
|||
var apiKey *APIKey
|
||||
var err error
|
||||
|
||||
apiKey, err = api.h.GetAPIKey(request.Prefix)
|
||||
apiKey, err = api.h.db.GetAPIKey(request.Prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = api.h.ExpireAPIKey(apiKey)
|
||||
err = api.h.db.ExpireAPIKey(apiKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -477,7 +479,7 @@ func (api headscaleV1APIServer) ListApiKeys(
|
|||
ctx context.Context,
|
||||
request *v1.ListApiKeysRequest,
|
||||
) (*v1.ListApiKeysResponse, error) {
|
||||
apiKeys, err := api.h.ListAPIKeys()
|
||||
apiKeys, err := api.h.db.ListAPIKeys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -495,12 +497,12 @@ func (api headscaleV1APIServer) DebugCreateMachine(
|
|||
ctx context.Context,
|
||||
request *v1.DebugCreateMachineRequest,
|
||||
) (*v1.DebugCreateMachineResponse, error) {
|
||||
user, err := api.h.GetUser(request.GetUser())
|
||||
user, err := api.h.db.GetUser(request.GetUser())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routes, err := stringToIPPrefix(request.GetRoutes())
|
||||
routes, err := util.StringToIPPrefix(request.GetRoutes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -517,7 +519,7 @@ func (api headscaleV1APIServer) DebugCreateMachine(
|
|||
Hostname: "DebugTestMachine",
|
||||
}
|
||||
|
||||
givenName, err := api.h.GenerateGivenName(request.GetKey(), request.GetName())
|
||||
givenName, err := api.h.db.GenerateGivenName(request.GetKey(), request.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -542,7 +544,7 @@ func (api headscaleV1APIServer) DebugCreateMachine(
|
|||
}
|
||||
|
||||
api.h.registrationCache.Set(
|
||||
NodePublicKeyStripPrefix(nodeKey),
|
||||
util.NodePublicKeyStripPrefix(nodeKey),
|
||||
newMachine,
|
||||
registerCacheExpiration,
|
||||
)
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/samber/lo"
|
||||
"go4.org/netipx"
|
||||
|
@ -21,23 +23,23 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
ErrMachineNotFound = Error("machine not found")
|
||||
ErrMachineRouteIsNotAvailable = Error("route is not available on machine")
|
||||
ErrMachineAddressesInvalid = Error("failed to parse machine addresses")
|
||||
ErrMachineNotFoundRegistrationCache = Error(
|
||||
"machine not found in registration cache",
|
||||
)
|
||||
ErrCouldNotConvertMachineInterface = Error("failed to convert machine interface")
|
||||
ErrHostnameTooLong = Error("Hostname too long")
|
||||
ErrDifferentRegisteredUser = Error(
|
||||
"machine was previously registered with a different user",
|
||||
)
|
||||
MachineGivenNameHashLength = 8
|
||||
MachineGivenNameTrimSize = 2
|
||||
maxHostnameLength = 255
|
||||
)
|
||||
|
||||
const (
|
||||
maxHostnameLength = 255
|
||||
var (
|
||||
ErrMachineNotFound = errors.New("machine not found")
|
||||
ErrMachineRouteIsNotAvailable = errors.New("route is not available on machine")
|
||||
ErrMachineAddressesInvalid = errors.New("failed to parse machine addresses")
|
||||
ErrMachineNotFoundRegistrationCache = errors.New(
|
||||
"machine not found in registration cache",
|
||||
)
|
||||
ErrCouldNotConvertMachineInterface = errors.New("failed to convert machine interface")
|
||||
ErrHostnameTooLong = errors.New("hostname too long")
|
||||
ErrDifferentRegisteredUser = errors.New(
|
||||
"machine was previously registered with a different user",
|
||||
)
|
||||
)
|
||||
|
||||
// Machine is a Headscale client.
|
||||
|
@ -188,8 +190,10 @@ func (machine *Machine) canAccess(filter []tailcfg.FilterRule, machine2 *Machine
|
|||
|
||||
// filterMachinesByACL wrapper function to not have devs pass around locks and maps
|
||||
// related to the application outside of tests.
|
||||
func (h *Headscale) filterMachinesByACL(currentMachine *Machine, peers Machines) Machines {
|
||||
return filterMachinesByACL(currentMachine, peers, h.aclRules)
|
||||
func (hsdb *HSDatabase) filterMachinesByACL(
|
||||
aclRules []tailcfg.FilterRule,
|
||||
currentMachine *Machine, peers Machines) Machines {
|
||||
return filterMachinesByACL(currentMachine, peers, aclRules)
|
||||
}
|
||||
|
||||
// filterMachinesByACL returns the list of peers authorized to be accessed from a given machine.
|
||||
|
@ -213,14 +217,14 @@ func filterMachinesByACL(
|
|||
return result
|
||||
}
|
||||
|
||||
func (h *Headscale) ListPeers(machine *Machine) (Machines, error) {
|
||||
func (hsdb *HSDatabase) ListPeers(machine *Machine) (Machines, error) {
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Finding direct peers")
|
||||
|
||||
machines := Machines{}
|
||||
if err := h.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where("node_key <> ?",
|
||||
if err := hsdb.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where("node_key <> ?",
|
||||
machine.NodeKey).Find(&machines).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Error accessing db")
|
||||
|
||||
|
@ -237,23 +241,27 @@ func (h *Headscale) ListPeers(machine *Machine) (Machines, error) {
|
|||
return machines, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) getPeers(machine *Machine) (Machines, error) {
|
||||
func (hsdb *HSDatabase) getPeers(
|
||||
aclPolicy *ACLPolicy,
|
||||
aclRules []tailcfg.FilterRule,
|
||||
machine *Machine,
|
||||
) (Machines, error) {
|
||||
var peers Machines
|
||||
var err error
|
||||
|
||||
// If ACLs rules are defined, filter visible host list with the ACLs
|
||||
// else use the classic user scope
|
||||
if h.aclPolicy != nil {
|
||||
if aclPolicy != nil {
|
||||
var machines []Machine
|
||||
machines, err = h.ListMachines()
|
||||
machines, err = hsdb.ListMachines()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error retrieving list of machines")
|
||||
|
||||
return Machines{}, err
|
||||
}
|
||||
peers = h.filterMachinesByACL(machine, machines)
|
||||
peers = hsdb.filterMachinesByACL(aclRules, machine, machines)
|
||||
} else {
|
||||
peers, err = h.ListPeers(machine)
|
||||
peers, err = hsdb.ListPeers(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -275,10 +283,14 @@ func (h *Headscale) getPeers(machine *Machine) (Machines, error) {
|
|||
return peers, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) getValidPeers(machine *Machine) (Machines, error) {
|
||||
func (hsdb *HSDatabase) getValidPeers(
|
||||
aclPolicy *ACLPolicy,
|
||||
aclRules []tailcfg.FilterRule,
|
||||
machine *Machine,
|
||||
) (Machines, error) {
|
||||
validPeers := make(Machines, 0)
|
||||
|
||||
peers, err := h.getPeers(machine)
|
||||
peers, err := hsdb.getPeers(aclPolicy, aclRules, machine)
|
||||
if err != nil {
|
||||
return Machines{}, err
|
||||
}
|
||||
|
@ -292,18 +304,18 @@ func (h *Headscale) getValidPeers(machine *Machine) (Machines, error) {
|
|||
return validPeers, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) ListMachines() ([]Machine, error) {
|
||||
func (hsdb *HSDatabase) ListMachines() ([]Machine, error) {
|
||||
machines := []Machine{}
|
||||
if err := h.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Find(&machines).Error; err != nil {
|
||||
if err := hsdb.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Find(&machines).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return machines, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) ListMachinesByGivenName(givenName string) ([]Machine, error) {
|
||||
func (hsdb *HSDatabase) ListMachinesByGivenName(givenName string) ([]Machine, error) {
|
||||
machines := []Machine{}
|
||||
if err := h.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where("given_name = ?", givenName).Find(&machines).Error; err != nil {
|
||||
if err := hsdb.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where("given_name = ?", givenName).Find(&machines).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -311,8 +323,8 @@ func (h *Headscale) ListMachinesByGivenName(givenName string) ([]Machine, error)
|
|||
}
|
||||
|
||||
// GetMachine finds a Machine by name and user and returns the Machine struct.
|
||||
func (h *Headscale) GetMachine(user string, name string) (*Machine, error) {
|
||||
machines, err := h.ListMachinesByUser(user)
|
||||
func (hsdb *HSDatabase) GetMachine(user string, name string) (*Machine, error) {
|
||||
machines, err := hsdb.ListMachinesByUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -327,8 +339,8 @@ func (h *Headscale) GetMachine(user string, name string) (*Machine, error) {
|
|||
}
|
||||
|
||||
// GetMachineByGivenName finds a Machine by given name and user and returns the Machine struct.
|
||||
func (h *Headscale) GetMachineByGivenName(user string, givenName string) (*Machine, error) {
|
||||
machines, err := h.ListMachinesByUser(user)
|
||||
func (hsdb *HSDatabase) GetMachineByGivenName(user string, givenName string) (*Machine, error) {
|
||||
machines, err := hsdb.ListMachinesByUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -343,9 +355,9 @@ func (h *Headscale) GetMachineByGivenName(user string, givenName string) (*Machi
|
|||
}
|
||||
|
||||
// GetMachineByID finds a Machine by ID and returns the Machine struct.
|
||||
func (h *Headscale) GetMachineByID(id uint64) (*Machine, error) {
|
||||
func (hsdb *HSDatabase) GetMachineByID(id uint64) (*Machine, error) {
|
||||
m := Machine{}
|
||||
if result := h.db.Preload("AuthKey").Preload("User").Find(&Machine{ID: id}).First(&m); result.Error != nil {
|
||||
if result := hsdb.db.Preload("AuthKey").Preload("User").Find(&Machine{ID: id}).First(&m); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
|
@ -353,11 +365,11 @@ func (h *Headscale) GetMachineByID(id uint64) (*Machine, error) {
|
|||
}
|
||||
|
||||
// GetMachineByMachineKey finds a Machine by its MachineKey and returns the Machine struct.
|
||||
func (h *Headscale) GetMachineByMachineKey(
|
||||
func (hsdb *HSDatabase) GetMachineByMachineKey(
|
||||
machineKey key.MachinePublic,
|
||||
) (*Machine, error) {
|
||||
m := Machine{}
|
||||
if result := h.db.Preload("AuthKey").Preload("User").First(&m, "machine_key = ?", MachinePublicKeyStripPrefix(machineKey)); result.Error != nil {
|
||||
if result := hsdb.db.Preload("AuthKey").Preload("User").First(&m, "machine_key = ?", util.MachinePublicKeyStripPrefix(machineKey)); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
|
@ -365,12 +377,12 @@ func (h *Headscale) GetMachineByMachineKey(
|
|||
}
|
||||
|
||||
// GetMachineByNodeKey finds a Machine by its current NodeKey.
|
||||
func (h *Headscale) GetMachineByNodeKey(
|
||||
func (hsdb *HSDatabase) GetMachineByNodeKey(
|
||||
nodeKey key.NodePublic,
|
||||
) (*Machine, error) {
|
||||
machine := Machine{}
|
||||
if result := h.db.Preload("AuthKey").Preload("User").First(&machine, "node_key = ?",
|
||||
NodePublicKeyStripPrefix(nodeKey)); result.Error != nil {
|
||||
if result := hsdb.db.Preload("AuthKey").Preload("User").First(&machine, "node_key = ?",
|
||||
util.NodePublicKeyStripPrefix(nodeKey)); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
|
@ -378,14 +390,14 @@ func (h *Headscale) GetMachineByNodeKey(
|
|||
}
|
||||
|
||||
// GetMachineByAnyNodeKey finds a Machine by its MachineKey, its current NodeKey or the old one, and returns the Machine struct.
|
||||
func (h *Headscale) GetMachineByAnyKey(
|
||||
func (hsdb *HSDatabase) GetMachineByAnyKey(
|
||||
machineKey key.MachinePublic, nodeKey key.NodePublic, oldNodeKey key.NodePublic,
|
||||
) (*Machine, error) {
|
||||
machine := Machine{}
|
||||
if result := h.db.Preload("AuthKey").Preload("User").First(&machine, "machine_key = ? OR node_key = ? OR node_key = ?",
|
||||
MachinePublicKeyStripPrefix(machineKey),
|
||||
NodePublicKeyStripPrefix(nodeKey),
|
||||
NodePublicKeyStripPrefix(oldNodeKey)); result.Error != nil {
|
||||
if result := hsdb.db.Preload("AuthKey").Preload("User").First(&machine, "machine_key = ? OR node_key = ? OR node_key = ?",
|
||||
util.MachinePublicKeyStripPrefix(machineKey),
|
||||
util.NodePublicKeyStripPrefix(nodeKey),
|
||||
util.NodePublicKeyStripPrefix(oldNodeKey)); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
|
@ -394,8 +406,8 @@ func (h *Headscale) GetMachineByAnyKey(
|
|||
|
||||
// UpdateMachineFromDatabase takes a Machine struct pointer (typically already loaded from database
|
||||
// and updates it with the latest data from the database.
|
||||
func (h *Headscale) UpdateMachineFromDatabase(machine *Machine) error {
|
||||
if result := h.db.Find(machine).First(&machine); result.Error != nil {
|
||||
func (hsdb *HSDatabase) UpdateMachineFromDatabase(machine *Machine) error {
|
||||
if result := hsdb.db.Find(machine).First(&machine); result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
|
@ -403,20 +415,28 @@ func (h *Headscale) UpdateMachineFromDatabase(machine *Machine) error {
|
|||
}
|
||||
|
||||
// SetTags takes a Machine struct pointer and update the forced tags.
|
||||
func (h *Headscale) SetTags(machine *Machine, tags []string) error {
|
||||
func (hsdb *HSDatabase) SetTags(
|
||||
machine *Machine,
|
||||
tags []string,
|
||||
// TODO(kradalby): This is a temporary measure to be able to detach the
|
||||
// database completely from the global h. In the future, as part of this
|
||||
// reorg, the rules will be generated on a per node basis, and not be prone
|
||||
// to throwing error at save.
|
||||
updateACL func() error) error {
|
||||
newTags := []string{}
|
||||
for _, tag := range tags {
|
||||
if !contains(newTags, tag) {
|
||||
if !util.StringOrPrefixListContains(newTags, tag) {
|
||||
newTags = append(newTags, tag)
|
||||
}
|
||||
}
|
||||
machine.ForcedTags = newTags
|
||||
if err := h.UpdateACLRules(); err != nil && !errors.Is(err, errEmptyPolicy) {
|
||||
if err := updateACL(); err != nil && !errors.Is(err, errEmptyPolicy) {
|
||||
return err
|
||||
}
|
||||
h.setLastStateChangeToNow()
|
||||
|
||||
if err := h.db.Save(machine).Error; err != nil {
|
||||
hsdb.notifyStateChange()
|
||||
|
||||
if err := hsdb.db.Save(machine).Error; err != nil {
|
||||
return fmt.Errorf("failed to update tags for machine in the database: %w", err)
|
||||
}
|
||||
|
||||
|
@ -424,13 +444,13 @@ func (h *Headscale) SetTags(machine *Machine, tags []string) error {
|
|||
}
|
||||
|
||||
// ExpireMachine takes a Machine struct and sets the expire field to now.
|
||||
func (h *Headscale) ExpireMachine(machine *Machine) error {
|
||||
func (hsdb *HSDatabase) ExpireMachine(machine *Machine) error {
|
||||
now := time.Now()
|
||||
machine.Expiry = &now
|
||||
|
||||
h.setLastStateChangeToNow()
|
||||
hsdb.notifyStateChange()
|
||||
|
||||
if err := h.db.Save(machine).Error; err != nil {
|
||||
if err := hsdb.db.Save(machine).Error; err != nil {
|
||||
return fmt.Errorf("failed to expire machine in the database: %w", err)
|
||||
}
|
||||
|
||||
|
@ -439,7 +459,7 @@ func (h *Headscale) ExpireMachine(machine *Machine) error {
|
|||
|
||||
// RenameMachine takes a Machine struct and a new GivenName for the machines
|
||||
// and renames it.
|
||||
func (h *Headscale) RenameMachine(machine *Machine, newName string) error {
|
||||
func (hsdb *HSDatabase) RenameMachine(machine *Machine, newName string) error {
|
||||
err := CheckForFQDNRules(
|
||||
newName,
|
||||
)
|
||||
|
@ -455,9 +475,9 @@ func (h *Headscale) RenameMachine(machine *Machine, newName string) error {
|
|||
}
|
||||
machine.GivenName = newName
|
||||
|
||||
h.setLastStateChangeToNow()
|
||||
hsdb.notifyStateChange()
|
||||
|
||||
if err := h.db.Save(machine).Error; err != nil {
|
||||
if err := hsdb.db.Save(machine).Error; err != nil {
|
||||
return fmt.Errorf("failed to rename machine in the database: %w", err)
|
||||
}
|
||||
|
||||
|
@ -465,15 +485,15 @@ func (h *Headscale) RenameMachine(machine *Machine, newName string) error {
|
|||
}
|
||||
|
||||
// RefreshMachine takes a Machine struct and sets the expire field to now.
|
||||
func (h *Headscale) RefreshMachine(machine *Machine, expiry time.Time) error {
|
||||
func (hsdb *HSDatabase) RefreshMachine(machine *Machine, expiry time.Time) error {
|
||||
now := time.Now()
|
||||
|
||||
machine.LastSuccessfulUpdate = &now
|
||||
machine.Expiry = &expiry
|
||||
|
||||
h.setLastStateChangeToNow()
|
||||
hsdb.notifyStateChange()
|
||||
|
||||
if err := h.db.Save(machine).Error; err != nil {
|
||||
if err := hsdb.db.Save(machine).Error; err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to refresh machine (update expiration) in the database: %w",
|
||||
err,
|
||||
|
@ -484,21 +504,21 @@ func (h *Headscale) RefreshMachine(machine *Machine, expiry time.Time) error {
|
|||
}
|
||||
|
||||
// DeleteMachine softs deletes a Machine from the database.
|
||||
func (h *Headscale) DeleteMachine(machine *Machine) error {
|
||||
err := h.DeleteMachineRoutes(machine)
|
||||
func (hsdb *HSDatabase) DeleteMachine(machine *Machine) error {
|
||||
err := hsdb.DeleteMachineRoutes(machine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&machine).Error; err != nil {
|
||||
if err := hsdb.db.Delete(&machine).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Headscale) TouchMachine(machine *Machine) error {
|
||||
return h.db.Updates(Machine{
|
||||
func (hsdb *HSDatabase) TouchMachine(machine *Machine) error {
|
||||
return hsdb.db.Updates(Machine{
|
||||
ID: machine.ID,
|
||||
LastSeen: machine.LastSeen,
|
||||
LastSuccessfulUpdate: machine.LastSuccessfulUpdate,
|
||||
|
@ -506,13 +526,13 @@ func (h *Headscale) TouchMachine(machine *Machine) error {
|
|||
}
|
||||
|
||||
// HardDeleteMachine hard deletes a Machine from the database.
|
||||
func (h *Headscale) HardDeleteMachine(machine *Machine) error {
|
||||
err := h.DeleteMachineRoutes(machine)
|
||||
func (hsdb *HSDatabase) HardDeleteMachine(machine *Machine) error {
|
||||
err := hsdb.DeleteMachineRoutes(machine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := h.db.Unscoped().Delete(&machine).Error; err != nil {
|
||||
if err := hsdb.db.Unscoped().Delete(&machine).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -524,8 +544,8 @@ func (machine *Machine) GetHostInfo() tailcfg.Hostinfo {
|
|||
return tailcfg.Hostinfo(machine.HostInfo)
|
||||
}
|
||||
|
||||
func (h *Headscale) isOutdated(machine *Machine) bool {
|
||||
if err := h.UpdateMachineFromDatabase(machine); err != nil {
|
||||
func (hsdb *HSDatabase) isOutdated(machine *Machine, lastChange time.Time) bool {
|
||||
if err := hsdb.UpdateMachineFromDatabase(machine); err != nil {
|
||||
// It does not seem meaningful to propagate this error as the end result
|
||||
// will have to be that the machine has to be considered outdated.
|
||||
return true
|
||||
|
@ -536,7 +556,6 @@ func (h *Headscale) isOutdated(machine *Machine) bool {
|
|||
// TODO(kradalby): Only request updates from users where we can talk to nodes
|
||||
// This would mostly be for a bit of performance, and can be calculated based on
|
||||
// ACLs.
|
||||
lastChange := h.getLastStateChange()
|
||||
lastUpdate := machine.CreatedAt
|
||||
if machine.LastSuccessfulUpdate != nil {
|
||||
lastUpdate = *machine.LastSuccessfulUpdate
|
||||
|
@ -576,15 +595,16 @@ func (machines MachinesP) String() string {
|
|||
return fmt.Sprintf("[ %s ](%d)", strings.Join(temp, ", "), len(temp))
|
||||
}
|
||||
|
||||
func (h *Headscale) toNodes(
|
||||
func (hsdb *HSDatabase) toNodes(
|
||||
machines Machines,
|
||||
aclPolicy *ACLPolicy,
|
||||
baseDomain string,
|
||||
dnsConfig *tailcfg.DNSConfig,
|
||||
) ([]*tailcfg.Node, error) {
|
||||
nodes := make([]*tailcfg.Node, len(machines))
|
||||
|
||||
for index, machine := range machines {
|
||||
node, err := h.toNode(machine, baseDomain, dnsConfig)
|
||||
node, err := hsdb.toNode(machine, aclPolicy, baseDomain, dnsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -597,13 +617,14 @@ func (h *Headscale) toNodes(
|
|||
|
||||
// toNode converts a Machine into a Tailscale Node. includeRoutes is false for shared nodes
|
||||
// as per the expected behaviour in the official SaaS.
|
||||
func (h *Headscale) toNode(
|
||||
func (hsdb *HSDatabase) toNode(
|
||||
machine Machine,
|
||||
aclPolicy *ACLPolicy,
|
||||
baseDomain string,
|
||||
dnsConfig *tailcfg.DNSConfig,
|
||||
) (*tailcfg.Node, error) {
|
||||
var nodeKey key.NodePublic
|
||||
err := nodeKey.UnmarshalText([]byte(NodePublicKeyEnsurePrefix(machine.NodeKey)))
|
||||
err := nodeKey.UnmarshalText([]byte(util.NodePublicKeyEnsurePrefix(machine.NodeKey)))
|
||||
if err != nil {
|
||||
log.Trace().
|
||||
Caller().
|
||||
|
@ -617,7 +638,7 @@ func (h *Headscale) toNode(
|
|||
// MachineKey is only used in the legacy protocol
|
||||
if machine.MachineKey != "" {
|
||||
err = machineKey.UnmarshalText(
|
||||
[]byte(MachinePublicKeyEnsurePrefix(machine.MachineKey)),
|
||||
[]byte(util.MachinePublicKeyEnsurePrefix(machine.MachineKey)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse machine public key: %w", err)
|
||||
|
@ -627,7 +648,7 @@ func (h *Headscale) toNode(
|
|||
var discoKey key.DiscoPublic
|
||||
if machine.DiscoKey != "" {
|
||||
err := discoKey.UnmarshalText(
|
||||
[]byte(DiscoPublicKeyEnsurePrefix(machine.DiscoKey)),
|
||||
[]byte(util.DiscoPublicKeyEnsurePrefix(machine.DiscoKey)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse disco public key: %w", err)
|
||||
|
@ -646,13 +667,13 @@ func (h *Headscale) toNode(
|
|||
[]netip.Prefix{},
|
||||
addrs...) // we append the node own IP, as it is required by the clients
|
||||
|
||||
primaryRoutes, err := h.getMachinePrimaryRoutes(&machine)
|
||||
primaryRoutes, err := hsdb.getMachinePrimaryRoutes(&machine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
primaryPrefixes := Routes(primaryRoutes).toPrefixes()
|
||||
|
||||
machineRoutes, err := h.GetMachineRoutes(&machine)
|
||||
machineRoutes, err := hsdb.GetMachineRoutes(&machine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -699,13 +720,13 @@ func (h *Headscale) toNode(
|
|||
|
||||
online := machine.isOnline()
|
||||
|
||||
tags, _ := getTags(h.aclPolicy, machine, h.cfg.OIDC.StripEmaildomain)
|
||||
tags, _ := getTags(aclPolicy, machine, hsdb.stripEmailDomain)
|
||||
tags = lo.Uniq(append(tags, machine.ForcedTags...))
|
||||
|
||||
node := tailcfg.Node{
|
||||
ID: tailcfg.NodeID(machine.ID), // this is the actual ID
|
||||
StableID: tailcfg.StableNodeID(
|
||||
strconv.FormatUint(machine.ID, Base10),
|
||||
strconv.FormatUint(machine.ID, util.Base10),
|
||||
), // in headscale, unlike tailcontrol server, IDs are permanent
|
||||
Name: hostname,
|
||||
|
||||
|
@ -827,7 +848,8 @@ func getTags(
|
|||
return validTags, invalidTags
|
||||
}
|
||||
|
||||
func (h *Headscale) RegisterMachineFromAuthCallback(
|
||||
func (hsdb *HSDatabase) RegisterMachineFromAuthCallback(
|
||||
cache *cache.Cache,
|
||||
nodeKeyStr string,
|
||||
userName string,
|
||||
machineExpiry *time.Time,
|
||||
|
@ -846,9 +868,9 @@ func (h *Headscale) RegisterMachineFromAuthCallback(
|
|||
Str("expiresAt", fmt.Sprintf("%v", machineExpiry)).
|
||||
Msg("Registering machine from API/CLI or auth callback")
|
||||
|
||||
if machineInterface, ok := h.registrationCache.Get(NodePublicKeyStripPrefix(nodeKey)); ok {
|
||||
if machineInterface, ok := cache.Get(util.NodePublicKeyStripPrefix(nodeKey)); ok {
|
||||
if registrationMachine, ok := machineInterface.(Machine); ok {
|
||||
user, err := h.GetUser(userName)
|
||||
user, err := hsdb.GetUser(userName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to find user in register machine from auth callback, %w",
|
||||
|
@ -869,12 +891,12 @@ func (h *Headscale) RegisterMachineFromAuthCallback(
|
|||
registrationMachine.Expiry = machineExpiry
|
||||
}
|
||||
|
||||
machine, err := h.RegisterMachine(
|
||||
machine, err := hsdb.RegisterMachine(
|
||||
registrationMachine,
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
h.registrationCache.Delete(nodeKeyStr)
|
||||
cache.Delete(nodeKeyStr)
|
||||
}
|
||||
|
||||
return machine, err
|
||||
|
@ -887,7 +909,7 @@ func (h *Headscale) RegisterMachineFromAuthCallback(
|
|||
}
|
||||
|
||||
// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
|
||||
func (h *Headscale) RegisterMachine(machine Machine,
|
||||
func (hsdb *HSDatabase) RegisterMachine(machine Machine,
|
||||
) (*Machine, error) {
|
||||
log.Debug().
|
||||
Str("machine", machine.Hostname).
|
||||
|
@ -900,7 +922,7 @@ func (h *Headscale) RegisterMachine(machine Machine,
|
|||
// so we store the machine.Expire and machine.Nodekey that has been set when
|
||||
// adding it to the registrationCache
|
||||
if len(machine.IPAddresses) > 0 {
|
||||
if err := h.db.Save(&machine).Error; err != nil {
|
||||
if err := hsdb.db.Save(&machine).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed register existing machine in the database: %w", err)
|
||||
}
|
||||
|
||||
|
@ -915,10 +937,10 @@ func (h *Headscale) RegisterMachine(machine Machine,
|
|||
return &machine, nil
|
||||
}
|
||||
|
||||
h.ipAllocationMutex.Lock()
|
||||
defer h.ipAllocationMutex.Unlock()
|
||||
hsdb.ipAllocationMutex.Lock()
|
||||
defer hsdb.ipAllocationMutex.Unlock()
|
||||
|
||||
ips, err := h.getAvailableIPs()
|
||||
ips, err := hsdb.getAvailableIPs()
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -931,7 +953,7 @@ func (h *Headscale) RegisterMachine(machine Machine,
|
|||
|
||||
machine.IPAddresses = ips
|
||||
|
||||
if err := h.db.Save(&machine).Error; err != nil {
|
||||
if err := hsdb.db.Save(&machine).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed register(save) machine in the database: %w", err)
|
||||
}
|
||||
|
||||
|
@ -945,10 +967,10 @@ func (h *Headscale) RegisterMachine(machine Machine,
|
|||
}
|
||||
|
||||
// GetAdvertisedRoutes returns the routes that are be advertised by the given machine.
|
||||
func (h *Headscale) GetAdvertisedRoutes(machine *Machine) ([]netip.Prefix, error) {
|
||||
func (hsdb *HSDatabase) GetAdvertisedRoutes(machine *Machine) ([]netip.Prefix, error) {
|
||||
routes := []Route{}
|
||||
|
||||
err := h.db.
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ? AND advertised = ?", machine.ID, true).Find(&routes).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
@ -970,10 +992,10 @@ func (h *Headscale) GetAdvertisedRoutes(machine *Machine) ([]netip.Prefix, error
|
|||
}
|
||||
|
||||
// GetEnabledRoutes returns the routes that are enabled for the machine.
|
||||
func (h *Headscale) GetEnabledRoutes(machine *Machine) ([]netip.Prefix, error) {
|
||||
func (hsdb *HSDatabase) GetEnabledRoutes(machine *Machine) ([]netip.Prefix, error) {
|
||||
routes := []Route{}
|
||||
|
||||
err := h.db.
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ? AND advertised = ? AND enabled = ?", machine.ID, true, true).
|
||||
Find(&routes).Error
|
||||
|
@ -995,13 +1017,13 @@ func (h *Headscale) GetEnabledRoutes(machine *Machine) ([]netip.Prefix, error) {
|
|||
return prefixes, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) IsRoutesEnabled(machine *Machine, routeStr string) bool {
|
||||
func (hsdb *HSDatabase) IsRoutesEnabled(machine *Machine, routeStr string) bool {
|
||||
route, err := netip.ParsePrefix(routeStr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
enabledRoutes, err := h.GetEnabledRoutes(machine)
|
||||
enabledRoutes, err := hsdb.GetEnabledRoutes(machine)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Could not get enabled routes")
|
||||
|
||||
|
@ -1018,7 +1040,7 @@ func (h *Headscale) IsRoutesEnabled(machine *Machine, routeStr string) bool {
|
|||
}
|
||||
|
||||
// enableRoutes enables new routes based on a list of new routes.
|
||||
func (h *Headscale) enableRoutes(machine *Machine, routeStrs ...string) error {
|
||||
func (hsdb *HSDatabase) enableRoutes(machine *Machine, routeStrs ...string) error {
|
||||
newRoutes := make([]netip.Prefix, len(routeStrs))
|
||||
for index, routeStr := range routeStrs {
|
||||
route, err := netip.ParsePrefix(routeStr)
|
||||
|
@ -1029,13 +1051,13 @@ func (h *Headscale) enableRoutes(machine *Machine, routeStrs ...string) error {
|
|||
newRoutes[index] = route
|
||||
}
|
||||
|
||||
advertisedRoutes, err := h.GetAdvertisedRoutes(machine)
|
||||
advertisedRoutes, err := hsdb.GetAdvertisedRoutes(machine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, newRoute := range newRoutes {
|
||||
if !contains(advertisedRoutes, newRoute) {
|
||||
if !util.StringOrPrefixListContains(advertisedRoutes, newRoute) {
|
||||
return fmt.Errorf(
|
||||
"route (%s) is not available on node %s: %w",
|
||||
machine.Hostname,
|
||||
|
@ -1047,7 +1069,7 @@ func (h *Headscale) enableRoutes(machine *Machine, routeStrs ...string) error {
|
|||
// Separate loop so we don't leave things in a half-updated state
|
||||
for _, prefix := range newRoutes {
|
||||
route := Route{}
|
||||
err := h.db.Preload("Machine").
|
||||
err := hsdb.db.Preload("Machine").
|
||||
Where("machine_id = ? AND prefix = ?", machine.ID, IPPrefix(prefix)).
|
||||
First(&route).Error
|
||||
if err == nil {
|
||||
|
@ -1056,10 +1078,10 @@ func (h *Headscale) enableRoutes(machine *Machine, routeStrs ...string) error {
|
|||
// Mark already as primary if there is only this node offering this subnet
|
||||
// (and is not an exit route)
|
||||
if !route.isExitRoute() {
|
||||
route.IsPrimary = h.isUniquePrefix(route)
|
||||
route.IsPrimary = hsdb.isUniquePrefix(route)
|
||||
}
|
||||
|
||||
err = h.db.Save(&route).Error
|
||||
err = hsdb.db.Save(&route).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to enable route: %w", err)
|
||||
}
|
||||
|
@ -1068,19 +1090,19 @@ func (h *Headscale) enableRoutes(machine *Machine, routeStrs ...string) error {
|
|||
}
|
||||
}
|
||||
|
||||
h.setLastStateChangeToNow()
|
||||
hsdb.notifyStateChange()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableAutoApprovedRoutes enables any routes advertised by a machine that match the ACL autoApprovers policy.
|
||||
func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
|
||||
func (hsdb *HSDatabase) EnableAutoApprovedRoutes(aclPolicy *ACLPolicy, machine *Machine) error {
|
||||
if len(machine.IPAddresses) == 0 {
|
||||
return nil // This machine has no IPAddresses, so can't possibly match any autoApprovers ACLs
|
||||
}
|
||||
|
||||
routes := []Route{}
|
||||
err := h.db.
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ? AND advertised = true AND enabled = false", machine.ID).
|
||||
Find(&routes).Error
|
||||
|
@ -1097,7 +1119,7 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
|
|||
approvedRoutes := []Route{}
|
||||
|
||||
for _, advertisedRoute := range routes {
|
||||
routeApprovers, err := h.aclPolicy.AutoApprovers.GetRouteApprovers(
|
||||
routeApprovers, err := aclPolicy.AutoApprovers.GetRouteApprovers(
|
||||
netip.Prefix(advertisedRoute.Prefix),
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -1113,7 +1135,7 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
|
|||
if approvedAlias == machine.User.Name {
|
||||
approvedRoutes = append(approvedRoutes, advertisedRoute)
|
||||
} else {
|
||||
approvedIps, err := h.aclPolicy.expandAlias([]Machine{*machine}, approvedAlias, h.cfg.OIDC.StripEmaildomain)
|
||||
approvedIps, err := aclPolicy.expandAlias([]Machine{*machine}, approvedAlias, hsdb.stripEmailDomain)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("alias", approvedAlias).
|
||||
|
@ -1132,7 +1154,7 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
|
|||
|
||||
for i, approvedRoute := range approvedRoutes {
|
||||
approvedRoutes[i].Enabled = true
|
||||
err = h.db.Save(&approvedRoutes[i]).Error
|
||||
err = hsdb.db.Save(&approvedRoutes[i]).Error
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Str("approvedRoute", approvedRoute.String()).
|
||||
|
@ -1146,10 +1168,10 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (h *Headscale) generateGivenName(suppliedName string, randomSuffix bool) (string, error) {
|
||||
func (hsdb *HSDatabase) generateGivenName(suppliedName string, randomSuffix bool) (string, error) {
|
||||
normalizedHostname, err := NormalizeToFQDNRules(
|
||||
suppliedName,
|
||||
h.cfg.OIDC.StripEmaildomain,
|
||||
hsdb.stripEmailDomain,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -1162,7 +1184,7 @@ func (h *Headscale) generateGivenName(suppliedName string, randomSuffix bool) (s
|
|||
normalizedHostname = normalizedHostname[:trimmedHostnameLength]
|
||||
}
|
||||
|
||||
suffix, err := GenerateRandomStringDNSSafe(MachineGivenNameHashLength)
|
||||
suffix, err := util.GenerateRandomStringDNSSafe(MachineGivenNameHashLength)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -1173,21 +1195,21 @@ func (h *Headscale) generateGivenName(suppliedName string, randomSuffix bool) (s
|
|||
return normalizedHostname, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) GenerateGivenName(machineKey string, suppliedName string) (string, error) {
|
||||
givenName, err := h.generateGivenName(suppliedName, false)
|
||||
func (hsdb *HSDatabase) GenerateGivenName(machineKey string, suppliedName string) (string, error) {
|
||||
givenName, err := hsdb.generateGivenName(suppliedName, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Tailscale rules (may differ) https://tailscale.com/kb/1098/machine-names/
|
||||
machines, err := h.ListMachinesByGivenName(givenName)
|
||||
machines, err := hsdb.ListMachinesByGivenName(givenName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, machine := range machines {
|
||||
if machine.MachineKey != machineKey && machine.GivenName == givenName {
|
||||
postfixedName, err := h.generateGivenName(suppliedName, true)
|
||||
postfixedName, err := hsdb.generateGivenName(suppliedName, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -9,19 +9,20 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"gopkg.in/check.v1"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
func (s *Suite) TestGetMachine(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "testmachine")
|
||||
_, err = app.db.GetMachine("test", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := &Machine{
|
||||
|
@ -34,20 +35,20 @@ func (s *Suite) TestGetMachine(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(machine)
|
||||
app.db.db.Save(machine)
|
||||
|
||||
_, err = app.GetMachine("test", "testmachine")
|
||||
_, err = app.db.GetMachine("test", "testmachine")
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetMachineByID(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachineByID(0)
|
||||
_, err = app.db.GetMachineByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := Machine{
|
||||
|
@ -60,20 +61,20 @@ func (s *Suite) TestGetMachineByID(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
_, err = app.GetMachineByID(0)
|
||||
_, err = app.db.GetMachineByID(0)
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetMachineByNodeKey(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachineByID(0)
|
||||
_, err = app.db.GetMachineByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
nodeKey := key.NewNode()
|
||||
|
@ -81,28 +82,28 @@ func (s *Suite) TestGetMachineByNodeKey(c *check.C) {
|
|||
|
||||
machine := Machine{
|
||||
ID: 0,
|
||||
MachineKey: MachinePublicKeyStripPrefix(machineKey.Public()),
|
||||
NodeKey: NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
MachineKey: util.MachinePublicKeyStripPrefix(machineKey.Public()),
|
||||
NodeKey: util.NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
_, err = app.GetMachineByNodeKey(nodeKey.Public())
|
||||
_, err = app.db.GetMachineByNodeKey(nodeKey.Public())
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetMachineByAnyNodeKey(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachineByID(0)
|
||||
_, err = app.db.GetMachineByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
nodeKey := key.NewNode()
|
||||
|
@ -112,22 +113,22 @@ func (s *Suite) TestGetMachineByAnyNodeKey(c *check.C) {
|
|||
|
||||
machine := Machine{
|
||||
ID: 0,
|
||||
MachineKey: MachinePublicKeyStripPrefix(machineKey.Public()),
|
||||
NodeKey: NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
MachineKey: util.MachinePublicKeyStripPrefix(machineKey.Public()),
|
||||
NodeKey: util.NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
_, err = app.GetMachineByAnyKey(machineKey.Public(), nodeKey.Public(), oldNodeKey.Public())
|
||||
_, err = app.db.GetMachineByAnyKey(machineKey.Public(), nodeKey.Public(), oldNodeKey.Public())
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestDeleteMachine(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
machine := Machine{
|
||||
ID: 0,
|
||||
|
@ -139,17 +140,17 @@ func (s *Suite) TestDeleteMachine(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(1),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
err = app.DeleteMachine(&machine)
|
||||
err = app.db.DeleteMachine(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine(user.Name, "testmachine")
|
||||
_, err = app.db.GetMachine(user.Name, "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestHardDeleteMachine(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
machine := Machine{
|
||||
ID: 0,
|
||||
|
@ -161,23 +162,23 @@ func (s *Suite) TestHardDeleteMachine(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(1),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
err = app.HardDeleteMachine(&machine)
|
||||
err = app.db.HardDeleteMachine(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine(user.Name, "testmachine3")
|
||||
_, err = app.db.GetMachine(user.Name, "testmachine3")
|
||||
c.Assert(err, check.NotNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestListPeers(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachineByID(0)
|
||||
_, err = app.db.GetMachineByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
for index := 0; index <= 10; index++ {
|
||||
|
@ -191,13 +192,13 @@ func (s *Suite) TestListPeers(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
}
|
||||
|
||||
machine0ByID, err := app.GetMachineByID(0)
|
||||
machine0ByID, err := app.db.GetMachineByID(0)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
peersOfMachine0, err := app.ListPeers(machine0ByID)
|
||||
peersOfMachine0, err := app.db.ListPeers(machine0ByID)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(len(peersOfMachine0), check.Equals, 9)
|
||||
|
@ -215,14 +216,14 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
|||
stor := make([]base, 0)
|
||||
|
||||
for _, name := range []string{"test", "admin"} {
|
||||
user, err := app.CreateUser(name)
|
||||
user, err := app.db.CreateUser(name)
|
||||
c.Assert(err, check.IsNil)
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
stor = append(stor, base{user, pak})
|
||||
}
|
||||
|
||||
_, err := app.GetMachineByID(0)
|
||||
_, err := app.db.GetMachineByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
for index := 0; index <= 10; index++ {
|
||||
|
@ -239,7 +240,7 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(stor[index%2].key.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
}
|
||||
|
||||
app.aclPolicy = &ACLPolicy{
|
||||
|
@ -266,19 +267,19 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
|||
err = app.UpdateACLRules()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
adminMachine, err := app.GetMachineByID(1)
|
||||
adminMachine, err := app.db.GetMachineByID(1)
|
||||
c.Logf("Machine(%v), user: %v", adminMachine.Hostname, adminMachine.User)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
testMachine, err := app.GetMachineByID(2)
|
||||
testMachine, err := app.db.GetMachineByID(2)
|
||||
c.Logf("Machine(%v), user: %v", testMachine.Hostname, testMachine.User)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machines, err := app.ListMachines()
|
||||
machines, err := app.db.ListMachines()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
peersOfTestMachine := app.filterMachinesByACL(testMachine, machines)
|
||||
peersOfAdminMachine := app.filterMachinesByACL(adminMachine, machines)
|
||||
peersOfTestMachine := app.db.filterMachinesByACL(app.aclRules, testMachine, machines)
|
||||
peersOfAdminMachine := app.db.filterMachinesByACL(app.aclRules, adminMachine, machines)
|
||||
|
||||
c.Log(peersOfTestMachine)
|
||||
c.Assert(len(peersOfTestMachine), check.Equals, 9)
|
||||
|
@ -294,13 +295,13 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *Suite) TestExpireMachine(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "testmachine")
|
||||
_, err = app.db.GetMachine("test", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := &Machine{
|
||||
|
@ -314,15 +315,15 @@ func (s *Suite) TestExpireMachine(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
Expiry: &time.Time{},
|
||||
}
|
||||
app.db.Save(machine)
|
||||
app.db.db.Save(machine)
|
||||
|
||||
machineFromDB, err := app.GetMachine("test", "testmachine")
|
||||
machineFromDB, err := app.db.GetMachine("test", "testmachine")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(machineFromDB, check.NotNil)
|
||||
|
||||
c.Assert(machineFromDB.isExpired(), check.Equals, false)
|
||||
|
||||
err = app.ExpireMachine(machineFromDB)
|
||||
err = app.db.ExpireMachine(machineFromDB)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(machineFromDB.isExpired(), check.Equals, true)
|
||||
|
@ -350,13 +351,13 @@ func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
|
|||
}
|
||||
|
||||
func (s *Suite) TestGenerateGivenName(c *check.C) {
|
||||
user1, err := app.CreateUser("user-1")
|
||||
user1, err := app.db.CreateUser("user-1")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user1.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user1.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("user-1", "testmachine")
|
||||
_, err = app.db.GetMachine("user-1", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := &Machine{
|
||||
|
@ -370,37 +371,37 @@ func (s *Suite) TestGenerateGivenName(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(machine)
|
||||
app.db.db.Save(machine)
|
||||
|
||||
givenName, err := app.GenerateGivenName("machine-key-2", "hostname-2")
|
||||
givenName, err := app.db.GenerateGivenName("machine-key-2", "hostname-2")
|
||||
comment := check.Commentf("Same user, unique machines, unique hostnames, no conflict")
|
||||
c.Assert(err, check.IsNil, comment)
|
||||
c.Assert(givenName, check.Equals, "hostname-2", comment)
|
||||
|
||||
givenName, err = app.GenerateGivenName("machine-key-1", "hostname-1")
|
||||
givenName, err = app.db.GenerateGivenName("machine-key-1", "hostname-1")
|
||||
comment = check.Commentf("Same user, same machine, same hostname, no conflict")
|
||||
c.Assert(err, check.IsNil, comment)
|
||||
c.Assert(givenName, check.Equals, "hostname-1", comment)
|
||||
|
||||
givenName, err = app.GenerateGivenName("machine-key-2", "hostname-1")
|
||||
givenName, err = app.db.GenerateGivenName("machine-key-2", "hostname-1")
|
||||
comment = check.Commentf("Same user, unique machines, same hostname, conflict")
|
||||
c.Assert(err, check.IsNil, comment)
|
||||
c.Assert(givenName, check.Matches, fmt.Sprintf("^hostname-1-[a-z0-9]{%d}$", MachineGivenNameHashLength), comment)
|
||||
|
||||
givenName, err = app.GenerateGivenName("machine-key-2", "hostname-1")
|
||||
givenName, err = app.db.GenerateGivenName("machine-key-2", "hostname-1")
|
||||
comment = check.Commentf("Unique users, unique machines, same hostname, conflict")
|
||||
c.Assert(err, check.IsNil, comment)
|
||||
c.Assert(givenName, check.Matches, fmt.Sprintf("^hostname-1-[a-z0-9]{%d}$", MachineGivenNameHashLength), comment)
|
||||
}
|
||||
|
||||
func (s *Suite) TestSetTags(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "testmachine")
|
||||
_, err = app.db.GetMachine("test", "testmachine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machine := &Machine{
|
||||
|
@ -413,21 +414,21 @@ func (s *Suite) TestSetTags(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(machine)
|
||||
app.db.db.Save(machine)
|
||||
|
||||
// assign simple tags
|
||||
sTags := []string{"tag:test", "tag:foo"}
|
||||
err = app.SetTags(machine, sTags)
|
||||
err = app.db.SetTags(machine, sTags, app.UpdateACLRules)
|
||||
c.Assert(err, check.IsNil)
|
||||
machine, err = app.GetMachine("test", "testmachine")
|
||||
machine, err = app.db.GetMachine("test", "testmachine")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(machine.ForcedTags, check.DeepEquals, StringList(sTags))
|
||||
|
||||
// assign duplicat tags, expect no errors but no doubles in DB
|
||||
eTags := []string{"tag:bar", "tag:test", "tag:unknown", "tag:test"}
|
||||
err = app.SetTags(machine, eTags)
|
||||
err = app.db.SetTags(machine, eTags, app.UpdateACLRules)
|
||||
c.Assert(err, check.IsNil)
|
||||
machine, err = app.GetMachine("test", "testmachine")
|
||||
machine, err = app.db.GetMachine("test", "testmachine")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(
|
||||
machine.ForcedTags,
|
||||
|
@ -562,7 +563,7 @@ func Test_getTags(t *testing.T) {
|
|||
test.args.stripEmailDomain,
|
||||
)
|
||||
for _, valid := range gotValid {
|
||||
if !contains(test.wantValid, valid) {
|
||||
if !util.StringOrPrefixListContains(test.wantValid, valid) {
|
||||
t.Errorf(
|
||||
"valids: getTags() = %v, want %v",
|
||||
gotValid,
|
||||
|
@ -573,7 +574,7 @@ func Test_getTags(t *testing.T) {
|
|||
}
|
||||
}
|
||||
for _, invalid := range gotInvalid {
|
||||
if !contains(test.wantInvalid, invalid) {
|
||||
if !util.StringOrPrefixListContains(test.wantInvalid, invalid) {
|
||||
t.Errorf(
|
||||
"invalids: getTags() = %v, want %v",
|
||||
gotInvalid,
|
||||
|
@ -1061,19 +1062,15 @@ func TestHeadscale_generateGivenName(t *testing.T) {
|
|||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
h *Headscale
|
||||
db *HSDatabase
|
||||
args args
|
||||
want *regexp.Regexp
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple machine name generation",
|
||||
h: &Headscale{
|
||||
cfg: &Config{
|
||||
OIDC: OIDCConfig{
|
||||
StripEmaildomain: true,
|
||||
},
|
||||
},
|
||||
db: &HSDatabase{
|
||||
stripEmailDomain: true,
|
||||
},
|
||||
args: args{
|
||||
suppliedName: "testmachine",
|
||||
|
@ -1084,12 +1081,8 @@ func TestHeadscale_generateGivenName(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "machine name with 53 chars",
|
||||
h: &Headscale{
|
||||
cfg: &Config{
|
||||
OIDC: OIDCConfig{
|
||||
StripEmaildomain: true,
|
||||
},
|
||||
},
|
||||
db: &HSDatabase{
|
||||
stripEmailDomain: true,
|
||||
},
|
||||
args: args{
|
||||
suppliedName: "testmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaachine",
|
||||
|
@ -1100,12 +1093,8 @@ func TestHeadscale_generateGivenName(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "machine name with 63 chars",
|
||||
h: &Headscale{
|
||||
cfg: &Config{
|
||||
OIDC: OIDCConfig{
|
||||
StripEmaildomain: true,
|
||||
},
|
||||
},
|
||||
db: &HSDatabase{
|
||||
stripEmailDomain: true,
|
||||
},
|
||||
args: args{
|
||||
suppliedName: "machineeee12345678901234567890123456789012345678901234567890123",
|
||||
|
@ -1116,12 +1105,8 @@ func TestHeadscale_generateGivenName(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "machine name with 64 chars",
|
||||
h: &Headscale{
|
||||
cfg: &Config{
|
||||
OIDC: OIDCConfig{
|
||||
StripEmaildomain: true,
|
||||
},
|
||||
},
|
||||
db: &HSDatabase{
|
||||
stripEmailDomain: true,
|
||||
},
|
||||
args: args{
|
||||
suppliedName: "machineeee123456789012345678901234567890123456789012345678901234",
|
||||
|
@ -1132,12 +1117,8 @@ func TestHeadscale_generateGivenName(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "machine name with 73 chars",
|
||||
h: &Headscale{
|
||||
cfg: &Config{
|
||||
OIDC: OIDCConfig{
|
||||
StripEmaildomain: true,
|
||||
},
|
||||
},
|
||||
db: &HSDatabase{
|
||||
stripEmailDomain: true,
|
||||
},
|
||||
args: args{
|
||||
suppliedName: "machineeee123456789012345678901234567890123456789012345678901234567890123",
|
||||
|
@ -1148,12 +1129,8 @@ func TestHeadscale_generateGivenName(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "machine name with random suffix",
|
||||
h: &Headscale{
|
||||
cfg: &Config{
|
||||
OIDC: OIDCConfig{
|
||||
StripEmaildomain: true,
|
||||
},
|
||||
},
|
||||
db: &HSDatabase{
|
||||
stripEmailDomain: true,
|
||||
},
|
||||
args: args{
|
||||
suppliedName: "test",
|
||||
|
@ -1164,12 +1141,8 @@ func TestHeadscale_generateGivenName(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "machine name with 63 chars with random suffix",
|
||||
h: &Headscale{
|
||||
cfg: &Config{
|
||||
OIDC: OIDCConfig{
|
||||
StripEmaildomain: true,
|
||||
},
|
||||
},
|
||||
db: &HSDatabase{
|
||||
stripEmailDomain: true,
|
||||
},
|
||||
args: args{
|
||||
suppliedName: "machineeee12345678901234567890123456789012345678901234567890123",
|
||||
|
@ -1181,7 +1154,7 @@ func TestHeadscale_generateGivenName(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.h.generateGivenName(tt.args.suppliedName, tt.args.randomSuffix)
|
||||
got, err := tt.db.generateGivenName(tt.args.suppliedName, tt.args.randomSuffix)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf(
|
||||
"Headscale.GenerateGivenName() error = %v, wantErr %v",
|
||||
|
@ -1239,10 +1212,10 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
|
|||
err := app.LoadACLPolicyFromBytes(acl, "hujson")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
nodeKey := key.NewNode()
|
||||
|
@ -1255,7 +1228,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
|
|||
machine := Machine{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
NodeKey: util.NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
DiscoKey: "faa",
|
||||
Hostname: "test",
|
||||
UserID: user.ID,
|
||||
|
@ -1268,18 +1241,18 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")},
|
||||
}
|
||||
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
err = app.processMachineRoutes(&machine)
|
||||
err = app.db.processMachineRoutes(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine0ByID, err := app.GetMachineByID(0)
|
||||
machine0ByID, err := app.db.GetMachineByID(0)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.EnableAutoApprovedRoutes(machine0ByID)
|
||||
err = app.db.EnableAutoApprovedRoutes(app.aclPolicy, machine0ByID)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes, err := app.GetEnabledRoutes(machine0ByID)
|
||||
enabledRoutes, err := app.db.GetEnabledRoutes(machine0ByID)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(enabledRoutes, check.HasLen, 3)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/oauth2"
|
||||
"tailscale.com/types/key"
|
||||
|
@ -21,16 +22,22 @@ import (
|
|||
|
||||
const (
|
||||
randomByteSize = 16
|
||||
)
|
||||
|
||||
errEmptyOIDCCallbackParams = Error("empty OIDC callback params")
|
||||
errNoOIDCIDToken = Error("could not extract ID Token for OIDC callback")
|
||||
errOIDCAllowedDomains = Error("authenticated principal does not match any allowed domain")
|
||||
errOIDCAllowedGroups = Error("authenticated principal is not in any allowed group")
|
||||
errOIDCAllowedUsers = Error("authenticated principal does not match any allowed user")
|
||||
errOIDCInvalidMachineState = Error(
|
||||
var (
|
||||
errEmptyOIDCCallbackParams = errors.New("empty OIDC callback params")
|
||||
errNoOIDCIDToken = errors.New("could not extract ID Token for OIDC callback")
|
||||
errOIDCAllowedDomains = errors.New(
|
||||
"authenticated principal does not match any allowed domain",
|
||||
)
|
||||
errOIDCAllowedGroups = errors.New("authenticated principal is not in any allowed group")
|
||||
errOIDCAllowedUsers = errors.New(
|
||||
"authenticated principal does not match any allowed user",
|
||||
)
|
||||
errOIDCInvalidMachineState = errors.New(
|
||||
"requested machine state key expired before authorisation completed",
|
||||
)
|
||||
errOIDCNodeKeyMissing = Error("could not get node key from cache")
|
||||
errOIDCNodeKeyMissing = errors.New("could not get node key from cache")
|
||||
)
|
||||
|
||||
type IDTokenClaims struct {
|
||||
|
@ -94,7 +101,7 @@ func (h *Headscale) RegisterOIDC(
|
|||
Bool("ok", ok).
|
||||
Msg("Received oidc register call")
|
||||
|
||||
if !NodePublicKeyRegex.Match([]byte(nodeKeyStr)) {
|
||||
if !util.NodePublicKeyRegex.Match([]byte(nodeKeyStr)) {
|
||||
log.Warn().Str("node_key", nodeKeyStr).Msg("Invalid node key passed to registration url")
|
||||
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
|
@ -115,7 +122,7 @@ func (h *Headscale) RegisterOIDC(
|
|||
// the template and log an error.
|
||||
var nodeKey key.NodePublic
|
||||
err := nodeKey.UnmarshalText(
|
||||
[]byte(NodePublicKeyEnsurePrefix(nodeKeyStr)),
|
||||
[]byte(util.NodePublicKeyEnsurePrefix(nodeKeyStr)),
|
||||
)
|
||||
|
||||
if !ok || nodeKeyStr == "" || err != nil {
|
||||
|
@ -149,7 +156,11 @@ func (h *Headscale) RegisterOIDC(
|
|||
stateStr := hex.EncodeToString(randomBlob)[:32]
|
||||
|
||||
// place the node key into the state cache, so it can be retrieved later
|
||||
h.registrationCache.Set(stateStr, NodePublicKeyStripPrefix(nodeKey), registerCacheExpiration)
|
||||
h.registrationCache.Set(
|
||||
stateStr,
|
||||
util.NodePublicKeyStripPrefix(nodeKey),
|
||||
registerCacheExpiration,
|
||||
)
|
||||
|
||||
// Add any extra parameter provided in the configuration to the Authorize Endpoint request
|
||||
extras := make([]oauth2.AuthCodeOption, 0, len(h.cfg.OIDC.ExtraParams))
|
||||
|
@ -406,7 +417,7 @@ func validateOIDCAllowedDomains(
|
|||
) error {
|
||||
if len(allowedDomains) > 0 {
|
||||
if at := strings.LastIndex(claims.Email, "@"); at < 0 ||
|
||||
!IsStringInSlice(allowedDomains, claims.Email[at+1:]) {
|
||||
!util.IsStringInSlice(allowedDomains, claims.Email[at+1:]) {
|
||||
log.Error().Msg("authenticated principal does not match any allowed domain")
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
|
@ -436,7 +447,7 @@ func validateOIDCAllowedGroups(
|
|||
) error {
|
||||
if len(allowedGroups) > 0 {
|
||||
for _, group := range allowedGroups {
|
||||
if IsStringInSlice(claims.Groups, group) {
|
||||
if util.IsStringInSlice(claims.Groups, group) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -466,7 +477,7 @@ func validateOIDCAllowedUsers(
|
|||
claims *IDTokenClaims,
|
||||
) error {
|
||||
if len(allowedUsers) > 0 &&
|
||||
!IsStringInSlice(allowedUsers, claims.Email) {
|
||||
!util.IsStringInSlice(allowedUsers, claims.Email) {
|
||||
log.Error().Msg("authenticated principal does not match any allowed user")
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
|
@ -531,7 +542,7 @@ func (h *Headscale) validateMachineForOIDCCallback(
|
|||
}
|
||||
|
||||
err := nodeKey.UnmarshalText(
|
||||
[]byte(NodePublicKeyEnsurePrefix(nodeKeyFromCache)),
|
||||
[]byte(util.NodePublicKeyEnsurePrefix(nodeKeyFromCache)),
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
|
@ -555,7 +566,7 @@ func (h *Headscale) validateMachineForOIDCCallback(
|
|||
// The error is not important, because if it does not
|
||||
// exist, then this is a new machine and we will move
|
||||
// on to registration.
|
||||
machine, _ := h.GetMachineByNodeKey(nodeKey)
|
||||
machine, _ := h.db.GetMachineByNodeKey(nodeKey)
|
||||
|
||||
if machine != nil {
|
||||
log.Trace().
|
||||
|
@ -563,7 +574,7 @@ func (h *Headscale) validateMachineForOIDCCallback(
|
|||
Str("machine", machine.Hostname).
|
||||
Msg("machine already registered, reauthenticating")
|
||||
|
||||
err := h.RefreshMachine(machine, expiry)
|
||||
err := h.db.RefreshMachine(machine, expiry)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -653,9 +664,9 @@ func (h *Headscale) findOrCreateNewUserForOIDCCallback(
|
|||
writer http.ResponseWriter,
|
||||
userName string,
|
||||
) (*User, error) {
|
||||
user, err := h.GetUser(userName)
|
||||
user, err := h.db.GetUser(userName)
|
||||
if errors.Is(err, ErrUserNotFound) {
|
||||
user, err = h.CreateUser(userName)
|
||||
user, err = h.db.CreateUser(userName)
|
||||
|
||||
if err != nil {
|
||||
log.Error().
|
||||
|
@ -702,7 +713,9 @@ func (h *Headscale) registerMachineForOIDCCallback(
|
|||
nodeKey *key.NodePublic,
|
||||
expiry time.Time,
|
||||
) error {
|
||||
if _, err := h.RegisterMachineFromAuthCallback(
|
||||
if _, err := h.db.RegisterMachineFromAuthCallback(
|
||||
// TODO(kradalby): find a better way to use the cache across modules
|
||||
h.registrationCache,
|
||||
nodeKey.String(),
|
||||
user.Name,
|
||||
&expiry,
|
||||
|
|
|
@ -10,16 +10,17 @@ import (
|
|||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrPreAuthKeyNotFound = Error("AuthKey not found")
|
||||
ErrPreAuthKeyExpired = Error("AuthKey expired")
|
||||
ErrSingleUseAuthKeyHasBeenUsed = Error("AuthKey has already been used")
|
||||
ErrUserMismatch = Error("user mismatch")
|
||||
ErrPreAuthKeyACLTagInvalid = Error("AuthKey tag is invalid")
|
||||
var (
|
||||
ErrPreAuthKeyNotFound = errors.New("AuthKey not found")
|
||||
ErrPreAuthKeyExpired = errors.New("AuthKey expired")
|
||||
ErrSingleUseAuthKeyHasBeenUsed = errors.New("AuthKey has already been used")
|
||||
ErrUserMismatch = errors.New("user mismatch")
|
||||
ErrPreAuthKeyACLTagInvalid = errors.New("AuthKey tag is invalid")
|
||||
)
|
||||
|
||||
// PreAuthKey describes a pre-authorization key usable in a particular user.
|
||||
|
@ -45,26 +46,30 @@ type PreAuthKeyACLTag struct {
|
|||
}
|
||||
|
||||
// CreatePreAuthKey creates a new PreAuthKey in a user, and returns it.
|
||||
func (h *Headscale) CreatePreAuthKey(
|
||||
func (hsdb *HSDatabase) CreatePreAuthKey(
|
||||
userName string,
|
||||
reusable bool,
|
||||
ephemeral bool,
|
||||
expiration *time.Time,
|
||||
aclTags []string,
|
||||
) (*PreAuthKey, error) {
|
||||
user, err := h.GetUser(userName)
|
||||
user, err := hsdb.GetUser(userName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, tag := range aclTags {
|
||||
if !strings.HasPrefix(tag, "tag:") {
|
||||
return nil, fmt.Errorf("%w: '%s' did not begin with 'tag:'", ErrPreAuthKeyACLTagInvalid, tag)
|
||||
return nil, fmt.Errorf(
|
||||
"%w: '%s' did not begin with 'tag:'",
|
||||
ErrPreAuthKeyACLTagInvalid,
|
||||
tag,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
kstr, err := h.generateKey()
|
||||
kstr, err := hsdb.generateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -79,7 +84,7 @@ func (h *Headscale) CreatePreAuthKey(
|
|||
Expiration: expiration,
|
||||
}
|
||||
|
||||
err = h.db.Transaction(func(db *gorm.DB) error {
|
||||
err = hsdb.db.Transaction(func(db *gorm.DB) error {
|
||||
if err := db.Save(&key).Error; err != nil {
|
||||
return fmt.Errorf("failed to create key in the database: %w", err)
|
||||
}
|
||||
|
@ -111,14 +116,14 @@ func (h *Headscale) CreatePreAuthKey(
|
|||
}
|
||||
|
||||
// ListPreAuthKeys returns the list of PreAuthKeys for a user.
|
||||
func (h *Headscale) ListPreAuthKeys(userName string) ([]PreAuthKey, error) {
|
||||
user, err := h.GetUser(userName)
|
||||
func (hsdb *HSDatabase) ListPreAuthKeys(userName string) ([]PreAuthKey, error) {
|
||||
user, err := hsdb.GetUser(userName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := []PreAuthKey{}
|
||||
if err := h.db.Preload("User").Preload("ACLTags").Where(&PreAuthKey{UserID: user.ID}).Find(&keys).Error; err != nil {
|
||||
if err := hsdb.db.Preload("User").Preload("ACLTags").Where(&PreAuthKey{UserID: user.ID}).Find(&keys).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -126,8 +131,8 @@ func (h *Headscale) ListPreAuthKeys(userName string) ([]PreAuthKey, error) {
|
|||
}
|
||||
|
||||
// GetPreAuthKey returns a PreAuthKey for a given key.
|
||||
func (h *Headscale) GetPreAuthKey(user string, key string) (*PreAuthKey, error) {
|
||||
pak, err := h.checkKeyValidity(key)
|
||||
func (hsdb *HSDatabase) GetPreAuthKey(user string, key string) (*PreAuthKey, error) {
|
||||
pak, err := hsdb.checkKeyValidity(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -141,8 +146,8 @@ func (h *Headscale) GetPreAuthKey(user string, key string) (*PreAuthKey, error)
|
|||
|
||||
// DestroyPreAuthKey destroys a preauthkey. Returns error if the PreAuthKey
|
||||
// does not exist.
|
||||
func (h *Headscale) DestroyPreAuthKey(pak PreAuthKey) error {
|
||||
return h.db.Transaction(func(db *gorm.DB) error {
|
||||
func (hsdb *HSDatabase) DestroyPreAuthKey(pak PreAuthKey) error {
|
||||
return hsdb.db.Transaction(func(db *gorm.DB) error {
|
||||
if result := db.Unscoped().Where(PreAuthKeyACLTag{PreAuthKeyID: pak.ID}).Delete(&PreAuthKeyACLTag{}); result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
@ -156,8 +161,8 @@ func (h *Headscale) DestroyPreAuthKey(pak PreAuthKey) error {
|
|||
}
|
||||
|
||||
// MarkExpirePreAuthKey marks a PreAuthKey as expired.
|
||||
func (h *Headscale) ExpirePreAuthKey(k *PreAuthKey) error {
|
||||
if err := h.db.Model(&k).Update("Expiration", time.Now()).Error; err != nil {
|
||||
func (hsdb *HSDatabase) ExpirePreAuthKey(k *PreAuthKey) error {
|
||||
if err := hsdb.db.Model(&k).Update("Expiration", time.Now()).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -165,9 +170,9 @@ func (h *Headscale) ExpirePreAuthKey(k *PreAuthKey) error {
|
|||
}
|
||||
|
||||
// UsePreAuthKey marks a PreAuthKey as used.
|
||||
func (h *Headscale) UsePreAuthKey(k *PreAuthKey) error {
|
||||
func (hsdb *HSDatabase) UsePreAuthKey(k *PreAuthKey) error {
|
||||
k.Used = true
|
||||
if err := h.db.Save(k).Error; err != nil {
|
||||
if err := hsdb.db.Save(k).Error; err != nil {
|
||||
return fmt.Errorf("failed to update key used status in the database: %w", err)
|
||||
}
|
||||
|
||||
|
@ -176,9 +181,9 @@ func (h *Headscale) UsePreAuthKey(k *PreAuthKey) error {
|
|||
|
||||
// checkKeyValidity does the heavy lifting for validation of the PreAuthKey coming from a node
|
||||
// If returns no error and a PreAuthKey, it can be used.
|
||||
func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
|
||||
func (hsdb *HSDatabase) checkKeyValidity(k string) (*PreAuthKey, error) {
|
||||
pak := PreAuthKey{}
|
||||
if result := h.db.Preload("User").Preload("ACLTags").First(&pak, "key = ?", k); errors.Is(
|
||||
if result := hsdb.db.Preload("User").Preload("ACLTags").First(&pak, "key = ?", k); errors.Is(
|
||||
result.Error,
|
||||
gorm.ErrRecordNotFound,
|
||||
) {
|
||||
|
@ -194,7 +199,7 @@ func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
|
|||
}
|
||||
|
||||
machines := []Machine{}
|
||||
if err := h.db.Preload("AuthKey").Where(&Machine{AuthKeyID: uint(pak.ID)}).Find(&machines).Error; err != nil {
|
||||
if err := hsdb.db.Preload("AuthKey").Where(&Machine{AuthKeyID: uint(pak.ID)}).Find(&machines).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -205,7 +210,7 @@ func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
|
|||
return &pak, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) generateKey() (string, error) {
|
||||
func (hsdb *HSDatabase) generateKey() (string, error) {
|
||||
size := 24
|
||||
bytes := make([]byte, size)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
|
@ -218,7 +223,7 @@ func (h *Headscale) generateKey() (string, error) {
|
|||
func (key *PreAuthKey) toProto() *v1.PreAuthKey {
|
||||
protoKey := v1.PreAuthKey{
|
||||
User: key.User.Name,
|
||||
Id: strconv.FormatUint(key.ID, Base10),
|
||||
Id: strconv.FormatUint(key.ID, util.Base10),
|
||||
Key: key.Key,
|
||||
Ephemeral: key.Ephemeral,
|
||||
Reusable: key.Reusable,
|
||||
|
|
|
@ -7,14 +7,14 @@ import (
|
|||
)
|
||||
|
||||
func (*Suite) TestCreatePreAuthKey(c *check.C) {
|
||||
_, err := app.CreatePreAuthKey("bogus", true, false, nil, nil)
|
||||
_, err := app.db.CreatePreAuthKey("bogus", true, false, nil, nil)
|
||||
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
key, err := app.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
||||
key, err := app.db.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// Did we get a valid key?
|
||||
|
@ -24,10 +24,10 @@ func (*Suite) TestCreatePreAuthKey(c *check.C) {
|
|||
// Make sure the User association is populated
|
||||
c.Assert(key.User.Name, check.Equals, user.Name)
|
||||
|
||||
_, err = app.ListPreAuthKeys("bogus")
|
||||
_, err = app.db.ListPreAuthKeys("bogus")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
keys, err := app.ListPreAuthKeys(user.Name)
|
||||
keys, err := app.db.ListPreAuthKeys(user.Name)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(keys), check.Equals, 1)
|
||||
|
||||
|
@ -36,41 +36,41 @@ func (*Suite) TestCreatePreAuthKey(c *check.C) {
|
|||
}
|
||||
|
||||
func (*Suite) TestExpiredPreAuthKey(c *check.C) {
|
||||
user, err := app.CreateUser("test2")
|
||||
user, err := app.db.CreateUser("test2")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
now := time.Now()
|
||||
pak, err := app.CreatePreAuthKey(user.Name, true, false, &now, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, true, false, &now, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
key, err := app.checkKeyValidity(pak.Key)
|
||||
key, err := app.db.checkKeyValidity(pak.Key)
|
||||
c.Assert(err, check.Equals, ErrPreAuthKeyExpired)
|
||||
c.Assert(key, check.IsNil)
|
||||
}
|
||||
|
||||
func (*Suite) TestPreAuthKeyDoesNotExist(c *check.C) {
|
||||
key, err := app.checkKeyValidity("potatoKey")
|
||||
key, err := app.db.checkKeyValidity("potatoKey")
|
||||
c.Assert(err, check.Equals, ErrPreAuthKeyNotFound)
|
||||
c.Assert(key, check.IsNil)
|
||||
}
|
||||
|
||||
func (*Suite) TestValidateKeyOk(c *check.C) {
|
||||
user, err := app.CreateUser("test3")
|
||||
user, err := app.db.CreateUser("test3")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
key, err := app.checkKeyValidity(pak.Key)
|
||||
key, err := app.db.checkKeyValidity(pak.Key)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(key.ID, check.Equals, pak.ID)
|
||||
}
|
||||
|
||||
func (*Suite) TestAlreadyUsedKey(c *check.C) {
|
||||
user, err := app.CreateUser("test4")
|
||||
user, err := app.db.CreateUser("test4")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine := Machine{
|
||||
|
@ -83,18 +83,18 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
key, err := app.checkKeyValidity(pak.Key)
|
||||
key, err := app.db.checkKeyValidity(pak.Key)
|
||||
c.Assert(err, check.Equals, ErrSingleUseAuthKeyHasBeenUsed)
|
||||
c.Assert(key, check.IsNil)
|
||||
}
|
||||
|
||||
func (*Suite) TestReusableBeingUsedKey(c *check.C) {
|
||||
user, err := app.CreateUser("test5")
|
||||
user, err := app.db.CreateUser("test5")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine := Machine{
|
||||
|
@ -107,30 +107,30 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
key, err := app.checkKeyValidity(pak.Key)
|
||||
key, err := app.db.checkKeyValidity(pak.Key)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(key.ID, check.Equals, pak.ID)
|
||||
}
|
||||
|
||||
func (*Suite) TestNotReusableNotBeingUsedKey(c *check.C) {
|
||||
user, err := app.CreateUser("test6")
|
||||
user, err := app.db.CreateUser("test6")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
key, err := app.checkKeyValidity(pak.Key)
|
||||
key, err := app.db.checkKeyValidity(pak.Key)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(key.ID, check.Equals, pak.ID)
|
||||
}
|
||||
|
||||
func (*Suite) TestEphemeralKey(c *check.C) {
|
||||
user, err := app.CreateUser("test7")
|
||||
user, err := app.db.CreateUser("test7")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, true, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, true, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
now := time.Now()
|
||||
|
@ -145,65 +145,65 @@ func (*Suite) TestEphemeralKey(c *check.C) {
|
|||
LastSeen: &now,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
_, err = app.checkKeyValidity(pak.Key)
|
||||
_, err = app.db.checkKeyValidity(pak.Key)
|
||||
// Ephemeral keys are by definition reusable
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test7", "testest")
|
||||
_, err = app.db.GetMachine("test7", "testest")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
app.expireEphemeralNodesWorker()
|
||||
|
||||
// The machine record should have been deleted
|
||||
_, err = app.GetMachine("test7", "testest")
|
||||
_, err = app.db.GetMachine("test7", "testest")
|
||||
c.Assert(err, check.NotNil)
|
||||
}
|
||||
|
||||
func (*Suite) TestExpirePreauthKey(c *check.C) {
|
||||
user, err := app.CreateUser("test3")
|
||||
user, err := app.db.CreateUser("test3")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, true, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(pak.Expiration, check.IsNil)
|
||||
|
||||
err = app.ExpirePreAuthKey(pak)
|
||||
err = app.db.ExpirePreAuthKey(pak)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(pak.Expiration, check.NotNil)
|
||||
|
||||
key, err := app.checkKeyValidity(pak.Key)
|
||||
key, err := app.db.checkKeyValidity(pak.Key)
|
||||
c.Assert(err, check.Equals, ErrPreAuthKeyExpired)
|
||||
c.Assert(key, check.IsNil)
|
||||
}
|
||||
|
||||
func (*Suite) TestNotReusableMarkedAsUsed(c *check.C) {
|
||||
user, err := app.CreateUser("test6")
|
||||
user, err := app.db.CreateUser("test6")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
pak.Used = true
|
||||
app.db.Save(&pak)
|
||||
app.db.db.Save(&pak)
|
||||
|
||||
_, err = app.checkKeyValidity(pak.Key)
|
||||
_, err = app.db.checkKeyValidity(pak.Key)
|
||||
c.Assert(err, check.Equals, ErrSingleUseAuthKeyHasBeenUsed)
|
||||
}
|
||||
|
||||
func (*Suite) TestPreAuthKeyACLTags(c *check.C) {
|
||||
user, err := app.CreateUser("test8")
|
||||
user, err := app.db.CreateUser("test8")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.CreatePreAuthKey(user.Name, false, false, nil, []string{"badtag"})
|
||||
_, err = app.db.CreatePreAuthKey(user.Name, false, false, nil, []string{"badtag"})
|
||||
c.Assert(err, check.NotNil) // Confirm that malformed tags are rejected
|
||||
|
||||
tags := []string{"tag:test1", "tag:test2"}
|
||||
tagsWithDuplicate := []string{"tag:test1", "tag:test2", "tag:test2"}
|
||||
_, err = app.CreatePreAuthKey(user.Name, false, false, nil, tagsWithDuplicate)
|
||||
_, err = app.db.CreatePreAuthKey(user.Name, false, false, nil, tagsWithDuplicate)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
listedPaks, err := app.ListPreAuthKeys("test8")
|
||||
listedPaks, err := app.db.ListPreAuthKeys("test8")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(listedPaks[0].toProto().AclTags, check.DeepEquals, tags)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
|
@ -82,7 +83,7 @@ func (h *Headscale) KeyHandler(
|
|||
// Old clients don't send a 'v' parameter, so we send the legacy public key
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err := writer.Write([]byte(MachinePublicKeyStripPrefix(h.privateKey.Public())))
|
||||
_, err := writer.Write([]byte(util.MachinePublicKeyStripPrefix(h.privateKey.Public())))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -102,7 +103,7 @@ func (h *Headscale) handleRegisterCommon(
|
|||
isNoise bool,
|
||||
) {
|
||||
now := time.Now().UTC()
|
||||
machine, err := h.GetMachineByAnyKey(machineKey, registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||
machine, err := h.db.GetMachineByAnyKey(machineKey, registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// If the machine has AuthKey set, handle registration via PreAuthKeys
|
||||
if registerRequest.Auth.AuthKey != "" {
|
||||
|
@ -120,7 +121,7 @@ func (h *Headscale) handleRegisterCommon(
|
|||
// is that the client will hammer headscale with requests until it gets a
|
||||
// successful RegisterResponse.
|
||||
if registerRequest.Followup != "" {
|
||||
if _, ok := h.registrationCache.Get(NodePublicKeyStripPrefix(registerRequest.NodeKey)); ok {
|
||||
if _, ok := h.registrationCache.Get(util.NodePublicKeyStripPrefix(registerRequest.NodeKey)); ok {
|
||||
log.Debug().
|
||||
Caller().
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
|
@ -152,7 +153,7 @@ func (h *Headscale) handleRegisterCommon(
|
|||
Bool("noise", isNoise).
|
||||
Msg("New machine not yet in the database")
|
||||
|
||||
givenName, err := h.GenerateGivenName(
|
||||
givenName, err := h.db.GenerateGivenName(
|
||||
machineKey.String(),
|
||||
registerRequest.Hostinfo.Hostname,
|
||||
)
|
||||
|
@ -171,10 +172,10 @@ func (h *Headscale) handleRegisterCommon(
|
|||
// We create the machine and then keep it around until a callback
|
||||
// happens
|
||||
newMachine := Machine{
|
||||
MachineKey: MachinePublicKeyStripPrefix(machineKey),
|
||||
MachineKey: util.MachinePublicKeyStripPrefix(machineKey),
|
||||
Hostname: registerRequest.Hostinfo.Hostname,
|
||||
GivenName: givenName,
|
||||
NodeKey: NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
NodeKey: util.NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
LastSeen: &now,
|
||||
Expiry: &time.Time{},
|
||||
}
|
||||
|
@ -210,11 +211,11 @@ func (h *Headscale) handleRegisterCommon(
|
|||
// So if we have a not valid MachineKey (but we were able to fetch the machine with the NodeKeys), we update it.
|
||||
var storedMachineKey key.MachinePublic
|
||||
err = storedMachineKey.UnmarshalText(
|
||||
[]byte(MachinePublicKeyEnsurePrefix(machine.MachineKey)),
|
||||
[]byte(util.MachinePublicKeyEnsurePrefix(machine.MachineKey)),
|
||||
)
|
||||
if err != nil || storedMachineKey.IsZero() {
|
||||
machine.MachineKey = MachinePublicKeyStripPrefix(machineKey)
|
||||
if err := h.db.Save(&machine).Error; err != nil {
|
||||
machine.MachineKey = util.MachinePublicKeyStripPrefix(machineKey)
|
||||
if err := h.db.db.Save(&machine).Error; err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "RegistrationHandler").
|
||||
|
@ -231,7 +232,7 @@ func (h *Headscale) handleRegisterCommon(
|
|||
// - Trying to log out (sending a expiry in the past)
|
||||
// - A valid, registered machine, looking for /map
|
||||
// - Expired machine wanting to reauthenticate
|
||||
if machine.NodeKey == NodePublicKeyStripPrefix(registerRequest.NodeKey) {
|
||||
if machine.NodeKey == util.NodePublicKeyStripPrefix(registerRequest.NodeKey) {
|
||||
// The client sends an Expiry in the past if the client is requesting to expire the key (aka logout)
|
||||
// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648
|
||||
if !registerRequest.Expiry.IsZero() &&
|
||||
|
@ -251,7 +252,7 @@ func (h *Headscale) handleRegisterCommon(
|
|||
}
|
||||
|
||||
// The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration
|
||||
if machine.NodeKey == NodePublicKeyStripPrefix(registerRequest.OldNodeKey) &&
|
||||
if machine.NodeKey == util.NodePublicKeyStripPrefix(registerRequest.OldNodeKey) &&
|
||||
!machine.isExpired() {
|
||||
h.handleMachineRefreshKeyCommon(
|
||||
writer,
|
||||
|
@ -282,9 +283,9 @@ func (h *Headscale) handleRegisterCommon(
|
|||
// we need to make sure the NodeKey matches the one in the request
|
||||
// TODO(juan): What happens when using fast user switching between two
|
||||
// headscale-managed tailnets?
|
||||
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
machine.NodeKey = util.NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
h.registrationCache.Set(
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
util.NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
*machine,
|
||||
registerCacheExpiration,
|
||||
)
|
||||
|
@ -311,7 +312,7 @@ func (h *Headscale) handleAuthKeyCommon(
|
|||
Msgf("Processing auth key for %s", registerRequest.Hostinfo.Hostname)
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
pak, err := h.checkKeyValidity(registerRequest.Auth.AuthKey)
|
||||
pak, err := h.db.checkKeyValidity(registerRequest.Auth.AuthKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -372,13 +373,13 @@ func (h *Headscale) handleAuthKeyCommon(
|
|||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Msg("Authentication key was valid, proceeding to acquire IP addresses")
|
||||
|
||||
nodeKey := NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
nodeKey := util.NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
|
||||
// retrieve machine information if it exist
|
||||
// The error is not important, because if it does not
|
||||
// exist, then this is a new machine and we will move
|
||||
// on to registration.
|
||||
machine, _ := h.GetMachineByAnyKey(machineKey, registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||
machine, _ := h.db.GetMachineByAnyKey(machineKey, registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||
if machine != nil {
|
||||
log.Trace().
|
||||
Caller().
|
||||
|
@ -388,7 +389,7 @@ func (h *Headscale) handleAuthKeyCommon(
|
|||
|
||||
machine.NodeKey = nodeKey
|
||||
machine.AuthKeyID = uint(pak.ID)
|
||||
err := h.RefreshMachine(machine, registerRequest.Expiry)
|
||||
err := h.db.RefreshMachine(machine, registerRequest.Expiry)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -403,7 +404,7 @@ func (h *Headscale) handleAuthKeyCommon(
|
|||
aclTags := pak.toProto().AclTags
|
||||
if len(aclTags) > 0 {
|
||||
// This conditional preserves the existing behaviour, although SaaS would reset the tags on auth-key login
|
||||
err = h.SetTags(machine, aclTags)
|
||||
err = h.db.SetTags(machine, aclTags, h.UpdateACLRules)
|
||||
|
||||
if err != nil {
|
||||
log.Error().
|
||||
|
@ -420,7 +421,7 @@ func (h *Headscale) handleAuthKeyCommon(
|
|||
} else {
|
||||
now := time.Now().UTC()
|
||||
|
||||
givenName, err := h.GenerateGivenName(MachinePublicKeyStripPrefix(machineKey), registerRequest.Hostinfo.Hostname)
|
||||
givenName, err := h.db.GenerateGivenName(util.MachinePublicKeyStripPrefix(machineKey), registerRequest.Hostinfo.Hostname)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -436,7 +437,7 @@ func (h *Headscale) handleAuthKeyCommon(
|
|||
Hostname: registerRequest.Hostinfo.Hostname,
|
||||
GivenName: givenName,
|
||||
UserID: pak.User.ID,
|
||||
MachineKey: MachinePublicKeyStripPrefix(machineKey),
|
||||
MachineKey: util.MachinePublicKeyStripPrefix(machineKey),
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
Expiry: ®isterRequest.Expiry,
|
||||
NodeKey: nodeKey,
|
||||
|
@ -445,7 +446,7 @@ func (h *Headscale) handleAuthKeyCommon(
|
|||
ForcedTags: pak.toProto().AclTags,
|
||||
}
|
||||
|
||||
machine, err = h.RegisterMachine(
|
||||
machine, err = h.db.RegisterMachine(
|
||||
machineToRegister,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -462,7 +463,7 @@ func (h *Headscale) handleAuthKeyCommon(
|
|||
}
|
||||
}
|
||||
|
||||
err = h.UsePreAuthKey(pak)
|
||||
err = h.db.UsePreAuthKey(pak)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -591,7 +592,7 @@ func (h *Headscale) handleMachineLogOutCommon(
|
|||
Str("machine", machine.Hostname).
|
||||
Msg("Client requested logout")
|
||||
|
||||
err := h.ExpireMachine(&machine)
|
||||
err := h.db.ExpireMachine(&machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -634,7 +635,7 @@ func (h *Headscale) handleMachineLogOutCommon(
|
|||
}
|
||||
|
||||
if machine.isEphemeral() {
|
||||
err = h.HardDeleteMachine(&machine)
|
||||
err = h.db.HardDeleteMachine(&machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
|
@ -720,9 +721,9 @@ func (h *Headscale) handleMachineRefreshKeyCommon(
|
|||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("We have the OldNodeKey in the database. This is a key refresh")
|
||||
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
machine.NodeKey = util.NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
|
||||
if err := h.db.Save(&machine).Error; err != nil {
|
||||
if err := h.db.db.Save(&machine).Error; err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
@ -29,10 +30,10 @@ func (h *Headscale) handlePollCommon(
|
|||
) {
|
||||
machine.Hostname = mapRequest.Hostinfo.Hostname
|
||||
machine.HostInfo = HostInfo(*mapRequest.Hostinfo)
|
||||
machine.DiscoKey = DiscoPublicKeyStripPrefix(mapRequest.DiscoKey)
|
||||
machine.DiscoKey = util.DiscoPublicKeyStripPrefix(mapRequest.DiscoKey)
|
||||
now := time.Now().UTC()
|
||||
|
||||
err := h.processMachineRoutes(machine)
|
||||
err := h.db.processMachineRoutes(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -53,7 +54,7 @@ func (h *Headscale) handlePollCommon(
|
|||
}
|
||||
|
||||
// update routes with peer information
|
||||
err = h.EnableAutoApprovedRoutes(machine)
|
||||
err = h.db.EnableAutoApprovedRoutes(h.aclPolicy, machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -77,7 +78,7 @@ func (h *Headscale) handlePollCommon(
|
|||
machine.LastSeen = &now
|
||||
}
|
||||
|
||||
if err := h.db.Updates(machine).Error; err != nil {
|
||||
if err := h.db.db.Updates(machine).Error; err != nil {
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
|
@ -325,7 +326,7 @@ func (h *Headscale) pollNetMapStream(
|
|||
// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
|
||||
// when an outdated machine object is kept alive, e.g. db is update from
|
||||
// command line, but then overwritten.
|
||||
err = h.UpdateMachineFromDatabase(machine)
|
||||
err = h.db.UpdateMachineFromDatabase(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
@ -346,7 +347,7 @@ func (h *Headscale) pollNetMapStream(
|
|||
Set(float64(now.Unix()))
|
||||
machine.LastSuccessfulUpdate = &now
|
||||
|
||||
err = h.TouchMachine(machine)
|
||||
err = h.db.TouchMachine(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
@ -409,7 +410,7 @@ func (h *Headscale) pollNetMapStream(
|
|||
// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
|
||||
// when an outdated machine object is kept alive, e.g. db is update from
|
||||
// command line, but then overwritten.
|
||||
err = h.UpdateMachineFromDatabase(machine)
|
||||
err = h.db.UpdateMachineFromDatabase(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
@ -425,7 +426,7 @@ func (h *Headscale) pollNetMapStream(
|
|||
}
|
||||
now := time.Now().UTC()
|
||||
machine.LastSeen = &now
|
||||
err = h.TouchMachine(machine)
|
||||
err = h.db.TouchMachine(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
@ -456,7 +457,7 @@ func (h *Headscale) pollNetMapStream(
|
|||
updateRequestsReceivedOnChannel.WithLabelValues(machine.User.Name, machine.Hostname).
|
||||
Inc()
|
||||
|
||||
if h.isOutdated(machine) {
|
||||
if h.db.isOutdated(machine, h.getLastStateChange()) {
|
||||
var lastUpdate time.Time
|
||||
if machine.LastSuccessfulUpdate != nil {
|
||||
lastUpdate = *machine.LastSuccessfulUpdate
|
||||
|
@ -524,7 +525,7 @@ func (h *Headscale) pollNetMapStream(
|
|||
// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
|
||||
// when an outdated machine object is kept alive, e.g. db is update from
|
||||
// command line, but then overwritten.
|
||||
err = h.UpdateMachineFromDatabase(machine)
|
||||
err = h.db.UpdateMachineFromDatabase(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
@ -544,7 +545,7 @@ func (h *Headscale) pollNetMapStream(
|
|||
Set(float64(now.Unix()))
|
||||
machine.LastSuccessfulUpdate = &now
|
||||
|
||||
err = h.TouchMachine(machine)
|
||||
err = h.db.TouchMachine(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
@ -578,7 +579,7 @@ func (h *Headscale) pollNetMapStream(
|
|||
// TODO: Abstract away all the database calls, this can cause race conditions
|
||||
// when an outdated machine object is kept alive, e.g. db is update from
|
||||
// command line, but then overwritten.
|
||||
err := h.UpdateMachineFromDatabase(machine)
|
||||
err := h.db.UpdateMachineFromDatabase(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
@ -594,7 +595,7 @@ func (h *Headscale) pollNetMapStream(
|
|||
}
|
||||
now := time.Now().UTC()
|
||||
machine.LastSeen = &now
|
||||
err = h.TouchMachine(machine)
|
||||
err = h.db.TouchMachine(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/smallzstd"
|
||||
|
@ -27,7 +28,7 @@ func (h *Headscale) getMapResponseData(
|
|||
}
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err = machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machine.MachineKey)))
|
||||
err = machineKey.UnmarshalText([]byte(util.MachinePublicKeyEnsurePrefix(machine.MachineKey)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -50,11 +51,16 @@ func (h *Headscale) getMapKeepAliveResponseData(
|
|||
}
|
||||
|
||||
if isNoise {
|
||||
return h.marshalMapResponse(keepAliveResponse, key.MachinePublic{}, mapRequest.Compress, isNoise)
|
||||
return h.marshalMapResponse(
|
||||
keepAliveResponse,
|
||||
key.MachinePublic{},
|
||||
mapRequest.Compress,
|
||||
isNoise,
|
||||
)
|
||||
}
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machine.MachineKey)))
|
||||
err := machineKey.UnmarshalText([]byte(util.MachinePublicKeyEnsurePrefix(machine.MachineKey)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -104,7 +110,7 @@ func (h *Headscale) marshalMapResponse(
|
|||
}
|
||||
|
||||
var respBody []byte
|
||||
if compression == ZstdCompression {
|
||||
if compression == util.ZstdCompression {
|
||||
respBody = zstdEncode(jsonBody)
|
||||
if !isNoise { // if legacy protocol
|
||||
respBody = h.privateKey.SealTo(machineKey, respBody)
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
|
@ -32,7 +33,7 @@ func (h *Headscale) RegistrationHandler(
|
|||
body, _ := io.ReadAll(req.Body)
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
||||
err := machineKey.UnmarshalText([]byte(util.MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
@ -44,7 +45,7 @@ func (h *Headscale) RegistrationHandler(
|
|||
return
|
||||
}
|
||||
registerRequest := tailcfg.RegisterRequest{}
|
||||
err = decode(body, ®isterRequest, &machineKey, h.privateKey)
|
||||
err = util.DecodeAndUnmarshalNaCl(body, ®isterRequest, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
|
@ -44,7 +45,7 @@ func (h *Headscale) PollNetMapHandler(
|
|||
body, _ := io.ReadAll(req.Body)
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
||||
err := machineKey.UnmarshalText([]byte(util.MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
|
@ -56,7 +57,7 @@ func (h *Headscale) PollNetMapHandler(
|
|||
return
|
||||
}
|
||||
mapRequest := tailcfg.MapRequest{}
|
||||
err = decode(body, &mapRequest, &machineKey, h.privateKey)
|
||||
err = util.DecodeAndUnmarshalNaCl(body, &mapRequest, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
|
@ -67,7 +68,7 @@ func (h *Headscale) PollNetMapHandler(
|
|||
return
|
||||
}
|
||||
|
||||
machine, err := h.GetMachineByMachineKey(machineKey)
|
||||
machine, err := h.db.GetMachineByMachineKey(machineKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Warn().
|
||||
|
|
|
@ -48,7 +48,11 @@ func (ns *noiseServer) NoisePollNetMapHandler(
|
|||
|
||||
ns.nodeKey = mapRequest.NodeKey
|
||||
|
||||
machine, err := ns.headscale.GetMachineByAnyKey(ns.conn.Peer(), mapRequest.NodeKey, key.NodePublic{})
|
||||
machine, err := ns.headscale.db.GetMachineByAnyKey(
|
||||
ns.conn.Peer(),
|
||||
mapRequest.NodeKey,
|
||||
key.NodePublic{},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Warn().
|
||||
|
|
|
@ -11,11 +11,8 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrRouteIsNotAvailable = Error("route is not available")
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRouteIsNotAvailable = errors.New("route is not available")
|
||||
ExitRouteV4 = netip.MustParsePrefix("0.0.0.0/0")
|
||||
ExitRouteV6 = netip.MustParsePrefix("::/0")
|
||||
)
|
||||
|
@ -51,9 +48,9 @@ func (rs Routes) toPrefixes() []netip.Prefix {
|
|||
return prefixes
|
||||
}
|
||||
|
||||
func (h *Headscale) GetRoutes() ([]Route, error) {
|
||||
func (hsdb *HSDatabase) GetRoutes() ([]Route, error) {
|
||||
var routes []Route
|
||||
err := h.db.Preload("Machine").Find(&routes).Error
|
||||
err := hsdb.db.Preload("Machine").Find(&routes).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -61,9 +58,9 @@ func (h *Headscale) GetRoutes() ([]Route, error) {
|
|||
return routes, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) GetMachineRoutes(m *Machine) ([]Route, error) {
|
||||
func (hsdb *HSDatabase) GetMachineRoutes(m *Machine) ([]Route, error) {
|
||||
var routes []Route
|
||||
err := h.db.
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ?", m.ID).
|
||||
Find(&routes).Error
|
||||
|
@ -74,9 +71,9 @@ func (h *Headscale) GetMachineRoutes(m *Machine) ([]Route, error) {
|
|||
return routes, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) GetRoute(id uint64) (*Route, error) {
|
||||
func (hsdb *HSDatabase) GetRoute(id uint64) (*Route, error) {
|
||||
var route Route
|
||||
err := h.db.Preload("Machine").First(&route, id).Error
|
||||
err := hsdb.db.Preload("Machine").First(&route, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -84,8 +81,8 @@ func (h *Headscale) GetRoute(id uint64) (*Route, error) {
|
|||
return &route, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) EnableRoute(id uint64) error {
|
||||
route, err := h.GetRoute(id)
|
||||
func (hsdb *HSDatabase) EnableRoute(id uint64) error {
|
||||
route, err := hsdb.GetRoute(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -94,14 +91,14 @@ func (h *Headscale) EnableRoute(id uint64) error {
|
|||
// be enabled at the same time, as per
|
||||
// https://github.com/juanfont/headscale/issues/804#issuecomment-1399314002
|
||||
if route.isExitRoute() {
|
||||
return h.enableRoutes(&route.Machine, ExitRouteV4.String(), ExitRouteV6.String())
|
||||
return hsdb.enableRoutes(&route.Machine, ExitRouteV4.String(), ExitRouteV6.String())
|
||||
}
|
||||
|
||||
return h.enableRoutes(&route.Machine, netip.Prefix(route.Prefix).String())
|
||||
return hsdb.enableRoutes(&route.Machine, netip.Prefix(route.Prefix).String())
|
||||
}
|
||||
|
||||
func (h *Headscale) DisableRoute(id uint64) error {
|
||||
route, err := h.GetRoute(id)
|
||||
func (hsdb *HSDatabase) DisableRoute(id uint64) error {
|
||||
route, err := hsdb.GetRoute(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -112,15 +109,15 @@ func (h *Headscale) DisableRoute(id uint64) error {
|
|||
if !route.isExitRoute() {
|
||||
route.Enabled = false
|
||||
route.IsPrimary = false
|
||||
err = h.db.Save(route).Error
|
||||
err = hsdb.db.Save(route).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return h.handlePrimarySubnetFailover()
|
||||
return hsdb.handlePrimarySubnetFailover()
|
||||
}
|
||||
|
||||
routes, err := h.GetMachineRoutes(&route.Machine)
|
||||
routes, err := hsdb.GetMachineRoutes(&route.Machine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -129,18 +126,18 @@ func (h *Headscale) DisableRoute(id uint64) error {
|
|||
if routes[i].isExitRoute() {
|
||||
routes[i].Enabled = false
|
||||
routes[i].IsPrimary = false
|
||||
err = h.db.Save(&routes[i]).Error
|
||||
err = hsdb.db.Save(&routes[i]).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return h.handlePrimarySubnetFailover()
|
||||
return hsdb.handlePrimarySubnetFailover()
|
||||
}
|
||||
|
||||
func (h *Headscale) DeleteRoute(id uint64) error {
|
||||
route, err := h.GetRoute(id)
|
||||
func (hsdb *HSDatabase) DeleteRoute(id uint64) error {
|
||||
route, err := hsdb.GetRoute(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -149,14 +146,14 @@ func (h *Headscale) DeleteRoute(id uint64) error {
|
|||
// be enabled at the same time, as per
|
||||
// https://github.com/juanfont/headscale/issues/804#issuecomment-1399314002
|
||||
if !route.isExitRoute() {
|
||||
if err := h.db.Unscoped().Delete(&route).Error; err != nil {
|
||||
if err := hsdb.db.Unscoped().Delete(&route).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return h.handlePrimarySubnetFailover()
|
||||
return hsdb.handlePrimarySubnetFailover()
|
||||
}
|
||||
|
||||
routes, err := h.GetMachineRoutes(&route.Machine)
|
||||
routes, err := hsdb.GetMachineRoutes(&route.Machine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -168,32 +165,32 @@ func (h *Headscale) DeleteRoute(id uint64) error {
|
|||
}
|
||||
}
|
||||
|
||||
if err := h.db.Unscoped().Delete(&routesToDelete).Error; err != nil {
|
||||
if err := hsdb.db.Unscoped().Delete(&routesToDelete).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return h.handlePrimarySubnetFailover()
|
||||
return hsdb.handlePrimarySubnetFailover()
|
||||
}
|
||||
|
||||
func (h *Headscale) DeleteMachineRoutes(m *Machine) error {
|
||||
routes, err := h.GetMachineRoutes(m)
|
||||
func (hsdb *HSDatabase) DeleteMachineRoutes(m *Machine) error {
|
||||
routes, err := hsdb.GetMachineRoutes(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range routes {
|
||||
if err := h.db.Unscoped().Delete(&routes[i]).Error; err != nil {
|
||||
if err := hsdb.db.Unscoped().Delete(&routes[i]).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return h.handlePrimarySubnetFailover()
|
||||
return hsdb.handlePrimarySubnetFailover()
|
||||
}
|
||||
|
||||
// isUniquePrefix returns if there is another machine providing the same route already.
|
||||
func (h *Headscale) isUniquePrefix(route Route) bool {
|
||||
func (hsdb *HSDatabase) isUniquePrefix(route Route) bool {
|
||||
var count int64
|
||||
h.db.
|
||||
hsdb.db.
|
||||
Model(&Route{}).
|
||||
Where("prefix = ? AND machine_id != ? AND advertised = ? AND enabled = ?",
|
||||
route.Prefix,
|
||||
|
@ -203,9 +200,9 @@ func (h *Headscale) isUniquePrefix(route Route) bool {
|
|||
return count == 0
|
||||
}
|
||||
|
||||
func (h *Headscale) getPrimaryRoute(prefix netip.Prefix) (*Route, error) {
|
||||
func (hsdb *HSDatabase) getPrimaryRoute(prefix netip.Prefix) (*Route, error) {
|
||||
var route Route
|
||||
err := h.db.
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("prefix = ? AND advertised = ? AND enabled = ? AND is_primary = ?", IPPrefix(prefix), true, true, true).
|
||||
First(&route).Error
|
||||
|
@ -222,9 +219,9 @@ func (h *Headscale) getPrimaryRoute(prefix netip.Prefix) (*Route, error) {
|
|||
|
||||
// getMachinePrimaryRoutes returns the routes that are enabled and marked as primary (for subnet failover)
|
||||
// Exit nodes are not considered for this, as they are never marked as Primary.
|
||||
func (h *Headscale) getMachinePrimaryRoutes(m *Machine) ([]Route, error) {
|
||||
func (hsdb *HSDatabase) getMachinePrimaryRoutes(m *Machine) ([]Route, error) {
|
||||
var routes []Route
|
||||
err := h.db.
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("machine_id = ? AND advertised = ? AND enabled = ? AND is_primary = ?", m.ID, true, true, true).
|
||||
Find(&routes).Error
|
||||
|
@ -235,9 +232,9 @@ func (h *Headscale) getMachinePrimaryRoutes(m *Machine) ([]Route, error) {
|
|||
return routes, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) processMachineRoutes(machine *Machine) error {
|
||||
func (hsdb *HSDatabase) processMachineRoutes(machine *Machine) error {
|
||||
currentRoutes := []Route{}
|
||||
err := h.db.Where("machine_id = ?", machine.ID).Find(¤tRoutes).Error
|
||||
err := hsdb.db.Where("machine_id = ?", machine.ID).Find(¤tRoutes).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -251,7 +248,7 @@ func (h *Headscale) processMachineRoutes(machine *Machine) error {
|
|||
if _, ok := advertisedRoutes[netip.Prefix(route.Prefix)]; ok {
|
||||
if !route.Advertised {
|
||||
currentRoutes[pos].Advertised = true
|
||||
err := h.db.Save(¤tRoutes[pos]).Error
|
||||
err := hsdb.db.Save(¤tRoutes[pos]).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -260,7 +257,7 @@ func (h *Headscale) processMachineRoutes(machine *Machine) error {
|
|||
} else if route.Advertised {
|
||||
currentRoutes[pos].Advertised = false
|
||||
currentRoutes[pos].Enabled = false
|
||||
err := h.db.Save(¤tRoutes[pos]).Error
|
||||
err := hsdb.db.Save(¤tRoutes[pos]).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -275,7 +272,7 @@ func (h *Headscale) processMachineRoutes(machine *Machine) error {
|
|||
Advertised: true,
|
||||
Enabled: false,
|
||||
}
|
||||
err := h.db.Create(&route).Error
|
||||
err := hsdb.db.Create(&route).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -285,10 +282,10 @@ func (h *Headscale) processMachineRoutes(machine *Machine) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (h *Headscale) handlePrimarySubnetFailover() error {
|
||||
func (hsdb *HSDatabase) handlePrimarySubnetFailover() error {
|
||||
// first, get all the enabled routes
|
||||
var routes []Route
|
||||
err := h.db.
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("advertised = ? AND enabled = ?", true, true).
|
||||
Find(&routes).Error
|
||||
|
@ -303,14 +300,14 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
|
|||
}
|
||||
|
||||
if !route.IsPrimary {
|
||||
_, err := h.getPrimaryRoute(netip.Prefix(route.Prefix))
|
||||
if h.isUniquePrefix(route) || errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
_, err := hsdb.getPrimaryRoute(netip.Prefix(route.Prefix))
|
||||
if hsdb.isUniquePrefix(route) || errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Info().
|
||||
Str("prefix", netip.Prefix(route.Prefix).String()).
|
||||
Str("machine", route.Machine.GivenName).
|
||||
Msg("Setting primary route")
|
||||
routes[pos].IsPrimary = true
|
||||
err := h.db.Save(&routes[pos]).Error
|
||||
err := hsdb.db.Save(&routes[pos]).Error
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error marking route as primary")
|
||||
|
||||
|
@ -336,7 +333,7 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
|
|||
|
||||
// find a new primary route
|
||||
var newPrimaryRoutes []Route
|
||||
err := h.db.
|
||||
err := hsdb.db.
|
||||
Preload("Machine").
|
||||
Where("prefix = ? AND machine_id != ? AND advertised = ? AND enabled = ?",
|
||||
route.Prefix,
|
||||
|
@ -375,7 +372,7 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
|
|||
|
||||
// disable the old primary route
|
||||
routes[pos].IsPrimary = false
|
||||
err = h.db.Save(&routes[pos]).Error
|
||||
err = hsdb.db.Save(&routes[pos]).Error
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error disabling old primary route")
|
||||
|
||||
|
@ -384,7 +381,7 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
|
|||
|
||||
// enable the new primary route
|
||||
newPrimaryRoute.IsPrimary = true
|
||||
err = h.db.Save(&newPrimaryRoute).Error
|
||||
err = hsdb.db.Save(&newPrimaryRoute).Error
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error enabling new primary route")
|
||||
|
||||
|
@ -396,7 +393,7 @@ func (h *Headscale) handlePrimarySubnetFailover() error {
|
|||
}
|
||||
|
||||
if routesChanged {
|
||||
h.setLastStateChangeToNow()
|
||||
hsdb.notifyStateChange()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -4,19 +4,20 @@ import (
|
|||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"gopkg.in/check.v1"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
func (s *Suite) TestGetRoutes(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "test_get_route_machine")
|
||||
_, err = app.db.GetMachine("test", "test_get_route_machine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
route, err := netip.ParsePrefix("10.0.0.0/24")
|
||||
|
@ -37,30 +38,30 @@ func (s *Suite) TestGetRoutes(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: HostInfo(hostInfo),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
err = app.processMachineRoutes(&machine)
|
||||
err = app.db.processMachineRoutes(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
advertisedRoutes, err := app.GetAdvertisedRoutes(&machine)
|
||||
advertisedRoutes, err := app.db.GetAdvertisedRoutes(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(advertisedRoutes), check.Equals, 1)
|
||||
|
||||
err = app.enableRoutes(&machine, "192.168.0.0/24")
|
||||
err = app.db.enableRoutes(&machine, "192.168.0.0/24")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
err = app.enableRoutes(&machine, "10.0.0.0/24")
|
||||
err = app.db.enableRoutes(&machine, "10.0.0.0/24")
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "test_enable_route_machine")
|
||||
_, err = app.db.GetMachine("test", "test_enable_route_machine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
route, err := netip.ParsePrefix(
|
||||
|
@ -88,54 +89,54 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: HostInfo(hostInfo),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
err = app.processMachineRoutes(&machine)
|
||||
err = app.db.processMachineRoutes(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
availableRoutes, err := app.GetAdvertisedRoutes(&machine)
|
||||
availableRoutes, err := app.db.GetAdvertisedRoutes(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(availableRoutes), check.Equals, 2)
|
||||
|
||||
noEnabledRoutes, err := app.GetEnabledRoutes(&machine)
|
||||
noEnabledRoutes, err := app.db.GetEnabledRoutes(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(noEnabledRoutes), check.Equals, 0)
|
||||
|
||||
err = app.enableRoutes(&machine, "192.168.0.0/24")
|
||||
err = app.db.enableRoutes(&machine, "192.168.0.0/24")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
err = app.enableRoutes(&machine, "10.0.0.0/24")
|
||||
err = app.db.enableRoutes(&machine, "10.0.0.0/24")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes, err := app.GetEnabledRoutes(&machine)
|
||||
enabledRoutes, err := app.db.GetEnabledRoutes(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes), check.Equals, 1)
|
||||
|
||||
// Adding it twice will just let it pass through
|
||||
err = app.enableRoutes(&machine, "10.0.0.0/24")
|
||||
err = app.db.enableRoutes(&machine, "10.0.0.0/24")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enableRoutesAfterDoubleApply, err := app.GetEnabledRoutes(&machine)
|
||||
enableRoutesAfterDoubleApply, err := app.db.GetEnabledRoutes(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enableRoutesAfterDoubleApply), check.Equals, 1)
|
||||
|
||||
err = app.enableRoutes(&machine, "150.0.10.0/25")
|
||||
err = app.db.enableRoutes(&machine, "150.0.10.0/25")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutesWithAdditionalRoute, err := app.GetEnabledRoutes(&machine)
|
||||
enabledRoutesWithAdditionalRoute, err := app.db.GetEnabledRoutes(&machine)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutesWithAdditionalRoute), check.Equals, 2)
|
||||
}
|
||||
|
||||
func (s *Suite) TestIsUniquePrefix(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "test_enable_route_machine")
|
||||
_, err = app.db.GetMachine("test", "test_enable_route_machine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
route, err := netip.ParsePrefix(
|
||||
|
@ -162,15 +163,15 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: HostInfo(hostInfo1),
|
||||
}
|
||||
app.db.Save(&machine1)
|
||||
app.db.db.Save(&machine1)
|
||||
|
||||
err = app.processMachineRoutes(&machine1)
|
||||
err = app.db.processMachineRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.enableRoutes(&machine1, route.String())
|
||||
err = app.db.enableRoutes(&machine1, route.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.enableRoutes(&machine1, route2.String())
|
||||
err = app.db.enableRoutes(&machine1, route2.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
hostInfo2 := tailcfg.Hostinfo{
|
||||
|
@ -187,39 +188,39 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) {
|
|||
AuthKeyID: uint(pak.ID),
|
||||
HostInfo: HostInfo(hostInfo2),
|
||||
}
|
||||
app.db.Save(&machine2)
|
||||
app.db.db.Save(&machine2)
|
||||
|
||||
err = app.processMachineRoutes(&machine2)
|
||||
err = app.db.processMachineRoutes(&machine2)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.enableRoutes(&machine2, route2.String())
|
||||
err = app.db.enableRoutes(&machine2, route2.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes1, err := app.GetEnabledRoutes(&machine1)
|
||||
enabledRoutes1, err := app.db.GetEnabledRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes1), check.Equals, 2)
|
||||
|
||||
enabledRoutes2, err := app.GetEnabledRoutes(&machine2)
|
||||
enabledRoutes2, err := app.db.GetEnabledRoutes(&machine2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes2), check.Equals, 1)
|
||||
|
||||
routes, err := app.getMachinePrimaryRoutes(&machine1)
|
||||
routes, err := app.db.getMachinePrimaryRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 2)
|
||||
|
||||
routes, err = app.getMachinePrimaryRoutes(&machine2)
|
||||
routes, err = app.db.getMachinePrimaryRoutes(&machine2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 0)
|
||||
}
|
||||
|
||||
func (s *Suite) TestSubnetFailover(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "test_enable_route_machine")
|
||||
_, err = app.db.GetMachine("test", "test_enable_route_machine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
prefix, err := netip.ParsePrefix(
|
||||
|
@ -249,25 +250,25 @@ func (s *Suite) TestSubnetFailover(c *check.C) {
|
|||
HostInfo: HostInfo(hostInfo1),
|
||||
LastSeen: &now,
|
||||
}
|
||||
app.db.Save(&machine1)
|
||||
app.db.db.Save(&machine1)
|
||||
|
||||
err = app.processMachineRoutes(&machine1)
|
||||
err = app.db.processMachineRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.enableRoutes(&machine1, prefix.String())
|
||||
err = app.db.enableRoutes(&machine1, prefix.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.enableRoutes(&machine1, prefix2.String())
|
||||
err = app.db.enableRoutes(&machine1, prefix2.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.handlePrimarySubnetFailover()
|
||||
err = app.db.handlePrimarySubnetFailover()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes1, err := app.GetEnabledRoutes(&machine1)
|
||||
enabledRoutes1, err := app.db.GetEnabledRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes1), check.Equals, 2)
|
||||
|
||||
route, err := app.getPrimaryRoute(prefix)
|
||||
route, err := app.db.getPrimaryRoute(prefix)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(route.MachineID, check.Equals, machine1.ID)
|
||||
|
||||
|
@ -286,70 +287,70 @@ func (s *Suite) TestSubnetFailover(c *check.C) {
|
|||
HostInfo: HostInfo(hostInfo2),
|
||||
LastSeen: &now,
|
||||
}
|
||||
app.db.Save(&machine2)
|
||||
app.db.db.Save(&machine2)
|
||||
|
||||
err = app.processMachineRoutes(&machine2)
|
||||
err = app.db.processMachineRoutes(&machine2)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.enableRoutes(&machine2, prefix2.String())
|
||||
err = app.db.enableRoutes(&machine2, prefix2.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.handlePrimarySubnetFailover()
|
||||
err = app.db.handlePrimarySubnetFailover()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes1, err = app.GetEnabledRoutes(&machine1)
|
||||
enabledRoutes1, err = app.db.GetEnabledRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes1), check.Equals, 2)
|
||||
|
||||
enabledRoutes2, err := app.GetEnabledRoutes(&machine2)
|
||||
enabledRoutes2, err := app.db.GetEnabledRoutes(&machine2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes2), check.Equals, 1)
|
||||
|
||||
routes, err := app.getMachinePrimaryRoutes(&machine1)
|
||||
routes, err := app.db.getMachinePrimaryRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 2)
|
||||
|
||||
routes, err = app.getMachinePrimaryRoutes(&machine2)
|
||||
routes, err = app.db.getMachinePrimaryRoutes(&machine2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 0)
|
||||
|
||||
// lets make machine1 lastseen 10 mins ago
|
||||
before := now.Add(-10 * time.Minute)
|
||||
machine1.LastSeen = &before
|
||||
err = app.db.Save(&machine1).Error
|
||||
err = app.db.db.Save(&machine1).Error
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.handlePrimarySubnetFailover()
|
||||
err = app.db.handlePrimarySubnetFailover()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
routes, err = app.getMachinePrimaryRoutes(&machine1)
|
||||
routes, err = app.db.getMachinePrimaryRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 1)
|
||||
|
||||
routes, err = app.getMachinePrimaryRoutes(&machine2)
|
||||
routes, err = app.db.getMachinePrimaryRoutes(&machine2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 1)
|
||||
|
||||
machine2.HostInfo = HostInfo(tailcfg.Hostinfo{
|
||||
RoutableIPs: []netip.Prefix{prefix, prefix2},
|
||||
})
|
||||
err = app.db.Save(&machine2).Error
|
||||
err = app.db.db.Save(&machine2).Error
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.processMachineRoutes(&machine2)
|
||||
err = app.db.processMachineRoutes(&machine2)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.enableRoutes(&machine2, prefix.String())
|
||||
err = app.db.enableRoutes(&machine2, prefix.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.handlePrimarySubnetFailover()
|
||||
err = app.db.handlePrimarySubnetFailover()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
routes, err = app.getMachinePrimaryRoutes(&machine1)
|
||||
routes, err = app.db.getMachinePrimaryRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 0)
|
||||
|
||||
routes, err = app.getMachinePrimaryRoutes(&machine2)
|
||||
routes, err = app.db.getMachinePrimaryRoutes(&machine2)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 2)
|
||||
}
|
||||
|
@ -358,13 +359,13 @@ func (s *Suite) TestSubnetFailover(c *check.C) {
|
|||
// including both the primary routes the node is responsible for, and the
|
||||
// exit node routes if enabled.
|
||||
func (s *Suite) TestAllowedIPRoutes(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "test_enable_route_machine")
|
||||
_, err = app.db.GetMachine("test", "test_enable_route_machine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
prefix, err := netip.ParsePrefix(
|
||||
|
@ -398,9 +399,9 @@ func (s *Suite) TestAllowedIPRoutes(c *check.C) {
|
|||
now := time.Now()
|
||||
machine1 := Machine{
|
||||
ID: 1,
|
||||
MachineKey: MachinePublicKeyStripPrefix(machineKey.Public()),
|
||||
NodeKey: NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
DiscoKey: DiscoPublicKeyStripPrefix(discoKey.Public()),
|
||||
MachineKey: util.MachinePublicKeyStripPrefix(machineKey.Public()),
|
||||
NodeKey: util.NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
DiscoKey: util.DiscoPublicKeyStripPrefix(discoKey.Public()),
|
||||
Hostname: "test_enable_route_machine",
|
||||
UserID: user.ID,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
|
@ -408,23 +409,23 @@ func (s *Suite) TestAllowedIPRoutes(c *check.C) {
|
|||
HostInfo: HostInfo(hostInfo1),
|
||||
LastSeen: &now,
|
||||
}
|
||||
app.db.Save(&machine1)
|
||||
app.db.db.Save(&machine1)
|
||||
|
||||
err = app.processMachineRoutes(&machine1)
|
||||
err = app.db.processMachineRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.enableRoutes(&machine1, prefix.String())
|
||||
err = app.db.enableRoutes(&machine1, prefix.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// We do not enable this one on purpose to test that it is not enabled
|
||||
// err = app.enableRoutes(&machine1, prefix2.String())
|
||||
// err = app.db.enableRoutes(&machine1, prefix2.String())
|
||||
// c.Assert(err, check.IsNil)
|
||||
|
||||
routes, err := app.GetMachineRoutes(&machine1)
|
||||
routes, err := app.db.GetMachineRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
for _, route := range routes {
|
||||
if route.isExitRoute() {
|
||||
err = app.EnableRoute(uint64(route.ID))
|
||||
err = app.db.EnableRoute(uint64(route.ID))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// We only enable one exit route, so we can test that both are enabled
|
||||
|
@ -432,14 +433,14 @@ func (s *Suite) TestAllowedIPRoutes(c *check.C) {
|
|||
}
|
||||
}
|
||||
|
||||
err = app.handlePrimarySubnetFailover()
|
||||
err = app.db.handlePrimarySubnetFailover()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes1, err := app.GetEnabledRoutes(&machine1)
|
||||
enabledRoutes1, err := app.db.GetEnabledRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes1), check.Equals, 3)
|
||||
|
||||
peer, err := app.toNode(machine1, "headscale.net", nil)
|
||||
peer, err := app.db.toNode(machine1, app.aclPolicy, "headscale.net", nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(len(peer.AllowedIPs), check.Equals, 3)
|
||||
|
@ -469,35 +470,35 @@ func (s *Suite) TestAllowedIPRoutes(c *check.C) {
|
|||
}
|
||||
}
|
||||
|
||||
err = app.DisableRoute(uint64(exitRouteV4.ID))
|
||||
err = app.db.DisableRoute(uint64(exitRouteV4.ID))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes1, err = app.GetEnabledRoutes(&machine1)
|
||||
enabledRoutes1, err = app.db.GetEnabledRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes1), check.Equals, 1)
|
||||
|
||||
// and now we delete only one of the exit routes
|
||||
// and we check if both are deleted
|
||||
routes, err = app.GetMachineRoutes(&machine1)
|
||||
routes, err = app.db.GetMachineRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 4)
|
||||
|
||||
err = app.DeleteRoute(uint64(exitRouteV4.ID))
|
||||
err = app.db.DeleteRoute(uint64(exitRouteV4.ID))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
routes, err = app.GetMachineRoutes(&machine1)
|
||||
routes, err = app.db.GetMachineRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(routes), check.Equals, 2)
|
||||
}
|
||||
|
||||
func (s *Suite) TestDeleteRoutes(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine("test", "test_enable_route_machine")
|
||||
_, err = app.db.GetMachine("test", "test_enable_route_machine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
prefix, err := netip.ParsePrefix(
|
||||
|
@ -527,24 +528,24 @@ func (s *Suite) TestDeleteRoutes(c *check.C) {
|
|||
HostInfo: HostInfo(hostInfo1),
|
||||
LastSeen: &now,
|
||||
}
|
||||
app.db.Save(&machine1)
|
||||
app.db.db.Save(&machine1)
|
||||
|
||||
err = app.processMachineRoutes(&machine1)
|
||||
err = app.db.processMachineRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.enableRoutes(&machine1, prefix.String())
|
||||
err = app.db.enableRoutes(&machine1, prefix.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.enableRoutes(&machine1, prefix2.String())
|
||||
err = app.db.enableRoutes(&machine1, prefix2.String())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
routes, err := app.GetMachineRoutes(&machine1)
|
||||
routes, err := app.db.GetMachineRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.DeleteRoute(uint64(routes[0].ID))
|
||||
err = app.db.DeleteRoute(uint64(routes[0].ID))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
enabledRoutes1, err := app.GetEnabledRoutes(&machine1)
|
||||
enabledRoutes1, err := app.db.GetEnabledRoutes(&machine1)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(enabledRoutes1), check.Equals, 1)
|
||||
}
|
||||
|
|
|
@ -9,17 +9,18 @@ import (
|
|||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrUserExists = Error("User already exists")
|
||||
ErrUserNotFound = Error("User not found")
|
||||
ErrUserStillHasNodes = Error("User not empty: node(s) found")
|
||||
ErrInvalidUserName = Error("Invalid user name")
|
||||
var (
|
||||
ErrUserExists = errors.New("user already exists")
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrUserStillHasNodes = errors.New("user not empty: node(s) found")
|
||||
ErrInvalidUserName = errors.New("invalid user name")
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -40,17 +41,17 @@ type User struct {
|
|||
|
||||
// CreateUser creates a new User. Returns error if could not be created
|
||||
// or another user already exists.
|
||||
func (h *Headscale) CreateUser(name string) (*User, error) {
|
||||
func (hsdb *HSDatabase) CreateUser(name string) (*User, error) {
|
||||
err := CheckForFQDNRules(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := User{}
|
||||
if err := h.db.Where("name = ?", name).First(&user).Error; err == nil {
|
||||
if err := hsdb.db.Where("name = ?", name).First(&user).Error; err == nil {
|
||||
return nil, ErrUserExists
|
||||
}
|
||||
user.Name = name
|
||||
if err := h.db.Create(&user).Error; err != nil {
|
||||
if err := hsdb.db.Create(&user).Error; err != nil {
|
||||
log.Error().
|
||||
Str("func", "CreateUser").
|
||||
Err(err).
|
||||
|
@ -64,13 +65,13 @@ func (h *Headscale) CreateUser(name string) (*User, error) {
|
|||
|
||||
// DestroyUser destroys a User. Returns error if the User does
|
||||
// not exist or if there are machines associated with it.
|
||||
func (h *Headscale) DestroyUser(name string) error {
|
||||
user, err := h.GetUser(name)
|
||||
func (hsdb *HSDatabase) DestroyUser(name string) error {
|
||||
user, err := hsdb.GetUser(name)
|
||||
if err != nil {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
machines, err := h.ListMachinesByUser(name)
|
||||
machines, err := hsdb.ListMachinesByUser(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -78,18 +79,18 @@ func (h *Headscale) DestroyUser(name string) error {
|
|||
return ErrUserStillHasNodes
|
||||
}
|
||||
|
||||
keys, err := h.ListPreAuthKeys(name)
|
||||
keys, err := hsdb.ListPreAuthKeys(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, key := range keys {
|
||||
err = h.DestroyPreAuthKey(key)
|
||||
err = hsdb.DestroyPreAuthKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if result := h.db.Unscoped().Delete(&user); result.Error != nil {
|
||||
if result := hsdb.db.Unscoped().Delete(&user); result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
|
@ -98,9 +99,9 @@ func (h *Headscale) DestroyUser(name string) error {
|
|||
|
||||
// RenameUser renames a User. Returns error if the User does
|
||||
// not exist or if another User exists with the new name.
|
||||
func (h *Headscale) RenameUser(oldName, newName string) error {
|
||||
func (hsdb *HSDatabase) RenameUser(oldName, newName string) error {
|
||||
var err error
|
||||
oldUser, err := h.GetUser(oldName)
|
||||
oldUser, err := hsdb.GetUser(oldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -108,7 +109,7 @@ func (h *Headscale) RenameUser(oldName, newName string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = h.GetUser(newName)
|
||||
_, err = hsdb.GetUser(newName)
|
||||
if err == nil {
|
||||
return ErrUserExists
|
||||
}
|
||||
|
@ -118,7 +119,7 @@ func (h *Headscale) RenameUser(oldName, newName string) error {
|
|||
|
||||
oldUser.Name = newName
|
||||
|
||||
if result := h.db.Save(&oldUser); result.Error != nil {
|
||||
if result := hsdb.db.Save(&oldUser); result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
|
@ -126,9 +127,9 @@ func (h *Headscale) RenameUser(oldName, newName string) error {
|
|||
}
|
||||
|
||||
// GetUser fetches a user by name.
|
||||
func (h *Headscale) GetUser(name string) (*User, error) {
|
||||
func (hsdb *HSDatabase) GetUser(name string) (*User, error) {
|
||||
user := User{}
|
||||
if result := h.db.First(&user, "name = ?", name); errors.Is(
|
||||
if result := hsdb.db.First(&user, "name = ?", name); errors.Is(
|
||||
result.Error,
|
||||
gorm.ErrRecordNotFound,
|
||||
) {
|
||||
|
@ -139,9 +140,9 @@ func (h *Headscale) GetUser(name string) (*User, error) {
|
|||
}
|
||||
|
||||
// ListUsers gets all the existing users.
|
||||
func (h *Headscale) ListUsers() ([]User, error) {
|
||||
func (hsdb *HSDatabase) ListUsers() ([]User, error) {
|
||||
users := []User{}
|
||||
if err := h.db.Find(&users).Error; err != nil {
|
||||
if err := hsdb.db.Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -149,18 +150,18 @@ func (h *Headscale) ListUsers() ([]User, error) {
|
|||
}
|
||||
|
||||
// ListMachinesByUser gets all the nodes in a given user.
|
||||
func (h *Headscale) ListMachinesByUser(name string) ([]Machine, error) {
|
||||
func (hsdb *HSDatabase) ListMachinesByUser(name string) ([]Machine, error) {
|
||||
err := CheckForFQDNRules(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := h.GetUser(name)
|
||||
user, err := hsdb.GetUser(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
machines := []Machine{}
|
||||
if err := h.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where(&Machine{UserID: user.ID}).Find(&machines).Error; err != nil {
|
||||
if err := hsdb.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where(&Machine{UserID: user.ID}).Find(&machines).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -168,17 +169,17 @@ func (h *Headscale) ListMachinesByUser(name string) ([]Machine, error) {
|
|||
}
|
||||
|
||||
// SetMachineUser assigns a Machine to a user.
|
||||
func (h *Headscale) SetMachineUser(machine *Machine, username string) error {
|
||||
func (hsdb *HSDatabase) SetMachineUser(machine *Machine, username string) error {
|
||||
err := CheckForFQDNRules(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user, err := h.GetUser(username)
|
||||
user, err := hsdb.GetUser(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
machine.User = *user
|
||||
if result := h.db.Save(&machine); result.Error != nil {
|
||||
if result := hsdb.db.Save(&machine); result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
|
@ -211,7 +212,7 @@ func (n *User) toTailscaleLogin() *tailcfg.Login {
|
|||
return &login
|
||||
}
|
||||
|
||||
func (h *Headscale) getMapResponseUserProfiles(
|
||||
func (hsdb *HSDatabase) getMapResponseUserProfiles(
|
||||
machine Machine,
|
||||
peers Machines,
|
||||
) []tailcfg.UserProfile {
|
||||
|
@ -225,8 +226,8 @@ func (h *Headscale) getMapResponseUserProfiles(
|
|||
for _, user := range userMap {
|
||||
displayName := user.Name
|
||||
|
||||
if h.cfg.BaseDomain != "" {
|
||||
displayName = fmt.Sprintf("%s@%s", user.Name, h.cfg.BaseDomain)
|
||||
if hsdb.baseDomain != "" {
|
||||
displayName = fmt.Sprintf("%s@%s", user.Name, hsdb.baseDomain)
|
||||
}
|
||||
|
||||
profiles = append(profiles,
|
||||
|
@ -242,7 +243,7 @@ func (h *Headscale) getMapResponseUserProfiles(
|
|||
|
||||
func (n *User) toProto() *v1.User {
|
||||
return &v1.User{
|
||||
Id: strconv.FormatUint(uint64(n.ID), Base10),
|
||||
Id: strconv.FormatUint(uint64(n.ID), util.Base10),
|
||||
Name: n.Name,
|
||||
CreatedAt: timestamppb.New(n.CreatedAt),
|
||||
}
|
||||
|
|
|
@ -9,42 +9,42 @@ import (
|
|||
)
|
||||
|
||||
func (s *Suite) TestCreateAndDestroyUser(c *check.C) {
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(user.Name, check.Equals, "test")
|
||||
|
||||
users, err := app.ListUsers()
|
||||
users, err := app.db.ListUsers()
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(users), check.Equals, 1)
|
||||
|
||||
err = app.DestroyUser("test")
|
||||
err = app.db.DestroyUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetUser("test")
|
||||
_, err = app.db.GetUser("test")
|
||||
c.Assert(err, check.NotNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestDestroyUserErrors(c *check.C) {
|
||||
err := app.DestroyUser("test")
|
||||
err := app.db.DestroyUser("test")
|
||||
c.Assert(err, check.Equals, ErrUserNotFound)
|
||||
|
||||
user, err := app.CreateUser("test")
|
||||
user, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.DestroyUser("test")
|
||||
err = app.db.DestroyUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
result := app.db.Preload("User").First(&pak, "key = ?", pak.Key)
|
||||
result := app.db.db.Preload("User").First(&pak, "key = ?", pak.Key)
|
||||
// destroying a user also deletes all associated preauthkeys
|
||||
c.Assert(result.Error, check.Equals, gorm.ErrRecordNotFound)
|
||||
|
||||
user, err = app.CreateUser("test")
|
||||
user, err = app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err = app.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
pak, err = app.db.CreatePreAuthKey(user.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine := Machine{
|
||||
|
@ -57,52 +57,52 @@ func (s *Suite) TestDestroyUserErrors(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
|
||||
err = app.DestroyUser("test")
|
||||
err = app.db.DestroyUser("test")
|
||||
c.Assert(err, check.Equals, ErrUserStillHasNodes)
|
||||
}
|
||||
|
||||
func (s *Suite) TestRenameUser(c *check.C) {
|
||||
userTest, err := app.CreateUser("test")
|
||||
userTest, err := app.db.CreateUser("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(userTest.Name, check.Equals, "test")
|
||||
|
||||
users, err := app.ListUsers()
|
||||
users, err := app.db.ListUsers()
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(users), check.Equals, 1)
|
||||
|
||||
err = app.RenameUser("test", "test-renamed")
|
||||
err = app.db.RenameUser("test", "test-renamed")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetUser("test")
|
||||
_, err = app.db.GetUser("test")
|
||||
c.Assert(err, check.Equals, ErrUserNotFound)
|
||||
|
||||
_, err = app.GetUser("test-renamed")
|
||||
_, err = app.db.GetUser("test-renamed")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = app.RenameUser("test-does-not-exit", "test")
|
||||
err = app.db.RenameUser("test-does-not-exit", "test")
|
||||
c.Assert(err, check.Equals, ErrUserNotFound)
|
||||
|
||||
userTest2, err := app.CreateUser("test2")
|
||||
userTest2, err := app.db.CreateUser("test2")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(userTest2.Name, check.Equals, "test2")
|
||||
|
||||
err = app.RenameUser("test2", "test-renamed")
|
||||
err = app.db.RenameUser("test2", "test-renamed")
|
||||
c.Assert(err, check.Equals, ErrUserExists)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||
userShared1, err := app.CreateUser("shared1")
|
||||
userShared1, err := app.db.CreateUser("shared1")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
userShared2, err := app.CreateUser("shared2")
|
||||
userShared2, err := app.db.CreateUser("shared2")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
userShared3, err := app.CreateUser("shared3")
|
||||
userShared3, err := app.db.CreateUser("shared3")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKeyShared1, err := app.CreatePreAuthKey(
|
||||
preAuthKeyShared1, err := app.db.CreatePreAuthKey(
|
||||
userShared1.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -111,7 +111,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKeyShared2, err := app.CreatePreAuthKey(
|
||||
preAuthKeyShared2, err := app.db.CreatePreAuthKey(
|
||||
userShared2.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -120,7 +120,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKeyShared3, err := app.CreatePreAuthKey(
|
||||
preAuthKeyShared3, err := app.db.CreatePreAuthKey(
|
||||
userShared3.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -129,7 +129,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
preAuthKey2Shared1, err := app.CreatePreAuthKey(
|
||||
preAuthKey2Shared1, err := app.db.CreatePreAuthKey(
|
||||
userShared1.Name,
|
||||
false,
|
||||
false,
|
||||
|
@ -138,7 +138,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachine(userShared1.Name, "test_get_shared_nodes_1")
|
||||
_, err = app.db.GetMachine(userShared1.Name, "test_get_shared_nodes_1")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
machineInShared1 := &Machine{
|
||||
|
@ -153,9 +153,9 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")},
|
||||
AuthKeyID: uint(preAuthKeyShared1.ID),
|
||||
}
|
||||
app.db.Save(machineInShared1)
|
||||
app.db.db.Save(machineInShared1)
|
||||
|
||||
_, err = app.GetMachine(userShared1.Name, machineInShared1.Hostname)
|
||||
_, err = app.db.GetMachine(userShared1.Name, machineInShared1.Hostname)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machineInShared2 := &Machine{
|
||||
|
@ -170,9 +170,9 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")},
|
||||
AuthKeyID: uint(preAuthKeyShared2.ID),
|
||||
}
|
||||
app.db.Save(machineInShared2)
|
||||
app.db.db.Save(machineInShared2)
|
||||
|
||||
_, err = app.GetMachine(userShared2.Name, machineInShared2.Hostname)
|
||||
_, err = app.db.GetMachine(userShared2.Name, machineInShared2.Hostname)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machineInShared3 := &Machine{
|
||||
|
@ -187,9 +187,9 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")},
|
||||
AuthKeyID: uint(preAuthKeyShared3.ID),
|
||||
}
|
||||
app.db.Save(machineInShared3)
|
||||
app.db.db.Save(machineInShared3)
|
||||
|
||||
_, err = app.GetMachine(userShared3.Name, machineInShared3.Hostname)
|
||||
_, err = app.db.GetMachine(userShared3.Name, machineInShared3.Hostname)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine2InShared1 := &Machine{
|
||||
|
@ -204,12 +204,12 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.4")},
|
||||
AuthKeyID: uint(preAuthKey2Shared1.ID),
|
||||
}
|
||||
app.db.Save(machine2InShared1)
|
||||
app.db.db.Save(machine2InShared1)
|
||||
|
||||
peersOfMachine1InShared1, err := app.getPeers(machineInShared1)
|
||||
peersOfMachine1InShared1, err := app.db.getPeers(app.aclPolicy, app.aclRules, machineInShared1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
userProfiles := app.getMapResponseUserProfiles(
|
||||
userProfiles := app.db.getMapResponseUserProfiles(
|
||||
*machineInShared1,
|
||||
peersOfMachine1InShared1,
|
||||
)
|
||||
|
@ -378,13 +378,13 @@ func TestCheckForFQDNRules(t *testing.T) {
|
|||
}
|
||||
|
||||
func (s *Suite) TestSetMachineUser(c *check.C) {
|
||||
oldUser, err := app.CreateUser("old")
|
||||
oldUser, err := app.db.CreateUser("old")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
newUser, err := app.CreateUser("new")
|
||||
newUser, err := app.db.CreateUser("new")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(oldUser.Name, false, false, nil, nil)
|
||||
pak, err := app.db.CreatePreAuthKey(oldUser.Name, false, false, nil, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
machine := Machine{
|
||||
|
@ -397,18 +397,18 @@ func (s *Suite) TestSetMachineUser(c *check.C) {
|
|||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
app.db.db.Save(&machine)
|
||||
c.Assert(machine.UserID, check.Equals, oldUser.ID)
|
||||
|
||||
err = app.SetMachineUser(&machine, newUser.Name)
|
||||
err = app.db.SetMachineUser(&machine, newUser.Name)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(machine.UserID, check.Equals, newUser.ID)
|
||||
c.Assert(machine.User.Name, check.Equals, newUser.Name)
|
||||
|
||||
err = app.SetMachineUser(&machine, "non-existing-user")
|
||||
err = app.db.SetMachineUser(&machine, "non-existing-user")
|
||||
c.Assert(err, check.Equals, ErrUserNotFound)
|
||||
|
||||
err = app.SetMachineUser(&machine, newUser.Name)
|
||||
err = app.db.SetMachineUser(&machine, newUser.Name)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(machine.UserID, check.Equals, newUser.ID)
|
||||
c.Assert(machine.User.Name, check.Equals, newUser.Name)
|
||||
|
|
42
hscontrol/util/addr.go
Normal file
42
hscontrol/util/addr.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"reflect"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
func GetIPPrefixEndpoints(na netip.Prefix) (netip.Addr, netip.Addr) {
|
||||
var network, broadcast netip.Addr
|
||||
ipRange := netipx.RangeOfPrefix(na)
|
||||
network = ipRange.From()
|
||||
broadcast = ipRange.To()
|
||||
|
||||
return network, broadcast
|
||||
}
|
||||
|
||||
func StringToIPPrefix(prefixes []string) ([]netip.Prefix, error) {
|
||||
result := make([]netip.Prefix, len(prefixes))
|
||||
|
||||
for index, prefixStr := range prefixes {
|
||||
prefix, err := netip.ParsePrefix(prefixStr)
|
||||
if err != nil {
|
||||
return []netip.Prefix{}, err
|
||||
}
|
||||
|
||||
result[index] = prefix
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func StringOrPrefixListContains[T string | netip.Prefix](ts []T, t T) bool {
|
||||
for _, v := range ts {
|
||||
if reflect.DeepEqual(v, t) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
43
hscontrol/util/file.go
Normal file
43
hscontrol/util/file.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
Base8 = 8
|
||||
Base10 = 10
|
||||
BitSize16 = 16
|
||||
BitSize32 = 32
|
||||
BitSize64 = 64
|
||||
)
|
||||
|
||||
func AbsolutePathFromConfigPath(path string) string {
|
||||
// If a relative path is provided, prefix it with the directory where
|
||||
// the config file was found.
|
||||
if (path != "") && !strings.HasPrefix(path, string(os.PathSeparator)) {
|
||||
dir, _ := filepath.Split(viper.ConfigFileUsed())
|
||||
if dir != "" {
|
||||
path = filepath.Join(dir, path)
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func GetFileMode(key string) fs.FileMode {
|
||||
modeStr := viper.GetString(key)
|
||||
|
||||
mode, err := strconv.ParseUint(modeStr, Base8, BitSize64)
|
||||
if err != nil {
|
||||
return PermissionFallback
|
||||
}
|
||||
|
||||
return fs.FileMode(mode)
|
||||
}
|
117
hscontrol/util/key.go
Normal file
117
hscontrol/util/key.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// These constants are copied from the upstream tailscale.com/types/key
|
||||
// library, because they are not exported.
|
||||
// https://github.com/tailscale/tailscale/tree/main/types/key
|
||||
|
||||
// nodePublicHexPrefix is the prefix used to identify a
|
||||
// hex-encoded node public key.
|
||||
//
|
||||
// This prefix is used in the control protocol, so cannot be
|
||||
// changed.
|
||||
nodePublicHexPrefix = "nodekey:"
|
||||
|
||||
// machinePublicHexPrefix is the prefix used to identify a
|
||||
// hex-encoded machine public key.
|
||||
//
|
||||
// This prefix is used in the control protocol, so cannot be
|
||||
// changed.
|
||||
machinePublicHexPrefix = "mkey:"
|
||||
|
||||
// discoPublicHexPrefix is the prefix used to identify a
|
||||
// hex-encoded disco public key.
|
||||
//
|
||||
// This prefix is used in the control protocol, so cannot be
|
||||
// changed.
|
||||
discoPublicHexPrefix = "discokey:"
|
||||
|
||||
// privateKey prefix.
|
||||
privateHexPrefix = "privkey:"
|
||||
|
||||
PermissionFallback = 0o700
|
||||
|
||||
ZstdCompression = "zstd"
|
||||
)
|
||||
|
||||
var (
|
||||
NodePublicKeyRegex = regexp.MustCompile("nodekey:[a-fA-F0-9]+")
|
||||
ErrCannotDecryptResponse = errors.New("cannot decrypt response")
|
||||
)
|
||||
|
||||
func MachinePublicKeyStripPrefix(machineKey key.MachinePublic) string {
|
||||
return strings.TrimPrefix(machineKey.String(), machinePublicHexPrefix)
|
||||
}
|
||||
|
||||
func NodePublicKeyStripPrefix(nodeKey key.NodePublic) string {
|
||||
return strings.TrimPrefix(nodeKey.String(), nodePublicHexPrefix)
|
||||
}
|
||||
|
||||
func DiscoPublicKeyStripPrefix(discoKey key.DiscoPublic) string {
|
||||
return strings.TrimPrefix(discoKey.String(), discoPublicHexPrefix)
|
||||
}
|
||||
|
||||
func MachinePublicKeyEnsurePrefix(machineKey string) string {
|
||||
if !strings.HasPrefix(machineKey, machinePublicHexPrefix) {
|
||||
return machinePublicHexPrefix + machineKey
|
||||
}
|
||||
|
||||
return machineKey
|
||||
}
|
||||
|
||||
func NodePublicKeyEnsurePrefix(nodeKey string) string {
|
||||
if !strings.HasPrefix(nodeKey, nodePublicHexPrefix) {
|
||||
return nodePublicHexPrefix + nodeKey
|
||||
}
|
||||
|
||||
return nodeKey
|
||||
}
|
||||
|
||||
func DiscoPublicKeyEnsurePrefix(discoKey string) string {
|
||||
if !strings.HasPrefix(discoKey, discoPublicHexPrefix) {
|
||||
return discoPublicHexPrefix + discoKey
|
||||
}
|
||||
|
||||
return discoKey
|
||||
}
|
||||
|
||||
func PrivateKeyEnsurePrefix(privateKey string) string {
|
||||
if !strings.HasPrefix(privateKey, privateHexPrefix) {
|
||||
return privateHexPrefix + privateKey
|
||||
}
|
||||
|
||||
return privateKey
|
||||
}
|
||||
|
||||
func DecodeAndUnmarshalNaCl(
|
||||
msg []byte,
|
||||
output interface{},
|
||||
pubKey *key.MachinePublic,
|
||||
privKey *key.MachinePrivate,
|
||||
) error {
|
||||
// log.Trace().
|
||||
// Str("pubkey", pubKey.ShortString()).
|
||||
// Int("length", len(msg)).
|
||||
// Msg("Trying to decrypt")
|
||||
|
||||
decrypted, ok := privKey.OpenFrom(*pubKey, msg)
|
||||
if !ok {
|
||||
return ErrCannotDecryptResponse
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(decrypted, output); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
12
hscontrol/util/net.go
Normal file
12
hscontrol/util/net.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
func GrpcSocketDialer(ctx context.Context, addr string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
|
||||
return d.DialContext(ctx, "unix", addr)
|
||||
}
|
85
hscontrol/util/string.go
Normal file
85
hscontrol/util/string.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
// GenerateRandomBytes returns securely generated random bytes.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomBytes(n int) ([]byte, error) {
|
||||
bytes := make([]byte, n)
|
||||
|
||||
// Note that err == nil only if we read len(b) bytes.
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// GenerateRandomStringURLSafe returns a URL-safe, base64 encoded
|
||||
// securely generated random string.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomStringURLSafe(n int) (string, error) {
|
||||
b, err := GenerateRandomBytes(n)
|
||||
|
||||
return base64.RawURLEncoding.EncodeToString(b), err
|
||||
}
|
||||
|
||||
// GenerateRandomStringDNSSafe returns a DNS-safe
|
||||
// securely generated random string.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomStringDNSSafe(size int) (string, error) {
|
||||
var str string
|
||||
var err error
|
||||
for len(str) < size {
|
||||
str, err = GenerateRandomStringURLSafe(size)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str = strings.ToLower(
|
||||
strings.ReplaceAll(strings.ReplaceAll(str, "_", ""), "-", ""),
|
||||
)
|
||||
}
|
||||
|
||||
return str[:size], nil
|
||||
}
|
||||
|
||||
func IsStringInSlice(slice []string, str string) bool {
|
||||
for _, s := range slice {
|
||||
if s == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func TailNodesToString(nodes []*tailcfg.Node) string {
|
||||
temp := make([]string, len(nodes))
|
||||
|
||||
for index, node := range nodes {
|
||||
temp[index] = node.Name
|
||||
}
|
||||
|
||||
return fmt.Sprintf("[ %s ](%d)", strings.Join(temp, ", "), len(temp))
|
||||
}
|
||||
|
||||
func TailMapResponseToString(resp tailcfg.MapResponse) string {
|
||||
return fmt.Sprintf(
|
||||
"{ Node: %s, Peers: %s }",
|
||||
resp.Node.Name,
|
||||
TailNodesToString(resp.Peers),
|
||||
)
|
||||
}
|
15
hscontrol/util/string_test.go
Normal file
15
hscontrol/util/string_test.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGenerateRandomStringDNSSafe(t *testing.T) {
|
||||
for i := 0; i < 100000; i++ {
|
||||
str, err := GenerateRandomStringDNSSafe(8)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, str, 8)
|
||||
}
|
||||
}
|
|
@ -1,361 +0,0 @@
|
|||
// Codehere is mostly taken from github.com/tailscale/tailscale
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package hscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
"go4.org/netipx"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrCannotDecryptResponse = Error("cannot decrypt response")
|
||||
ErrCouldNotAllocateIP = Error("could not find any suitable IP")
|
||||
|
||||
// These constants are copied from the upstream tailscale.com/types/key
|
||||
// library, because they are not exported.
|
||||
// https://github.com/tailscale/tailscale/tree/main/types/key
|
||||
|
||||
// nodePublicHexPrefix is the prefix used to identify a
|
||||
// hex-encoded node public key.
|
||||
//
|
||||
// This prefix is used in the control protocol, so cannot be
|
||||
// changed.
|
||||
nodePublicHexPrefix = "nodekey:"
|
||||
|
||||
// machinePublicHexPrefix is the prefix used to identify a
|
||||
// hex-encoded machine public key.
|
||||
//
|
||||
// This prefix is used in the control protocol, so cannot be
|
||||
// changed.
|
||||
machinePublicHexPrefix = "mkey:"
|
||||
|
||||
// discoPublicHexPrefix is the prefix used to identify a
|
||||
// hex-encoded disco public key.
|
||||
//
|
||||
// This prefix is used in the control protocol, so cannot be
|
||||
// changed.
|
||||
discoPublicHexPrefix = "discokey:"
|
||||
|
||||
// privateKey prefix.
|
||||
privateHexPrefix = "privkey:"
|
||||
|
||||
PermissionFallback = 0o700
|
||||
|
||||
ZstdCompression = "zstd"
|
||||
)
|
||||
|
||||
var NodePublicKeyRegex = regexp.MustCompile("nodekey:[a-fA-F0-9]+")
|
||||
|
||||
func MachinePublicKeyStripPrefix(machineKey key.MachinePublic) string {
|
||||
return strings.TrimPrefix(machineKey.String(), machinePublicHexPrefix)
|
||||
}
|
||||
|
||||
func NodePublicKeyStripPrefix(nodeKey key.NodePublic) string {
|
||||
return strings.TrimPrefix(nodeKey.String(), nodePublicHexPrefix)
|
||||
}
|
||||
|
||||
func DiscoPublicKeyStripPrefix(discoKey key.DiscoPublic) string {
|
||||
return strings.TrimPrefix(discoKey.String(), discoPublicHexPrefix)
|
||||
}
|
||||
|
||||
func MachinePublicKeyEnsurePrefix(machineKey string) string {
|
||||
if !strings.HasPrefix(machineKey, machinePublicHexPrefix) {
|
||||
return machinePublicHexPrefix + machineKey
|
||||
}
|
||||
|
||||
return machineKey
|
||||
}
|
||||
|
||||
func NodePublicKeyEnsurePrefix(nodeKey string) string {
|
||||
if !strings.HasPrefix(nodeKey, nodePublicHexPrefix) {
|
||||
return nodePublicHexPrefix + nodeKey
|
||||
}
|
||||
|
||||
return nodeKey
|
||||
}
|
||||
|
||||
func DiscoPublicKeyEnsurePrefix(discoKey string) string {
|
||||
if !strings.HasPrefix(discoKey, discoPublicHexPrefix) {
|
||||
return discoPublicHexPrefix + discoKey
|
||||
}
|
||||
|
||||
return discoKey
|
||||
}
|
||||
|
||||
func PrivateKeyEnsurePrefix(privateKey string) string {
|
||||
if !strings.HasPrefix(privateKey, privateHexPrefix) {
|
||||
return privateHexPrefix + privateKey
|
||||
}
|
||||
|
||||
return privateKey
|
||||
}
|
||||
|
||||
// Error is used to compare errors as per https://dave.cheney.net/2016/04/07/constant-errors
|
||||
type Error string
|
||||
|
||||
func (e Error) Error() string { return string(e) }
|
||||
|
||||
func decode(
|
||||
msg []byte,
|
||||
output interface{},
|
||||
pubKey *key.MachinePublic,
|
||||
privKey *key.MachinePrivate,
|
||||
) error {
|
||||
log.Trace().
|
||||
Str("pubkey", pubKey.ShortString()).
|
||||
Int("length", len(msg)).
|
||||
Msg("Trying to decrypt")
|
||||
|
||||
decrypted, ok := privKey.OpenFrom(*pubKey, msg)
|
||||
if !ok {
|
||||
return ErrCannotDecryptResponse
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(decrypted, output); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Headscale) getAvailableIPs() (MachineAddresses, error) {
|
||||
var ips MachineAddresses
|
||||
var err error
|
||||
ipPrefixes := h.cfg.IPPrefixes
|
||||
for _, ipPrefix := range ipPrefixes {
|
||||
var ip *netip.Addr
|
||||
ip, err = h.getAvailableIP(ipPrefix)
|
||||
if err != nil {
|
||||
return ips, err
|
||||
}
|
||||
ips = append(ips, *ip)
|
||||
}
|
||||
|
||||
return ips, err
|
||||
}
|
||||
|
||||
func GetIPPrefixEndpoints(na netip.Prefix) (netip.Addr, netip.Addr) {
|
||||
var network, broadcast netip.Addr
|
||||
ipRange := netipx.RangeOfPrefix(na)
|
||||
network = ipRange.From()
|
||||
broadcast = ipRange.To()
|
||||
|
||||
return network, broadcast
|
||||
}
|
||||
|
||||
func (h *Headscale) getAvailableIP(ipPrefix netip.Prefix) (*netip.Addr, error) {
|
||||
usedIps, err := h.getUsedIPs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipPrefixNetworkAddress, ipPrefixBroadcastAddress := GetIPPrefixEndpoints(ipPrefix)
|
||||
|
||||
// Get the first IP in our prefix
|
||||
ip := ipPrefixNetworkAddress.Next()
|
||||
|
||||
for {
|
||||
if !ipPrefix.Contains(ip) {
|
||||
return nil, ErrCouldNotAllocateIP
|
||||
}
|
||||
|
||||
switch {
|
||||
case ip.Compare(ipPrefixBroadcastAddress) == 0:
|
||||
fallthrough
|
||||
case usedIps.Contains(ip):
|
||||
fallthrough
|
||||
case ip == netip.Addr{} || ip.IsLoopback():
|
||||
ip = ip.Next()
|
||||
|
||||
continue
|
||||
|
||||
default:
|
||||
return &ip, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Headscale) getUsedIPs() (*netipx.IPSet, error) {
|
||||
// FIXME: This really deserves a better data model,
|
||||
// but this was quick to get running and it should be enough
|
||||
// to begin experimenting with a dual stack tailnet.
|
||||
var addressesSlices []string
|
||||
h.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices)
|
||||
|
||||
var ips netipx.IPSetBuilder
|
||||
for _, slice := range addressesSlices {
|
||||
var machineAddresses MachineAddresses
|
||||
err := machineAddresses.Scan(slice)
|
||||
if err != nil {
|
||||
return &netipx.IPSet{}, fmt.Errorf(
|
||||
"failed to read ip from database: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
for _, ip := range machineAddresses {
|
||||
ips.Add(ip)
|
||||
}
|
||||
}
|
||||
|
||||
ipSet, err := ips.IPSet()
|
||||
if err != nil {
|
||||
return &netipx.IPSet{}, fmt.Errorf(
|
||||
"failed to build IP Set: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return ipSet, nil
|
||||
}
|
||||
|
||||
func tailNodesToString(nodes []*tailcfg.Node) string {
|
||||
temp := make([]string, len(nodes))
|
||||
|
||||
for index, node := range nodes {
|
||||
temp[index] = node.Name
|
||||
}
|
||||
|
||||
return fmt.Sprintf("[ %s ](%d)", strings.Join(temp, ", "), len(temp))
|
||||
}
|
||||
|
||||
func tailMapResponseToString(resp tailcfg.MapResponse) string {
|
||||
return fmt.Sprintf(
|
||||
"{ Node: %s, Peers: %s }",
|
||||
resp.Node.Name,
|
||||
tailNodesToString(resp.Peers),
|
||||
)
|
||||
}
|
||||
|
||||
func GrpcSocketDialer(ctx context.Context, addr string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
|
||||
return d.DialContext(ctx, "unix", addr)
|
||||
}
|
||||
|
||||
func stringToIPPrefix(prefixes []string) ([]netip.Prefix, error) {
|
||||
result := make([]netip.Prefix, len(prefixes))
|
||||
|
||||
for index, prefixStr := range prefixes {
|
||||
prefix, err := netip.ParsePrefix(prefixStr)
|
||||
if err != nil {
|
||||
return []netip.Prefix{}, err
|
||||
}
|
||||
|
||||
result[index] = prefix
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func contains[T string | netip.Prefix](ts []T, t T) bool {
|
||||
for _, v := range ts {
|
||||
if reflect.DeepEqual(v, t) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateRandomBytes returns securely generated random bytes.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomBytes(n int) ([]byte, error) {
|
||||
bytes := make([]byte, n)
|
||||
|
||||
// Note that err == nil only if we read len(b) bytes.
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// GenerateRandomStringURLSafe returns a URL-safe, base64 encoded
|
||||
// securely generated random string.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomStringURLSafe(n int) (string, error) {
|
||||
b, err := GenerateRandomBytes(n)
|
||||
|
||||
return base64.RawURLEncoding.EncodeToString(b), err
|
||||
}
|
||||
|
||||
// GenerateRandomStringDNSSafe returns a DNS-safe
|
||||
// securely generated random string.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomStringDNSSafe(size int) (string, error) {
|
||||
var str string
|
||||
var err error
|
||||
for len(str) < size {
|
||||
str, err = GenerateRandomStringURLSafe(size)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str = strings.ToLower(
|
||||
strings.ReplaceAll(strings.ReplaceAll(str, "_", ""), "-", ""),
|
||||
)
|
||||
}
|
||||
|
||||
return str[:size], nil
|
||||
}
|
||||
|
||||
func IsStringInSlice(slice []string, str string) bool {
|
||||
for _, s := range slice {
|
||||
if s == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func AbsolutePathFromConfigPath(path string) string {
|
||||
// If a relative path is provided, prefix it with the directory where
|
||||
// the config file was found.
|
||||
if (path != "") && !strings.HasPrefix(path, string(os.PathSeparator)) {
|
||||
dir, _ := filepath.Split(viper.ConfigFileUsed())
|
||||
if dir != "" {
|
||||
path = filepath.Join(dir, path)
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func GetFileMode(key string) fs.FileMode {
|
||||
modeStr := viper.GetString(key)
|
||||
|
||||
mode, err := strconv.ParseUint(modeStr, Base8, BitSize64)
|
||||
if err != nil {
|
||||
return PermissionFallback
|
||||
}
|
||||
|
||||
return fs.FileMode(mode)
|
||||
}
|
|
@ -15,6 +15,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/juanfont/headscale/integration/dockertestutil"
|
||||
"github.com/juanfont/headscale/integration/hsic"
|
||||
"github.com/ory/dockertest/v3"
|
||||
|
@ -220,7 +221,7 @@ func (s *AuthOIDCScenario) runMockOIDC(accessTTL time.Duration) (*hscontrol.OIDC
|
|||
}
|
||||
portNotation := fmt.Sprintf("%d/tcp", port)
|
||||
|
||||
hash, _ := hscontrol.GenerateRandomStringDNSSafe(hsicOIDCMockHashLength)
|
||||
hash, _ := util.GenerateRandomStringDNSSafe(hsicOIDCMockHashLength)
|
||||
|
||||
hostname := fmt.Sprintf("hs-oidcmock-%s", hash)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/juanfont/headscale/integration/dockertestutil"
|
||||
"github.com/juanfont/headscale/integration/hsic"
|
||||
"github.com/juanfont/headscale/integration/tsic"
|
||||
|
@ -110,7 +110,7 @@ func (s *EmbeddedDERPServerScenario) CreateHeadscaleEnv(
|
|||
return err
|
||||
}
|
||||
|
||||
hash, err := hscontrol.GenerateRandomStringDNSSafe(scenarioHashLength)
|
||||
hash, err := util.GenerateRandomStringDNSSafe(scenarioHashLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/davecgh/go-spew/spew"
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/juanfont/headscale/integration/dockertestutil"
|
||||
"github.com/juanfont/headscale/integration/integrationutil"
|
||||
"github.com/ory/dockertest/v3"
|
||||
|
@ -132,7 +133,7 @@ func WithHostPortBindings(bindings map[string][]string) Option {
|
|||
// in the Docker container name.
|
||||
func WithTestName(testName string) Option {
|
||||
return func(hsic *HeadscaleInContainer) {
|
||||
hash, _ := hscontrol.GenerateRandomStringDNSSafe(hsicHashLength)
|
||||
hash, _ := util.GenerateRandomStringDNSSafe(hsicHashLength)
|
||||
|
||||
hostname := fmt.Sprintf("hs-%s-%s", testName, hash)
|
||||
hsic.hostname = hostname
|
||||
|
@ -167,7 +168,7 @@ func New(
|
|||
network *dockertest.Network,
|
||||
opts ...Option,
|
||||
) (*HeadscaleInContainer, error) {
|
||||
hash, err := hscontrol.GenerateRandomStringDNSSafe(hsicHashLength)
|
||||
hash, err := util.GenerateRandomStringDNSSafe(hsicHashLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/juanfont/headscale/integration/dockertestutil"
|
||||
"github.com/juanfont/headscale/integration/hsic"
|
||||
"github.com/juanfont/headscale/integration/tsic"
|
||||
|
@ -105,7 +105,7 @@ type Scenario struct {
|
|||
// NewScenario creates a test Scenario which can be used to bootstraps a ControlServer with
|
||||
// a set of Users and TailscaleClients.
|
||||
func NewScenario() (*Scenario, error) {
|
||||
hash, err := hscontrol.GenerateRandomStringDNSSafe(scenarioHashLength)
|
||||
hash, err := util.GenerateRandomStringDNSSafe(scenarioHashLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/juanfont/headscale/hscontrol"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/juanfont/headscale/integration/dockertestutil"
|
||||
"github.com/juanfont/headscale/integration/integrationutil"
|
||||
"github.com/ory/dockertest/v3"
|
||||
|
@ -150,7 +150,7 @@ func New(
|
|||
network *dockertest.Network,
|
||||
opts ...Option,
|
||||
) (*TailscaleInContainer, error) {
|
||||
hash, err := hscontrol.GenerateRandomStringDNSSafe(tsicHashLength)
|
||||
hash, err := util.GenerateRandomStringDNSSafe(tsicHashLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue