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 package endpoint
import ( import (
"regexp"
"strings" "strings"
log "github.com/sirupsen/logrus"
) )
// DomainFilter holds a lists of valid domain names // DomainFilter holds a lists of valid domain names
@ -26,6 +29,10 @@ type DomainFilter struct {
Filters []string Filters []string
// exclude define what domains not to match // exclude define what domains not to match
exclude []string 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 // 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 // NewDomainFilterWithExclusions returns a new DomainFilter, given a list of matches and exclusions
func NewDomainFilterWithExclusions(domainFilters []string, excludeDomains []string) DomainFilter { 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 // NewDomainFilter returns a new DomainFilter given a comma separated list of domains
func NewDomainFilter(domainFilters []string) DomainFilter { 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. // Match checks whether a domain can be found in the DomainFilter.
// RegexFilter takes precedence over Filters
func (df DomainFilter) Match(domain string) bool { 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) 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 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 // IsConfigured returns true if DomainFilter is configured, false otherwise
func (df DomainFilter) IsConfigured() bool { 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 df.Filters[0] != ""
} }
return len(df.Filters) > 0 return len(df.Filters) > 0

View File

@ -115,7 +115,13 @@ func main() {
// Combine multiple sources into a single, deduplicated source. // Combine multiple sources into a single, deduplicated source.
endpointsSource := source.NewDedupSource(source.NewMultiSource(sources)) 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) zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType) zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter) zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)

View File

@ -61,6 +61,8 @@ type Config struct {
GoogleBatchChangeInterval time.Duration GoogleBatchChangeInterval time.Duration
DomainFilter []string DomainFilter []string
ExcludeDomains []string ExcludeDomains []string
RegexDomainFilter string
RegexDomainExclusion string
ZoneIDFilter []string ZoneIDFilter []string
AlibabaCloudConfigFile string AlibabaCloudConfigFile string
AlibabaCloudZoneType string AlibabaCloudZoneType string
@ -164,6 +166,8 @@ var defaultConfig = &Config{
GoogleBatchChangeInterval: time.Second, GoogleBatchChangeInterval: time.Second,
DomainFilter: []string{}, DomainFilter: []string{},
ExcludeDomains: []string{}, ExcludeDomains: []string{},
RegexDomainFilter: "",
RegexDomainExclusion: "",
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json", AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "", AWSZoneType: "",
AWSZoneTagFilter: []string{}, 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("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("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("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("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-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) 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, GoogleBatchChangeInterval: time.Second,
DomainFilter: []string{""}, DomainFilter: []string{""},
ExcludeDomains: []string{""}, ExcludeDomains: []string{""},
RegexDomainFilter: "",
RegexDomainExclusion: "",
ZoneIDFilter: []string{""}, ZoneIDFilter: []string{""},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json", AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "", AWSZoneType: "",
@ -117,6 +119,8 @@ var (
GoogleBatchChangeInterval: time.Second * 2, GoogleBatchChangeInterval: time.Second * 2,
DomainFilter: []string{"example.org", "company.com"}, DomainFilter: []string{"example.org", "company.com"},
ExcludeDomains: []string{"xapi.example.org", "xapi.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"}, ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json", AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "private", AWSZoneType: "private",
@ -247,6 +251,8 @@ func TestParseFlags(t *testing.T) {
"--domain-filter=company.com", "--domain-filter=company.com",
"--exclude-domains=xapi.example.org", "--exclude-domains=xapi.example.org",
"--exclude-domains=xapi.company.com", "--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/ZTST1",
"--zone-id-filter=/hostedzone/ZTST2", "--zone-id-filter=/hostedzone/ZTST2",
"--aws-zone-type=private", "--aws-zone-type=private",
@ -325,6 +331,8 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_OVH_ENDPOINT": "ovh-ca", "EXTERNAL_DNS_OVH_ENDPOINT": "ovh-ca",
"EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com", "EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com",
"EXTERNAL_DNS_EXCLUDE_DOMAINS": "xapi.example.org\nxapi.company.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_SERVER": "http://ns.example.com:8081",
"EXTERNAL_DNS_PDNS_API_KEY": "some-secret-key", "EXTERNAL_DNS_PDNS_API_KEY": "some-secret-key",
"EXTERNAL_DNS_PDNS_TLS_ENABLED": "1", "EXTERNAL_DNS_PDNS_TLS_ENABLED": "1",