README.md
author Tero Marttila <terom@paivola.fi>
Mon, 09 Mar 2015 23:31:13 +0200
changeset 738 3104fdf7ea26
parent 728 d3cea9988848
permissions -rw-r--r--
pvl.hosts.hosts: drop support for instanced ip.* in favor of improved interface:ip.* =
# pvl-hosts

DNS/DHCP hosts management/integration for ISC bind9 and dhcpd.

## Hosts
The `pvl.hosts-*` tools read hosts files as input, which have an ini format, using section names as hostnames to configure attributes for that host:

    [foo]
        ip          = 192.0.2.1
        ethernet    = 00:11:22:33:44:55

    [bar]
        ip          = 192.0.2.2
        ethernet    = 01:23:45:67:89:ab

The domain name for a host is determined from the basename of the config file, so this example file would generate something like the following output for use in a `zone "example.com" { ... }` zonefile:
    
    $ bin/pvl.hosts-forward etc/hosts/example.com 
    foo                               A     192.0.2.1
    bar                               A     192.0.2.2

And correspondingly, the reverse zone for `2.0.192.in-addr.arpa`:

    $ bin/pvl.hosts-reverse --zone-prefix=192.0.2.0/24 etc/hosts/example.com
    1                                 PTR   foo.example.com.
    2                                 PTR   bar.example.com.

And the associated DHCP hosts:

    $ bin/pvl.hosts-dhcp etc/hosts/example.com 
    host foo {
        option host-name foo;
        hardware ethernet 00:11:22:33:44:55;
        fixed-address 192.0.2.1;
    }

    host bar {
        option host-name bar;
        hardware ethernet 01:23:45:67:89:ab;
        fixed-address 192.0.2.2;
    }

### Include directories
Host configs can be included:

    $ cat etc/hosts/test
    include = test.d/

    $ cat etc/hosts/test.d/foo 
    ip = 192.0.2.1

    $ cat etc/hosts/test.d/bar 
    ip = 192.0.2.2

    $ bin/pvl.hosts-forward etc/hosts/test
    foo                               A     192.0.2.1
    bar                               A     192.0.2.2

Including a directory of files is equivalent to substituiting each file as a named section at the level of the include = statement. Note that this means that included files are treated directly as host definitions, IOW, you should NOT include a section name in an included host file unless you want to declare an additional subdomain:

    $ cat etc/hosts/wrong.test 
    include = wrong.d/
    
    $ etc/hosts/wrong.d/host
    [host]
        ip  = 192.0.2.6

Using the --root-zone option to generate the full FQDN for the host:

    $ bin/pvl.hosts-forward --root-zone etc/hosts/wrong.test 
    host.host.wrong.test              A     192.0.2.6

### Host aliases
Hosts can specify DNS aliases:

    [foo]
        ip          = 127.0.0.1
        alias       = test1
        alias4      = test

    [bar]
        ip          = 127.0.0.2
        alias       = test2
        alias4      = test

    $ bin/pvl.hosts-forward etc/hosts/alias.test 
    foo                               A     127.0.0.1
    test1                             CNAME foo
    test                              A     127.0.0.1
    bar                               A     127.0.0.2
    test2                             CNAME bar
    test                              A     127.0.0.2

Normal CNAME aliases cannot overlap with other hosts, but the IPv4/IPv6-only `alias4`/`alias6` may overlap.

### Generated hosts
The hosts file format supports something similar to bind9's $GENERATE directive for hosts:

    [dyn{1-8}]
        ip  = 10.1.16.$

    $ bin/pvl.hosts-forward etc/hosts/dyn.test 
    dyn1                              A     10.1.16.1
    dyn2                              A     10.1.16.2
    dyn3                              A     10.1.16.3
    dyn4                              A     10.1.16.4
    dyn5                              A     10.1.16.5
    dyn6                              A     10.1.16.6
    dyn7                              A     10.1.16.7
    dyn8                              A     10.1.16.8
    
This feature can be used for generating reverse delegations:
    [foo-{240-247}]
        forward =
        reverse = $.240/29.0.0.10.in-addr.arpa
        ip      = 10.0.0.$

    $ bin/pvl.hosts-reverse --zone-prefix=10.0.0.0/16 etc/hosts/reverse.test 
    240.0                             CNAME 240.240/29.0.0.10.in-addr.arpa.
    241.0                             CNAME 241.240/29.0.0.10.in-addr.arpa.
    242.0                             CNAME 242.240/29.0.0.10.in-addr.arpa.
    243.0                             CNAME 243.240/29.0.0.10.in-addr.arpa.
    244.0                             CNAME 244.240/29.0.0.10.in-addr.arpa.
    245.0                             CNAME 245.240/29.0.0.10.in-addr.arpa.
    246.0                             CNAME 246.240/29.0.0.10.in-addr.arpa.
    247.0                             CNAME 247.240/29.0.0.10.in-addr.arpa.

### DHCP Options
The hosts need not specify any fixed ip address, leaving IP address allocation to dhcpd:

    [foo]
        ethernet    = 00:11:22:33:44:55 
    
    $ bin/pvl.hosts-dhcp etc/hosts/dhcp.test 
    host foo {
        option host-name foo;
        hardware ethernet 00:11:22:33:44:55;
    }

### DHCP Boot options
The hosts can specify DHCP boot server/file options:

    boot.next-server  = boot.test

    [foo]
        ethernet        = 00:11:22:33:44:55
        boot            = boot2.test:/debian/wheezy/pxelinux.0

    [bar]
        ethernet        = 00:11:22:33:44:55
        boot.filename   = /debian/jessie/pxelinux.0

    $ bin/pvl.hosts-dhcp etc/hosts/boot.test 
    host foo {
        option host-name foo;
        hardware ethernet 00:11:22:33:44:55;
        next-server boot2.test;
        filename "/debian/wheezy/pxelinux.0";
    }

    host bar {
        option host-name bar;
        hardware ethernet 00:11:22:33:44:55;
        next-server boot.test;
        filename "/debian/jessie/pxelinux.0";
    }

### DHCP hosts in multiple subnets/domains
A host with different interfaces in multiple domains must specify unique interface names:

    [foo]
        [[asdf]]
            ip              = 10.1.0.1
            ethernet.eth1   = 00:11:22:33:44:55

    [bar]
        [[asdf]]
            ip              = 10.2.0.1
            ethernet.eth2   = 55:44:33:22:11:00

    $ bin/pvl.hosts-dhcp etc/hosts/dhcp-test 
    host asdf-eth1 {
        option host-name asdf;
        hardware ethernet 00:11:22:33:44:55;
        fixed-address 10.1.0.1;
    }

    host asdf-eth2 {
        option host-name asdf;
        hardware ethernet 55:44:33:22:11:00;
        fixed-address 10.2.0.1;
    }

### DHCP subgroups
Hosts can be assigned to DHCP subgroups by hardware ethernet:

#### `dhcpd.conf`
    class "test-hosts" {
        match hardware;
    }

#### `etc/hosts/dhcp-classes.test`
    [foo]
        ethernet        = 00:11:22:33:44:55
        dhcp:subclass   = test-hosts

#### `bin/pvl.hosts-dhcp etc/hosts/dhcp-classes.test`
    host foo {
        option host-name foo;
        hardware ethernet 00:11:22:33:44:55;
    }

    subclass "test-hosts" 1:00:11:22:33:44:55;

# `update`
A script to drive the *pvl.hosts* tools for maintaing a set of zone/host files for a DNS/DHCP server.

## Source host files

Creating a tree of symlinks for managing split zonefile domains can be useful:

    $ tree etc/zones/
    etc/zones/
    ├── forward
    │   └── test
    │       ├── asdf.test -> ../../../hosts/asdf.test
    │       └── test -> ../../../hosts/test
    ├── reverse
    │   └── 192.0.2
    │       ├── asdf.test -> ../../../hosts/asdf.test
    │       └── test -> ../../../hosts/test
    └── test

Given a structure like above, the `pvl.hosts-forward` can generate a single forward zone containing all sub-domains:

    $ bin/pvl.hosts-forward --hosts-include etc/hosts/ etc/zones/forward/test/
    foo                               A     192.0.2.1
    bar                               A     192.0.2.2
    quux.asdf                         A     192.0.2.5

Note that the directory name is treated separately as a zone origin; the file names within the domain are still treated as a flat namespace independent of the directory name (which is different than *pvl.hosts* would behave for `include = etc/zones/forward/test/`).

The same trick also works for `pvl.hosts-reverse`:

    $ bin/pvl.hosts-reverse --hosts-include etc/hosts/ etc/zones/reverse/192.0.2/
    1                                 PTR   foo.test.
    2                                 PTR   bar.test.
    5                                 PTR   quux.asdf.test.

## Source zone files

The zonefile header should be written out manually, using an `$INCLUDE` directive to reference the (generated) hosts zonefile:

    $ cat etc/zones/test 
    $TTL 3600

    @                   SOA     foo.test. hostmaster.test. (
                                0               ; serial
                                1d              ; refresh
                                5m              ; retry
                                10d             ; expiry
                                300             ; negative
                        )

                        NS      foo
                        NS      bar

    $INCLUDE "forward/test"

## Operation

Use the *update* script to generate a complete set of output zonefiles:

    $ ./bin/update -C
      var: apply dir
      var/dhcp: apply dir
      var/zones: apply dir
      var/include-cache: apply dir
      var/serials: apply dir
      var/dhcp/hosts: apply dir
      var/zones/includes: apply dir
      var/zones/forward: apply dir
      var/zones/reverse: apply dir
    Commit...
    Using commit timestamp: 1425379711
    Updating forward host zones...
      var/zones/forward/test: Generating forward hosts zone: etc/zones/forward/test
    Updating reverse host zones...
      var/zones/reverse/192.0.2: Generating reverse hosts zone: etc/zones/reverse/192.0.2
    Updating DHCP hosts...
    Copying zone includes...
    Updating zones...
      var/serials/test: Update serial:  <- 1425379711
      var/zones/test: Generate zone: etc/zones/test @ 1425379711
    Updating DHCP confs...
    Testing zones...
    Reload zones...
      Reload zones
     * Reloading domain name service... bind9 [ OK ] 
    Testing DHCP...
    Reload DHCP...
      Reload DHCP
    isc-dhcp-server stop/waiting
    isc-dhcp-server start/running, process 32581

The update script tracks hostfile/zonefile dependencies, and only updates the necessary output files:

    $ touch etc/hosts/test.d/foo && ./bin/update -C
    Commit...
    Using commit timestamp: 1425379801
    Updating forward host zones...
      var/zones/forward/test: Generating forward hosts zone: etc/zones/forward/test
    Updating reverse host zones...
      var/zones/reverse/192.0.2: Generating reverse hosts zone: etc/zones/reverse/192.0.2
    Updating DHCP hosts...
    Copying zone includes...
    Updating zones...
      var/serials/test: Update serial: 1425379801 <- 1425379801
      var/zones/test: Generate zone: etc/zones/test @ 1425379801
    Updating DHCP confs...
    Testing zones...
    Reload zones...
      Reload zones
     * Reloading domain name service... bind9 ...done.
    Testing DHCP...
    Reload DHCP...
      Reload DHCP
    isc-dhcp-server stop/waiting
    isc-dhcp-server start/running, process 775

Use `-n` to enable noop mode and preview changes before updating:

    sed -i s/quux/quux2/ etc/hosts/asdf.test && ./bin/update -C -n
    Commit...
      /home/tjmartti/pvl/pvl-hosts: skip commit
    Using local unix time for uncommited changes: 1425380558
    Updating forward host zones...
      var/zones/forward/test: Generating forward hosts zone: etc/zones/forward/test
            --- var/zones/forward/test      2015-03-03 12:55:53.480735624 +0200
            +++ var/zones/forward/test.new  2015-03-03 13:02:38.708732551 +0200
            @@ -2,2 +2,2 @@
             bar                               A     192.0.2.2
            -quux.asdf                         A     192.0.2.5
            +quux22.asdf                       A     192.0.2.5
    Updating reverse host zones...
      var/zones/reverse/192.0.2: Generating reverse hosts zone: etc/zones/reverse/192.0.2
            --- var/zones/reverse/192.0.2   2015-03-03 12:55:53.596735623 +0200
            +++ var/zones/reverse/192.0.2.new       2015-03-03 13:02:38.832732550 +0200
            @@ -2,2 +2,2 @@
             2                                 PTR   bar.test.
            -5                                 PTR   quux.asdf.test.
            +5                                 PTR   quux22.asdf.test.
    Updating DHCP hosts...
    Copying zone includes...
    Updating zones...
    Updating DHCP confs...
    Testing zones...
    Reload zones...
      Skip reload zones
    Testing DHCP...
    Reload DHCP...
      Skip reload DHCP

Note that noop mode does not yet handle dependency chains, i.e. you will not see which zones get updated serials without also using `-F`, which force-updates all output files regardless of dependency states.

Finally, the default operation mode of update is to commit any changes, and update the zones using the commit timestamp as a serial. Use the `-p` flag to show output diffs as with `-n`:

    $ sed -i s/quux/quux2/ etc/hosts/asdf.test && ./bin/update -m "rename quux to quux2" -p
    Commit...
      /home/tjmartti/pvl/pvl-hosts: commit: rename quux to quux2
        diff -r 8790e1e28661 etc/hosts/asdf.test
        --- a/etc/hosts/asdf.test   Tue Mar 03 13:05:13 2015 +0200
        +++ b/etc/hosts/asdf.test   Tue Mar 03 13:06:03 2015 +0200
        @@ -1,2 +1,2 @@
        -[quux]
        +[quux2]
             ip  = 192.0.2.5
    Using commit timestamp: 1425380763
    Updating forward host zones...
      var/zones/forward/test: Generating forward hosts zone: etc/zones/forward/test
            --- var/zones/forward/test      2015-03-03 13:04:09.556731909 +0200
            +++ var/zones/forward/test.new  2015-03-03 13:06:04.260731122 +0200
            @@ -2,2 +2,2 @@
             bar                               A     192.0.2.2
            -quux22.asdf                       A     192.0.2.5
            +quux2.asdf                        A     192.0.2.5
    Updating reverse host zones...
      var/zones/reverse/192.0.2: Generating reverse hosts zone: etc/zones/reverse/192.0.2
            --- var/zones/reverse/192.0.2   2015-03-03 13:04:09.684731908 +0200
            +++ var/zones/reverse/192.0.2.new       2015-03-03 13:06:04.384731122 +0200
            @@ -2,2 +2,2 @@
             2                                 PTR   bar.test.
            -5                                 PTR   quux22.asdf.test.
            +5                                 PTR   quux2.asdf.test.
    Updating DHCP hosts...
    Copying zone includes...
    Updating zones...
      var/serials/test: Update serial: 1425380649 <- 1425380763
      var/zones/test: Generate zone: etc/zones/test @ 1425380763
            --- var/zones/test      2015-03-03 13:04:09.812731907 +0200
            +++ var/zones/test.new  2015-03-03 13:06:04.512731121 +0200
            @@ -1,3 +1,3 @@
             $TTL   3600
            -@                                 SOA   foo.test. hostmaster.test. 1425380649 1d 5m 10d 300
            +@                                 SOA   foo.test. hostmaster.test. 1425380763 1d 5m 10d 300
                                               NS    foo
    Updating DHCP confs...
    Testing zones...
    Reload zones...
      Reload zones
     * Reloading domain name service... bind9 [ OK ] 
    Testing DHCP...
    Reload DHCP...
      Reload DHCP
    isc-dhcp-server stop/waiting
    isc-dhcp-server start/running, process 2839


## Output zone files

The generated zone files can then be loaded by bind:

    $ cat var/zones/test 
    $TTL    3600
    @                                 SOA   foo.test. hostmaster.test. 1425049508 1d 5m 10d 300
                                      NS    foo
                                      NS    bar
    $INCLUDE        "./var/zones/forward/test"

    $ cat var/zones/forward/test 
    foo                               A     192.0.2.1
    bar                               A     192.0.2.2
    quux.asdf                         A     192.0.2.5

# *pvl-dns*
Low-level zonefile utilities.

## `bin/pvl.dns-process`
Process a zonefile to modify:

* `SOA` record serial
* `$INCLUDE` paths

    $ bin/pvl.dns-process --serial $(date +%s) --include-path var/zones etc/zones/test 
    $TTL    3600
    @                                 SOA   foo.test. hostmaster.test. 1425049088 1d 5m 10d 300
                                      NS    foo
                                      NS    bar
    $INCLUDE        "var/zones/forward/test"

## `bin/pvl.dns-zone`
Load a zonefile and output any ZoneRecords that it contains, including `$GENERATE`ed and `$INCLUDE`ed records:

    $ bin/pvl.dns-zone --zone=test var/zones/test 
    @                         3600    SOA   foo.test. hostmaster.test. 1425049248 1d 5m 10d 300
    @                         3600    NS    foo
    @                         3600    NS    bar
    foo                       3600    A     192.0.2.1
    bar                       3600    A     192.0.2.2
    quux.asdf                 3600    A     192.0.2.5


Optionally `--check-hosts` for dupliates `A`/`AAAA` records.

Use `--reverse-prefix=192.0.2` to generate a reverse-dns zone from `A`/`AAAA` records:

    $ bin/pvl.dns-zone --zone=test var/zones/test --reverse-prefix=192.0.2
    1                                 PTR   foo.test.
    2                                 PTR   bar.test.
    5                                 PTR   quux.asdf.test.

# Experimental features 

Features that are still under development

* DHCP host status tracking from syslog/dhcpd.leases into a database 
* SNMP network topology discovery