New in version 1.10.0: Intial release with QNAME and Response IP Address triggers
Changed in version 1.14.0: Full set of RPZ triggers and actions are supported
Response Policy Zones¶
Response Policy Zones (RPZ) is a mechanism that makes it possible to define your local policies in a standardised way and load your policies from external sources.
Unbound has support for local-zone and local-data. This makes it possible to
give a custom answer back for specified domain names. It also contains the
respip module which makes it possible to rewrite answers containing specified
IP addresses. Although these options are heavily used by users, they are Unbound
specific. If you operate multiple resolvers from multiple vendors you have to maintain
your policies for multiple configurations, which all will have their own syntax.
Using the Unbound specific configuration also makes it challenging to consume
policies from external sources.
To get these external sources to work manually, you have to fetch the external policies in the offered format, reformat it in such a way that Unbound will understand, and keep this list up-to-date, for example using unbound-control(8).
To automate this process in a generic, standardised way, Response Policy Zones (RPZ) is a policy format that will work on different resolver implementations, and that has capabilities to be directly transferred and loaded from external sources.
We’ll first discuss the different policies and RPZ actions with examples, and then show how to implement RPZ in a configuration.
RPZ policies are formatted in DNS zone files. This makes it possible to easily consume and keep them to up-to-date by using DNS zone transfers. Something that Unbound is already capable of doing for its auth-zone feature.
Each policy in the policy zone consists of a trigger and an action. The trigger describes when the policy should be applied. The action describes what action should be taken if the policy needs to be applied. Each trigger and action combination is defined as a Resource Record (RR) in the policy zone. The owner of the RR states the trigger, the type and RDATA state the action.
Unbound supports all the RPZ policies descriped in the RPZ internet draft: the QNAME trigger and the Response IP Address trigger, which we will go through below.
Description and example
The query name:
The IP address of the client:
response IP address in the answer:
The nameserver name:
The nameserver IP address:
Note that the IP address encoding for RPZ triggers in the IN-ADDR.ARPA naming
192.0.2.24 will be written as
In the implementation step we will go trough all the triggers.
Aside from RPZ triggers, RPZ also specifies actions as a result of these triggers. Unbound currently supports the following actions: NXDOMAIN, NODATA, PASSTHRU, DROP, Local Data, and TCP-only.
The Local Data action responds with a preconfigured resource record. Queries for types that do not exist in the policy zones will result in a NODATA answer.
Other RPZ actions that are supported by Unbound are the NXDOMAIN, NODATA, PASSTHRU, DROP and TCP-Only actions. All of these actions are defined by having a CNAME to a specific name.
The CNAME targets for the other RPZ actions are:
RR type and RDATA
The NODATA action returns a response with no attached data. The DROP action ignores (drops) the query. The TCP-Only action responds to the query over TCP. The PASSTHRU action makes it possible to exclude a domain, or IP address, from your policies so that if the PASSTHRU action is triggered no other policy from any of the available policy zones will be applied.
How to use RPZ with Unbound¶
The RPZ implementation in Unbound depends on the
respip module, this module
needs to be loaded using
module-config. Each policy zone is configured in
Unbound using the
rpz clause. The full documentation for RPZ in Unbound can
be found in the manpages/unbound.conf. A minimal configuration
with a single policy zone can look like, where additional elements can be
server: module-config: "respip validator iterator" rpz: # The name of the RPZ authority zone name: rpz.nlnetlabs.nl # The filename where the zone is stored. If left empty zonefile: rpz.nlnetlabs.nl # The location of the remote RPZ zonefile. # url: http://www.example.com/example.org.zone (not a real RPZ file) # Always use this RPZ action for matching triggers from this zone. # Possible action are: nxdomain, nodata, passthru, drop, disabled, # and cname. # rpz-action-override: nxdomain # Log all applied RPZ actions for this RPZ zone. Default is no. # rpz-log: yes # Specify a string to be part of the log line. # rpz-log-name: nlnetlabs
In above example the policy zone will be loaded from the file
rpz.nlnetlabs.nl. An example RPZ file with all the triggers and actions
looks like this:
$ORIGIN rpz.nlnetlabs.nl. # QNAME trigger with local data action example.com.rpz.nlnetlabs.nl. TXT "trigger for example.com" *.example.com CNAME . # IPv4 subnet (192.0.2.0/28) which drops clients and IPv6 subnet (2001:db8::3/128) which is not subject to policy 18.104.22.168.192.rpz-client-ip CNAME rpz-drop. 128.3.zz.db8.2001.rpz-client-ip CNAME rpz-passthru. # Clients at 22.214.171.124 only get responses over TCP. 126.96.36.199.rpz-client-ip CNAME rpz-tcp-only. # Fills the responses for these queries with NXDOMAIN and the correct # answers respectively 188.8.131.52.192.rpz-ip CNAME . 184.108.40.206.192.rpz-ip CNAME rpz-passthru. # Answers queries for the nlnetlabs.nl nameserver with NXDOMAIN ns.nlnetlabs.nl.rpz-nsdname CNAME . # Drops queries for the nameserver at 192.0.2.0/24 subnet 220.127.116.11.192.rpz-nsip CNAME rpz-drop.
It is also possible to load the zone using DNS zone transfers. Both AXFR and IXFR is supported, all additions and deletion in the zone will be picked up by Unbound and reflected in the local policies. Transferring the policy using a DNS zone transfer is as easy as specifying the server to get the zone from:
server: module-config: "respip validator iterator" rpz: name: rpz.nlnetlabs.nl master: <ip address of server to transfer from> zonefile: rpz.nlnetlabs.nl
The zone will now be transferred from the configured address and saved to a
zonefile on disk. It is possible to have more than one policy zone in Unbound.
Having multiple policy zones is as simple as having multiple
server: module-config: "respip validator iterator" rpz: name: rpz.nlnetlabs.nl zonefile: rpz.nlnetlabs.nl rpz: name: rpz2.nlnetlabs.nl zonefile: rpz2.nlnetlabs.nl
The policy zones will be applied in the configured order. In the example,
Unbound will only look at the
rpz2.nlnetlabs.nl policies if there is no
match in the
rpz.nlnetlabs.nl zone. If there is no match in any of the
configured zones Unbound will continue to resolve the domain by sending upstream
queries. Note that a PASSTHRU action is considered a match, having that action
in the first zone will therefore stop Unbound from looking further at other
Unbound has the possibility to override the actions that will be used for
policies in a zone that matches the zone’s triggers. This can be done using the
rpz-action-override configuration option. The possible values for the option
cname. The first four options of this list will do the same as the RPZ
actions with the same name.
cname override option will make it possible to apply a local data action
using a CNAME for all matching triggers in the policy zone. The CNAME to use in
the answer can be configured using the
option. Using these overrides is nice if you use an external feed to get a list
of triggers, but would like to redirect all your users to your own domain:
RPZ zone (rpz.nlnetlabs.nl): $ORIGIN rpz.nlnetlabs.nl. drop.example.com.rpz.nlnetlabs.nl. CNAME rpz-drop. 18.104.22.168.93.rpz-ip.rpz.nlnetlabs.nl. A 192.0.2.1
This also requires a change in the Unbound config:
server: module-config: "respip validator iterator" rpz: name: rpz.nlnetlabs.nl zonefile: rpz.nlnetlabs.nl rpz-action-override: cname rpz-cname-override: "example.nl."
disabled option will stop Unbound from applying any of the actions in
the zone. This, combined with the
rpz-log option, is a nice way to test what
would happen to your traffic when a policy will be enabled, without directly
impacting your users. The difference between
that disabled is not considered to be a valid match and will therefore not stop
Unbound from looking at the next configured policy zone.
rpz-log is set to yes, Unbound will log all applied actions for a
policy zone. With
rpz-log enabled you can specify a name for the log using
rpz-log-name, this way you can easily find all matches for a specific zone.
It is also possible to get statistics per applied RPZ action using
unbound-control stats. This requires the
extended-statistics to be
Unbound’s RPZ implementation works together with the tags functionality. This
makes is possible to enable (some of) the policy zones only for a set of the
users. To do this the tags need to be defined using
define-tag, the correct
tags need to be matched with the client IP addresses using
access-control-tag, and the tags need to be specified for the policy zones
for which they apply.
server: module-config: "respip validator iterator" define-tag: "malware social" access-control-tag 127.0.0.10/32 "social" access-control-tag 127.0.0.20/32 "social malware" access-control-tag 127.0.0.30/32 "malware" rpz: name: malware.rpz.example.com zonefile: malware.rpz.example.com tags: "malware" rpz: name: social.rpz.example.com zonefile: social.rpz.example.com tags: "social"
Queries from 127.0.0.1 will not be filtered. For queries coming from 127.0.0.10 only the policies from the social.rpz.example.com zone will be used, for 127.0.0.30 only the policies from the malware.rpz.example.com zone will be used, and queries originated from 127.0.0.20 will be subjected to the policies from both zones.