feat: add regex domain filters

Signed-off-by: Enrique Gonzalez <goga.enrique@gmail.com>
This commit is contained in:
Enrique Gonzalez 2020-04-09 14:37:45 +02:00
parent d0c776b0e7
commit c5e0227180
No known key found for this signature in database
GPG Key ID: 77F8AF07F85DEBB9
4 changed files with 66 additions and 4 deletions

View File

@ -17,7 +17,10 @@ limitations under the License.
package endpoint
import (
"regexp"
"strings"
log "github.com/sirupsen/logrus"
)
// DomainFilter holds a lists of valid domain names
@ -26,6 +29,10 @@ type DomainFilter struct {
Filters []string
// exclude define what domains not to match
exclude []string
// regex defines a regular expression to match the domains
regex string
// regexExclusion defines a regular expression to exclude the domains matched
regexExclusion string
}
// prepareFilters provides consistent trimming for filters/exclude params
@ -39,16 +46,26 @@ func prepareFilters(filters []string) []string {
// NewDomainFilterWithExclusions returns a new DomainFilter, given a list of matches and exclusions
func NewDomainFilterWithExclusions(domainFilters []string, excludeDomains []string) DomainFilter {
return DomainFilter{prepareFilters(domainFilters), prepareFilters(excludeDomains)}
return DomainFilter{prepareFilters(domainFilters), prepareFilters(excludeDomains), "", ""}
}
// NewDomainFilter returns a new DomainFilter given a comma separated list of domains
func NewDomainFilter(domainFilters []string) DomainFilter {
return DomainFilter{prepareFilters(domainFilters), []string{}}
return DomainFilter{prepareFilters(domainFilters), []string{}, "", ""}
}
// NewRegexDomainFilter returns a new DomainFilter given a regular expression
func NewRegexDomainFilter(regexDomainFilter string, regexDomainExclusion string) DomainFilter {
return DomainFilter{[]string{}, []string{}, regexDomainFilter, regexDomainExclusion}
}
// Match checks whether a domain can be found in the DomainFilter.
// RegexFilter takes precedence over Filters
func (df DomainFilter) Match(domain string) bool {
if df.regex != "" {
return matchRegex(df.regex, df.regexExclusion, domain)
}
return matchFilter(df.Filters, domain, true) && !matchFilter(df.exclude, domain, false)
}
@ -78,9 +95,34 @@ func matchFilter(filters []string, domain string, emptyval bool) bool {
return false
}
// matchRegex determines if a domain matches the configured regular expressions in the DomainFilter.
// The negativeRegex, if set, takes precedence over regex. Therefore,
// matchRegex returns true when only regex regular expression matches the domain.
// Otherwise, if either negativeRegex matches or regex does not match the domain, it will return false.
func matchRegex(regex string, negativeRegex string, domain string) bool {
strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))
if negativeRegex != "" {
match, err := regexp.MatchString(negativeRegex, strippedDomain)
if err != nil {
log.Errorf("Failed to filter domain %s with the regex-exclusion filter: %v", domain, err)
}
if match {
return false
}
}
match, err := regexp.MatchString(regex, strippedDomain)
if err != nil {
log.Errorf("Failed to filter domain %s with the regex filter: %v", domain, err)
}
return match
}
// IsConfigured returns true if DomainFilter is configured, false otherwise
func (df DomainFilter) IsConfigured() bool {
if len(df.Filters) == 1 {
if df.regex != "" {
return true
} else if len(df.Filters) == 1 {
return df.Filters[0] != ""
}
return len(df.Filters) > 0

View File

@ -115,7 +115,13 @@ func main() {
// Combine multiple sources into a single, deduplicated source.
endpointsSource := source.NewDedupSource(source.NewMultiSource(sources))
domainFilter := endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
// RegexDomainFilter overrides DomainFilter
var domainFilter endpoint.DomainFilter
if cfg.RegexDomainFilter != "" {
domainFilter = endpoint.NewRegexDomainFilter(cfg.RegexDomainFilter, cfg.RegexDomainExclusion)
} else {
domainFilter = endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
}
zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)

View File

@ -61,6 +61,8 @@ type Config struct {
GoogleBatchChangeInterval time.Duration
DomainFilter []string
ExcludeDomains []string
RegexDomainFilter string
RegexDomainExclusion string
ZoneIDFilter []string
AlibabaCloudConfigFile string
AlibabaCloudZoneType string
@ -164,6 +166,8 @@ var defaultConfig = &Config{
GoogleBatchChangeInterval: time.Second,
DomainFilter: []string{},
ExcludeDomains: []string{},
RegexDomainFilter: "",
RegexDomainExclusion: "",
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "",
AWSZoneTagFilter: []string{},
@ -315,6 +319,8 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns")
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains)
app.Flag("regex-domain-filter", "Limit possible domains and target zones by a Regex filter; Overrides domain-filter (optional)").Default("").StringVar(&cfg.RegexDomainFilter)
app.Flag("regex-domain-exclusion", "Regex filter that excludes domains and target zones matched by regex-domain-filter (optional)").Default("").StringVar(&cfg.RegexDomainExclusion)
app.Flag("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter)
app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
app.Flag("google-batch-change-size", "When using the Google provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.GoogleBatchChangeSize)).IntVar(&cfg.GoogleBatchChangeSize)

View File

@ -44,6 +44,8 @@ var (
GoogleBatchChangeInterval: time.Second,
DomainFilter: []string{""},
ExcludeDomains: []string{""},
RegexDomainFilter: "",
RegexDomainExclusion: "",
ZoneIDFilter: []string{""},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "",
@ -117,6 +119,8 @@ var (
GoogleBatchChangeInterval: time.Second * 2,
DomainFilter: []string{"example.org", "company.com"},
ExcludeDomains: []string{"xapi.example.org", "xapi.company.com"},
RegexDomainFilter: "(example\\.org|company\\.com)$",
RegexDomainExclusion: "xapi\\.(example\\.org|company\\.com)$",
ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "private",
@ -247,6 +251,8 @@ func TestParseFlags(t *testing.T) {
"--domain-filter=company.com",
"--exclude-domains=xapi.example.org",
"--exclude-domains=xapi.company.com",
"--regex-domain-filter=(example\\.org|company\\.com)$",
"--regex-domain-exclusion=xapi\\.(example\\.org|company\\.com)$",
"--zone-id-filter=/hostedzone/ZTST1",
"--zone-id-filter=/hostedzone/ZTST2",
"--aws-zone-type=private",
@ -325,6 +331,8 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_OVH_ENDPOINT": "ovh-ca",
"EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com",
"EXTERNAL_DNS_EXCLUDE_DOMAINS": "xapi.example.org\nxapi.company.com",
"EXTERNAL_DNS_REGEX_DOMAIN_FILTER": "(example\\.org|company\\.com)$",
"EXTERNAL_DNS_REGEX_DOMAIN_EXCLUSION": "xapi\\.(example\\.org|company\\.com)$",
"EXTERNAL_DNS_PDNS_SERVER": "http://ns.example.com:8081",
"EXTERNAL_DNS_PDNS_API_KEY": "some-secret-key",
"EXTERNAL_DNS_PDNS_TLS_ENABLED": "1",