Skip to content

Commit 53b05d6

Browse files
jaymzhfacebook-github-bot
authored andcommitted
New cookbook: fb_bind (facebook#273)
Summary: This cookbook manages Bind. It's got multiple ways of handling zonefiles to accomodate shops that generate their own outside of Chef, while providing a nice node-based API and helper functions for others. Like every other cookbook it's very opinionated on where it puts it's config, but not on the config itself. This helps with cleanup of unowned cruft. Signed-off-by: Phil Dibowitz <phil@ipom.com> Pull Request resolved: facebook#273 Differential Revision: D69810712 fbshipit-source-id: 9fd87179861eb068a4578ecfd2310065e04c3eae
1 parent def3aa7 commit 53b05d6

File tree

14 files changed

+1114
-0
lines changed

14 files changed

+1114
-0
lines changed

cookbooks/fb_bind/README.md

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
fb_bind Cookbook
2+
================
3+
4+
Requirements
5+
------------
6+
7+
Attributes
8+
----------
9+
* node['fb_bind']['config']
10+
* node['fb_bind']['default_zone_ttl']
11+
* node['fb_bind']['empty_rfc1918_zones']
12+
* node['fb_bind']['include_record_comments_in_zonefiles']
13+
* node['fb_bind']['manage_packages']
14+
* node['fb_bind']['sysconfig']
15+
* node['fb_bind']['zones']
16+
17+
Usage
18+
-----
19+
20+
This cookbook manages all aspect of your Bind9 service from config to zones.
21+
It has a variety of features and helpers to make manging your config as easy
22+
as possible. It has taken a variety of features that both the Debian and Fedora
23+
echosystems provide for best practices, but also uses the directories and setups
24+
of your distro appropriately.
25+
26+
Like all FB-API cookbooks it enforces a specific place for the configuration
27+
files, but allows you to change the contents of those files in any way.
28+
29+
This means that on Redhat-like OSes, the configuration file is not in the
30+
default place (it's in `/etc/bind/named.conf` if you use this cookbook).
31+
32+
Importantly, it allows a variety of ways to manage your zonedata including
33+
external to Chef, if necessary. See the "Zones" section below.
34+
35+
### Configuration
36+
37+
The has in `node['fb_bind']['config']` maps directly to `named.conf` syntax,
38+
with the exception that you do not defined zone data here. Do not add a `zone`
39+
or `zones` entry in this hash or the verifier will fail the run. The
40+
`named.conf` template will use the data in `node['fb_bind']['zones']` to
41+
generate the appropriate `zoone` stanzas.
42+
43+
Bind expects some configurations to be quoted and some to be note, and this
44+
cookbook does a lot of work to get this right without you having to insert
45+
quoted strings into your configuration. Generally speaking, just fill out the
46+
hash in the most obvious way and this cookbook should generate a config with
47+
the correct syntax.
48+
49+
In addition this cookbook will intelligently convert booleans to `no` and
50+
`yes`, and we recommend you use booleans for boolean configurations.
51+
52+
As an example:
53+
54+
```ruby
55+
{
56+
# move bind's working directory
57+
'directory' => '/mnt/dns_data',
58+
'auth-nxdomain' => false,
59+
'allow_update' => [ 'myacl' ];
60+
'version' => 'go away',
61+
}.each do |key, val|
62+
node.default['fb_bind']['config']['options'][key] = val
63+
end
64+
65+
{
66+
'acl myacl' => [ '1.2.3.4' ],
67+
'acl empty' => [ 'none' ],
68+
}.each do |key, val|
69+
node.default['fb_bind']['config'][key] = val
70+
end
71+
```
72+
73+
Would generate a config like:
74+
75+
```text
76+
acl myacl { 1.2.3.4; };
77+
acl empty { none; };
78+
options {
79+
directory "/mnt/dns_data";
80+
auth-nxdomain no;
81+
allow_update { myacl; }
82+
version "go away";
83+
# other default configs from this cookbook here
84+
}
85+
```
86+
87+
Note that the arguments to `directory` and `version` are properly quoted while,
88+
the arguement to `allow_update` (`myacl`) is not.
89+
90+
#### Handling IPs nicely in configs: stable_resolve helper
91+
92+
By definition, bind needs IPs in lots of places. In many cases it may be
93+
reasonable to hard-code IPs that are unlikely to change. In other cases you may
94+
have a hosting provider you need to notify, and want to base this on hostnames
95+
rather than IPs so that as their infrastructure changes, you automatically keep
96+
up, but you don't want your DNS server to go down due to a DNS issue (cascading
97+
failures are rough). To that end, this cookbook provides the `stable_resolve`
98+
helper.
99+
100+
`stable_resolve` will attempt to resolve a hostname and return all IP addresses
101+
associated with it, however if resolution fails, it will use it's cache to
102+
return the last known set of IPs for that name. Further it will never resolve
103+
the same name twice in a run no matter how often it is called. Here's an
104+
example usage:
105+
106+
```ruby
107+
{
108+
'primaries sample' => %w{
109+
axfr1.sample.com
110+
axfr2.sample.com
111+
axfr3.sample.com
112+
axfr4.sample.com
113+
axfr5.sample.com
114+
}.map { |x| FB::Bind.stable_resolve(x, node) }.flatten,
115+
}.each do |key, val|
116+
node.default['fb_bind']['config'][key] = val
117+
end
118+
119+
node.default['fb_bind']['config']['options']['also-notify'] = ['sample']
120+
```
121+
122+
### Zone data
123+
124+
There are three ways this cookbook allows you to specify zone data:
125+
126+
* Directly in the node object - This is the default and what we recommend for
127+
most cases
128+
* Via a flat file from another cookbook (i.e. `cookbook_file`) - Can be useful
129+
for transitions
130+
* Completely separately - For environments where zone data is managed by a
131+
separate process
132+
133+
In the first two methods, the zonefiles will be validated using
134+
`named-checkzone`, while if you use the third method, validating configurations
135+
is left up to whatever system is writing the files.
136+
137+
#### Zone data in the node
138+
139+
The `node['fb_bind']['zones']` hash includes the basic config for the zone and,
140+
optionally, the records themselves. Here's a simple example:
141+
142+
```ruby
143+
node.default['fb_bind']['zones']['sample.conf'] = {
144+
'type' => 'primary',
145+
'_records' => {},
146+
}
147+
```
148+
149+
The `_records` hash is a human-usable name for the record pointing to a hash
150+
for the record itself. Most record hashes have the following entries:
151+
152+
* `name` - Optional, defaults to `@`
153+
* `class` - Optional, defaults to `IN`
154+
* `type` - Type of record (`A`, `NS`, `AAAA`, etc.), will automatically be
155+
upcased for you, so make it whatever casing you want
156+
* `ttl` - Optional, no default (not included in record of ommitted)
157+
* `value` - The value of the record
158+
159+
Only SOA records deviate from this format. For SOA records, `value` is replaced
160+
by `mname`, `rname`, `serial`, `refresh`, `retry`, `expire`, and
161+
`negative-cache-ttl`, the 7 parts of an SOA record.
162+
163+
Finally, `ttl` can be specified as the `$TTL` line in the zonefile. If not
164+
specified, it will default to `node['fb_bind']['default_zone_ttl']`.
165+
166+
Here's a more complete example:
167+
168+
```ruby
169+
node.default['fb_bind']['zones']['sample.conf'] = {
170+
'type' => 'primary',
171+
'_records' => {
172+
'ttl' => 3600,
173+
'soa' => {
174+
'type' => 'SOA',
175+
'mname' => 'ns1.sample.com.',
176+
'rname' => 'hostnaster.ipom.com.',
177+
'serial' => '2025021500',
178+
'refresh' => '86400',
179+
'retry' => '43200',
180+
'expire' => '2419200',
181+
'negative-cache-ttl' => '3600',
182+
},
183+
'ns1' => {
184+
'type' => 'ns'
185+
'value' => 'ns1.sample.com',
186+
},
187+
'ns2' => {
188+
'type' => 'ns',
189+
'value' => 'ns2.sample.com',
190+
},
191+
'external ns' => {
192+
'type' => 'ns',
193+
'value' => 'ns1.sample-partner.com',
194+
},
195+
'root A' => {
196+
'type' => 'a',
197+
'value' => '192.168.1.2',
198+
},
199+
'www' => {
200+
'type' => 'a',
201+
'value' => '192.168.1.2',
202+
},
203+
'mx' => {
204+
'type' => 'mx',
205+
'ttl' => 300,
206+
'value' => '10 mail.sample.com',
207+
},
208+
},
209+
}
210+
```
211+
212+
```text
213+
$TTL 3600
214+
@ IN SOA ns1.sample.com. hostmaster.sample.com. (
215+
2025021500 ; Serial
216+
86400 ; Refresh
217+
43200 ; Retry
218+
2419200 ; Expire
219+
3600 ; Negative Cache TTL
220+
)
221+
@ IN NS ns1.sample.com
222+
@ IN NS ns2.sample.com
223+
@ IN NS ns1.sample-partner.com
224+
@ IN A 192.168.1.2
225+
www IN A 192.168.1.2
226+
@ IN MX 10 mail.sample.com
227+
```
228+
229+
If you'd like the human-readable keys to be inserted into the zonefile as
230+
comments you can set `node['fb_bind']['include_record_comments_in_zonefiles']`
231+
to `true` and it'll look like this:
232+
233+
```text
234+
...
235+
; ns1
236+
@ IN NS ns1.sample.com
237+
; ns2
238+
@ IN NS ns2.sample.com
239+
; external ns
240+
@ IN NS ns1.sample-partner.com
241+
; root A
242+
@ IN A 192.168.1.2
243+
; www
244+
www IN A 192.168.1.2
245+
; MX
246+
@ IN MX 10 mail.sample.com
247+
```
248+
249+
##### A note on TXT records
250+
251+
It's worth noting here that the handling of `TXT` records is special. You can
252+
specify a txt record (like for DKIM keys) as long as you want and this cookbook
253+
will appropriate chunk it into the proper 255-char sizes.
254+
255+
#### Zone data as a cookbookfile
256+
257+
If you prefer to manage your zones as flat files, you can specify them like
258+
so:
259+
260+
```ruby
261+
node.default['fb_bind']['zones']['sample.com'] => {
262+
'type' => 'primary',
263+
'_zonefile_cookbook' => 'mycookbook',
264+
}
265+
```
266+
267+
The files will be automatically installed to the system via `cookbook_file`.
268+
The files **must** be named `db.$ZONE` (e.g. `db.sample.com`).
269+
270+
It is important that `fb_bind` install the files rather than your own
271+
`cookbook_file`, as the files must be there early enough for the service to
272+
start.
273+
274+
#### Zone data managed by an external process
275+
276+
If you specify `_filename` in the zone configuration, `fb_bind` will create
277+
the correct `zone` stanza in `named.conf` and do nothing else. It will assume
278+
some external process is managing the zones.
279+
280+
The `_filename` argument **should be a fully-qualified path**. Here is an
281+
example in which you are using the same paths `fb_bind` does (at least on
282+
Debian-like distributions), though doing that is not necessary.
283+
284+
```ruby
285+
node.default['fb_bind']['zones']['sample.com'] => {
286+
'type' => 'primary',
287+
'_filename' => '/etc/bind/primary/db.sample.com',
288+
}
289+
```
290+
291+
If you decide to use the same paths as this cookbook, it is critical you name
292+
the zonefiles `db.$ZONE` to prevent `fb_bind` from cleaning them up.
293+
294+
You can also put them elsewhere:
295+
296+
```ruby
297+
node.default['fb_bind']['zones']['sample.com'] => {
298+
'type' => 'primary',
299+
'_filename' => '/mnt/dns_data/primary/db.sample.com',
300+
}
301+
```
302+
303+
### DNSSEC
304+
305+
DNSSEC is incredibly easily to enable. Simply set two keys on the zone:
306+
307+
```ruby
308+
node.default['fb_bind']['zones']['sample.com'] => {
309+
'type' => 'primary',
310+
'dnssec-policy' => 'default',
311+
'inline-signing' => 'yes',
312+
'_records' => { ... }
313+
}
314+
```
315+
316+
Bind will dynamically create keys and sign the zones appopropriately without
317+
you having to do anything else.
318+
319+
### Cleanup
320+
321+
This cookbook will cleanup files in its primary zonefile directory that it does
322+
not own. It is for this reason, it uses a subdirectory of the config directory
323+
called `primary`. It will intelligently handle the extra files created by
324+
DNSSEC signed zonefiles and not remove thise files for zones it knows about
325+
while also cleaning them up for zones it does not know about.
326+
327+
Optionally, you can set `node['fb_bind']['clean_config_dir']`, and `fb_bind`
328+
will cleanup stray file from your configuration directory. A few things to note
329+
here:
330+
331+
* Even though Redhat-like packages drop their config directly in `/etc`,
332+
this cookbook specifies `/etc/bind/named.conf` as the configuration file.
333+
This makes it much safer for us to cleanup the directory, when desired.
334+
* We will intelligently exclude stuff like
335+
* Our 'primary' dir
336+
* The `keys-directory`
337+
* The `rndc.key` file
338+
339+
### Forcing empty RFC1918 zones
340+
341+
If you'd like to force that RC1918 zones are created and empty, you can set
342+
`node['fb_bind']['empty_rfc1918_zones']` and will create valid empty zones for
343+
them so that they cannot be used. This is often recommended for public servers.
344+
345+
### Packages
346+
347+
This cookbook will install the relevant packages and restart the service if
348+
necessary. If you prefer to manage packages yourself, simpley set
349+
`node['fb_bind']['manage_packages']` to `false`.
350+
351+
### Sysconfig
352+
353+
You can populate the hash in `node['fb_bind']['sysconfig']` to populate
354+
variables for the Unit file. Use **lowercase** for keys, and we will upcase
355+
them on generation of the file.
356+
357+
The unit files on different OSes are different, and we try to do some basic
358+
verifiction relevant to your OS.
359+
360+
On Redhat-like OSes, we force the `NAMEDCONF` variable, which points to the
361+
Bind's config file, and we ensure no `-c` options have been added into
362+
`OPTIONS`, which would cause multiple `-c`s and fail startup.
363+
364+
### A word on terminology
365+
366+
This cookbook uses the newer terms 'primary' and 'secondary' for zone types
367+
everywhere, including 'primaries' for lists of primary hosts for secondary
368+
zones. The old keywords are supported in Bind9 as synonyms, but we default to
369+
the current names.
370+
371+
See https://bind9.readthedocs.io/en/v9.18.14/chapter3.html for details.

0 commit comments

Comments
 (0)