Monday, March 26, 2012

Dynamic DNS updates with nsupdate and BIND 9

I first saw nsupdate mentioned on the devops-toolchain mailing list as a tool for dynamically updating DNS zone files from the command line. Since this definitely beats manual editing of zone files, I'd thought I'd give it a try. My setup is BIND 9 on Ubuntu 10.04. I won't go into the details of setting up BIND 9 on Ubuntu -- see a good article about this here.

It took me a while to get nsupdate to work. There are lots of resources out there, but as usual it's hard to separate the grain from the chaff. When everything was said and done, the solution was relatively simple. Here it is.

Generate TSIG keys

dnssec-keygen -r /dev/urandom -a HMAC-MD5 -b 512 -n HOST

This generates 2 files of the form:

-rw-------   1 root bind   122 2012-03-21 15:47
-rw-------   1 root bind   229 2012-03-21 15:47

Note that I specified /dev/urandom as the source of randomness, which may not meet your security requirements. When I didn't specify the -r /dev/urandom parameter, the dnssec-keygen command appeared to hang.

Also note that the type of the key needs to be HOST (specified via -n HOST).

Add key to DNS master server configuration and allow updates

I modified /etc/bind/named.conf.local and added a 'key' section:

key "" {
 algorithm hmac-md5;
 secret "JKlA76FvmGboEQ8R2yoc9AtpFqkIncH5yf2mXY+s8m6a/RRC0thUVGnqrJSO1QKhzlnkbxTjmArap+WuVW9iLQ==";

The key name can be anything you want. The secret is the actual key, which can be found in both of the files generated by dnssec-keygen.

I also added an allow-update directive to the zone that I wanted to modify via nsupdate. This is still in /etc/bind/named.conf.local:

zone "" {
       type master;
       file "/var/lib/bind/";
       allow-update { key ""; };

I then restarted bind9 on the master DNS server via 'service bind9 restart'. I checked /var/log/daemon.log to make sure there were no errors during the restart.

Note that you can use a more finely grained control over which operations you allow for the updates. See the 'Allowing Updates' section in this 'Secure DDNS Howto' document.

Use nsupdate to do remote updates

On a remote trusted host of your choice, copy the private file generated by dnssec-keygen, and create a file containing the desired updates to the zone file on the master. This file is of the form:

# cat nsupdate.txt
debug yes
update add 86400 CNAME ns1

Then run nsupdate and specify the kddey and the file you just created:

# nsupdate -k -v nsupdate.txt

If everything goes well, you should see something like this in the debug output of nsupdate (because we specified 'debug yes' in the nsupdate.txt file):


;; TSIG PSEUDOSECTION: 0 ANY TSIG 1332788750 300 16 UxiMG7+X2RejWzQ9rkuPaQ== 3305 NOERROR 0 

Reply from update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:   3305
;; flags: qr ra ; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 1
;; TSIG PSEUDOSECTION: 0 ANY TSIG 1332788857 300 16 KBubhEggwBHnPlbmlQ7iTw== 3305 NOERROR 0 

On the master DNS server, you should see something like this in /var/log/daemon.log:

Mar 26 12:07:37 dns01 named[14952]: client signer "" approved
Mar 26 12:07:37 dns01 named[14952]: client updating zone '': adding an RR at '' CNAME
Mar 26 12:07:37 dns01 named[14952]: zone sending notifies (serial 2012032104)
Mar 26 12:07:37 dns01 named[14952]: client transfer of '': IXFR started
Mar 26 12:07:37 dns01 named[14952]: client transfer of '': IXFR ended

One other important note: the modifications made with nsupdate take effect immediately on the DNS master server (and they also get pushed from there to slave servers), but they are not written immediately to the actual DNS zone file on disk on the master server. Instead, a journal file is used, in the same directory as the zone file. The journal entries get applied periodically to the main zone file. If you restart bind9, the journal entries also get applied.

That's about it. If everything went well, you now have an API of sorts into your Bind 9 server. Now go automate all the things!

More resources:

Thursday, March 08, 2012

PostgreSQL dump/restore and client_encoding

I started to look into EnterpriseDB recently. Pretty pleased with it so far. At first I launched the beta version of their Postgres Plus Cloud Database product, but since this version is in the process of being decomissioned, I've had to transfer the database I had already created to a newly created cluster in their DBaaS model -- which basically means that the cluster manager is maintained by them, and the cluster member servers (1 master + N replicas) are part of your EC2 footprint.

In any case, I did a pg_dump of the database from the initial master, then I tried to load the dump via psql into a newly created database on the new master. However, the client_encoding parameter in postgresql.conf was SQL_ASCII on the first master, and UTF8 on the second. This posed a problem. The psql load operation failed with errors of the type:

ERROR:  invalid byte sequence for encoding "UTF8": 0xe92044
CONTEXT:  COPY table1, line 6606
ERROR:  invalid byte sequence for encoding "UTF8": 0xa0
CONTEXT:  COPY table2, line 978
ERROR:  invalid byte sequence for encoding "UTF8": 0xd454
CONTEXT:  COPY table3, line 3295

Obviously the encodings didn't match. I first tried to re-run the pg_dump on the first master (which had client_encode = 'SQL_ASCII') and specified "--encoding utf8" on the pg_dump command line. This didn't do the trick. I had the same exact errors when loading the dump on the second master.

One solution suggested by EnterpriseDB was to set client_encoding to SQL_ASCII on the new master, restart Postgres and retry the load. I found another solution though in a blog post very aptly titled (for my purposes) 'PostgreSQL database migration, the SQL_ASCII to UTF8 problem'. What I ended up doing, following the advice in the post, was to install the GNU recode utility (I did a yum install recode), then run the initial dump through recode, converting it from ascii to utf8. Something like this:

cat dump.sql | recode iso-8859-1..u8 > utf8_withrecode.sql

Then I modified the line

SET client_encoding = 'SQL_ASCII';

and turned it into:

SET client_encoding = 'UTF8';

after which loading the dump into the new master with psql worked like a charm. for the fun part of doing some load testing against this Postgres cluster!

Modifying EC2 security groups via AWS Lambda functions

One task that comes up again and again is adding, removing or updating source CIDR blocks in various security groups in an EC2 infrastructur...