[go: up one dir, main page]

Skip to content
Alex Dalitz edited this page Jul 11, 2018 · 12 revisions

Welcome to the dnsruby wiki! This page contains old blog articles which were originally hosted by Nominet UK, which help get you started with dnsruby.

In this post, I’d like to look at how to use dnsruby to accomplish some common tasks.

Getting Started

To follow these examples, you’ll need to install dnsruby :

$ gem install dnsruby

I’ll run these examples in Ruby’s interactive shell :

$ irb

First, I need to include Dnsruby :

require 'rubygems'
require 'dnsruby'
include Dnsruby

Now I’ll load the system’s default resolvers :

res = Resolver.new

And display them :

res.single_resolvers.each {|s| print "Server address : #{s.server}\n"}
# Outputs:
# Server address : 192.168.1.1
# Server address : 192.168.2.2

Now I’ll use them to run a couple of queries :

ret = res.query("example.com") # Defaults to A record
print ret.answer
# prints: example.com.	172789	IN	A	208.77.188.166=> nil
res.query("example.com", "MX") # Query the MX record

This time, I’ll use some defined nameservers :

res = Resolver.new({:nameserver => ["ns1.nic.uk", "ns2.nic.uk"]})

Asynchronous Queries

To run an asynchronous query, I’ll define a Queue to hold the results, and then prepare the query. This time, I’ll construct a Message to hold the query data, and set the RD (recursion desired) bit on the header to 0 :

queue = Queue.new
m = Message.new("co.uk", Types.NS)
m.header.rd = false
message_id = res.send_async(m, queue, 1)

Now my code can get on with other tasks, until I’m ready to get the response. Queue#pop is a blocking call, but you can check if it is empty using Queue#empty?.

id, reply, error = queue.pop # id == message_id

The [id, reply, error] tuple is popped off the queue. The id identifies which query the response is for (it should match the id returned by the send_async call), reply holds the best response that was received, and any errors will be held in error (which should be nil in this example).

Message Options

Now I’ll ask for a Message to be sent without checking (or the response being stored in) the cache. I’ll also make sure that no DNSSEC validation is performed on the response :

m.do_caching = false
m.do_validation = false
res.send_message(m)

I can ask for a Message to be sent without any pre- or post-processing. No EDNS headers are applied, the header flags are not adjusted, and no caching or validation is performed. This method is most useful for tools authors :

res.send_plain_message(Message.new("nic.uk"))

TSIG and Dynamic Updates

I can also use TSIG signatures to communicate securely with a resolver. In this example, I’ll use TSIG to sign a dynamic update. First, I’ll have to define the server to use, and the TSIG key to speak to it with :

res = Dnsruby::Resolver.new("ns0.validation-test-servers.nominet.org.uk")
res.dnssec = false
tsig = Dnsruby::RR.create({
        :name        => "rubytsig",
        :type        => "TSIG",
         :key         => "8n6gugn4aJ7MazyNlMccGKH1WxD2B3UvN/O/RA6iBupO2/03u9CTa3Ewz3gBWTSBCH3crY4Kk+tigNdeJBAvrw==",
      })

Now I’ll create the dynamic update packet :

update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk")
# ... add stuff to the update
update.absent("notthere.update.validation-test-servers.nominet.org.uk", 'TXT')

And apply the TSIG signature and send the message :

tsig.apply(update)
response = res.send_message(update)
print "TSIG response was verified? : #{response.verified?}\n"

I could also have configured the Resolver to sign all packets with TSIG :

res.tsig = tsig.name, tsig.key

Recursive Queries

In addition to defining nameservers to do recursive queries on my behalf, I can also get Dnsruby to query recursively from the root. A static cache is built up, so the more client queries that are run, the less packets need be sent per client query.

rec = Recursor.new
ret = rec.query("uk-dnssec.nic.uk", "NS")

In my next article, I’ll look at how use Dnsruby with DNSSEC.


In this post, I’d like to look at how to use Dnsruby with DNSSEC. As before, I’ll run these examples in irb, and assume that you’ve included Dnsruby there.

Dnsruby has DNSSEC support switched on by default. This means it will attempt to validate any DNS responses against its trust anchors. However, by default, no trust anchors are configured – to get dnsruby to validate responses, you must first configure a trust anchor (or DLV repository).

Trust Anchors

DNSSEC works by following a chain of trust from parent zone to child zone. This chain of trust must start somewhere – the “trust anchor”. In a world with a signed root, the root would be the anchor. Delegations to children zones would be signed, all the way down to the domain that is being queried. The querier can then be sure that the signed response is genuine.

Unfortunately, the root is not yet signed – we have many “islands of security”. Each island is signed, but has no chain to it from the root. It is possible to configure dnsruby with the keys for these zones using Dnsruby::Dnssec#add_trust_anchor() – it’s also possible to define an expiration time for each anchor. Dnsruby will then follow the chain of trust from the anchor down to the queried domain in the signed zone.

Managing these trust anchors quickly becomes a headache. You need to have secure means of obtaining and verifying them, and rolling over to new keys as time goes on. Fortunately, there are two mechanisms to help with this : IANA’s TAR and ISC’s DLV repository.

IANA (who manage the root zone) have created a Trust Anchor Repository (ITAR) which can be used until the root is signed. This holds delegation records for the DNSSEC-signed TLDs. It is possible to download this repository and configure dnsruby with the anchors. A method to do this is defined in Dnsruby::Dnssec#load_itar, but it is not currently secure. If you need to use the ITAR securely, you are currently advised to add the trust anchors from the ITAR directly into dnsruby. A secure method will be provided in future releases.

DLV

Even if the root was signed, there will still be some domains in unsigned zones, which wish to benefit from DNSSEC security. For example, signed-zone.unsigned-zone.example.org – there can be no chain of trust from the root to signed-zone. A solution exists for signed-zone : DNSSEC Lookaside Validation (DLV). Here, a DLV repository holds secure delegation records for zones like signed-zone. Instead of following the chain of trust from the root, a validator follows the chain of trust from the closest parent zone known to the DLV repository. Of course, this method involves more validation queries for each application query.

As an example, considering querying for random.example.com – first, the query itself must be made. Then, if unsuccessful, a DLV query for random.example.com.dlv.isc.org must be made, followed by a query for example.com.dlv.isc.org, followed by a query for com.dlv.isc.org. If none of these succeed, then the message cannot be validated. Imagine that a response was received for the com.dlv.isc.org zone : then, the chain of trust could be followed through example.com down to random.example.com. Keys discovered from the DLV repository are cached.

Configuring Trust Anchors

To configure a trust anchor (in this case for the uk-dnssec.nic.uk DNSSEC test zone) :

trusted_key = Dnsruby::RR.create({:name => "uk-dnssec.nic.uk.",
    :type => Dnsruby::Types.DNSKEY,
    :flags => 257,
    :protocol => 3,
    :algorithm => 5,
    :key=> "AQPJO6LjrCHhzSF9PIVV7YoQ8iE31FXvghx+14E+jsv4uWJR9jLrxMYm sFOGAKWhiis832ISbPTYtF8sxbNVEotgf9eePruAFPIg6ZixG4yMO9XG LXmcKTQ/cVudqkU00V7M0cUzsYrhc4gPH/NKfQJBC5dbBkbIXJkksPLv Fe8lReKYqocYP6Bng1eBTtkA+N+6mSXzCwSApbNysFnm6yfQwtKlr75p m+pd0/Um+uBkR4nJQGYNt0mPuw4QVBu1TfF5mQYIFoDYASLiDQpvNRN3 US0U5DEG9mARulKSSw448urHvOBwT9Gx5qF2NE4H9ySjOdftjpj62kjb Lmc8/v+z"
  })
Dnsruby::Dnssec.add_trust_anchor(trusted_key)

Dnsruby will now attempt to validate any responses from the uk-dnssec.nic.uk zone (or its children).

To configure dnsruby to use ISC’s DLV repository, you must first obtain the key (from here). You can then configure dnsruby :

dlv_key = RR.create("DLV_KEY_STRING_FROM_ISC")
Dnssec.add_dlv_key(dlv_key)

This method queries the DLV registry to get the ZSK (zone signing key) from the above KSK (key signing key). Dnsruby will now attempt to validate all responses against the DLV repository, if it can’t validate from any trust anchors.

Configuring Validation Policy

It is possible to configure the validation policy to vary the precedence of search order – from the root only, or local anchors only, or either first. Separate key caches are maintained by each validator, making it possible to configure them dynamically. DLV validation is only performed once the DLV key has been added. Here is an example of changing the validation policy :

Dnsruby::Dnssec.validation_policy = Dnsruby::Dnssec::ValidationPolicy::ROOT_THEN_LOCAL_ANCHORS

It is possible to clear all trusted keys (which will also stop DLV validation) by calling :

Dnsruby::Dnssec.clear_trusted_keys()

You can remove just the trust anchors (leaving DLV keys and validation from the root, and all keys generated from them), and the keys generated from them, by calling :

Dnsruby::Dnssec.clear_trust_anchors()

Configuring Validation Resolver

When a response is validated, it may be necessary to make several more queries in order to follow the chain of trust. As more queries are made, more chains are followed. Trusted keys are cached as they are discovered (for the length of time they are indicated to be good for) – this means that future queries for domains in those zones will not require so many validation queries to be performed.

It’s possible to configure dnsruby to use different methods for performing the validation queries. They can either be directed to recursive nameservers (which can be the system defaults, or a client-supplied set of addresses), or they can be performed recursively. I have found that many resolvers do not yet speak a perfect dialect of DNSSEC – performing validation queries recursively ensures that the correct DNSSEC-signed responses are received. The default is to perform validation recursively. Of course, while the caches are being built up when dnsruby starts, more queries will be performed than if the queries were directed to recursive nameservers.

To ask dnsruby to use query a recursive nameserver, call :

Dnsruby::Dnssec.do_validation_with_recursor = false

Dnsruby will now use the system default configured nameservers for validation.

To use a specific set of servers to perform validation :

res = Dnsruby::Resolver.new({:nameserver => ['192.168.1.1', '192.168.2.1']})
Dnsruby::Dnssec.default_resolver = res

Validating Responses

Once dnsruby has been configured with a trust anchor, it will attempt to validate any responses for domains within that zone (or its subzones). If it detects that validation is necessary, then it will fire up a new thread to handle that validation. Since many queries may need to be performed in order to validate the reponse, this can take some time longer than the original query would have done alone. This means that the query timing settings in the Resolver class apply only to each query – not to the whole validation process.

For example, a query may have a Resolver#query_timeout of 5 seconds. As long as the answer for that query is returned in 5 seconds, then no timeout will occur – even if it then takes another 6 seconds to validate that response. Future versions of dnsruby will include the ability for client applications to receive events detailing the progress of each asynchronous query (e.g. RECEIVED, VALIDATED).

It is possible to disable validation on a Message basis. Simply set :

msg.do_validation = false

before sending the Message – dnsruby will not validate the response to that query.

Message Security Levels

Messages can have one of four security levels (defined in Dnsruby::Message::SecurityLevel) : BOGUS, UNCHECKED, INSECURE and SECURE. Dnsruby will only raise an error if it detects that a response is BOGUS – this means that the message does not contain the correct set of signatures. INSECURE means that the response has been verified to have come from a non-secured zone. SECURE means that the chain of trust has been correctly followed from a configured trust anchor to the response, and that all signatures check OK. Note that an NXDOMAIN response can still be SECURE – this means that the NSEC(3) records have been verified to prove non-existence.

To check the security level of a Message, use Message#security_level :

if (msg.security_level == Dnsruby::Message::SecurityLevel::SECURE)
  print "Response was validated OK\n"
end

Examples of Use

DNSSEC examples can be found in the EXAMPLES file in the dnsruby distribution.

Limitations

Dnsruby does not yet perform NSEC3 validation (although NSEC3/NSEC3PARAM records can be read from the wire, or presentation format). This will be added to a future release.