* plugin/template: Add expr-lang variables Signed-off-by: Andri Yngvason <andri@yngvason.is> * plugin/template: Add extra expressions that must match Signed-off-by: Andri Yngvason <andri@yngvason.is> * plugin/template: README: Add embedded device resolution example Signed-off-by: Andri Yngvason <andri@yngvason.is> --------- Signed-off-by: Andri Yngvason <andri@yngvason.is>
14 KiB
template
Name
template - allows for dynamic responses based on the incoming query.
Description
The template plugin allows you to dynamically respond to queries by just writing a (Go) template.
Syntax
template CLASS TYPE [ZONE...] {
match REGEX...
var NAME EXPRESSION
expr EXPRESSION
answer RR
additional RR
authority RR
rcode CODE
ederror EXTENDED_ERROR_CODE [EXTRA_REASON]
fallthrough [FALLTHROUGH-ZONE...]
}
- CLASS the query class (usually IN or ANY).
- TYPE the query type (A, PTR, ... can be ANY to match all types).
- ZONE the zone scope(s) for this template. Defaults to the server zones.
matchREGEX Go regexp that are matched against the incoming question name. Specifying no regex matches everything (default:.*). First matching regex wins. Regex patterns must not exceed 10000 characters.answer|additional|authorityRR A RFC 1035 style resource record fragment built by a Go template that contains the reply. Specifying no answer will result in a response with an empty answer section.varNAME EXPRESSION sets the variable NAME to the result of EXPRESSION, evaluated for each matching query and available to the templates as.Var.NAME. Multiple variables may be set; each one may use the variables declared before it. See the Expressions section.exprEXPRESSION a condition that must evaluate totruefor the query to match. All conditions must be true for a complete match; otherwise the query is subject tofallthrough, as if no regex had matched. Conditions may use the variables declared withvar. See the Expressions section.rcodeCODE A response code (NXDOMAIN, SERVFAIL, ...). The default isNOERROR. Valid response code values are per theRcodeToStringmap defined by themiekg/dnspackage inmsg.go.ederrorEXTENDED_ERROR_CODE is an extended DNS error code as a number defined inRFC8914(0, 1, 2,..., 24). EXTRA_REASON is an additional string explaining the reason for returning the error.fallthroughContinue with the next template instance if the template's ZONE matches a query name but no regex match. If there is no next template, continue resolution with the next plugin. If [FALLTHROUGH-ZONE...] are listed (for examplein-addr.arpaandip6.arpa), then only queries for those zones will be subject to fallthrough. Withoutfallthrough, when the template's ZONE matches a query but no regex match then aSERVFAILresponse is returned.
Also see contains an additional reading list.
Templates
Each resource record is a full-featured Go template with the following predefined data
.Zonethe matched zone string (e.g.example.)..Namethe query name, as a string (lowercased)..Classthe query class (usuallyIN)..Typethe RR type requested (e.g.PTR)..Matchan array of all matches.index .Match 0refers to the whole match..Groupa map of the named capture groups..Messagethe complete incoming DNS message..Questionthe matched question section..Remoteclient’s IP address.Varthe variables defined withvar(e.g..Var.myvariable)..Metaa function that takes a metadata name and returns the value, if the metadata plugin is enabled. For example,.Meta "kubernetes/client-namespace"
and the following predefined template functions
parseIntinterprets a string in the given base and bit size. Equivalent to strconv.ParseUint.
The output of the template must be a RFC 1035 style resource record (commonly referred to as a "zone file").
WARNING there is a syntactical problem with Go templates and CoreDNS config files. Expressions
like {{$var}} will be interpreted as a reference to an environment variable by CoreDNS (and
Caddy) while {{ $var }} will work. See Bugs and corefile(5).
Expressions
The EXPRESSION of a var or expr is written in the expr language, the same as used by the view plugin. See
https://expr-lang.org/docs/language-definition as a detailed reference for valid syntax.
Expressions can reference the DNS query functions and utility functions listed in the view plugin's documentation, the
variables declared by preceding var options, and
group(name string) string: the capture group named name of the matching regex, or""if there is no such group.group(index int) string: the index-th capture group of the matching regex,group(0)being the entire match, or""if there is no such group.
A variable name must be a valid identifier and must not be the name of an existing function, or of a keyword or literal of
the expr language, such as len or true.
Metrics
If monitoring is enabled (via the prometheus plugin) then the following metrics are exported:
coredns_template_matches_total{server, zone, view, class, type}the total number of matched requests by regex.coredns_template_template_failures_total{server, zone, view, class, type, section, template}the number of times the Go templating failed. Regex, section and template label values can be used to map the error back to the config file.coredns_template_rr_failures_total{server, zone, view, class, type, section, template}the number of times the templated resource record was invalid and could not be parsed. Regex, section and template label values can be used to map the error back to the config file.
Both failure cases indicate a problem with the template configuration. The server label indicates
the server incrementing the metric, see the metrics plugin for details.
Examples
Resolve everything to NXDOMAIN
The most simplistic template is
. {
template ANY ANY {
rcode NXDOMAIN
}
}
- This template uses the default zone (
.or all queries) - All queries will be answered (no
fallthrough) - The answer is always NXDOMAIN
Resolve .invalid as NXDOMAIN
The .invalid domain is a reserved TLD (see RFC 2606 Reserved Top Level DNS Names) to indicate invalid domains.
. {
forward . 8.8.8.8
template ANY ANY invalid {
rcode NXDOMAIN
authority "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)"
ederror 21 "Blocked according to RFC2606"
}
}
- A query to .invalid will result in NXDOMAIN (rcode)
- A dummy SOA record is sent to hand out a TTL of 60s for caching purposes
- Querying
.invalidin theCHclass will also cause a NXDOMAIN/SOA response - The default regex is
.*
Block invalid search domain completions
Imagine you run example.com with a datacenter dc1.example.com. The datacenter domain
is part of the DNS search domain.
However something.example.com.dc1.example.com would indicate a fully qualified
domain name (something.example.com) that inadvertently has the default domain or search
path (dc1.example.com) added.
. {
forward . 8.8.8.8
template IN ANY example.com.dc1.example.com {
rcode NXDOMAIN
authority "{{ .Zone }} 60 IN SOA ns.example.com hostmaster.example.com (1 60 60 60 60)"
}
}
A more verbose regex based equivalent would be
. {
forward . 8.8.8.8
template IN ANY example.com {
match "example\.com\.(dc1\.example\.com\.)$"
rcode NXDOMAIN
authority "{{ index .Match 1 }} 60 IN SOA ns.{{ index .Match 1 }} hostmaster.{{ index .Match 1 }} (1 60 60 60 60)"
fallthrough
}
}
The regex-based version can do more complex matching/templating while zone-based templating is easier to read and use.
Resolve A/PTR for .example
. {
forward . 8.8.8.8
# ip-a-b-c-d.example A a.b.c.d
template IN A example {
match (^|[.])ip-(?P<a>[0-9]*)-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$
answer "{{ .Name }} 60 IN A {{ .Group.a }}.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"
fallthrough
}
# d.c.b.a.in-addr.arpa PTR ip-a-b-c-d.example
template IN PTR in-addr.arpa {
match ^(?P<d>[0-9]*)[.](?P<c>[0-9]*)[.](?P<b>[0-9]*)[.](?P<a>[0-9]*)[.]in-addr[.]arpa[.]$
answer "{{ .Name }} 60 IN PTR ip-{{ .Group.a }}-{{ .Group.b }}-{{ .Group.c }}-{{ .Group.d }}.example."
}
}
An IPv4 address consists of 4 bytes, a.b.c.d. Named groups make it less error-prone to reverse the
IP address in the PTR case. Try to use named groups to explain what your regex and template are doing.
Note that the A record is actually a wildcard: any subdomain of the IP address will resolve to the IP address.
Having templates to map certain PTR/A pairs is a common pattern.
Fallthrough is needed for mixed domains where only some responses are templated.
Resolve device addresses for HTTPS on LAN
For an embedded device on a local network to be trusted, it needs to have a certificate signed by a
CA and the CA needs to verify that the device controls the name for which it is issued. As the local
address may change, a certificate is issued for the wildcard subdomain *.<id>.example.com instead,
where <id> is a unique id for the given device. The same trick as in the example above can be used
to find out the actual IP address.
Resolving to public IP addresses would allow for this to be used in phishing attacks, so an additional expression must be satisfied to limit the allowed IP range.
. {
forward . 8.8.8.8
template IN A example.com {
match ^(?P<ip>[0-9]{1,3}(-[0-9]{1,3}){3})[.](?P<id>[a-z2-7]{26})[.]example[.]com[.]$
var ip replace(group('ip'), '-', '.')
expr "any(['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '169.254.0.0/16'], incidr(ip, #))"
answer "{{ .Name }} 60 IN A {{ .Var.ip }}"
fallthrough
}
}
The regular expression for the unique device id matches a base32 encoded string of a 128-bit device id, with the padding removed.
Note that an expression using # must be quoted, or it will be interpreted as a comment.
Resolve hexadecimal ip pattern using parseInt
. {
forward . 8.8.8.8
template IN A example {
match "^ip0a(?P<b>[a-f0-9]{2})(?P<c>[a-f0-9]{2})(?P<d>[a-f0-9]{2})[.]example[.]$"
answer "{{ .Name }} 60 IN A 10.{{ parseInt .Group.b 16 8 }}.{{ parseInt .Group.c 16 8 }}.{{ parseInt .Group.d 16 8 }}"
fallthrough
}
}
An IPv4 address can be expressed in a more compact form using its hexadecimal encoding.
For example ip-10-123-123.example. can instead be expressed as ip0a7b7b7b.example.
Resolve multiple ip patterns
. {
forward . 8.8.8.8
template IN A example {
match "^ip-(?P<a>10)-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]dc[.]example[.]$"
match "^(?P<a>[0-9]*)[.](?P<b>[0-9]*)[.](?P<c>[0-9]*)[.](?P<d>[0-9]*)[.]ext[.]example[.]$"
answer "{{ .Name }} 60 IN A {{ .Group.a}}.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"
fallthrough
}
}
Named capture groups can be used to template one response for multiple patterns.
Resolve A and MX records for IP templates in .example
. {
forward . 8.8.8.8
template IN A example {
match ^ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$
answer "{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"
fallthrough
}
template IN MX example {
match ^ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$
answer "{{ .Name }} 60 IN MX 10 {{ .Name }}"
additional "{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"
fallthrough
}
}
Adding authoritative nameservers to the response
. {
forward . 8.8.8.8
template IN A example {
match ^ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$
answer "{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"
authority "example. 60 IN NS ns0.example."
authority "example. 60 IN NS ns1.example."
additional "ns0.example. 60 IN A 203.0.113.8"
additional "ns1.example. 60 IN A 198.51.100.8"
fallthrough
}
template IN MX example {
match ^ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$
answer "{{ .Name }} 60 IN MX 10 {{ .Name }}"
additional "{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"
authority "example. 60 IN NS ns0.example."
authority "example. 60 IN NS ns1.example."
additional "ns0.example. 60 IN A 203.0.113.8"
additional "ns1.example. 60 IN A 198.51.100.8"
fallthrough
}
}
Fabricate a CNAME
This example responds with a CNAME to google.com for any DNS query made exactly for foogle.com.
The answer will also contain a record for google.com if the upstream nameserver can return a record for it of the
requested type.
. {
template IN ANY foogle.com {
match "^foogle\.com\.$"
answer "foogle.com 60 IN CNAME google.com"
}
forward . 8.8.8.8
}
Also see
- Go regexp for details about the regex implementation
- RE2 syntax reference for details about the regex syntax
- RFC 1034 and RFC 1035 for the resource record format
- Go template for the template language reference
Bugs
CoreDNS supports caddyfile environment variables
with notion of {$ENV_VAR}. This parser feature will break Go template variables notations like{{$variable}}.
The equivalent notation {{ $variable }} will work.
Try to avoid Go template variables in the context of this plugin.