mirror of
https://github.com/netbirdio/netbird.git
synced 2026-03-31 06:34:14 -04:00
[management] Streamline domain validation (#5211)
This commit is contained in:
@@ -10,7 +10,30 @@ const maxDomains = 32
|
||||
|
||||
var domainRegex = regexp.MustCompile(`^(?:\*\.)?(?:(?:xn--)?[a-zA-Z0-9_](?:[a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])?\.)*(?:xn--)?[a-zA-Z0-9](?:[a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])?$`)
|
||||
|
||||
// ValidateDomains checks if each domain in the list is valid and returns a punycode-encoded DomainList.
|
||||
// IsValidDomain checks if a single domain string is valid.
|
||||
// Does not convert unicode to punycode - domain must already be ASCII/punycode.
|
||||
// Allows wildcard prefix (*.example.com).
|
||||
func IsValidDomain(domain string) bool {
|
||||
if domain == "" {
|
||||
return false
|
||||
}
|
||||
return domainRegex.MatchString(strings.ToLower(domain))
|
||||
}
|
||||
|
||||
// IsValidDomainNoWildcard checks if a single domain string is valid without wildcard prefix.
|
||||
// Use for zone domains and CNAME targets where wildcards are not allowed.
|
||||
func IsValidDomainNoWildcard(domain string) bool {
|
||||
if domain == "" {
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(domain, "*.") {
|
||||
return false
|
||||
}
|
||||
return domainRegex.MatchString(strings.ToLower(domain))
|
||||
}
|
||||
|
||||
// ValidateDomains validates domains and converts unicode to punycode.
|
||||
// Allows wildcard prefix (*.example.com). Maximum 32 domains.
|
||||
func ValidateDomains(domains []string) (List, error) {
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("domains list is empty")
|
||||
@@ -37,7 +60,10 @@ func ValidateDomains(domains []string) (List, error) {
|
||||
return domainList, nil
|
||||
}
|
||||
|
||||
// ValidateDomainsList checks if each domain in the list is valid
|
||||
// ValidateDomainsList validates domains without punycode conversion.
|
||||
// Use this for domains that must already be in ASCII/punycode format (e.g., extra DNS labels).
|
||||
// Unlike ValidateDomains, this does not convert unicode to punycode - unicode domains will fail.
|
||||
// Allows wildcard prefix (*.example.com). Maximum 32 domains.
|
||||
func ValidateDomainsList(domains []string) error {
|
||||
if len(domains) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -2,12 +2,16 @@ package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateDomains(t *testing.T) {
|
||||
label63 := strings.Repeat("a", 63)
|
||||
label64 := strings.Repeat("a", 64)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
domains []string
|
||||
@@ -26,6 +30,48 @@ func TestValidateDomains(t *testing.T) {
|
||||
expected: List{"sub.ex-ample.com"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid uppercase domain normalized to lowercase",
|
||||
domains: []string{"EXAMPLE.COM"},
|
||||
expected: List{"example.com"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid mixed case domain",
|
||||
domains: []string{"ExAmPlE.CoM"},
|
||||
expected: List{"example.com"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Single letter TLD",
|
||||
domains: []string{"example.x"},
|
||||
expected: List{"example.x"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Two letter domain labels",
|
||||
domains: []string{"a.b"},
|
||||
expected: List{"a.b"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Single character domain",
|
||||
domains: []string{"x"},
|
||||
expected: List{"x"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Wildcard with single letter TLD",
|
||||
domains: []string{"*.x"},
|
||||
expected: List{"*.x"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Multi-level with single letter labels",
|
||||
domains: []string{"a.b.c"},
|
||||
expected: List{"a.b.c"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid Unicode domain",
|
||||
domains: []string{"münchen.de"},
|
||||
@@ -45,17 +91,92 @@ func TestValidateDomains(t *testing.T) {
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid domain format",
|
||||
name: "Valid domain starting with digit",
|
||||
domains: []string{"123.example.com"},
|
||||
expected: List{"123.example.com"},
|
||||
wantErr: false,
|
||||
},
|
||||
// Numeric TLDs are allowed for internal/private DNS use cases.
|
||||
// While ICANN doesn't issue all-numeric gTLDs, the DNS protocol permits them
|
||||
// and resolvers like systemd-resolved handle them correctly.
|
||||
{
|
||||
name: "Numeric TLD allowed",
|
||||
domains: []string{"example.123"},
|
||||
expected: List{"example.123"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Single digit TLD allowed",
|
||||
domains: []string{"example.1"},
|
||||
expected: List{"example.1"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "All numeric labels allowed",
|
||||
domains: []string{"123.456"},
|
||||
expected: List{"123.456"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Single numeric label allowed",
|
||||
domains: []string{"123"},
|
||||
expected: List{"123"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid domain with double hyphen",
|
||||
domains: []string{"test--example.com"},
|
||||
expected: List{"test--example.com"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid leading hyphen",
|
||||
domains: []string{"-example.com"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid domain format 2",
|
||||
name: "Invalid trailing hyphen",
|
||||
domains: []string{"example.com-"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid leading dot",
|
||||
domains: []string{".com"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid dot only",
|
||||
domains: []string{"."},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid double dot",
|
||||
domains: []string{"example..com"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid special characters",
|
||||
domains: []string{"example?,.com"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid space in domain",
|
||||
domains: []string{"space .example.com"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid trailing space",
|
||||
domains: []string{"example.com "},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Multiple domains valid and invalid",
|
||||
domains: []string{"google.com", "invalid,nbdomain.com", "münchen.de"},
|
||||
@@ -86,6 +207,30 @@ func TestValidateDomains(t *testing.T) {
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Valid 63 char label (max)",
|
||||
domains: []string{label63 + ".com"},
|
||||
expected: List{Domain(label63 + ".com")},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid 64 char label (exceeds max)",
|
||||
domains: []string{label64 + ".com"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Valid 253 char domain (max)",
|
||||
domains: []string{strings.Repeat("a.", 126) + "a"},
|
||||
expected: List{Domain(strings.Repeat("a.", 126) + "a")},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid 254+ char domain (exceeds max)",
|
||||
domains: []string{strings.Repeat("ab.", 85)},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -118,6 +263,57 @@ func TestValidateDomainsList(t *testing.T) {
|
||||
domains: []string{"sub.ex-ample.com"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Uppercase domain accepted",
|
||||
domains: []string{"EXAMPLE.COM"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Single letter TLD",
|
||||
domains: []string{"example.x"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Two letter domain labels",
|
||||
domains: []string{"a.b"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Single character domain",
|
||||
domains: []string{"x"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Wildcard with single letter TLD",
|
||||
domains: []string{"*.x"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Multi-level with single letter labels",
|
||||
domains: []string{"a.b.c"},
|
||||
wantErr: false,
|
||||
},
|
||||
// Numeric TLDs are allowed for internal/private DNS use cases.
|
||||
{
|
||||
name: "Numeric TLD allowed",
|
||||
domains: []string{"example.123"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Single digit TLD allowed",
|
||||
domains: []string{"example.1"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "All numeric labels allowed",
|
||||
domains: []string{"123.456"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Single numeric label allowed",
|
||||
domains: []string{"123"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Underscores in labels",
|
||||
domains: []string{"_jabber._tcp.gmail.com"},
|
||||
|
||||
Reference in New Issue
Block a user