aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md387
1 files changed, 387 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fa05aba
--- /dev/null
+++ b/README.md
@@ -0,0 +1,387 @@
+zc zone compiler
+================
+
+## Overview ##
+
+This is a small tool for generating DNS zones from relatively
+simple text files, with some automation to handle complex or
+repetitive tasks and to automate generation of reverse zone data.
+
+`zc` ("zone compiler") is a Python script which uses an external
+package (Bob Halley's excellent [dnspython][] toolkit) to do a lot of
+the the heavy lifting.
+
+`zc` can be used either as a straightforward command line tool or as a
+pair of `pre-receive` and `post-receive` hooks in a bare git
+repository. In the latter mode, `zc` pulls its input data and
+configuration directly from commits pushed to the master branch in the
+git repository, using another external library ([GitPython][]).
+
+Upshot of all this is that, once the git repository has been set up,
+you just clone a copy of the repository, edit flat text files with
+your favorite editor, commit and push, and you're done. Compilation
+will happen automatically when you push, any serious errors will abort
+the push so you can fix them and try again, and output will be
+installed automatically if there were no serious errors.
+
+
+## Command line use ##
+
+If you just want to use `zc` as a command line tool, it's simple.
+Usage as of this writing (subject to change, run `zc --help` for
+current syntax):
+
+ usage: zc [-h] [-o OUTPUT_DIRECTORY] [-l {debug,info,warning,error}]
+ input [input ...]
+
+ Generate zone files from a simpl(er) flat text file.
+
+ General intent here is to let users specify normal hosts in a simple
+ and compact format, with a few utilities we provide to automate
+ complex or repetitive stuff, including automatic generation of AAAA
+ RRs based on a mapping scheme from A RRs.
+
+ After generating the text of the forward zone, we run it through
+ dnspython's zone parser, then generate reverse zones by translating
+ the A and AAAA RRs in the forward zone into the corresponding PTR RRs.
+
+ positional arguments:
+ input input file
+
+ optional arguments:
+ -h, --help show this help message and exit
+ -o OUTPUT_DIRECTORY, --output-directory OUTPUT_DIRECTORY
+ directory for output files (default: .)
+ -l {debug,info,warning,error}, --log-level {debug,info,warning,error}
+ how loudly to bark (default: warning)
+
+You can supply more than one input file, `zc` will process them all
+together before writing out any of the zone files.
+
+
+## Use as git hooks ##
+
+When used as git hooks, configuration can't come from the command
+line, so it comes from two places:
+
+1. A file called `config.json` in the repository, and
+
+2. Variables set in the configuration of the bare git repository where
+ the `pre-receive` and `post-receive` hooks are installed.
+
+The general idea is that stuff which should be under control of the
+data owner is controlled by `config.json`, while stuff that should be
+controlled by the server operator is controlled by configuration
+variables in the server git repository. These are described below.
+
+The general idea is that we want to make sure that the zones compile
+correctly *before* allowing the `git push` operation to complete,
+then, assuming everything's OK, we want to install the zones *after*
+the commit completes.
+
+All the real work happens in the `pre-receive` hook, the
+`post-receive` hook's job is just to trigger final installation of the
+output zone files after `git-receive-pack` finishes accepting the
+push. If you don't understand git well enough to know what that
+means, don't worry about it. If you want to learn more, see
+[githooks][], but the learning curve necessary for the documentation
+to make any sense is a bit steep, so bring a bag lunch.
+
+`zc` determines whether it's running as one of the git hooks or not by
+examining the name by which it was invoked: if the name ends with
+`/pre-receive` or `/post-receive`, it's a hook, otherwise you get the
+command line behavior. In practice, this means that you can just
+install `zc`, symlink the correct hook names to whereever you
+installed the `zc` script, and the right thing should happen.
+
+
+### `config.json` settings ###
+
+The list of input files and the verbosity are set in the JSON file,
+while the output directory is set in the git configuration on the
+server where the bare git repository lives.
+
+ {
+ "zones": ["foo.zone", "bar.zone"],
+ "log-level": "info"
+ }
+
+The `zones` parameter is mandatory, and specifies the names of the
+input files within the git repository (at the moment we only look at
+the top-level directory -- we could change this given a reason).
+
+The `log-level` parameter is optional, and defaults to `warning`.
+
+
+### git server repository settings ###
+
+All of the following settings have defaults except for `zc.output-directory`.
+
+ git --git-dir /where/ever.git config zc.output-directory /my/output/directory
+
+The `zc.output-directory` parameter in the git repository's
+configuration file specifies the location of the directory to which
+`zc` should write its final output. `zc` also uses this directory to
+stash a FIFO which it uses to coordinate actions between the
+`pre-receive` and `post-receive` hooks.
+
+There is no default for `zc.output-directory`, you must set it.
+
+ git --git-dir /where/ever.git config zc.hook-timeout 15
+
+`zc.hook-timeout` controls how many seconds the `pre-receive` hook
+should wait for confirmation from the `post-receive` hook before
+giving up. The default value of 15 seconds should be fine unless your
+server is really slow.
+
+ git --git-dir /where/ever.git config zc.post-command 'rndc reload'
+
+`zc.post-command`, if set, specifies a command to run after all
+generated files have been installed. The default is not to run any
+such command.
+
+ git --git-dir /where/ever.git config zc.log-file /var/log/zc/zc.log
+ git --git-dir /where/ever.git config zc.log-level warning
+ git --git-dir /where/ever.git config zc.log-file-hours 24
+ git --git-dir /where/ever.git config zc.log-file-count 7
+
+When running in git hook mode, `zc` can log to both `stderr` (which
+git passes back to the user executing the push) and to a log file.
+The `zc.log-*` parameters control the log file.
+
+`zc.log-file` is the name of the log file; if not set, `zc` will not
+log to a file.
+
+`zc.log-level` is optional, and defaults to `warning`.
+
+`zc.log-file-hours` controls how many hours should elapse before `zc`
+rotates its log file. The default is 24 hours.
+
+`zc.log-file-count` controls how many old log files `zc` should keep.
+The default is 7.
+
+
+## Zone generation ##
+
+Other than `config.json`, the input files to `zc` look the same
+regardless of whether you're running `zc` on the command line or via
+git hooks.
+
+While `zc` generates both forward and reverse zones, the underlying
+mechanisms are (deliberately) very different, so it's simplest to
+consider them separately. Forward zones are driven from human-edited
+files, while reverse zones are generated completely automatically from
+the corresponding forward zones.
+
+
+### Forward zone generation ###
+
+Forward zone generation starts with a flat text file which is parsed
+line-by-line to produce a forward zone file. There three basic kinds
+of lines in this file:
+
+1. Stuff passed unchanged through `zc`: blank lines, comments, raw DNS
+ RRs (for things other than addresses), and standard control
+ operations like $TTL and $ORIGIN.
+
+2. Name-address pairs, processed to generate A and AAAA RRs.
+
+3. Control operations, all of which have names starting with "$".
+
+`zc` requires that the text file start with a `$ORIGIN` control to
+specify the name of the zone itself.
+
+Other than the above, the one bit of processing `zc` performs is
+replacement of the string "@SERIAL@ in an SOA RR with a
+seconds-since-epoch integer timestamp.
+
+
+#### Name-address pairs ####
+
+"Name-address pairs" are exactly what they sound like: something that
+a DNS zone file parser would consider a valid owner name, and and an
+IP address (IPv4 or IPv6).
+
+Processing of one name-address pair produces either one or two RRs,
+depending on whether automatic generation of IPv6 addresses from IPv4
+addresses is enabled when `zc` processes this name-address pair (see
+the `$MAP` control operation, below).
+
+Example:
+
+ $MAP yes
+
+ ; A couple of dual-stack hosts, with IPv6 addresses generated
+ ; algorithmically from the IPv4 addresses.
+
+ tweedledee 10.0.0.1
+ tweedledum 10.0.0.2
+
+ $MAP no
+
+ ; Three single-stack hosts, addresses are what you see, RR type
+ ; inferred from the address family
+
+ larry 10.0.0.3
+ moe 2002:a00::4
+ curly 10.0.0.5
+
+
+#### `$MAP` and `$MAP_RULE` ####
+
+The `$MAP` control operation enables or disables automatic generation
+of IPv6 addresses from IPv4 addresses, according to zone-specific
+mappings specified by the `$MAP_RULE` operation.
+
+`$MAP` is simple: it takes one argument, `yes` or `no` (`on`,
+`off`, `true`, and `false` are allowed as aliases).
+
+`$MAP_RULE` takes two arguments: a prefix and a format string. You
+can specify `$MAP_RULE` more than once to build up an ordered set of
+mapping rules. When mapping is enabled, a given address will be
+checked against the prefix of each rule in turn: the format string
+from the first matching rule (if any) will be used to format the
+mapped address.
+
+Format strings are in the syntax used by Python's `str.format()`
+operator; the `.format()` operator will be called with one argument,
+the input address converted to a tuple of integers, one per byte in
+the binary representation of the input address. So an input address
+of `10.0.0.44` would be yield the tuple `(10, 0, 0, 44)`, and so forth.
+
+ $MAP_RULE 10.1.3.0/24 2002:a00:0000:f{0[2]}::{0[3]}
+ $MAP_RULE 10.1.0.0/16 2002:a00:0000:{0[2]}::{0[3]}
+
+ $MAP on
+
+ larry 10.1.2.3
+ moe 10.1.3.2
+
+ $MAP off
+
+ curly 10.1.4.1
+
+This mechanism is intended primarily for mapping IPv4 addresses to
+IPv6 addresses. The mechanism itself is address-family-agnostic: in
+principal, it should work equally well in the other direction if you
+can specify a useful set of rules, but the author has not tested this.
+
+
+#### `$RANGE` ####
+
+The `$RANGE` control operation is a variation on the same general idea
+as the (BIND9-specific) `$GENERATE` control operation, but is, in the
+author's opinion, a bit easier both to use and to read. For all but
+the most esoteric uses, it takes three of four arguments:
+
+1. A format string to generate the name field of the resulting RRs.
+
+2. A start addresses (IPv4 or IPv6).
+
+3. A stop address
+
+4. An optional numeric `offset`.
+
+The basic idea here is to generate a sequence of A or AAAA RRs (type
+selected automatically to fit the addresses provided) for every
+address in the specified range, inclusive, with names generated
+according to a format string containing a numeric field.
+
+An `offset` value of zero would start with the name generated by
+applying the format string to the number zero; an `offset` value of
+one would start with the name generated by applying the format string
+to the number 1, and so forth. If the `offset` field isn't specified
+at all, it defaults to the numeric value of the least significant
+octet of the start address.
+
+Examples:
+
+ ; Access points using generate(). This is equivalent to:
+ ;
+ ; ap-101 10.0.1.101
+ ; ap-102 10.0.1.102
+ ; ...
+ ; ap-200 10.0.1.200
+
+ $RANGE ap-{:d} 10.0.1.101 10.0.1.200
+
+ ; Switches, also using $RANGE, but with numbering explicitly
+ ; specified rather than inferred from the IPv4 addressing,
+ ; equivalent to:
+ ;
+ ; sw-1 10.0.3.17
+ ; sw-2 10.0.3.18
+ ; ...
+ ; sw-26 10.0.3.42
+
+ $RANGE sw-{:d} 10.0.3.17 10.0.3.42 1
+
+ ; Finally, a whole lot of DHCP client addresses, for IPv4
+ ; addresses ranging from 10.1.0.50 to 10.2.255.254, names left as
+ ; an exercise for the reader.
+
+ $RANGE dhcp-f{:03x} 10.1.0.50 10.2.255.254 50
+
+
+#### `$INCLUDE` and `$GENERATE` ####
+
+The `$INCLUDE` and `$GENERATE` control operators are not currently implemented.
+
+`$INCLUDE` is a standard control operator, but we appear to have no
+current need for it.
+
+`$GENERATE` is a BIND-specific control operator. We could implement
+it if there were a real need, but the `$RANGE` operator covers the
+things for which we have been using `$GENERATE` in the forward zone.
+
+Our current use of `$GENERATE` in reverse zones is a source of
+consistency problems, and is therefore unsupported with prejudice.
+
+
+#### `$REVERSE_ZONE` ####
+
+The `$REVERSE_ZONE` control operation has no effect on the forward
+zone. Rather, it's a mechanism for specifying the list of reverse
+zones which should be generated from this forward zone. We include
+this in the input source for the forward zone in order to keep all the
+data describing the zone in one place.
+
+If you don't use the `$REVERSE_ZONE` control, `zc` will not generate
+any reverse data for this forward zone.
+
+Sample:
+
+ $REVERSE_ZONE 0.10.in-addr.arpa
+ $REVERSE_ZONE 1.10.in-addr.arpa
+ $REVERSE_ZONE 2.10.in-addr.arpa
+ $REVERSE_ZONE 0.0.0.0.0.0.a.0.2.0.0.2.ip6.arpa
+
+
+### Reverse zone generation ###
+
+As noted above, reverse zones are generated entirely from data
+extracted from the forward zone. This is deliberate: we are trying to
+make sure that the reverse data corresponds to the forward data, and
+giving the user an opportunity to get creative here is just asking for
+trouble.
+
+The basic strategy is:
+
+* Create a reverse zone object for every name listed in the forward
+ zone's source via `$REVERSE_ZONE` operators.
+
+* For each `A` and `AAAA` RR in the forward zone, generate the
+ corresponding `PTR` RR and and find the reverse zone in which that
+ RR belongs; whine for each PTR RR that doesn't fit into any
+ specified reverse zone.
+
+* Populate the zone apex data (SOA and apex NS RRsets) of each reverse
+ zone by copying the corresponding rdata from the forward zone. Yes,
+ this assumes that the forward and reverse zones are served by the
+ same servers; we could "fix" that given a need, but as of this
+ writing no such need exists, and this keeps it simple.
+
+
+[dnspython]: http://www.dnspython.org
+[GitPython]: https://github.com/gitpython-developers/GitPython
+[githooks]: https://git-scm.com/docs/githooks