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 }