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)
|
- 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`
|
- `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)
|
- `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
|
### 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)
|
- 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)
|
- 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)
|
- 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)
|
## 0.22.3 (2023-05-12)
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,10 @@ database:
|
||||||
sqlite:
|
sqlite:
|
||||||
path: /var/lib/headscale/db.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 config
|
||||||
# postgres:
|
# postgres:
|
||||||
# # If using a Unix socket to connect to Postgres, set the socket path in the 'host' field and leave 'port' blank.
|
# # 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.
|
# Only works if there is at least a nameserver defined.
|
||||||
magic_dns: true
|
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.
|
# Defines the base domain to create the hostnames for MagicDNS.
|
||||||
# `base_domain` must be a FQDNs, without the trailing dot.
|
# `base_domain` must be a FQDNs, without the trailing dot.
|
||||||
# The FQDN of the hosts will be
|
# 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
|
## 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>
|
- 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”
|
||||||
After saving and restarting the app, selecting the regular _Sign in_ option (non-SSO) should open up the headscale authentication page.
|
- 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.
|
`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.
|
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).
|
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
|
### 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.
|
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
|
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:
|
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
|
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:
|
1. Start the headscale server:
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1716062047,
|
"lastModified": 1720181791,
|
||||||
"narHash": "sha256-OhysviwHQz4p2HZL4g7XGMLoUbWMjkMr/ogaR3VUYNA=",
|
"narHash": "sha256-i4vJL12/AdyuQuviMMd1Hk2tsGt02hDNhA0Zj1m16N8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "02923630b89aa1ab36ef8e422501a6f4fd4b2016",
|
"rev": "4284c2b73c8bce4b46a6adf23e16d9e2ec8da4bb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -92,7 +92,7 @@ func NewHeadscaleDatabase(
|
||||||
RenameColumn(&types.Node{}, "nickname", "given_name")
|
RenameColumn(&types.Node{}, "nickname", "given_name")
|
||||||
|
|
||||||
dbConn.Model(&types.Node{}).Where("auth_key_id = ?", 0).Update("auth_key_id", nil)
|
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
|
// find all occourences of "false" and drop them. Then
|
||||||
// remove the column.
|
// remove the column.
|
||||||
if tx.Migrator().HasColumn(&types.Node{}, "registered") {
|
if tx.Migrator().HasColumn(&types.Node{}, "registered") {
|
||||||
|
@ -319,14 +319,8 @@ func NewHeadscaleDatabase(
|
||||||
// no longer used.
|
// no longer used.
|
||||||
ID: "202402151347",
|
ID: "202402151347",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
err := tx.Migrator().DropColumn(&types.Node{}, "last_successful_update")
|
_ = tx.Migrator().DropColumn(&types.Node{}, "last_successful_update")
|
||||||
if err != nil && strings.Contains(err.Error(), `of relation "nodes" does not exist`) {
|
return nil
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
},
|
},
|
||||||
Rollback: func(tx *gorm.DB) error {
|
Rollback: func(tx *gorm.DB) error {
|
||||||
return nil
|
return nil
|
||||||
|
@ -440,13 +434,29 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
|
||||||
Msg("Opening database")
|
Msg("Opening database")
|
||||||
|
|
||||||
db, err := gorm.Open(
|
db, err := gorm.Open(
|
||||||
sqlite.Open(cfg.Sqlite.Path+"?_synchronous=1&_journal_mode=WAL"),
|
sqlite.Open(cfg.Sqlite.Path),
|
||||||
&gorm.Config{
|
&gorm.Config{
|
||||||
Logger: dbLogger,
|
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 pure Go SQLite library does not handle locking in
|
||||||
// the same way as the C based one and we cant use the gorm
|
// the same way as the C based one and we cant use the gorm
|
||||||
|
|
|
@ -215,7 +215,7 @@ func SetTags(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
newTags := types.StringList{}
|
var newTags types.StringList
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
if !util.StringOrPrefixListContains(newTags, tag) {
|
if !util.StringOrPrefixListContains(newTags, tag) {
|
||||||
newTags = append(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)
|
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 {
|
for _, route := range routes {
|
||||||
prefixes = append(prefixes, netip.Prefix(route.Prefix))
|
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)
|
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 {
|
for _, route := range routes {
|
||||||
prefixes = append(prefixes, netip.Prefix(route.Prefix))
|
prefixes = append(prefixes, netip.Prefix(route.Prefix))
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,7 +222,7 @@ func DeleteRoute(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
routesToDelete := types.Routes{}
|
var routesToDelete types.Routes
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
if r.IsExitRoute() {
|
if r.IsExitRoute() {
|
||||||
routesToDelete = append(routesToDelete, r)
|
routesToDelete = append(routesToDelete, r)
|
||||||
|
@ -623,7 +623,7 @@ func EnableAutoApprovedRoutes(
|
||||||
|
|
||||||
log.Trace().Interface("routes", routes).Msg("routes for autoapproving")
|
log.Trace().Interface("routes", routes).Msg("routes for autoapproving")
|
||||||
|
|
||||||
approvedRoutes := types.Routes{}
|
var approvedRoutes types.Routes
|
||||||
|
|
||||||
for _, advertisedRoute := range routes {
|
for _, advertisedRoute := range routes {
|
||||||
if advertisedRoute.Enabled {
|
if advertisedRoute.Enabled {
|
||||||
|
|
|
@ -81,7 +81,7 @@ func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDERPMap(cfg types.DERPConfig) *tailcfg.DERPMap {
|
func GetDERPMap(cfg types.DERPConfig) *tailcfg.DERPMap {
|
||||||
derpMaps := make([]*tailcfg.DERPMap, 0)
|
var derpMaps []*tailcfg.DERPMap
|
||||||
|
|
||||||
for _, path := range cfg.Paths {
|
for _, path := range cfg.Paths {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
|
|
|
@ -143,6 +143,18 @@ var registerWebAPITemplate = template.Must(
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Registration - Headscale</title>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>headscale</h1>
|
<h1>headscale</h1>
|
||||||
|
@ -150,7 +162,7 @@ var registerWebAPITemplate = template.Must(
|
||||||
<p>
|
<p>
|
||||||
Run the command below in the headscale server to add this machine to your network:
|
Run the command below in the headscale server to add this machine to your network:
|
||||||
</p>
|
</p>
|
||||||
<pre><code>headscale nodes register --user USERNAME --key {{.Key}}</code></pre>
|
<code>headscale nodes register --user USERNAME --key {{.Key}}</code>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`))
|
`))
|
||||||
|
|
|
@ -102,7 +102,7 @@ func generateUserProfiles(
|
||||||
userMap[peer.User.Name] = peer.User // not worth checking if already is there
|
userMap[peer.User.Name] = peer.User // not worth checking if already is there
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles := []tailcfg.UserProfile{}
|
var profiles []tailcfg.UserProfile
|
||||||
for _, user := range userMap {
|
for _, user := range userMap {
|
||||||
displayName := user.Name
|
displayName := user.Name
|
||||||
|
|
||||||
|
@ -122,37 +122,41 @@ func generateUserProfiles(
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateDNSConfig(
|
func generateDNSConfig(
|
||||||
base *tailcfg.DNSConfig,
|
cfg *types.Config,
|
||||||
baseDomain string,
|
baseDomain string,
|
||||||
node *types.Node,
|
node *types.Node,
|
||||||
peers types.Nodes,
|
peers types.Nodes,
|
||||||
) *tailcfg.DNSConfig {
|
) *tailcfg.DNSConfig {
|
||||||
dnsConfig := base.Clone()
|
if cfg.DNSConfig == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsConfig := cfg.DNSConfig.Clone()
|
||||||
|
|
||||||
// if MagicDNS is enabled
|
// if MagicDNS is enabled
|
||||||
if base != nil && base.Proxied {
|
if dnsConfig.Proxied {
|
||||||
// Only inject the Search Domain of the current user
|
if cfg.DNSUserNameInMagicDNS {
|
||||||
// shared nodes should use their full FQDN
|
// Only inject the Search Domain of the current user
|
||||||
dnsConfig.Domains = append(
|
// shared nodes should use their full FQDN
|
||||||
dnsConfig.Domains,
|
dnsConfig.Domains = append(
|
||||||
fmt.Sprintf(
|
dnsConfig.Domains,
|
||||||
"%s.%s",
|
fmt.Sprintf(
|
||||||
node.User.Name,
|
"%s.%s",
|
||||||
baseDomain,
|
node.User.Name,
|
||||||
),
|
baseDomain,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
|
||||||
userSet := mapset.NewSet[types.User]()
|
userSet := mapset.NewSet[types.User]()
|
||||||
userSet.Add(node.User)
|
userSet.Add(node.User)
|
||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
userSet.Add(p.User)
|
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)
|
addNextDNSMetadata(dnsConfig.Resolvers, node)
|
||||||
|
@ -568,7 +572,7 @@ func appendPeerChanges(
|
||||||
profiles := generateUserProfiles(node, changed, cfg.BaseDomain)
|
profiles := generateUserProfiles(node, changed, cfg.BaseDomain)
|
||||||
|
|
||||||
dnsConfig := generateDNSConfig(
|
dnsConfig := generateDNSConfig(
|
||||||
cfg.DNSConfig,
|
cfg,
|
||||||
cfg.BaseDomain,
|
cfg.BaseDomain,
|
||||||
node,
|
node,
|
||||||
peers,
|
peers,
|
||||||
|
|
|
@ -127,7 +127,10 @@ func TestDNSConfigMapResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
got := generateDNSConfig(
|
got := generateDNSConfig(
|
||||||
&dnsConfigOrig,
|
&types.Config{
|
||||||
|
DNSConfig: &dnsConfigOrig,
|
||||||
|
DNSUserNameInMagicDNS: true,
|
||||||
|
},
|
||||||
baseDomain,
|
baseDomain,
|
||||||
nodeInShared1,
|
nodeInShared1,
|
||||||
peersOfNodeInShared1,
|
peersOfNodeInShared1,
|
||||||
|
@ -187,9 +190,9 @@ func Test_fullMapResponse(t *testing.T) {
|
||||||
UserID: 0,
|
UserID: 0,
|
||||||
User: types.User{Name: "mini"},
|
User: types.User{Name: "mini"},
|
||||||
ForcedTags: []string{},
|
ForcedTags: []string{},
|
||||||
AuthKey: &types.PreAuthKey{},
|
AuthKey: &types.PreAuthKey{},
|
||||||
LastSeen: &lastSeen,
|
LastSeen: &lastSeen,
|
||||||
Expiry: &expire,
|
Expiry: &expire,
|
||||||
Hostinfo: &tailcfg.Hostinfo{},
|
Hostinfo: &tailcfg.Hostinfo{},
|
||||||
Routes: []types.Route{
|
Routes: []types.Route{
|
||||||
{
|
{
|
||||||
|
|
|
@ -77,7 +77,7 @@ func tailNode(
|
||||||
keyExpiry = time.Time{}
|
keyExpiry = time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
hostname, err := node.GetFQDN(cfg.DNSConfig, cfg.BaseDomain)
|
hostname, err := node.GetFQDN(cfg, cfg.BaseDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("tailNode, failed to create FQDN: %s", err)
|
return nil, fmt.Errorf("tailNode, failed to create FQDN: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package notifier
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -221,6 +222,11 @@ func TestBatcher(t *testing.T) {
|
||||||
// We will call flush manually for the tests,
|
// We will call flush manually for the tests,
|
||||||
// so do not run the worker.
|
// so do not run the worker.
|
||||||
BatchChangeDelay: time.Hour,
|
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)
|
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 != "" {
|
if diff := cmp.Diff(tt.want, got, util.Comparers...); diff != "" {
|
||||||
t.Errorf("batcher() unexpected result (-want +got):\n%s", diff)
|
t.Errorf("batcher() unexpected result (-want +got):\n%s", diff)
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,14 +180,14 @@ func (pol *ACLPolicy) CompileFilterRules(
|
||||||
return tailcfg.FilterAllowAll, nil
|
return tailcfg.FilterAllowAll, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rules := []tailcfg.FilterRule{}
|
var rules []tailcfg.FilterRule
|
||||||
|
|
||||||
for index, acl := range pol.ACLs {
|
for index, acl := range pol.ACLs {
|
||||||
if acl.Action != "accept" {
|
if acl.Action != "accept" {
|
||||||
return nil, ErrInvalidAction
|
return nil, ErrInvalidAction
|
||||||
}
|
}
|
||||||
|
|
||||||
srcIPs := []string{}
|
var srcIPs []string
|
||||||
for srcIndex, src := range acl.Sources {
|
for srcIndex, src := range acl.Sources {
|
||||||
srcs, err := pol.expandSource(src, nodes)
|
srcs, err := pol.expandSource(src, nodes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -221,7 +221,7 @@ func (pol *ACLPolicy) CompileFilterRules(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dests := []tailcfg.NetPortRange{}
|
var dests []tailcfg.NetPortRange
|
||||||
for _, dest := range expanded.Prefixes() {
|
for _, dest := range expanded.Prefixes() {
|
||||||
for _, port := range *ports {
|
for _, port := range *ports {
|
||||||
pr := tailcfg.NetPortRange{
|
pr := tailcfg.NetPortRange{
|
||||||
|
@ -251,8 +251,7 @@ func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.F
|
||||||
|
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
// record if the rule is actually relevant for the given node.
|
// record if the rule is actually relevant for the given node.
|
||||||
dests := []tailcfg.NetPortRange{}
|
var dests []tailcfg.NetPortRange
|
||||||
|
|
||||||
DEST_LOOP:
|
DEST_LOOP:
|
||||||
for _, dest := range rule.DstPorts {
|
for _, dest := range rule.DstPorts {
|
||||||
expanded, err := util.ParseIPSet(dest.IP, nil)
|
expanded, err := util.ParseIPSet(dest.IP, nil)
|
||||||
|
@ -301,7 +300,7 @@ func (pol *ACLPolicy) CompileSSHPolicy(
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rules := []*tailcfg.SSHRule{}
|
var rules []*tailcfg.SSHRule
|
||||||
|
|
||||||
acceptAction := tailcfg.SSHAction{
|
acceptAction := tailcfg.SSHAction{
|
||||||
Message: "",
|
Message: "",
|
||||||
|
@ -533,8 +532,7 @@ func (pol *ACLPolicy) expandSource(
|
||||||
return []string{}, err
|
return []string{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixes := []string{}
|
var prefixes []string
|
||||||
|
|
||||||
for _, prefix := range ipSet.Prefixes() {
|
for _, prefix := range ipSet.Prefixes() {
|
||||||
prefixes = append(prefixes, prefix.String())
|
prefixes = append(prefixes, prefix.String())
|
||||||
}
|
}
|
||||||
|
@ -615,8 +613,8 @@ func excludeCorrectlyTaggedNodes(
|
||||||
nodes types.Nodes,
|
nodes types.Nodes,
|
||||||
user string,
|
user string,
|
||||||
) types.Nodes {
|
) types.Nodes {
|
||||||
out := types.Nodes{}
|
var out types.Nodes
|
||||||
tags := []string{}
|
var tags []string
|
||||||
for tag := range aclPolicy.TagOwners {
|
for tag := range aclPolicy.TagOwners {
|
||||||
owners, _ := expandOwnersFromTag(aclPolicy, user)
|
owners, _ := expandOwnersFromTag(aclPolicy, user)
|
||||||
ns := append(owners, user)
|
ns := append(owners, user)
|
||||||
|
@ -661,7 +659,7 @@ func expandPorts(portsStr string, isWild bool) (*[]tailcfg.PortRange, error) {
|
||||||
return nil, ErrWildcardIsNeeded
|
return nil, ErrWildcardIsNeeded
|
||||||
}
|
}
|
||||||
|
|
||||||
ports := []tailcfg.PortRange{}
|
var ports []tailcfg.PortRange
|
||||||
for _, portStr := range strings.Split(portsStr, ",") {
|
for _, portStr := range strings.Split(portsStr, ",") {
|
||||||
log.Trace().Msgf("parsing portstring: %s", portStr)
|
log.Trace().Msgf("parsing portstring: %s", portStr)
|
||||||
rang := strings.Split(portStr, "-")
|
rang := strings.Split(portStr, "-")
|
||||||
|
@ -737,7 +735,7 @@ func expandOwnersFromTag(
|
||||||
func (pol *ACLPolicy) expandUsersFromGroup(
|
func (pol *ACLPolicy) expandUsersFromGroup(
|
||||||
group string,
|
group string,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
users := []string{}
|
var users []string
|
||||||
log.Trace().Caller().Interface("pol", pol).Msg("test")
|
log.Trace().Caller().Interface("pol", pol).Msg("test")
|
||||||
aclGroups, ok := pol.Groups[group]
|
aclGroups, ok := pol.Groups[group]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -772,7 +770,7 @@ func (pol *ACLPolicy) expandIPsFromGroup(
|
||||||
group string,
|
group string,
|
||||||
nodes types.Nodes,
|
nodes types.Nodes,
|
||||||
) (*netipx.IPSet, error) {
|
) (*netipx.IPSet, error) {
|
||||||
build := netipx.IPSetBuilder{}
|
var build netipx.IPSetBuilder
|
||||||
|
|
||||||
users, err := pol.expandUsersFromGroup(group)
|
users, err := pol.expandUsersFromGroup(group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -792,7 +790,7 @@ func (pol *ACLPolicy) expandIPsFromTag(
|
||||||
alias string,
|
alias string,
|
||||||
nodes types.Nodes,
|
nodes types.Nodes,
|
||||||
) (*netipx.IPSet, error) {
|
) (*netipx.IPSet, error) {
|
||||||
build := netipx.IPSetBuilder{}
|
var build netipx.IPSetBuilder
|
||||||
|
|
||||||
// check for forced tags
|
// check for forced tags
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
|
@ -841,7 +839,7 @@ func (pol *ACLPolicy) expandIPsFromUser(
|
||||||
user string,
|
user string,
|
||||||
nodes types.Nodes,
|
nodes types.Nodes,
|
||||||
) (*netipx.IPSet, error) {
|
) (*netipx.IPSet, error) {
|
||||||
build := netipx.IPSetBuilder{}
|
var build netipx.IPSetBuilder
|
||||||
|
|
||||||
filteredNodes := filterNodesByUser(nodes, user)
|
filteredNodes := filterNodesByUser(nodes, user)
|
||||||
filteredNodes = excludeCorrectlyTaggedNodes(pol, filteredNodes, user)
|
filteredNodes = excludeCorrectlyTaggedNodes(pol, filteredNodes, user)
|
||||||
|
@ -866,7 +864,7 @@ func (pol *ACLPolicy) expandIPsFromSingleIP(
|
||||||
|
|
||||||
matches := nodes.FilterByIP(ip)
|
matches := nodes.FilterByIP(ip)
|
||||||
|
|
||||||
build := netipx.IPSetBuilder{}
|
var build netipx.IPSetBuilder
|
||||||
build.Add(ip)
|
build.Add(ip)
|
||||||
|
|
||||||
for _, node := range matches {
|
for _, node := range matches {
|
||||||
|
@ -881,7 +879,7 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix(
|
||||||
nodes types.Nodes,
|
nodes types.Nodes,
|
||||||
) (*netipx.IPSet, error) {
|
) (*netipx.IPSet, error) {
|
||||||
log.Trace().Str("prefix", prefix.String()).Msg("expandAlias got prefix")
|
log.Trace().Str("prefix", prefix.String()).Msg("expandAlias got prefix")
|
||||||
build := netipx.IPSetBuilder{}
|
var build netipx.IPSetBuilder
|
||||||
build.AddPrefix(prefix)
|
build.AddPrefix(prefix)
|
||||||
|
|
||||||
// This is suboptimal and quite expensive, but if we only add the prefix, we will miss all the relevant IPv6
|
// 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(
|
func (pol *ACLPolicy) TagsOfNode(
|
||||||
node *types.Node,
|
node *types.Node,
|
||||||
) ([]string, []string) {
|
) ([]string, []string) {
|
||||||
validTags := make([]string, 0)
|
var validTags []string
|
||||||
invalidTags := make([]string, 0)
|
var invalidTags []string
|
||||||
|
|
||||||
// TODO(kradalby): Why is this sometimes nil? coming from tailNode?
|
// TODO(kradalby): Why is this sometimes nil? coming from tailNode?
|
||||||
if node == nil {
|
if node == nil {
|
||||||
|
@ -973,7 +971,7 @@ func (pol *ACLPolicy) TagsOfNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterNodesByUser(nodes types.Nodes, user string) types.Nodes {
|
func filterNodesByUser(nodes types.Nodes, user string) types.Nodes {
|
||||||
out := types.Nodes{}
|
var out types.Nodes
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if node.User.Name == user {
|
if node.User.Name == user {
|
||||||
out = append(out, node)
|
out = append(out, node)
|
||||||
|
@ -989,7 +987,7 @@ func FilterNodesByACL(
|
||||||
nodes types.Nodes,
|
nodes types.Nodes,
|
||||||
filter []tailcfg.FilterRule,
|
filter []tailcfg.FilterRule,
|
||||||
) types.Nodes {
|
) types.Nodes {
|
||||||
result := types.Nodes{}
|
var result types.Nodes
|
||||||
|
|
||||||
for index, peer := range nodes {
|
for index, peer := range nodes {
|
||||||
if peer.ID == node.ID {
|
if peer.ID == node.ID {
|
||||||
|
|
|
@ -943,7 +943,7 @@ func Test_listNodesInUser(t *testing.T) {
|
||||||
},
|
},
|
||||||
user: "mickael",
|
user: "mickael",
|
||||||
},
|
},
|
||||||
want: types.Nodes{},
|
want: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -1645,7 +1645,7 @@ func TestACLPolicy_generateFilterRules(t *testing.T) {
|
||||||
name: "no-policy",
|
name: "no-policy",
|
||||||
field: field{},
|
field: field{},
|
||||||
args: args{},
|
args: args{},
|
||||||
want: []tailcfg.FilterRule{},
|
want: nil,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2896,7 +2896,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||||
User: types.User{Name: "marc"},
|
User: types.User{Name: "marc"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: types.Nodes{},
|
want: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Investigating 699
|
// 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
|
ACMEURL string
|
||||||
ACMEEmail string
|
ACMEEmail string
|
||||||
|
|
||||||
DNSConfig *tailcfg.DNSConfig
|
DNSConfig *tailcfg.DNSConfig
|
||||||
|
DNSUserNameInMagicDNS bool
|
||||||
|
|
||||||
UnixSocket string
|
UnixSocket string
|
||||||
UnixSocketPermission fs.FileMode
|
UnixSocketPermission fs.FileMode
|
||||||
|
@ -81,7 +82,8 @@ type Config struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SqliteConfig struct {
|
type SqliteConfig struct {
|
||||||
Path string
|
Path string
|
||||||
|
WriteAheadLog bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostgresConfig struct {
|
type PostgresConfig struct {
|
||||||
|
@ -205,6 +207,7 @@ func LoadConfig(path string, isFile bool) error {
|
||||||
|
|
||||||
viper.SetDefault("dns_config", nil)
|
viper.SetDefault("dns_config", nil)
|
||||||
viper.SetDefault("dns_config.override_local_dns", true)
|
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.enabled", false)
|
||||||
viper.SetDefault("derp.server.stun.enabled", true)
|
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.max_idle_conns", 10)
|
||||||
viper.SetDefault("database.postgres.conn_max_idle_time_secs", 3600)
|
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.scope", []string{oidc.ScopeOpenID, "profile", "email"})
|
||||||
viper.SetDefault("oidc.strip_email_domain", true)
|
viper.SetDefault("oidc.strip_email_domain", true)
|
||||||
viper.SetDefault("oidc.only_start_if_oidc_is_available", true)
|
viper.SetDefault("oidc.only_start_if_oidc_is_available", true)
|
||||||
|
@ -463,6 +468,7 @@ func GetDatabaseConfig() DatabaseConfig {
|
||||||
Path: util.AbsolutePathFromConfigPath(
|
Path: util.AbsolutePathFromConfigPath(
|
||||||
viper.GetString("database.sqlite.path"),
|
viper.GetString("database.sqlite.path"),
|
||||||
),
|
),
|
||||||
|
WriteAheadLog: viper.GetBool("database.sqlite.write_ahead_log"),
|
||||||
},
|
},
|
||||||
Postgres: PostgresConfig{
|
Postgres: PostgresConfig{
|
||||||
Host: viper.GetString("database.postgres.host"),
|
Host: viper.GetString("database.postgres.host"),
|
||||||
|
@ -556,16 +562,6 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) {
|
||||||
dnsConfig.Domains = domains
|
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") {
|
if viper.IsSet("dns_config.extra_records") {
|
||||||
var extraRecords []tailcfg.DNSRecord
|
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
|
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
|
return dnsConfig, baseDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -735,7 +741,8 @@ func GetHeadscaleConfig() (*Config, error) {
|
||||||
|
|
||||||
TLS: GetTLSConfig(),
|
TLS: GetTLSConfig(),
|
||||||
|
|
||||||
DNSConfig: dnsConfig,
|
DNSConfig: dnsConfig,
|
||||||
|
DNSUserNameInMagicDNS: viper.GetBool("dns_config.use_username_in_magic_dns"),
|
||||||
|
|
||||||
ACMEEmail: viper.GetString("tls.acme_email"),
|
ACMEEmail: viper.GetString("tls.acme_email"),
|
||||||
ACMEURL: viper.GetString("tls.acme_url"),
|
ACMEURL: viper.GetString("tls.acme_url"),
|
||||||
|
|
|
@ -394,23 +394,32 @@ func (node *Node) Proto() *v1.Node {
|
||||||
return nodeProto
|
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
|
var hostname string
|
||||||
if dnsConfig != nil && dnsConfig.Proxied { // MagicDNS
|
if cfg.DNSConfig != nil && cfg.DNSConfig.Proxied { // MagicDNS
|
||||||
if node.GivenName == "" {
|
if node.GivenName == "" {
|
||||||
return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeHasNoGivenName)
|
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(
|
hostname = fmt.Sprintf(
|
||||||
"%s.%s.%s",
|
"%s.%s",
|
||||||
node.GivenName,
|
node.GivenName,
|
||||||
node.User.Name,
|
|
||||||
baseDomain,
|
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 {
|
if len(hostname) > MaxHostnameLength {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"failed to create valid FQDN (%s): %w",
|
"failed to create valid FQDN (%s): %w",
|
||||||
|
|
|
@ -126,11 +126,87 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
node Node
|
node Node
|
||||||
dns tailcfg.DNSConfig
|
cfg Config
|
||||||
domain string
|
domain string
|
||||||
want string
|
want string
|
||||||
wantErr 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",
|
name: "all-set",
|
||||||
node: Node{
|
node: Node{
|
||||||
|
@ -139,11 +215,14 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
Name: "user",
|
Name: "user",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dns: tailcfg.DNSConfig{
|
cfg: Config{
|
||||||
Proxied: true,
|
DNSConfig: &tailcfg.DNSConfig{
|
||||||
|
Proxied: true,
|
||||||
|
},
|
||||||
|
DNSUserNameInMagicDNS: false,
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
want: "test.user.example.com",
|
want: "test.example.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no-given-name",
|
name: "no-given-name",
|
||||||
|
@ -152,8 +231,11 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
Name: "user",
|
Name: "user",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dns: tailcfg.DNSConfig{
|
cfg: Config{
|
||||||
Proxied: true,
|
DNSConfig: &tailcfg.DNSConfig{
|
||||||
|
Proxied: true,
|
||||||
|
},
|
||||||
|
DNSUserNameInMagicDNS: false,
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
wantErr: "failed to create valid FQDN: node has no given name",
|
wantErr: "failed to create valid FQDN: node has no given name",
|
||||||
|
@ -164,11 +246,14 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
GivenName: "test",
|
GivenName: "test",
|
||||||
User: User{},
|
User: User{},
|
||||||
},
|
},
|
||||||
dns: tailcfg.DNSConfig{
|
cfg: Config{
|
||||||
Proxied: true,
|
DNSConfig: &tailcfg.DNSConfig{
|
||||||
|
Proxied: true,
|
||||||
|
},
|
||||||
|
DNSUserNameInMagicDNS: false,
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
wantErr: "failed to create valid FQDN: node user has no name",
|
want: "test.example.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no-magic-dns",
|
name: "no-magic-dns",
|
||||||
|
@ -178,8 +263,11 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
Name: "user",
|
Name: "user",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dns: tailcfg.DNSConfig{
|
cfg: Config{
|
||||||
Proxied: false,
|
DNSConfig: &tailcfg.DNSConfig{
|
||||||
|
Proxied: false,
|
||||||
|
},
|
||||||
|
DNSUserNameInMagicDNS: false,
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
want: "test",
|
want: "test",
|
||||||
|
@ -199,7 +287,7 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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) {
|
if (err != nil) && (err.Error() != tc.wantErr) {
|
||||||
t.Errorf("GetFQDN() error = %s, wantErr %s", err, tc.wantErr)
|
t.Errorf("GetFQDN() error = %s, wantErr %s", err, tc.wantErr)
|
||||||
|
|
Loading…
Reference in a new issue