mirror of
https://github.com/juanfont/headscale.git
synced 2026-03-20 00:24:20 +01:00
feat: add prominent warning banner for non-standard IP prefixes
Add a highly visible ASCII-art warning banner that is printed at startup when the configured IP prefixes fall outside the standard Tailscale CGNAT (100.64.0.0/10) or ULA (fd7a:115c:a1e0::/48) ranges. The warning fires once even if both v4 and v6 are non-standard, and the warnBanner() function is reusable for other critical configuration warnings in the future. Also updates config-example.yaml to clarify that subsets of the default ranges are fine, but ranges outside CGNAT/ULA are not. Closes #3055
This commit is contained in:
committed by
Kristoffer Dalby
parent
3d53f97c82
commit
5105033224
@@ -50,12 +50,21 @@ noise:
|
||||
# List of IP prefixes to allocate tailaddresses from.
|
||||
# Each prefix consists of either an IPv4 or IPv6 address,
|
||||
# and the associated prefix length, delimited by a slash.
|
||||
# It must be within IP ranges supported by the Tailscale
|
||||
# client - i.e., subnets of 100.64.0.0/10 and fd7a:115c:a1e0::/48.
|
||||
# See below:
|
||||
# IPv6: https://github.com/tailscale/tailscale/blob/22ebb25e833264f58d7c3f534a8b166894a89536/net/tsaddr/tsaddr.go#LL81C52-L81C71
|
||||
#
|
||||
# WARNING: These prefixes MUST be subsets of the standard Tailscale ranges:
|
||||
# - IPv4: 100.64.0.0/10 (CGNAT range)
|
||||
# - IPv6: fd7a:115c:a1e0::/48 (Tailscale ULA range)
|
||||
#
|
||||
# Using a SUBSET of these ranges is supported and useful if you want to
|
||||
# limit IP allocation to a smaller block (e.g., 100.64.0.0/24).
|
||||
#
|
||||
# Using ranges OUTSIDE of CGNAT/ULA is NOT supported and will cause
|
||||
# undefined behaviour. The Tailscale client has hard-coded assumptions
|
||||
# about these ranges and will break in subtle, hard-to-debug ways.
|
||||
#
|
||||
# See:
|
||||
# IPv4: https://github.com/tailscale/tailscale/blob/22ebb25e833264f58d7c3f534a8b166894a89536/net/tsaddr/tsaddr.go#L33
|
||||
# Any other range is NOT supported, and it will cause unexpected issues.
|
||||
# IPv6: https://github.com/tailscale/tailscale/blob/22ebb25e833264f58d7c3f534a8b166894a89536/net/tsaddr/tsaddr.go#LL81C52-L81C71
|
||||
prefixes:
|
||||
v4: 100.64.0.0/10
|
||||
v6: fd7a:115c:a1e0::/48
|
||||
|
||||
@@ -842,54 +842,72 @@ func dnsToTailcfgDNS(dns DNSConfig) *tailcfg.DNSConfig {
|
||||
return &cfg
|
||||
}
|
||||
|
||||
func prefixV4() (*netip.Prefix, error) {
|
||||
// warnBanner prints a highly visible warning banner to the log output.
|
||||
// It wraps the provided lines in an ASCII-art box with a "Warning!" header.
|
||||
// This is intended for critical configuration issues that users must not ignore.
|
||||
func warnBanner(lines []string) {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString("\n")
|
||||
b.WriteString("################################################################\n")
|
||||
b.WriteString("### __ __ _ _ ###\n")
|
||||
b.WriteString("### \\ \\ / / (_) | | ###\n")
|
||||
b.WriteString("### \\ \\ /\\ / /_ _ _ __ _ __ _ _ __ __ _| | ###\n")
|
||||
b.WriteString("### \\ \\/ \\/ / _` | '__| '_ \\| | '_ \\ / _` | | ###\n")
|
||||
b.WriteString("### \\ /\\ / (_| | | | | | | | | | | (_| |_| ###\n")
|
||||
b.WriteString("### \\/ \\/ \\__,_|_| |_| |_|_|_| |_|\\__, (_) ###\n")
|
||||
b.WriteString("### __/ | ###\n")
|
||||
b.WriteString("### |___/ ###\n")
|
||||
b.WriteString("################################################################\n")
|
||||
b.WriteString("### ###\n")
|
||||
|
||||
for _, line := range lines {
|
||||
b.WriteString(fmt.Sprintf("### %-56s ###\n", line))
|
||||
}
|
||||
|
||||
b.WriteString("### ###\n")
|
||||
b.WriteString("################################################################")
|
||||
|
||||
log.Warn().Msg(b.String())
|
||||
}
|
||||
|
||||
func prefixV4() (*netip.Prefix, bool, error) {
|
||||
prefixV4Str := viper.GetString("prefixes.v4")
|
||||
|
||||
if prefixV4Str == "" {
|
||||
return nil, nil //nolint:nilnil // empty prefix is valid, not an error
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
prefixV4, err := netip.ParsePrefix(prefixV4Str)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing IPv4 prefix from config: %w", err)
|
||||
return nil, false, fmt.Errorf("parsing IPv4 prefix from config: %w", err)
|
||||
}
|
||||
|
||||
builder := netipx.IPSetBuilder{}
|
||||
builder.AddPrefix(tsaddr.CGNATRange())
|
||||
|
||||
ipSet, _ := builder.IPSet()
|
||||
if !ipSet.ContainsPrefix(prefixV4) {
|
||||
log.Warn().
|
||||
Msgf("Prefix %s is not in the %s range. This is an unsupported configuration.",
|
||||
prefixV4Str, tsaddr.CGNATRange())
|
||||
}
|
||||
|
||||
return &prefixV4, nil
|
||||
return &prefixV4, !ipSet.ContainsPrefix(prefixV4), nil
|
||||
}
|
||||
|
||||
func prefixV6() (*netip.Prefix, error) {
|
||||
func prefixV6() (*netip.Prefix, bool, error) {
|
||||
prefixV6Str := viper.GetString("prefixes.v6")
|
||||
|
||||
if prefixV6Str == "" {
|
||||
return nil, nil //nolint:nilnil // empty prefix is valid, not an error
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
prefixV6, err := netip.ParsePrefix(prefixV6Str)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing IPv6 prefix from config: %w", err)
|
||||
return nil, false, fmt.Errorf("parsing IPv6 prefix from config: %w", err)
|
||||
}
|
||||
|
||||
builder := netipx.IPSetBuilder{}
|
||||
builder.AddPrefix(tsaddr.TailscaleULARange())
|
||||
ipSet, _ := builder.IPSet()
|
||||
|
||||
if !ipSet.ContainsPrefix(prefixV6) {
|
||||
log.Warn().
|
||||
Msgf("Prefix %s is not in the %s range. This is an unsupported configuration.",
|
||||
prefixV6Str, tsaddr.TailscaleULARange())
|
||||
}
|
||||
|
||||
return &prefixV6, nil
|
||||
return &prefixV6, !ipSet.ContainsPrefix(prefixV6), nil
|
||||
}
|
||||
|
||||
// LoadCLIConfig returns the needed configuration for the CLI client
|
||||
@@ -921,12 +939,12 @@ func LoadServerConfig() (*Config, error) {
|
||||
logConfig := logConfig()
|
||||
zerolog.SetGlobalLevel(logConfig.Level)
|
||||
|
||||
prefix4, err := prefixV4()
|
||||
prefix4, v4NonStandard, err := prefixV4()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefix6, err := prefixV6()
|
||||
prefix6, v6NonStandard, err := prefixV6()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -935,6 +953,26 @@ func LoadServerConfig() (*Config, error) {
|
||||
return nil, ErrNoPrefixConfigured
|
||||
}
|
||||
|
||||
if v4NonStandard || v6NonStandard {
|
||||
warnBanner([]string{
|
||||
"You have overridden the default Headscale IP prefixes",
|
||||
"with a range outside of the standard CGNAT and/or ULA",
|
||||
"ranges. This is NOT a supported configuration.",
|
||||
"",
|
||||
"Using subsets of the default ranges (100.64.0.0/10 for",
|
||||
"IPv4, fd7a:115c:a1e0::/48 for IPv6) is fine. Using",
|
||||
"ranges outside of these will cause undefined behaviour",
|
||||
"as the Tailscale client is NOT designed to operate on",
|
||||
"any other ranges.",
|
||||
"",
|
||||
"Please revert your prefixes to subsets of the standard",
|
||||
"ranges as described in the example configuration.",
|
||||
"",
|
||||
"Any issue raised using a range outside of the supported",
|
||||
"range will be labelled as wontfix and closed.",
|
||||
})
|
||||
}
|
||||
|
||||
allocStr := viper.GetString("prefixes.allocation")
|
||||
|
||||
var alloc IPAllocationStrategy
|
||||
|
||||
Reference in New Issue
Block a user