mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-26 17:03:06 +00:00
Merge branch 'juanfont:main' into main
This commit is contained in:
commit
1fc14790f9
22 changed files with 288 additions and 118 deletions
|
@ -40,6 +40,10 @@ after improving the test harness as part of adopting [#1460](https://github.com/
|
|||
- Prefixes are now defined per v4 and v6 range. [#1756](https://github.com/juanfont/headscale/pull/1756)
|
||||
- `ip_prefixes` option is now `prefixes.v4` and `prefixes.v6`
|
||||
- `prefixes.allocation` can be set to assign IPs at `sequential` or `random`. [#1869](https://github.com/juanfont/headscale/pull/1869)
|
||||
- MagicDNS domains no longer contain usernames []()
|
||||
- This is in preperation to fix Headscales implementation of tags which currently does not correctly remove the link between a tagged device and a user. As tagged devices will not have a user, this will require a change to the DNS generation, removing the username, see [#1369](https://github.com/juanfont/headscale/issues/1369) for more information.
|
||||
- `use_username_in_magic_dns` can be used to turn this behaviour on again, but note that this option _will be removed_ when tags are fixed.
|
||||
- This option brings Headscales behaviour in line with Tailscale.
|
||||
|
||||
### Changes
|
||||
|
||||
|
@ -58,6 +62,8 @@ after improving the test harness as part of adopting [#1460](https://github.com/
|
|||
- Log available update as warning [#1877](https://github.com/juanfont/headscale/pull/1877)
|
||||
- Add `autogroup:internet` to Policy [#1917](https://github.com/juanfont/headscale/pull/1917)
|
||||
- Restore foreign keys and add constraints [#1562](https://github.com/juanfont/headscale/pull/1562)
|
||||
- Make registration page easier to use on mobile devices
|
||||
- Make write-ahead-log default on and configurable for SQLite [#1985](https://github.com/juanfont/headscale/pull/1985)
|
||||
|
||||
## 0.22.3 (2023-05-12)
|
||||
|
||||
|
|
|
@ -145,6 +145,10 @@ database:
|
|||
sqlite:
|
||||
path: /var/lib/headscale/db.sqlite
|
||||
|
||||
# Enable WAL mode for SQLite. This is recommended for production environments.
|
||||
# https://www.sqlite.org/wal.html
|
||||
write_ahead_log: true
|
||||
|
||||
# # Postgres config
|
||||
# postgres:
|
||||
# # If using a Unix socket to connect to Postgres, set the socket path in the 'host' field and leave 'port' blank.
|
||||
|
@ -268,6 +272,15 @@ dns_config:
|
|||
# Only works if there is at least a nameserver defined.
|
||||
magic_dns: true
|
||||
|
||||
# DEPRECATED
|
||||
# Use the username as part of the DNS name for nodes, with this option enabled:
|
||||
# node1.username.example.com
|
||||
# while when this is disabled:
|
||||
# node1.example.com
|
||||
# This is a legacy option as Headscale has have this wrongly implemented
|
||||
# while in upstream Tailscale, the username is not included.
|
||||
use_username_in_magic_dns: false
|
||||
|
||||
# Defines the base domain to create the hostnames for MagicDNS.
|
||||
# `base_domain` must be a FQDNs, without the trailing dot.
|
||||
# The FQDN of the hosts will be
|
||||
|
|
|
@ -12,8 +12,8 @@ Ensure that the installed version is at least 1.30.0, as that is the first relea
|
|||
|
||||
## Configuring the headscale URL
|
||||
|
||||
After opening the app, the kebab menu icon (three dots) on the top bar on the right must be repeatedly opened and closed until the _Change server_ option appears in the menu. This is where you can enter your headscale URL.
|
||||
After opening the app:
|
||||
|
||||
A screen recording of this process can be seen in the `tailscale-android` PR which implemented this functionality: <https://github.com/tailscale/tailscale-android/pull/55>
|
||||
|
||||
After saving and restarting the app, selecting the regular _Sign in_ option (non-SSO) should open up the headscale authentication page.
|
||||
- Open setting and go into account settings
|
||||
- In the kebab menu icon (three dots) on the top bar on the right select “Use an alternate server”
|
||||
- Enter your server URL and follow the instructions
|
||||
|
|
|
@ -8,7 +8,7 @@ hide:
|
|||
|
||||
`headscale` is an open source, self-hosted implementation of the Tailscale control server.
|
||||
|
||||
This page contains the documentation for the latest version of headscale. Please also check our [FAQ](/faq/).
|
||||
This page contains the documentation for the latest version of headscale. Please also check our [FAQ](faq.md).
|
||||
|
||||
Join our [Discord](https://discord.gg/c84AZQhmpx) server for a chat and community support.
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@ The reverse proxy MUST be configured to support WebSockets, as it is needed for
|
|||
|
||||
WebSockets support is required when using the headscale embedded DERP server. In this case, you will also need to expose the UDP port used for STUN (by default, udp/3478). Please check our [config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml).
|
||||
|
||||
### Cloudflare
|
||||
|
||||
Running headscale behind a cloudflare proxy or cloudflare tunnel is not supported and will not work as Cloudflare does not support WebSocket POSTs as required by the Tailscale protocol. See [this issue](https://github.com/juanfont/headscale/issues/1468)
|
||||
|
||||
### TLS
|
||||
|
||||
Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration values to your headscale config file.
|
||||
|
|
|
@ -57,7 +57,7 @@ describing how to make `headscale` run properly in a server environment.
|
|||
touch /etc/headscale/config.yaml
|
||||
```
|
||||
|
||||
**(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
|
||||
**(Strongly Recommended)** Download a copy of the [example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
|
||||
|
||||
1. Start the headscale server:
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ describing how to make `headscale` run properly in a server environment.
|
|||
touch /etc/headscale/config.yaml
|
||||
```
|
||||
|
||||
**(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
|
||||
**(Strongly Recommended)** Download a copy of the [example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
|
||||
|
||||
1. Start the headscale server:
|
||||
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1716062047,
|
||||
"narHash": "sha256-OhysviwHQz4p2HZL4g7XGMLoUbWMjkMr/ogaR3VUYNA=",
|
||||
"lastModified": 1720181791,
|
||||
"narHash": "sha256-i4vJL12/AdyuQuviMMd1Hk2tsGt02hDNhA0Zj1m16N8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "02923630b89aa1ab36ef8e422501a6f4fd4b2016",
|
||||
"rev": "4284c2b73c8bce4b46a6adf23e16d9e2ec8da4bb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -92,7 +92,7 @@ func NewHeadscaleDatabase(
|
|||
RenameColumn(&types.Node{}, "nickname", "given_name")
|
||||
|
||||
dbConn.Model(&types.Node{}).Where("auth_key_id = ?", 0).Update("auth_key_id", nil)
|
||||
// If the Node table has a column for registered,
|
||||
// If the Node table has a column for registered,
|
||||
// find all occourences of "false" and drop them. Then
|
||||
// remove the column.
|
||||
if tx.Migrator().HasColumn(&types.Node{}, "registered") {
|
||||
|
@ -319,14 +319,8 @@ func NewHeadscaleDatabase(
|
|||
// no longer used.
|
||||
ID: "202402151347",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
err := tx.Migrator().DropColumn(&types.Node{}, "last_successful_update")
|
||||
if err != nil && strings.Contains(err.Error(), `of relation "nodes" does not exist`) {
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
_ = tx.Migrator().DropColumn(&types.Node{}, "last_successful_update")
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return nil
|
||||
|
@ -440,13 +434,29 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
|
|||
Msg("Opening database")
|
||||
|
||||
db, err := gorm.Open(
|
||||
sqlite.Open(cfg.Sqlite.Path+"?_synchronous=1&_journal_mode=WAL"),
|
||||
sqlite.Open(cfg.Sqlite.Path),
|
||||
&gorm.Config{
|
||||
Logger: dbLogger,
|
||||
},
|
||||
)
|
||||
|
||||
db.Exec("PRAGMA foreign_keys=ON")
|
||||
if err := db.Exec(`
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA busy_timeout=10000;
|
||||
PRAGMA auto_vacuum=INCREMENTAL;
|
||||
PRAGMA synchronous=NORMAL;
|
||||
`).Error; err != nil {
|
||||
return nil, fmt.Errorf("enabling foreign keys: %w", err)
|
||||
}
|
||||
|
||||
if cfg.Sqlite.WriteAheadLog {
|
||||
if err := db.Exec(`
|
||||
PRAGMA journal_mode=WAL;
|
||||
PRAGMA wal_autocheckpoint=0;
|
||||
`).Error; err != nil {
|
||||
return nil, fmt.Errorf("setting WAL mode: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// The pure Go SQLite library does not handle locking in
|
||||
// the same way as the C based one and we cant use the gorm
|
||||
|
|
|
@ -215,7 +215,7 @@ func SetTags(
|
|||
return nil
|
||||
}
|
||||
|
||||
newTags := types.StringList{}
|
||||
var newTags types.StringList
|
||||
for _, tag := range tags {
|
||||
if !util.StringOrPrefixListContains(newTags, tag) {
|
||||
newTags = append(newTags, tag)
|
||||
|
@ -452,7 +452,7 @@ func GetAdvertisedRoutes(tx *gorm.DB, node *types.Node) ([]netip.Prefix, error)
|
|||
return nil, fmt.Errorf("getting advertised routes for node(%d): %w", node.ID, err)
|
||||
}
|
||||
|
||||
prefixes := []netip.Prefix{}
|
||||
var prefixes []netip.Prefix
|
||||
for _, route := range routes {
|
||||
prefixes = append(prefixes, netip.Prefix(route.Prefix))
|
||||
}
|
||||
|
@ -478,7 +478,7 @@ func GetEnabledRoutes(tx *gorm.DB, node *types.Node) ([]netip.Prefix, error) {
|
|||
return nil, fmt.Errorf("getting enabled routes for node(%d): %w", node.ID, err)
|
||||
}
|
||||
|
||||
prefixes := []netip.Prefix{}
|
||||
var prefixes []netip.Prefix
|
||||
for _, route := range routes {
|
||||
prefixes = append(prefixes, netip.Prefix(route.Prefix))
|
||||
}
|
||||
|
|
|
@ -222,7 +222,7 @@ func DeleteRoute(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
routesToDelete := types.Routes{}
|
||||
var routesToDelete types.Routes
|
||||
for _, r := range routes {
|
||||
if r.IsExitRoute() {
|
||||
routesToDelete = append(routesToDelete, r)
|
||||
|
@ -623,7 +623,7 @@ func EnableAutoApprovedRoutes(
|
|||
|
||||
log.Trace().Interface("routes", routes).Msg("routes for autoapproving")
|
||||
|
||||
approvedRoutes := types.Routes{}
|
||||
var approvedRoutes types.Routes
|
||||
|
||||
for _, advertisedRoute := range routes {
|
||||
if advertisedRoute.Enabled {
|
||||
|
|
|
@ -81,7 +81,7 @@ func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap {
|
|||
}
|
||||
|
||||
func GetDERPMap(cfg types.DERPConfig) *tailcfg.DERPMap {
|
||||
derpMaps := make([]*tailcfg.DERPMap, 0)
|
||||
var derpMaps []*tailcfg.DERPMap
|
||||
|
||||
for _, path := range cfg.Paths {
|
||||
log.Debug().
|
||||
|
|
|
@ -143,6 +143,18 @@ var registerWebAPITemplate = template.Must(
|
|||
<html>
|
||||
<head>
|
||||
<title>Registration - Headscale</title>
|
||||
<meta name=viewport content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
font-family: sans;
|
||||
}
|
||||
code {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
border: 1px solid #bbb;
|
||||
background-color: #eee;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>headscale</h1>
|
||||
|
@ -150,7 +162,7 @@ var registerWebAPITemplate = template.Must(
|
|||
<p>
|
||||
Run the command below in the headscale server to add this machine to your network:
|
||||
</p>
|
||||
<pre><code>headscale nodes register --user USERNAME --key {{.Key}}</code></pre>
|
||||
<code>headscale nodes register --user USERNAME --key {{.Key}}</code>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
|
|
|
@ -102,7 +102,7 @@ func generateUserProfiles(
|
|||
userMap[peer.User.Name] = peer.User // not worth checking if already is there
|
||||
}
|
||||
|
||||
profiles := []tailcfg.UserProfile{}
|
||||
var profiles []tailcfg.UserProfile
|
||||
for _, user := range userMap {
|
||||
displayName := user.Name
|
||||
|
||||
|
@ -122,37 +122,41 @@ func generateUserProfiles(
|
|||
}
|
||||
|
||||
func generateDNSConfig(
|
||||
base *tailcfg.DNSConfig,
|
||||
cfg *types.Config,
|
||||
baseDomain string,
|
||||
node *types.Node,
|
||||
peers types.Nodes,
|
||||
) *tailcfg.DNSConfig {
|
||||
dnsConfig := base.Clone()
|
||||
if cfg.DNSConfig == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dnsConfig := cfg.DNSConfig.Clone()
|
||||
|
||||
// if MagicDNS is enabled
|
||||
if base != nil && base.Proxied {
|
||||
// Only inject the Search Domain of the current user
|
||||
// shared nodes should use their full FQDN
|
||||
dnsConfig.Domains = append(
|
||||
dnsConfig.Domains,
|
||||
fmt.Sprintf(
|
||||
"%s.%s",
|
||||
node.User.Name,
|
||||
baseDomain,
|
||||
),
|
||||
)
|
||||
if dnsConfig.Proxied {
|
||||
if cfg.DNSUserNameInMagicDNS {
|
||||
// Only inject the Search Domain of the current user
|
||||
// shared nodes should use their full FQDN
|
||||
dnsConfig.Domains = append(
|
||||
dnsConfig.Domains,
|
||||
fmt.Sprintf(
|
||||
"%s.%s",
|
||||
node.User.Name,
|
||||
baseDomain,
|
||||
),
|
||||
)
|
||||
|
||||
userSet := mapset.NewSet[types.User]()
|
||||
userSet.Add(node.User)
|
||||
for _, p := range peers {
|
||||
userSet.Add(p.User)
|
||||
userSet := mapset.NewSet[types.User]()
|
||||
userSet.Add(node.User)
|
||||
for _, p := range peers {
|
||||
userSet.Add(p.User)
|
||||
}
|
||||
for _, user := range userSet.ToSlice() {
|
||||
dnsRoute := fmt.Sprintf("%v.%v", user.Name, baseDomain)
|
||||
dnsConfig.Routes[dnsRoute] = nil
|
||||
}
|
||||
}
|
||||
for _, user := range userSet.ToSlice() {
|
||||
dnsRoute := fmt.Sprintf("%v.%v", user.Name, baseDomain)
|
||||
dnsConfig.Routes[dnsRoute] = nil
|
||||
}
|
||||
} else {
|
||||
dnsConfig = base
|
||||
}
|
||||
|
||||
addNextDNSMetadata(dnsConfig.Resolvers, node)
|
||||
|
@ -568,7 +572,7 @@ func appendPeerChanges(
|
|||
profiles := generateUserProfiles(node, changed, cfg.BaseDomain)
|
||||
|
||||
dnsConfig := generateDNSConfig(
|
||||
cfg.DNSConfig,
|
||||
cfg,
|
||||
cfg.BaseDomain,
|
||||
node,
|
||||
peers,
|
||||
|
|
|
@ -127,7 +127,10 @@ func TestDNSConfigMapResponse(t *testing.T) {
|
|||
}
|
||||
|
||||
got := generateDNSConfig(
|
||||
&dnsConfigOrig,
|
||||
&types.Config{
|
||||
DNSConfig: &dnsConfigOrig,
|
||||
DNSUserNameInMagicDNS: true,
|
||||
},
|
||||
baseDomain,
|
||||
nodeInShared1,
|
||||
peersOfNodeInShared1,
|
||||
|
@ -187,9 +190,9 @@ func Test_fullMapResponse(t *testing.T) {
|
|||
UserID: 0,
|
||||
User: types.User{Name: "mini"},
|
||||
ForcedTags: []string{},
|
||||
AuthKey: &types.PreAuthKey{},
|
||||
LastSeen: &lastSeen,
|
||||
Expiry: &expire,
|
||||
AuthKey: &types.PreAuthKey{},
|
||||
LastSeen: &lastSeen,
|
||||
Expiry: &expire,
|
||||
Hostinfo: &tailcfg.Hostinfo{},
|
||||
Routes: []types.Route{
|
||||
{
|
||||
|
|
|
@ -77,7 +77,7 @@ func tailNode(
|
|||
keyExpiry = time.Time{}
|
||||
}
|
||||
|
||||
hostname, err := node.GetFQDN(cfg.DNSConfig, cfg.BaseDomain)
|
||||
hostname, err := node.GetFQDN(cfg, cfg.BaseDomain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tailNode, failed to create FQDN: %s", err)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package notifier
|
|||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -221,6 +222,11 @@ func TestBatcher(t *testing.T) {
|
|||
// We will call flush manually for the tests,
|
||||
// so do not run the worker.
|
||||
BatchChangeDelay: time.Hour,
|
||||
|
||||
// Since we do not load the config, we wont get the
|
||||
// default, so set it manually so we dont time out
|
||||
// and have flakes.
|
||||
NotifierSendTimeout: time.Second,
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -241,6 +247,16 @@ func TestBatcher(t *testing.T) {
|
|||
got = append(got, out)
|
||||
}
|
||||
|
||||
// Make the inner order stable for comparison.
|
||||
for _, u := range got {
|
||||
sort.Slice(u.ChangeNodes, func(i, j int) bool {
|
||||
return u.ChangeNodes[i] < u.ChangeNodes[j]
|
||||
})
|
||||
sort.Slice(u.ChangePatches, func(i, j int) bool {
|
||||
return u.ChangePatches[i].NodeID < u.ChangePatches[j].NodeID
|
||||
})
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tt.want, got, util.Comparers...); diff != "" {
|
||||
t.Errorf("batcher() unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
|
|
|
@ -180,14 +180,14 @@ func (pol *ACLPolicy) CompileFilterRules(
|
|||
return tailcfg.FilterAllowAll, nil
|
||||
}
|
||||
|
||||
rules := []tailcfg.FilterRule{}
|
||||
var rules []tailcfg.FilterRule
|
||||
|
||||
for index, acl := range pol.ACLs {
|
||||
if acl.Action != "accept" {
|
||||
return nil, ErrInvalidAction
|
||||
}
|
||||
|
||||
srcIPs := []string{}
|
||||
var srcIPs []string
|
||||
for srcIndex, src := range acl.Sources {
|
||||
srcs, err := pol.expandSource(src, nodes)
|
||||
if err != nil {
|
||||
|
@ -221,7 +221,7 @@ func (pol *ACLPolicy) CompileFilterRules(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
dests := []tailcfg.NetPortRange{}
|
||||
var dests []tailcfg.NetPortRange
|
||||
for _, dest := range expanded.Prefixes() {
|
||||
for _, port := range *ports {
|
||||
pr := tailcfg.NetPortRange{
|
||||
|
@ -251,8 +251,7 @@ func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.F
|
|||
|
||||
for _, rule := range rules {
|
||||
// record if the rule is actually relevant for the given node.
|
||||
dests := []tailcfg.NetPortRange{}
|
||||
|
||||
var dests []tailcfg.NetPortRange
|
||||
DEST_LOOP:
|
||||
for _, dest := range rule.DstPorts {
|
||||
expanded, err := util.ParseIPSet(dest.IP, nil)
|
||||
|
@ -301,7 +300,7 @@ func (pol *ACLPolicy) CompileSSHPolicy(
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
rules := []*tailcfg.SSHRule{}
|
||||
var rules []*tailcfg.SSHRule
|
||||
|
||||
acceptAction := tailcfg.SSHAction{
|
||||
Message: "",
|
||||
|
@ -533,8 +532,7 @@ func (pol *ACLPolicy) expandSource(
|
|||
return []string{}, err
|
||||
}
|
||||
|
||||
prefixes := []string{}
|
||||
|
||||
var prefixes []string
|
||||
for _, prefix := range ipSet.Prefixes() {
|
||||
prefixes = append(prefixes, prefix.String())
|
||||
}
|
||||
|
@ -615,8 +613,8 @@ func excludeCorrectlyTaggedNodes(
|
|||
nodes types.Nodes,
|
||||
user string,
|
||||
) types.Nodes {
|
||||
out := types.Nodes{}
|
||||
tags := []string{}
|
||||
var out types.Nodes
|
||||
var tags []string
|
||||
for tag := range aclPolicy.TagOwners {
|
||||
owners, _ := expandOwnersFromTag(aclPolicy, user)
|
||||
ns := append(owners, user)
|
||||
|
@ -661,7 +659,7 @@ func expandPorts(portsStr string, isWild bool) (*[]tailcfg.PortRange, error) {
|
|||
return nil, ErrWildcardIsNeeded
|
||||
}
|
||||
|
||||
ports := []tailcfg.PortRange{}
|
||||
var ports []tailcfg.PortRange
|
||||
for _, portStr := range strings.Split(portsStr, ",") {
|
||||
log.Trace().Msgf("parsing portstring: %s", portStr)
|
||||
rang := strings.Split(portStr, "-")
|
||||
|
@ -737,7 +735,7 @@ func expandOwnersFromTag(
|
|||
func (pol *ACLPolicy) expandUsersFromGroup(
|
||||
group string,
|
||||
) ([]string, error) {
|
||||
users := []string{}
|
||||
var users []string
|
||||
log.Trace().Caller().Interface("pol", pol).Msg("test")
|
||||
aclGroups, ok := pol.Groups[group]
|
||||
if !ok {
|
||||
|
@ -772,7 +770,7 @@ func (pol *ACLPolicy) expandIPsFromGroup(
|
|||
group string,
|
||||
nodes types.Nodes,
|
||||
) (*netipx.IPSet, error) {
|
||||
build := netipx.IPSetBuilder{}
|
||||
var build netipx.IPSetBuilder
|
||||
|
||||
users, err := pol.expandUsersFromGroup(group)
|
||||
if err != nil {
|
||||
|
@ -792,7 +790,7 @@ func (pol *ACLPolicy) expandIPsFromTag(
|
|||
alias string,
|
||||
nodes types.Nodes,
|
||||
) (*netipx.IPSet, error) {
|
||||
build := netipx.IPSetBuilder{}
|
||||
var build netipx.IPSetBuilder
|
||||
|
||||
// check for forced tags
|
||||
for _, node := range nodes {
|
||||
|
@ -841,7 +839,7 @@ func (pol *ACLPolicy) expandIPsFromUser(
|
|||
user string,
|
||||
nodes types.Nodes,
|
||||
) (*netipx.IPSet, error) {
|
||||
build := netipx.IPSetBuilder{}
|
||||
var build netipx.IPSetBuilder
|
||||
|
||||
filteredNodes := filterNodesByUser(nodes, user)
|
||||
filteredNodes = excludeCorrectlyTaggedNodes(pol, filteredNodes, user)
|
||||
|
@ -866,7 +864,7 @@ func (pol *ACLPolicy) expandIPsFromSingleIP(
|
|||
|
||||
matches := nodes.FilterByIP(ip)
|
||||
|
||||
build := netipx.IPSetBuilder{}
|
||||
var build netipx.IPSetBuilder
|
||||
build.Add(ip)
|
||||
|
||||
for _, node := range matches {
|
||||
|
@ -881,7 +879,7 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix(
|
|||
nodes types.Nodes,
|
||||
) (*netipx.IPSet, error) {
|
||||
log.Trace().Str("prefix", prefix.String()).Msg("expandAlias got prefix")
|
||||
build := netipx.IPSetBuilder{}
|
||||
var build netipx.IPSetBuilder
|
||||
build.AddPrefix(prefix)
|
||||
|
||||
// This is suboptimal and quite expensive, but if we only add the prefix, we will miss all the relevant IPv6
|
||||
|
@ -931,8 +929,8 @@ func isAutoGroup(str string) bool {
|
|||
func (pol *ACLPolicy) TagsOfNode(
|
||||
node *types.Node,
|
||||
) ([]string, []string) {
|
||||
validTags := make([]string, 0)
|
||||
invalidTags := make([]string, 0)
|
||||
var validTags []string
|
||||
var invalidTags []string
|
||||
|
||||
// TODO(kradalby): Why is this sometimes nil? coming from tailNode?
|
||||
if node == nil {
|
||||
|
@ -973,7 +971,7 @@ func (pol *ACLPolicy) TagsOfNode(
|
|||
}
|
||||
|
||||
func filterNodesByUser(nodes types.Nodes, user string) types.Nodes {
|
||||
out := types.Nodes{}
|
||||
var out types.Nodes
|
||||
for _, node := range nodes {
|
||||
if node.User.Name == user {
|
||||
out = append(out, node)
|
||||
|
@ -989,7 +987,7 @@ func FilterNodesByACL(
|
|||
nodes types.Nodes,
|
||||
filter []tailcfg.FilterRule,
|
||||
) types.Nodes {
|
||||
result := types.Nodes{}
|
||||
var result types.Nodes
|
||||
|
||||
for index, peer := range nodes {
|
||||
if peer.ID == node.ID {
|
||||
|
|
|
@ -943,7 +943,7 @@ func Test_listNodesInUser(t *testing.T) {
|
|||
},
|
||||
user: "mickael",
|
||||
},
|
||||
want: types.Nodes{},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@ -1645,7 +1645,7 @@ func TestACLPolicy_generateFilterRules(t *testing.T) {
|
|||
name: "no-policy",
|
||||
field: field{},
|
||||
args: args{},
|
||||
want: []tailcfg.FilterRule{},
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
|
@ -2896,7 +2896,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
|||
User: types.User{Name: "marc"},
|
||||
},
|
||||
},
|
||||
want: types.Nodes{},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
// Investigating 699
|
||||
|
@ -3426,7 +3426,7 @@ func TestSSHRules(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{}},
|
||||
want: &tailcfg.SSHPolicy{Rules: nil},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,8 @@ type Config struct {
|
|||
ACMEURL string
|
||||
ACMEEmail string
|
||||
|
||||
DNSConfig *tailcfg.DNSConfig
|
||||
DNSConfig *tailcfg.DNSConfig
|
||||
DNSUserNameInMagicDNS bool
|
||||
|
||||
UnixSocket string
|
||||
UnixSocketPermission fs.FileMode
|
||||
|
@ -81,7 +82,8 @@ type Config struct {
|
|||
}
|
||||
|
||||
type SqliteConfig struct {
|
||||
Path string
|
||||
Path string
|
||||
WriteAheadLog bool
|
||||
}
|
||||
|
||||
type PostgresConfig struct {
|
||||
|
@ -205,6 +207,7 @@ func LoadConfig(path string, isFile bool) error {
|
|||
|
||||
viper.SetDefault("dns_config", nil)
|
||||
viper.SetDefault("dns_config.override_local_dns", true)
|
||||
viper.SetDefault("dns_config.use_username_in_magic_dns", false)
|
||||
|
||||
viper.SetDefault("derp.server.enabled", false)
|
||||
viper.SetDefault("derp.server.stun.enabled", true)
|
||||
|
@ -224,6 +227,8 @@ func LoadConfig(path string, isFile bool) error {
|
|||
viper.SetDefault("database.postgres.max_idle_conns", 10)
|
||||
viper.SetDefault("database.postgres.conn_max_idle_time_secs", 3600)
|
||||
|
||||
viper.SetDefault("database.sqlite.write_ahead_log", true)
|
||||
|
||||
viper.SetDefault("oidc.scope", []string{oidc.ScopeOpenID, "profile", "email"})
|
||||
viper.SetDefault("oidc.strip_email_domain", true)
|
||||
viper.SetDefault("oidc.only_start_if_oidc_is_available", true)
|
||||
|
@ -463,6 +468,7 @@ func GetDatabaseConfig() DatabaseConfig {
|
|||
Path: util.AbsolutePathFromConfigPath(
|
||||
viper.GetString("database.sqlite.path"),
|
||||
),
|
||||
WriteAheadLog: viper.GetBool("database.sqlite.write_ahead_log"),
|
||||
},
|
||||
Postgres: PostgresConfig{
|
||||
Host: viper.GetString("database.postgres.host"),
|
||||
|
@ -556,16 +562,6 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) {
|
|||
dnsConfig.Domains = domains
|
||||
}
|
||||
|
||||
if viper.IsSet("dns_config.domains") {
|
||||
domains := viper.GetStringSlice("dns_config.domains")
|
||||
if len(dnsConfig.Resolvers) > 0 {
|
||||
dnsConfig.Domains = domains
|
||||
} else if domains != nil {
|
||||
log.Warn().
|
||||
Msg("Warning: dns_config.domains is set, but no nameservers are configured. Ignoring domains.")
|
||||
}
|
||||
}
|
||||
|
||||
if viper.IsSet("dns_config.extra_records") {
|
||||
var extraRecords []tailcfg.DNSRecord
|
||||
|
||||
|
@ -591,8 +587,18 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) {
|
|||
baseDomain = "headscale.net" // does not really matter when MagicDNS is not enabled
|
||||
}
|
||||
|
||||
log.Trace().Interface("dns_config", dnsConfig).Msg("DNS configuration loaded")
|
||||
if !viper.GetBool("dns_config.use_username_in_magic_dns") {
|
||||
dnsConfig.Domains = []string{baseDomain}
|
||||
} else {
|
||||
log.Warn().Msg("DNS: Usernames in DNS has been deprecated, this option will be remove in future versions")
|
||||
log.Warn().Msg("DNS: see 0.23.0 changelog for more information.")
|
||||
}
|
||||
|
||||
if domains := viper.GetStringSlice("dns_config.domains"); len(domains) > 0 {
|
||||
dnsConfig.Domains = append(dnsConfig.Domains, domains...)
|
||||
}
|
||||
|
||||
log.Trace().Interface("dns_config", dnsConfig).Msg("DNS configuration loaded")
|
||||
return dnsConfig, baseDomain
|
||||
}
|
||||
|
||||
|
@ -735,7 +741,8 @@ func GetHeadscaleConfig() (*Config, error) {
|
|||
|
||||
TLS: GetTLSConfig(),
|
||||
|
||||
DNSConfig: dnsConfig,
|
||||
DNSConfig: dnsConfig,
|
||||
DNSUserNameInMagicDNS: viper.GetBool("dns_config.use_username_in_magic_dns"),
|
||||
|
||||
ACMEEmail: viper.GetString("tls.acme_email"),
|
||||
ACMEURL: viper.GetString("tls.acme_url"),
|
||||
|
|
|
@ -394,23 +394,32 @@ func (node *Node) Proto() *v1.Node {
|
|||
return nodeProto
|
||||
}
|
||||
|
||||
func (node *Node) GetFQDN(dnsConfig *tailcfg.DNSConfig, baseDomain string) (string, error) {
|
||||
func (node *Node) GetFQDN(cfg *Config, baseDomain string) (string, error) {
|
||||
var hostname string
|
||||
if dnsConfig != nil && dnsConfig.Proxied { // MagicDNS
|
||||
if cfg.DNSConfig != nil && cfg.DNSConfig.Proxied { // MagicDNS
|
||||
if node.GivenName == "" {
|
||||
return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeHasNoGivenName)
|
||||
}
|
||||
|
||||
if node.User.Name == "" {
|
||||
return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeUserHasNoName)
|
||||
}
|
||||
|
||||
hostname = fmt.Sprintf(
|
||||
"%s.%s.%s",
|
||||
"%s.%s",
|
||||
node.GivenName,
|
||||
node.User.Name,
|
||||
baseDomain,
|
||||
)
|
||||
|
||||
if cfg.DNSUserNameInMagicDNS {
|
||||
if node.User.Name == "" {
|
||||
return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeUserHasNoName)
|
||||
}
|
||||
|
||||
hostname = fmt.Sprintf(
|
||||
"%s.%s.%s",
|
||||
node.GivenName,
|
||||
node.User.Name,
|
||||
baseDomain,
|
||||
)
|
||||
}
|
||||
|
||||
if len(hostname) > MaxHostnameLength {
|
||||
return "", fmt.Errorf(
|
||||
"failed to create valid FQDN (%s): %w",
|
||||
|
|
|
@ -126,11 +126,87 @@ func TestNodeFQDN(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
node Node
|
||||
dns tailcfg.DNSConfig
|
||||
cfg Config
|
||||
domain string
|
||||
want string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "all-set-with-username",
|
||||
node: Node{
|
||||
GivenName: "test",
|
||||
User: User{
|
||||
Name: "user",
|
||||
},
|
||||
},
|
||||
cfg: Config{
|
||||
DNSConfig: &tailcfg.DNSConfig{
|
||||
Proxied: true,
|
||||
},
|
||||
DNSUserNameInMagicDNS: true,
|
||||
},
|
||||
domain: "example.com",
|
||||
want: "test.user.example.com",
|
||||
},
|
||||
{
|
||||
name: "no-given-name-with-username",
|
||||
node: Node{
|
||||
User: User{
|
||||
Name: "user",
|
||||
},
|
||||
},
|
||||
cfg: Config{
|
||||
DNSConfig: &tailcfg.DNSConfig{
|
||||
Proxied: true,
|
||||
},
|
||||
DNSUserNameInMagicDNS: true,
|
||||
},
|
||||
domain: "example.com",
|
||||
wantErr: "failed to create valid FQDN: node has no given name",
|
||||
},
|
||||
{
|
||||
name: "no-user-name-with-username",
|
||||
node: Node{
|
||||
GivenName: "test",
|
||||
User: User{},
|
||||
},
|
||||
cfg: Config{
|
||||
DNSConfig: &tailcfg.DNSConfig{
|
||||
Proxied: true,
|
||||
},
|
||||
DNSUserNameInMagicDNS: true,
|
||||
},
|
||||
domain: "example.com",
|
||||
wantErr: "failed to create valid FQDN: node user has no name",
|
||||
},
|
||||
{
|
||||
name: "no-magic-dns-with-username",
|
||||
node: Node{
|
||||
GivenName: "test",
|
||||
User: User{
|
||||
Name: "user",
|
||||
},
|
||||
},
|
||||
cfg: Config{
|
||||
DNSConfig: &tailcfg.DNSConfig{
|
||||
Proxied: false,
|
||||
},
|
||||
DNSUserNameInMagicDNS: true,
|
||||
},
|
||||
domain: "example.com",
|
||||
want: "test",
|
||||
},
|
||||
{
|
||||
name: "no-dnsconfig-with-username",
|
||||
node: Node{
|
||||
GivenName: "test",
|
||||
User: User{
|
||||
Name: "user",
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
want: "test",
|
||||
},
|
||||
{
|
||||
name: "all-set",
|
||||
node: Node{
|
||||
|
@ -139,11 +215,14 @@ func TestNodeFQDN(t *testing.T) {
|
|||
Name: "user",
|
||||
},
|
||||
},
|
||||
dns: tailcfg.DNSConfig{
|
||||
Proxied: true,
|
||||
cfg: Config{
|
||||
DNSConfig: &tailcfg.DNSConfig{
|
||||
Proxied: true,
|
||||
},
|
||||
DNSUserNameInMagicDNS: false,
|
||||
},
|
||||
domain: "example.com",
|
||||
want: "test.user.example.com",
|
||||
want: "test.example.com",
|
||||
},
|
||||
{
|
||||
name: "no-given-name",
|
||||
|
@ -152,8 +231,11 @@ func TestNodeFQDN(t *testing.T) {
|
|||
Name: "user",
|
||||
},
|
||||
},
|
||||
dns: tailcfg.DNSConfig{
|
||||
Proxied: true,
|
||||
cfg: Config{
|
||||
DNSConfig: &tailcfg.DNSConfig{
|
||||
Proxied: true,
|
||||
},
|
||||
DNSUserNameInMagicDNS: false,
|
||||
},
|
||||
domain: "example.com",
|
||||
wantErr: "failed to create valid FQDN: node has no given name",
|
||||
|
@ -164,11 +246,14 @@ func TestNodeFQDN(t *testing.T) {
|
|||
GivenName: "test",
|
||||
User: User{},
|
||||
},
|
||||
dns: tailcfg.DNSConfig{
|
||||
Proxied: true,
|
||||
cfg: Config{
|
||||
DNSConfig: &tailcfg.DNSConfig{
|
||||
Proxied: true,
|
||||
},
|
||||
DNSUserNameInMagicDNS: false,
|
||||
},
|
||||
domain: "example.com",
|
||||
wantErr: "failed to create valid FQDN: node user has no name",
|
||||
domain: "example.com",
|
||||
want: "test.example.com",
|
||||
},
|
||||
{
|
||||
name: "no-magic-dns",
|
||||
|
@ -178,8 +263,11 @@ func TestNodeFQDN(t *testing.T) {
|
|||
Name: "user",
|
||||
},
|
||||
},
|
||||
dns: tailcfg.DNSConfig{
|
||||
Proxied: false,
|
||||
cfg: Config{
|
||||
DNSConfig: &tailcfg.DNSConfig{
|
||||
Proxied: false,
|
||||
},
|
||||
DNSUserNameInMagicDNS: false,
|
||||
},
|
||||
domain: "example.com",
|
||||
want: "test",
|
||||
|
@ -199,7 +287,7 @@ func TestNodeFQDN(t *testing.T) {
|
|||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.node.GetFQDN(&tc.dns, tc.domain)
|
||||
got, err := tc.node.GetFQDN(&tc.cfg, tc.domain)
|
||||
|
||||
if (err != nil) && (err.Error() != tc.wantErr) {
|
||||
t.Errorf("GetFQDN() error = %s, wantErr %s", err, tc.wantErr)
|
||||
|
|
Loading…
Reference in a new issue