hscontrol: route hostname handling through dnsname and NodeStore
Ingest (registration and MapRequest updates) now calls dnsname.SanitizeHostname directly and lets NodeStore auto-bump on collision. Admin rename uses dnsname.ValidLabel + SetGivenName so conflicts are surfaced to the caller instead of silently mutated. Three duplicate invalidDNSRegex definitions, the old NormaliseHostname and ValidateHostname helpers, EnsureHostname, InvalidString, ApplyHostnameFromHostInfo, GivenNameHasBeenChanged, generateGivenName and EnsureUniqueGivenName are removed along with their tests. ValidateHostname's username half is retained as ValidateUsername for users.go. The SaaS-matching collision rule replaces the random "invalid-xxxxxx" fallback and the 8-character hash suffix; the empty-input fallback is the literal "node". TestUpdateHostnameFromClient now exercises the rewrite end-to-end with awkward macOS/Windows names. Fixes #3188 Fixes #2926 Fixes #2343 Fixes #2762 Fixes #2449 Updates #2177 Updates #2121 Updates #363
This commit is contained in:
parent
a2c3ac095e
commit
d6dfdc100c
15 changed files with 104 additions and 1707 deletions
|
|
@ -4,7 +4,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
|
@ -23,21 +22,14 @@ const (
|
|||
LabelHostnameLength = 63
|
||||
)
|
||||
|
||||
var invalidDNSRegex = regexp.MustCompile("[^a-z0-9-.]+")
|
||||
|
||||
// DNS validation errors.
|
||||
// DNS validation errors. Hostname-side validation lives on
|
||||
// `tailscale.com/util/dnsname` and NodeStore collision handling; only
|
||||
// the username-side errors stay in this package.
|
||||
var (
|
||||
ErrInvalidHostName = errors.New("invalid hostname")
|
||||
ErrUsernameTooShort = errors.New("username must be at least 2 characters long")
|
||||
ErrUsernameMustStartLetter = errors.New("username must start with a letter")
|
||||
ErrUsernameTooManyAt = errors.New("username cannot contain more than one '@'")
|
||||
ErrUsernameInvalidChar = errors.New("username contains invalid character")
|
||||
ErrHostnameTooShort = errors.New("hostname is too short, must be at least 2 characters")
|
||||
ErrHostnameTooLong = errors.New("hostname is too long, must not exceed 63 characters")
|
||||
ErrHostnameMustBeLowercase = errors.New("hostname must be lowercase")
|
||||
ErrHostnameHyphenBoundary = errors.New("hostname cannot start or end with a hyphen")
|
||||
ErrHostnameDotBoundary = errors.New("hostname cannot start or end with a dot")
|
||||
ErrHostnameInvalidChars = errors.New("hostname contains invalid characters")
|
||||
)
|
||||
|
||||
// ValidateUsername checks if a username is valid.
|
||||
|
|
@ -79,76 +71,6 @@ func ValidateUsername(username string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ValidateHostname checks if a hostname meets DNS requirements.
|
||||
// This function does NOT modify the input - it only validates.
|
||||
// The hostname must already be lowercase and contain only valid characters.
|
||||
func ValidateHostname(name string) error {
|
||||
if len(name) < 2 {
|
||||
return fmt.Errorf("%w: %q", ErrHostnameTooShort, name)
|
||||
}
|
||||
|
||||
if len(name) > LabelHostnameLength {
|
||||
return fmt.Errorf("%w: %q", ErrHostnameTooLong, name)
|
||||
}
|
||||
|
||||
if strings.ToLower(name) != name {
|
||||
return fmt.Errorf("%w: %q (try %q)", ErrHostnameMustBeLowercase, name, strings.ToLower(name))
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "-") || strings.HasSuffix(name, "-") {
|
||||
return fmt.Errorf("%w: %q", ErrHostnameHyphenBoundary, name)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") {
|
||||
return fmt.Errorf("%w: %q", ErrHostnameDotBoundary, name)
|
||||
}
|
||||
|
||||
if invalidDNSRegex.MatchString(name) {
|
||||
return fmt.Errorf("%w: %q", ErrHostnameInvalidChars, name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NormaliseHostname transforms a string into a valid DNS hostname.
|
||||
// Returns error if the transformation results in an invalid hostname.
|
||||
//
|
||||
// Transformations applied:
|
||||
// - Converts to lowercase
|
||||
// - Removes invalid DNS characters
|
||||
// - Truncates to 63 characters if needed
|
||||
//
|
||||
// After transformation, validates the result.
|
||||
func NormaliseHostname(name string) (string, error) {
|
||||
// Early return if already valid
|
||||
err := ValidateHostname(name)
|
||||
if err == nil {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// Transform to lowercase
|
||||
name = strings.ToLower(name)
|
||||
|
||||
// Strip invalid DNS characters
|
||||
name = invalidDNSRegex.ReplaceAllString(name, "")
|
||||
|
||||
// Truncate to DNS label limit
|
||||
if len(name) > LabelHostnameLength {
|
||||
name = name[:LabelHostnameLength]
|
||||
}
|
||||
|
||||
// Validate result after transformation
|
||||
err = ValidateHostname(name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"hostname invalid after normalisation: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// generateMagicDNSRootDomains generates a list of DNS entries to be included in `Routes` in `MapResponse`.
|
||||
// This list of reverse DNS entries instructs the OS on what subnets and domains the Tailscale embedded DNS
|
||||
// server (listening in 100.100.100.100 udp/53) should be used for.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue