Merge branch 'juanfont:main' into main

This commit is contained in:
Kedas 2024-07-13 13:11:13 -07:00 committed by GitHub
commit 1fc14790f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 288 additions and 118 deletions

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

View 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:

View file

@ -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:

View file

@ -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": {

View file

@ -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`) {
_ = tx.Migrator().DropColumn(&types.Node{}, "last_successful_update")
return nil
} else {
return err
}
return err
},
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

View file

@ -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))
}

View file

@ -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 {

View file

@ -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().

View file

@ -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>
`))

View file

@ -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,15 +122,20 @@ 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 {
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(
@ -151,8 +156,7 @@ func generateDNSConfig(
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,

View file

@ -127,7 +127,10 @@ func TestDNSConfigMapResponse(t *testing.T) {
}
got := generateDNSConfig(
&dnsConfigOrig,
&types.Config{
DNSConfig: &dnsConfigOrig,
DNSUserNameInMagicDNS: true,
},
baseDomain,
nodeInShared1,
peersOfNodeInShared1,

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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},
},
}

View file

@ -64,6 +64,7 @@ type Config struct {
ACMEEmail string
DNSConfig *tailcfg.DNSConfig
DNSUserNameInMagicDNS bool
UnixSocket string
UnixSocketPermission fs.FileMode
@ -82,6 +83,7 @@ type Config struct {
type SqliteConfig struct {
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
}
@ -736,6 +742,7 @@ func GetHeadscaleConfig() (*Config, error) {
TLS: GetTLSConfig(),
DNSConfig: dnsConfig,
DNSUserNameInMagicDNS: viper.GetBool("dns_config.use_username_in_magic_dns"),
ACMEEmail: viper.GetString("tls.acme_email"),
ACMEURL: viper.GetString("tls.acme_url"),

View file

@ -394,13 +394,20 @@ 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)
}
hostname = fmt.Sprintf(
"%s.%s",
node.GivenName,
baseDomain,
)
if cfg.DNSUserNameInMagicDNS {
if node.User.Name == "" {
return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeUserHasNoName)
}
@ -411,6 +418,8 @@ func (node *Node) GetFQDN(dnsConfig *tailcfg.DNSConfig, baseDomain string) (stri
node.User.Name,
baseDomain,
)
}
if len(hostname) > MaxHostnameLength {
return "", fmt.Errorf(
"failed to create valid FQDN (%s): %w",

View file

@ -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{
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,9 +231,12 @@ func TestNodeFQDN(t *testing.T) {
Name: "user",
},
},
dns: tailcfg.DNSConfig{
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{
cfg: Config{
DNSConfig: &tailcfg.DNSConfig{
Proxied: true,
},
DNSUserNameInMagicDNS: false,
},
domain: "example.com",
wantErr: "failed to create valid FQDN: node user has no name",
want: "test.example.com",
},
{
name: "no-magic-dns",
@ -178,9 +263,12 @@ func TestNodeFQDN(t *testing.T) {
Name: "user",
},
},
dns: tailcfg.DNSConfig{
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)