mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	config: loosen up BaseDomain and ServerURL checks (#2248)
* config: loosen up BaseDomain and ServerURL checks Requirements [here][1]: > OK: > server_url: headscale.com, base: clients.headscale.com > server_url: headscale.com, base: headscale.net > > Not OK: > server_url: server.headscale.com, base: headscale.com > > Essentially we have to prevent the possibility where the headscale > server has a URL which can also be assigned to a node. > > So for the Not OK scenario: > > if the server is: server.headscale.com, and a node joins with the name > server, it will be assigned server.headscale.com and that will break > the connection for nodes which will now try to connect to that node > instead of the headscale server. Fixes #2210 [1]: https://github.com/juanfont/headscale/issues/2210#issuecomment-2488165187 * server_url and base_domain: re-word error message, fix a one-off bug and add a test case for the bug. * lint * lint again
This commit is contained in:
		
							parent
							
								
									5fbf3f8327
								
							
						
					
					
						commit
						c6336adb01
					
				@ -23,6 +23,7 @@
 | 
				
			|||||||
- Added conversion of 'Hostname' to 'givenName' in a node with FQDN rules applied [#2198](https://github.com/juanfont/headscale/pull/2198)
 | 
					- Added conversion of 'Hostname' to 'givenName' in a node with FQDN rules applied [#2198](https://github.com/juanfont/headscale/pull/2198)
 | 
				
			||||||
- Fixed updating of hostname and givenName when it is updated in HostInfo [#2199](https://github.com/juanfont/headscale/pull/2199)
 | 
					- Fixed updating of hostname and givenName when it is updated in HostInfo [#2199](https://github.com/juanfont/headscale/pull/2199)
 | 
				
			||||||
- Fixed missing `stable-debug` container tag [#2232](https://github.com/juanfont/headscale/pr/2232)
 | 
					- Fixed missing `stable-debug` container tag [#2232](https://github.com/juanfont/headscale/pr/2232)
 | 
				
			||||||
 | 
					- Loosened up `server_url` and `base_domain` check. It was overly strict in some cases.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 0.23.0 (2024-09-18)
 | 
					## 0.23.0 (2024-09-18)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -28,8 +28,9 @@ const (
 | 
				
			|||||||
	maxDuration           time.Duration = 1<<63 - 1
 | 
						maxDuration           time.Duration = 1<<63 - 1
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var errOidcMutuallyExclusive = errors.New(
 | 
					var (
 | 
				
			||||||
	"oidc_client_secret and oidc_client_secret_path are mutually exclusive",
 | 
						errOidcMutuallyExclusive = errors.New("oidc_client_secret and oidc_client_secret_path are mutually exclusive")
 | 
				
			||||||
 | 
						errServerURLSuffix       = errors.New("server_url cannot be part of base_domain in a way that could make the DERP and headscale server unreachable")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type IPAllocationStrategy string
 | 
					type IPAllocationStrategy string
 | 
				
			||||||
@ -827,11 +828,10 @@ func LoadServerConfig() (*Config, error) {
 | 
				
			|||||||
	// - DERP run on their own domains
 | 
						// - DERP run on their own domains
 | 
				
			||||||
	// - Control plane runs on login.tailscale.com/controlplane.tailscale.com
 | 
						// - Control plane runs on login.tailscale.com/controlplane.tailscale.com
 | 
				
			||||||
	// - MagicDNS (BaseDomain) for users is on a *.ts.net domain per tailnet (e.g. tail-scale.ts.net)
 | 
						// - MagicDNS (BaseDomain) for users is on a *.ts.net domain per tailnet (e.g. tail-scale.ts.net)
 | 
				
			||||||
	if dnsConfig.BaseDomain != "" &&
 | 
						if dnsConfig.BaseDomain != "" {
 | 
				
			||||||
		strings.Contains(serverURL, dnsConfig.BaseDomain) {
 | 
							if err := isSafeServerURL(serverURL, dnsConfig.BaseDomain); err != nil {
 | 
				
			||||||
		return nil, errors.New(
 | 
								return nil, err
 | 
				
			||||||
			"server_url cannot contain the base_domain, this will cause the headscale server and embedded DERP to become unreachable from the Tailscale node.",
 | 
							}
 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &Config{
 | 
						return &Config{
 | 
				
			||||||
@ -924,6 +924,37 @@ func LoadServerConfig() (*Config, error) {
 | 
				
			|||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BaseDomain cannot be a suffix of the server URL.
 | 
				
			||||||
 | 
					// This is because Tailscale takes over the domain in BaseDomain,
 | 
				
			||||||
 | 
					// causing the headscale server and DERP to be unreachable.
 | 
				
			||||||
 | 
					// For Tailscale upstream, the following is true:
 | 
				
			||||||
 | 
					// - DERP run on their own domains.
 | 
				
			||||||
 | 
					// - Control plane runs on login.tailscale.com/controlplane.tailscale.com.
 | 
				
			||||||
 | 
					// - MagicDNS (BaseDomain) for users is on a *.ts.net domain per tailnet (e.g. tail-scale.ts.net).
 | 
				
			||||||
 | 
					func isSafeServerURL(serverURL, baseDomain string) error {
 | 
				
			||||||
 | 
						server, err := url.Parse(serverURL)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						serverDomainParts := strings.Split(server.Host, ".")
 | 
				
			||||||
 | 
						baseDomainParts := strings.Split(baseDomain, ".")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(serverDomainParts) <= len(baseDomainParts) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s := len(serverDomainParts)
 | 
				
			||||||
 | 
						b := len(baseDomainParts)
 | 
				
			||||||
 | 
						for i := range len(baseDomainParts) {
 | 
				
			||||||
 | 
							if serverDomainParts[s-i-1] != baseDomainParts[b-i-1] {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return errServerURLSuffix
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type deprecator struct {
 | 
					type deprecator struct {
 | 
				
			||||||
	warns  set.Set[string]
 | 
						warns  set.Set[string]
 | 
				
			||||||
	fatals set.Set[string]
 | 
						fatals set.Set[string]
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
package types
 | 
					package types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
@ -139,7 +140,7 @@ func TestReadConfig(t *testing.T) {
 | 
				
			|||||||
				return LoadServerConfig()
 | 
									return LoadServerConfig()
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			want:    nil,
 | 
								want:    nil,
 | 
				
			||||||
			wantErr: "server_url cannot contain the base_domain, this will cause the headscale server and embedded DERP to become unreachable from the Tailscale node.",
 | 
								wantErr: errServerURLSuffix.Error(),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name:       "base-domain-not-in-server-url",
 | 
								name:       "base-domain-not-in-server-url",
 | 
				
			||||||
@ -333,3 +334,64 @@ tls_letsencrypt_challenge_type: TLS-ALPN-01
 | 
				
			|||||||
	err = LoadConfig(tmpDir, false)
 | 
						err = LoadConfig(tmpDir, false)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OK
 | 
				
			||||||
 | 
					// server_url: headscale.com, base: clients.headscale.com
 | 
				
			||||||
 | 
					// server_url: headscale.com, base: headscale.net
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// NOT OK
 | 
				
			||||||
 | 
					// server_url: server.headscale.com, base: headscale.com.
 | 
				
			||||||
 | 
					func TestSafeServerURL(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							serverURL, baseDomain,
 | 
				
			||||||
 | 
							wantErr string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								serverURL:  "https://example.com",
 | 
				
			||||||
 | 
								baseDomain: "example.org",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								serverURL:  "https://headscale.com",
 | 
				
			||||||
 | 
								baseDomain: "headscale.com",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								serverURL:  "https://headscale.com",
 | 
				
			||||||
 | 
								baseDomain: "clients.headscale.com",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								serverURL:  "https://headscale.com",
 | 
				
			||||||
 | 
								baseDomain: "clients.subdomain.headscale.com",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								serverURL:  "https://headscale.kristoffer.com",
 | 
				
			||||||
 | 
								baseDomain: "mybase",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								serverURL:  "https://server.headscale.com",
 | 
				
			||||||
 | 
								baseDomain: "headscale.com",
 | 
				
			||||||
 | 
								wantErr:    errServerURLSuffix.Error(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								serverURL:  "https://server.subdomain.headscale.com",
 | 
				
			||||||
 | 
								baseDomain: "headscale.com",
 | 
				
			||||||
 | 
								wantErr:    errServerURLSuffix.Error(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								serverURL: "http://foo\x00",
 | 
				
			||||||
 | 
								wantErr:   `parse "http://foo\x00": net/url: invalid control character in URL`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							testName := fmt.Sprintf("server=%s domain=%s", tt.serverURL, tt.baseDomain)
 | 
				
			||||||
 | 
							t.Run(testName, func(t *testing.T) {
 | 
				
			||||||
 | 
								err := isSafeServerURL(tt.serverURL, tt.baseDomain)
 | 
				
			||||||
 | 
								if tt.wantErr != "" {
 | 
				
			||||||
 | 
									assert.EqualError(t, err, tt.wantErr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								assert.NoError(t, err)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@ prefixes:
 | 
				
			|||||||
database:
 | 
					database:
 | 
				
			||||||
  type: sqlite3
 | 
					  type: sqlite3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
server_url: "https://derp.no"
 | 
					server_url: "https://server.derp.no"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dns:
 | 
					dns:
 | 
				
			||||||
  magic_dns: true
 | 
					  magic_dns: true
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user