stricter hostname validation and replace (#2383)

This commit is contained in:
Kristoffer Dalby
2025-10-22 13:50:39 +02:00
committed by GitHub
parent 2c9e98d3f5
commit 1cdea7ed9b
16 changed files with 888 additions and 193 deletions

View File

@@ -27,7 +27,7 @@ var (
invalidCharsInUserRegex = regexp.MustCompile("[^a-z0-9-.]+")
)
var ErrInvalidUserName = errors.New("invalid user name")
var ErrInvalidHostName = errors.New("invalid hostname")
// ValidateUsername checks if a username is valid.
// It must be at least 2 characters long, start with a letter, and contain
@@ -67,42 +67,86 @@ func ValidateUsername(username string) error {
return nil
}
func CheckForFQDNRules(name string) error {
// Ensure the username meets the minimum length requirement
// 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 errors.New("name must be at least 2 characters long")
return fmt.Errorf(
"hostname %q is too short, must be at least 2 characters",
name,
)
}
if len(name) > LabelHostnameLength {
return fmt.Errorf(
"DNS segment must not be over 63 chars. %v doesn't comply with this rule: %w",
"hostname %q is too long, must not exceed 63 characters",
name,
ErrInvalidUserName,
)
}
if strings.ToLower(name) != name {
return fmt.Errorf(
"DNS segment should be lowercase. %v doesn't comply with this rule: %w",
"hostname %q must be lowercase (try %q)",
name,
strings.ToLower(name),
)
}
if strings.HasPrefix(name, "-") || strings.HasSuffix(name, "-") {
return fmt.Errorf(
"hostname %q cannot start or end with a hyphen",
name,
)
}
if strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") {
return fmt.Errorf(
"hostname %q cannot start or end with a dot",
name,
ErrInvalidUserName,
)
}
if invalidDNSRegex.MatchString(name) {
return fmt.Errorf(
"DNS segment should only be composed of lowercase ASCII letters numbers, hyphen and dots. %v doesn't comply with these rules: %w",
"hostname %q contains invalid characters, only lowercase letters, numbers, hyphens and dots are allowed",
name,
ErrInvalidUserName,
)
}
return nil
}
func ConvertWithFQDNRules(name string) string {
// 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
if err := ValidateHostname(name); err == nil {
return name, nil
}
// Transform to lowercase
name = strings.ToLower(name)
// Strip invalid DNS characters
name = invalidDNSRegex.ReplaceAllString(name, "")
return name
// Truncate to DNS label limit
if len(name) > LabelHostnameLength {
name = name[:LabelHostnameLength]
}
// Validate result after transformation
if err := ValidateHostname(name); 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`.