package domain import ( "context" "net" "strings" log "github.com/sirupsen/logrus" ) type resolver interface { LookupCNAME(context.Context, string) (string, error) } type Validator struct { resolver resolver } // NewValidator initializes a validator with a specific DNS resolver. // If a Validator is used without specifying a resolver, then it will // use the net.DefaultResolver. func NewValidator(resolver resolver) *Validator { return &Validator{ resolver: resolver, } } // IsValid looks up the CNAME record for the passed domain with a prefix // and compares it against the acceptable domains. // If the returned CNAME matches any accepted domain, it will return true, // otherwise, including in the event of a DNS error, it will return false. // The comparison is very simple, so wildcards will not match if included // in the acceptable domain list. func (v *Validator) IsValid(ctx context.Context, domain string, accept []string) bool { _, valid := v.ValidateWithCluster(ctx, domain, accept) return valid } // ValidateWithCluster validates a custom domain and returns the matched cluster address. // Returns the cluster address and true if valid, or empty string and false if invalid. func (v *Validator) ValidateWithCluster(ctx context.Context, domain string, accept []string) (string, bool) { if v.resolver == nil { v.resolver = net.DefaultResolver } lookupDomain := "validation." + domain log.WithFields(log.Fields{ "domain": domain, "lookupDomain": lookupDomain, "acceptList": accept, }).Debug("looking up CNAME for domain validation") cname, err := v.resolver.LookupCNAME(ctx, lookupDomain) if err != nil { log.WithFields(log.Fields{ "domain": domain, "lookupDomain": lookupDomain, }).WithError(err).Warn("CNAME lookup failed for domain validation") return "", false } nakedCNAME := strings.TrimSuffix(cname, ".") log.WithFields(log.Fields{ "domain": domain, "cname": cname, "nakedCNAME": nakedCNAME, "acceptList": accept, }).Debug("CNAME lookup result for domain validation") for _, acceptDomain := range accept { normalizedAccept := strings.TrimSuffix(acceptDomain, ".") if nakedCNAME == normalizedAccept { log.WithFields(log.Fields{ "domain": domain, "cname": nakedCNAME, "cluster": acceptDomain, }).Info("domain CNAME matched cluster") return acceptDomain, true } } log.WithFields(log.Fields{ "domain": domain, "cname": nakedCNAME, "acceptList": accept, }).Warn("domain CNAME does not match any accepted cluster") return "", false } // ExtractClusterFromFreeDomain extracts the cluster address from a free domain. // Free domains have the format: .. (e.g., myapp.abc123.eu.proxy.netbird.io) // It matches the domain suffix against available clusters and returns the matching cluster. func ExtractClusterFromFreeDomain(domain string, availableClusters []string) (string, bool) { for _, cluster := range availableClusters { if strings.HasSuffix(domain, "."+cluster) { return cluster, true } } return "", false }