Client based filtering in Unbound

December 22, 2016 by Ralph Dolmans

We noticed a demand from resolver operators to depend DNS answers on the address of the client. The tag functionality introduced in Unbound 1.5.10 and the new views functionality in Unbound 1.6.0 meet these wishes.

Tags

Unbound’s tags functionality makes it possible to divide client source addresses in categories (tags), and use local-zone and local-data information for these specific tags.

Let’s say we like to have two tags, one for domains containing malware, and one for domains of gambling sites. Before these tags can be used, you need to define them in the Unbound configuration using define-tags:

define-tags: "malware gambling"

Now that we made Unbound aware of the existing tags, we can start using them. The access-control-tag element is used to specify the tag to use for a client addresses. It is possible to add multiple tags to an access-control element:

access-control-tag: 10.0.1.0/24 "malware"
access-control-tag: 10.0.2.0/24 "malware"
access-control-tag: 10.0.3.0/24 "gambling"
access-control-tag: 10.0.4.0/24 "malware gambling"

Unbound will create an access-control-tag element with the “allow” type if the IP netblock in the access-control-tag element does not match an existing access-control.

When a query comes in from an address with a tag, Unbound starts searching its local-zone tree for the best match. The best match is the most specific local-zone with a matching tag, or without any tag. That means that local-zones without any tag will be used for all clients and tagged local-zones only for clients with matching tags.

Adding tags to local-zones can be done using the local-zone-tag element.

local-zone: malwarehere.example refuse
local-zone: somegamblingsite.example static
local-zone: matchestwotags.example transparent
local-zone: notags.example inform

local-zone-tag: malwarehere.example malware
local-zone-tag: somegamblingsite.example malware
local-zone-tag: matchestwotags.example "malware gambling"

A local-zone can have multiple tags, as illustrated in above example. The tagged local-zones will be used if one or more tags match the client. So, the matchestwotags.example local-zone will be used for all clients with at least the malware or gambling tag. The used local-zone type will be the type specified in the matching local-zone. It is possible to depend the local-zone type on the client address and tag combination. Setting tag specific local-zone types can be done using access-control-tag-action.

access-control-tag-action: 10.0.1.0/24 "malware" refuse
access-control-tag-action: 10.0.2.0/24 "malware" deny

Besides configuring a local-zone type for some specific client address/tag match, it is also possible to set the used local-data RRs. This can be done using the access-control-tag-data element.

access-control-tag-data: 10.0.4.0/24 "gambling" "A 127.0.0.1"

Sometimes you might want to override a local-zone type for a specific netblock, regardless the type configured for tagged and untagged localzones, and regardless the type configured using access-control-tag action. This override can be done using local-zone-override.

Views

Unbound’s tags make is possible to divide a large number of local-zones in categories, and assign these categories to a large number of netblocks. The tags on the netblocks and local-zones are stored in bitmaps, it is therefore advised to keep the number of tags low. If a lot of clients have their own local-zones, without sharing these to other netblocks, it can results in lots of tags. In this situation is is more convenient to give the client’s netblock its own tree containing local-zones. Another benefit of having a separate local zone tree is that it makes it possible to apply a local-zone action to a part of the domain space, without having other local-zone elements of subdomains overriding this. Configuring a client specific local-zone tree can be done using views.

Starting from version 1.6.0, Unbound offers the possibility to configures views. A view in Unbound is a named list of configuration options. The currently supported view configuration options are local-zone and local-data.

A view is configured using a view clause. There may be multiple view clauses each with a unique name.

view:
    name: "firstview"
    local-zone: example.com inform
    local-data: 'example.com TXT "this is an example"'
    local-zone: refused.example.nl refuse

Mapping a view to a client can be done using the access-control-view element.

access-control-view: 10.0.5.0/24 firstview

By default, view configuration options override the global (outside the view) configuration. So, when a client matches a view it will only use the view’s local-zone tree. This behaviour can be changed by setting view-first to yes. If view-first is enabled, Unbound will try to use the view’s local-zone tree, and if there is no match it will search the global tree.

I Can’t Believe It’s Not DNS!

August 16, 2016 by yuri

“I Can’t Believe It’s Not DNS!” is an authoritative DNS server on ESP8266 written in MicroPython. It has the following anti-features:

  • No storage of zone files, AXFR each boot.
  • DNSSEC filtering.
  • TSIG-less AXFR support!
  • Notify ‘handling’.
  • Highly optimized: no sanity checks.

Jumping on the Bandwagon

The Espressif ESP8266 is one of the favorite microcontrollers of IoT-Hipsters for some time. There are many models varying in specs. Typically these devices have 0.5 to 4 MiB of flash, 64KiB instruction memory, run at 80MHz and have a couple of GPIO pins and an analogue input. Their unique selling point however is that they have Wifi built in and cost only a few bucks. Literally. For 4 Euro you can be known as ‘Mr fancy pants with his  own reset button and usb port’.

Its wifi is quite capable and can do 802.11 b/g/n. So what do these hipsters use these chips for? Well sadly, mainly for logging their sensor data to the cloud via JSON over a websocket. Because websockets are better regular sockets right? Right.

(Close) Encounter of the First Kind

What’s interesting is that not only can you program these thing directly (there is a gcc based toolchain available), but there is also a ready firmware for it: NodeMCU. NodeMCU lets you write code in Lua. Having experience with the ESP nor Lua I needed a ‘hello world’ type of project. Something doable, but a bit more out of my comfort zone than blinking an LED. Oh I know, I’ll write an DNS server.

Long story short: Lua on the ESP is a pain and I don’t like it. NodeMCU could echo DNS queries over TCP with almost 2qps. Over UDP, who knows? The API never allowed me to discover the source address or port of an incoming UDP message. Don’t stray of the path of the IoT people when using NodeMCU.

And Now For Something Completely Different

Recently, after a successful crowdfunding campaign, MicroPython was released for the ESP and I decided I would give it another go. And thus “I Can’t Believe It’s Not DNS!” was born. “Not DNS” because while it might reply with DNS-like messages it is far from complete and correct. But it will serve you with about 200qps!

The premise is that this is going to be a plug and forget kind of device. We can’t just ssh in to it and change the zonefile, thus on startup we need to do an AXFR. My zone is around 20 records or so, it surely must fit? Well no. After receiving the AXFR and trying to use the data required more allocations: ENOMEM. Crud.

Python to the rescue! We can make an iterator that we would feed a socket and would spit out parsed Resource records! Little memory overhead, easy does it. Except… DNS uses compression pointers. Compression pointers greatly reduce the size of DNS packets by eliminating repeating of owner names. BUT we don’t have enough memory to buffer the entire AXFR to resolve those pointers. We need a plan.

Almost There

Plan A. So a compression pointer is just an octet pointing back relative to its current position right? (HINT: No it isn’t, I’m being an idiot). So I just need to keep a sliding window of the last 256 bytes! In reality a compression pointer is 14 bits wide and absolute from the start of the message. Most names will point to one of the very first resource records in the message. So let us also keep a copy of the first 256 bytes. That surely must catch 99 percent of all cases, and we just drop any record we can’t resolve the pointer for. Who cares! Well, that is mostly true. But I wasn’t satisfied with the amount of records dropped in my small zone. So nothing else to do but store the AXFR on flash you say? Oh you don’t know me! It’s personal now, I have a plan B.

Being There

Plan B. What if we don’t resolve the compression pointers during the AXFR? That’s right, just let them sit unresolved for a bit. In the mean time drop all those pesky DNSSEC records we are offered. Those are to big anyway and I really don’t want to deal with NSEC lookups on this tiny device. Also, while we are at it drop anything other than class IN, that does not exist in my world. We end up with just a small set of records. But how do we resolve the owner names, we don’t have this data any more?

I know somebody who has this data… the master! You know what? With that set of records in hand do _another_ AXFR a couple of bytes at the time and resolve those pointers on the fly without the need to buffer anything longer than a label (max 63 bytes). Of course compression pointers can be nested so we need to repeat this process in a loop until every pointer is resolved!

Are You Being Served?

Serving queries is the easy part. Lets do as little as possible. When a query comes in we chop of anything beyond the question section. BAM! We have most of our reply done. Fiddle a bit with the flags and section counts, assume query name is uncompressed and append our resource record. Our database only contains TYPE and RDATA. Query name? Always a pointer to byte 12 in the query packet. Class? always IN. TLL? always 15 minutes, deal with it.

Finally we need a mechanism to update our little DNS server if the zone has changed. Serious software would keep track of the version of the zone via the SOA serial number. Poll for a new version on set times, listen to notifies from the master and make an intelligible decision when and how to update the zone. We don’t have the memory available to be intelligible. But we can listen for notify queries. If we receive a notify, any notify – we optimized out any ACL or checking of the zone name, we simply reboot(). The ESP8266 will powercycle and the new version of the zone will be transferred and served. SOA serial management made easy!

Final Thoughts

It should be clear to everybody this software is crap. It sort of mimics DNS but really it isn’t. You should not use this, I should not use this (but you know I will because hosting my zone on a ESP8266 is freaking awesome!)

Algorithm Rollover in OpenDNSSEC 1.3

October 29, 2015 by yuri

Erratum: Unfortunately it appears that this method does not work for OpenDNSSEC 1.4.x. It still works for 1.3.x, specifically 1.3.18 is tested (thanks Michał Kępień!).

The current version of OpenDNSSEC is unable to perform an algorithm rollover. Blindly changing the KSK and ZSK algorithm in the kasp.xml will result in a bogus zone. The only option to do it is to go unsigned: Remove the DS record, wait, publish unsigned zone, and then start signing with the new algorithm. This is undesirable.

There is however a way to roll to a new algorithm securely if you are clever about it and don’t mind some manual intervention. We have worked out a way for you to do this. The nice part is that it requires no manual interaction with the HSM, and that ods-signerd, the signer daemon, can keep running without any interruptions. This means there needs to be no service downtime, your zonefile can still be updated and resigned during this process. For simplicity, a requirement is that your new keys must be of the same length as your old keys.

Also, please note that since we are working with key pairs, the key material should be compatible. This method allows us to switch between different RSA signature algorithms. For example from RSA/SHA-1 to RSA/SHA-256 but not from RSA/SHA-256 to P-256/SHA-256.

1 – Stop Enforcer

First we must stop ods-enforcerd. We will not start it again until the whole process is finished. Make sure nothing autostarts it, which could very well disrupt the whole rollover process badly. Also please make sure you are not currently in a rollover of some sort. Your signconf must mention two keys exactly. A KSK and a ZSK.

2 – Duplicate ZSK

We will edit the signing configuration for your zone. To persuade the signer to double sign everything we will introduce a new ZSK. But rather than generating a new key we will reuse the key material of the old key. This file will generally live under /var/opendnssec/signconf/.

Thus the following section…

<Keys>
  <Key>
    <Flags>257</Flags>
    <Algorithm>5</Algorithm>
    <Locator>b530cf857e0bf2768e3cbf8d59a572d6</Locator>
    <KSK />
    <Publish />
  </Key>
  <Key>
    <Flags>256</Flags>
    <Algorithm>5</Algorithm>
    <Locator>d4823c2f7eedceab5e5e3fd2c16c5dc4</Locator>
    <ZSK />
    <Publish />
  </Key>
</Keys>

would become

<Keys>
  <Key>
    <Flags>257</Flags>
    <Algorithm>5</Algorithm>
    <Locator>b530cf857e0bf2768e3cbf8d59a572d6</Locator>
    <KSK />
    <Publish />
  </Key>
  <Key>
    <Flags>256</Flags>
    <Algorithm>5</Algorithm>
    <Locator>d4823c2f7eedceab5e5e3fd2c16c5dc4</Locator>
    <ZSK />
    <Publish />
  </Key>
  <Key>
    <Flags>256</Flags>
    <Algorithm>8</Algorithm>
    <Locator>d4823c2f7eedceab5e5e3fd2c16c5dc4</Locator>
    <ZSK />
   </Key>
</Keys>

Take care not to include the “<Publish />” element for the new key.

Now the signer must pickup this change:

$ ods-signer update opendnssec.org

Note that at this point the signer is instructed to double sign your entire zone. It might take a long time for large zones, and your zone will possibly almost double in size. This is the only way to do a secure algorithm rollover in DNSSEC. Also, all your DNSSEC responses increase in size.

One might be tempted to also publish the DNSKEY records of the KSK and ZSK at this time. However I advise against that. If this is done, it is possible for a resolver to retrieve the new DNSKEY RRset (containing the new algorithm) but to have RRsets in its cache with signatures created by the old DNSKEY RRset (i.e., without the new algorithm). [see RFC6781]

We have to make sure every signature is picked up by intermediate resolvers before introducing the new DNSKEY records. Whatever the longest TTL is in your zone, dictates how long you have to wait before starting STEP 3.

3 – Duplicate KSK

For the KSK we do the same trick as for the ZSK: introduce a new key that uses the same key material as the old key. Your <Keys> section in the signconf will now look like this:

<Keys>
  <Key>
    <Flags>257</Flags>
    <Algorithm>5</Algorithm>
    <Locator>b530cf857e0bf2768e3cbf8d59a572d6</Locator>
    <KSK />
    <Publish />
  </Key>
  <Key>
    <Flags>256</Flags>
    <Algorithm>5</Algorithm>
    <Locator>d4823c2f7eedceab5e5e3fd2c16c5dc4</Locator>
    <ZSK />
    <Publish />
  </Key>
  <Key>
    <Flags>257</Flags>
    <Algorithm>8</Algorithm>
    <Locator>b530cf857e0bf2768e3cbf8d59a572d6</Locator>
    <KSK />
    <Publish />
  </Key>
  <Key>
    <Flags>256</Flags>
    <Algorithm>8</Algorithm>
    <Locator>d4823c2f7eedceab5e5e3fd2c16c5dc4</Locator>
    <ZSK />
    <Publish />
  </Key>
</Keys>

This time we add the “<Publish />” element to our new ZSK. Again we notify the signer of the changed signconf for our zone. Since algoritihm rollover is not officially supported it might take some effort to convince the signer to pick up all the changes:

$ ods-signer update opendnssec.org
$ ods-signer clear opendnssec.org
$ ods-signer sign opendnssec.org

At this point only the DNSKEY RRset should be changed. Once the resulting zone is transferred proceed to the next step.

We must be sure everyone was able to pick up the new DNSKEY RRset. Thus before proceeding to the next step wait at least the TTL of the DNSKEY RRset. You can find the TTL in the <keys> section of kasp.xml. Don’t even consider rushing over this step! Waiting here is of utter most importance.

4 – Generate DS

Since our DNSKEY changed, our DS will also be different. We can’t ask ods-ksmutil for the DS this time since it isn’t in the database yet. We can find the DNSKEY ofcource in the signed zonefile, a grep or even a simple dig should give it to us. Then we need to calculate the DS.

$ dig opendnssec.org DNSKEY |grep 257 > dnskeys
$ ldns-key2ds -n dnskeys

At this point you can switch the old DS record at the parent for the new one. Either in one action or add the newest first and then remove the oldest.

Then there is more waiting involved: the TTL of the DS record. Query your parent or look it up in the KASP. <Parent><DS><TTL>.

5 – Clean up

We will now remove the old KSK section from signconf, notify the signer and wait for TTL of DNSKEY RRset. We then remove the old ZSK entry from the signconf and notify the signer. (No need to wait here.)

$ ods-signer clear opendnssec.org
$ ods-signer sign opendnssec.org

6 – Mangle the Database

To get the enforcer state in line with the latest signconf is easy. Only the algorithm number is set different. A quick database edit can fix it. For Example:

$ sqlite3 /var/opendnssec/kasp.db
sqlite> UPDATE `keypairs` SET `algorithm`=8 WHERE 
  `HSMkey_id`='d4823c2f7eedceab5e5e3fd2c16c5dc4' OR 
  `HSMkey_id`='b530cf857e0bf2768e3cbf8d59a572d6';
$ mysql -u ods-db-usr opendnssec -p
mysql> UPDATE `keypairs` SET `algorithm`=8 WHERE
  `HSMkey_id`='d4823c2f7eedceab5e5e3fd2c16c5dc4' OR
  `HSMkey_id`='b530cf857e0bf2768e3cbf8d59a572d6';

The HSMkey_id can be find in the signconf as <Locator>.

7 – Start Enforcer

If everything was executed according to plan, the enforcer daemon once started should produce the same signconf as the one we last fabricated by hand. I recommend to verify this before feeding the signconf to the signer.

Wed Sep 25 2013

© Stichting NLnet Labs

Science Park 400, 1098 XH Amsterdam, The Netherlands

labs@nlnetlabs.nl, subsidised by NLnet and SIDN.