firewall - enables filtering of queries and responses based on expressions.
The firewall plugin defines a list of rules that trigger a workflow action on a DNS query and its response. A rule list is an ordered set of rules that are evaluated in sequence. Rules can be an expression rule, or a policy engine rule. An expression rule has two parts: an action and an expression. When the rule is evaluated, first the expression is evaluated.
- If the expression evaluates to
true
the action is performed on the query and the rule list evaluation ceases. - If the expression does not evaluates to
true
then next rule in sequence is evaluated.
The firewall plugin can also refer to other policy engines to determine the action to take.
firewall DIRECTION {
ACTION EXPRESSION
POLICY-PLUGIN ENGINE-NAME
}
-
DIRECTION indicates if the rule list will be applied to queries or responses. It can be
query
orresponse
. -
ACTION defines the workflow action to take if the EXPRESSION evaluates to
true
. If no actions are defined for thequery
DIRECTION, the default action is toblock
. Available actions:allow
: continue the DNS resolution process (i.e., continue to the next plugin in the chain)refuse
: interrupt the DNS resolution, reply with REFUSED response codeblock
: interrupt the DNS resolution, reply with NXDOMAIN response codedrop
: interrupt the DNS resolution, do not reply (client will time out)
An action must be followed by an EXPRESSION, which defines the boolean expression for the rule. See Expressions section below.
-
POLICY-PLUGIN : is the name of another plugin that implements a firewall policy engine. ENGINE-NAME is the name of an engine defined in your Corefile. Requests/responses will be evaluated by that plugin policy engine to determine the action.
Expressions follow a c-like expression format where the variables are either
the metadata
of CoreDNS or the fields of a DNS query/response. Common operators apply.
Expression Examples:
client_ip == '10.0.0.20'
type == 'AAAA'
type IN ('AAAA', 'A', 'TXT')
type IN ('AAAA', 'A') && name =~ 'google.com'
[mac/address] =~ '.*:FF:.*'
NOTE: because of the /
separator included in a label of metadata, those labels must be enclosed in
brackets [...]
.
The following variables are supported for querying information in expressions. All values are strings (see atoi
function
below for arithmetic operations).
type
: type of the request (A, AAAA, TXT, ...)name
: name of the request (the domain name requested)class
: class of the request (IN, CH, ...)proto
: protocol used (tcp or udp)client_ip
: client's IP address, for IPv6 addresses these are enclosed in brackets:[::1]
size
: request size in bytesport
: client's portrcode
: response CODE (NOERROR, NXDOMAIN, SERVFAIL, ...)rsize
: raw (uncompressed), response size (a client may receive a smaller response)>rflags
: response flags, each set flag will be displayed, e.g., "aa, tc". This includes the qr bit as well>bufsize
: the EDNS0 buffer size advertised in the query>do
: the EDNS0 DO (DNSSEC OK) bit set in the query>id
: query ID>opcode
: query OPCODEserver_ip
: server's IP address; for IPv6 addresses these are enclosed in brackets:[::1]
server_port
: client's portresponse_ip
: the IP address returned in the first A or AAAA record of the Answer section
atoi(string)
: convert a string to a numeric value.incidr(ip, cidr)
: returns true ifip
is in the subnet defined bycidr
.random()
: returns a random floating point number in the range [0.0, 1.0).
In addition to using the built-in action/expression syntax, the firewall plugin can use a policy engine plugin to evaluate policy.
To use a policy engine plugin, you'll need to compile the plugin into CoreDNS, then declare the plugin in your Corefile, and reference the plugin as an action of a firewall rule. See the "Using a Policy Engine Plugin" example below.
When authoring a new policy engine plugin, the plugin must implement the Engineer
interface defined in firewall/policy.
This repository includes two Policy Engine Plugins:
- themis - enables Infoblox's Themis policy engine to be used as a CoreDNS firewall policy engine
- opa - enables OPA to be used as a CoreDNS firewall policy engine.
Firewall and other associated policy plugins in this repository are external plugins, which means they are not included in CoreDNS releases. To use the plugins in this repository, you'll need to build a CoreDNS image with the plugins you want to add included in the plugin list. In a nutshell you'll need to:
- Clone https://github.com/coredns/coredns release, e.g.,
git clone -b v1.6.9 --depth 1 https://github.com/coredns/coredns .
- Add this plugin (
firewall:github.com/coredns/policy/plugin/firewall
), and any desired policy engines to plugin.cfg per instructions therein. Order in this file is important, as it dictates the order of plugin execution when processing a query. The firewall plugin should execute before plugins that generate responses. make -f Makefile.release DOCKER=your-docker-repo release
make -f Makefile.release DOCKER=your-docker-repo docker
make -f Makefile.release DOCKER=your-docker-repo docker-push
This example shows how to use firewall to create a basic client IP address-based ACL. Here 10.120.1.11
is expressly REFUSED.
Other clients in 10.120.0.0/24
and 10.120.1.0/24
are allowed. All other clients are not responded to.
. {
firewall query {
refuse client_ip == '10.120.1.11'
allow client_ip =~ '10\.120\.0\..*'
allow client_ip =~ '10\.120\.1\..*'
drop true
}
}
Allow all queries for example.com. Allow A and AAAA queries for google.com. NXDOMAIN all other queries.
. {
firewall query {
allow name =~ 'example.com'
allow name =~ 'google.com' && (type == 'A' || type == 'AAAA')
block true
}
}
Allow all queries, but block all responses that contain an IP matching 10.120.1.*
in the first record
of the Answer section.
. {
firewall query {
allow true
}
firewall response {
block response_ip =~ '10\.120\.1\..*'
}
}
This example uses the metadata_edns0 plugin to define labels group_id
and client_id
with values extracted from EDNS0.
The firewall rules use those metadata to REFUSE any query without a group_id of 123456789
or client_id of ABCDEF
.
example.org {
metadata
metadata_edns0 {
group_id edns0 0xffed bytes
client_id edns0 0xffee bytes
}
firewall query {
refuse [metadata_edns0/client_id] != 'ABCDEF'
refuse [metadata_edns0/group_id] != '123456789'
allow true
}
}
This example shows how firewall could be useful in a Kubernetes-based multi-tenancy application. It uses the kubernetes
plugin metadata to prevent Pods in certain Namespaces from looking up Services in other Namespaces.
Specifically, if the requesting Pod is in a Namespace beginning with 'tenant-', it permits only lookups to
Services that are in the same Namespace or in the 'default' Namespace. Note here that pods verified
is
required in the kubernetes plugin to enable the use of the [kubernetes/client-namespace]
metadata variable. Also note that
this is at the DNS level only, and does not prevent network access between tenant Namespaces, e.g., if Service IPs are known.
cluster.local {
metadata
kubernetes {
pods verified
}
firewall query {
allow [kubernetes/client-namespace] !~ '^tenant-'
allow [kubernetes/namespace] == [kubernetes/client-namespace]
allow [kubernetes/namespace] == 'default'
block true
}
}
The following example illustrates how the a policy engine plugin (themis in this example) can be used by the firewall plugin.
. {
firewall query {
themis myengine
}
themis myengine {
}
}