diff --git a/.gitignore b/.gitignore index 45b891c..cbccf1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,20 @@ -.vagrant -Berksfile.lock *~ *# .#* \#*# .*.sw[a-z] *.un~ +pkg/ + +# Berkshelf +.vagrant /cookbooks +Berksfile.lock # Bundler Gemfile.lock bin/* .bundle/* +.kitchen/ +.kitchen.local.yml diff --git a/.kitchen.yml b/.kitchen.yml new file mode 100644 index 0000000..76df55f --- /dev/null +++ b/.kitchen.yml @@ -0,0 +1,115 @@ +--- +driver: + name: vagrant + +provisioner: + name: chef_zero + +platforms: + - name: ubuntu-12.04 + - name: centos-6.4 + +suites: + - name: default + run_list: + - recipe[bind9-chroot::default] + attributes: { + 'bind9': { + 'zones': [ + { + 'domain':'example.com', + 'type':'master', + 'allow_transfer': [ + '192.168.1.2', + '192.168.1.3' + ], + 'also_notify': [ + '192.168.1.2', + '192.168.1.3' + ], + 'zone_info': { + 'serial': '00000', + 'soa': 'ns.example.com', + 'contact': 'root.example.com', + 'global_ttl': 300, + 'nameserver': [ + 'ns1.example.com', + 'ns2.example.com' + ], + 'mail_exchange': [ + { + 'host': 'ASPMX.L.GOOGLE.COM', + 'priority': 10, + } + ], + 'records': [ + { + 'name': 'www', + 'type': 'A', + 'ip': '127.0.0.1' + } + ] + } + }, + { + 'domain':'example.net', + 'type':'slave', + 'masters': [ + '192.168.1.1' + ] + } + ] + } + } + - name: chroot + run_list: + - recipe[bind9-chroot::default] + attributes: { + 'bind9': { + 'chroot_dir': '/var/chroot/named', + 'zones': [ + { + 'domain':'example.com', + 'type':'master', + 'allow_transfer': [ + '192.168.1.2', + '192.168.1.3' + ], + 'also_notify': [ + '192.168.1.2', + '192.168.1.3' + ], + 'zone_info': { + 'serial': '00000', + 'soa': 'ns.example.com', + 'contact': 'root.example.com', + 'global_ttl': 300, + 'nameserver': [ + 'ns1.example.com', + 'ns2.example.com' + ], + 'mail_exchange': [ + { + 'host': 'ASPMX.L.GOOGLE.COM', + 'priority': 10, + } + ], + 'records': [ + { + 'name': 'www', + 'type': 'A', + 'ip': '127.0.0.1' + } + ] + } + }, + { + 'domain':'example.net', + 'type':'slave', + 'masters': [ + '192.168.1.1' + ] + } + ] + } + } diff --git a/Berksfile b/Berksfile index c4bb297..935460d 100644 --- a/Berksfile +++ b/Berksfile @@ -1,3 +1,4 @@ site :opscode +#source "http://api.berkshelf.com" metadata diff --git a/Gemfile b/Gemfile index 3017623..0122ab8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,12 @@ source 'https://rubygems.org' gem 'berkshelf' +gem 'test-kitchen' +gem 'busser' +gem 'kitchen-vagrant' +gem 'foodcritic' +gem 'chefspec' +gem 'strainer' +gem 'guard' +gem 'guard-foodcritic' +gem 'guard-rspec' diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000..bd01721 --- /dev/null +++ b/Guardfile @@ -0,0 +1,16 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard :rspec do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^spec/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } +end + + +guard "foodcritic", cookbook_paths:"." do + watch(%r{attributes/.+\.rb$}) + watch(%r{providers/.+\.rb$}) + watch(%r{recipes/.+\.rb$}) + watch(%r{resources/.+\.rb$}) +end diff --git a/README.md b/README.md index 3737cd9..6a231e2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ #Description -This cookbook takes care of the installation and configuration of BIND9. You're able to define some global variables and manage your zonefiles via data bags (json example below). +This cookbook takes care of the installation and configuration of BIND9. You're able to define some global variables and manage your zonefiles via node attributes (example below). This allows zone creation to be controlled by a wrapper cookbook using any mechanism to fill the attributes such as data bags, searches, or other attributes. It also supports automatic serial number generation and automatic resource records for chef nodes (see optional json in example below) No DNSSEC, no configurable logging, no rndc shell operations or other safety checks (named-checkconf, etc.). @@ -34,106 +34,121 @@ It's so much better if you take a look at the ```attributes/default.rb``` file f #Usage -Add ```"recipe[bind9-chroot]"``` to your run list. If you want to use BIND9 for serving domains you need add the appropriate data via data bags (example below). -Please note that the data bag's structure is mandatory except: +Add ```"recipe[bind9-chroot]"``` to your run list. If you want to use BIND9 for serving domains you need to fill in the appropriate node attributes (example below). +Please note that the node attribute structure is mandatory except: * TTL for DNS records (if you decide to leave it empty, the global TTL will take over). * IP for DNS records (if not available, ```node['ipaddress']``` will be used). In order to run a a chroot'ed Bind9 server, set the ```node[:bind9][:chroot_dir]``` to the desired chroot path and optionally the ```node[:bind9][:disclose]``` attributes. -To use this cookbook with Chef Solo, add ```"recipe[chef-solo-search]"``` to your run list, and create the data bags either manually or using the ```knife-solo_data_bag``` gem. - #Examples -To create and view the data bags: - - $ knife data bag create zones - $ knife data bag create zones exampleDOTcom - $ ... do something ... - $ knife data bag from file zones exampleDOTcom.json - -An example of a data bag with mail records and specific IPs. - - { - "id": "exampleDOTcom", - "domain": "example.com", - "type": "master", - "allow_transfer": [ "8.8.4.4", - "8.8.8.8" ], - "zone_info": { - "global_ttl": 300, - "soa": "ns.example.com.", - "contact": "user.example.com.", - "nameserver": [ "ns.example.com.", - "ns.example.net." ], - "mail_exchange": [{ - "host": "ASPMX.L.GOOGLE.COM.", - "priority": 10 - },{ - "host": "ALT1.ASPMX.L.GOOGLE.COM.", - "priority": 20 - },{ - "host": "ALT2.ASPMX.L.GOOGLE.COM.", - "priority": 20 - },{ - "host": "ASPMX2.GOOGLEMAIL.COM.", - "priority": 30 - },{ - "host": "ASPMX3.GOOGLEMAIL.COM.", - "priority": 30 - },{ - "host": "ASPMX4.GOOGLEMAIL.COM.", - "priority": 30 - },{ - "host": "ASPMX5.GOOGLEMAIL.COM.", - "priority": 30 - }], - "records": [{ - "name": "www", - "type": "A", - "ip": "127.0.0.1" - },{ - "name": "img", - "ttl": 30, - "type": "A", - "ip": "127.0.0.1" - },{ - "name": "mail", - "type": "CNAME", - "ip": "ghs.google.com." - }] - } +An example of node attributes with mail records and specific IPs. + + node[:bind9][:zones] = [ + { + "domain" => "example.com", + "type" => "master", + "allow_transfer" => [ + "8.8.4.4", + "8.8.8.8" + ], + "zone_info" => { + "global_ttl" => 300, + "soa" => "ns.example.com.", + "contact" => "user.example.com.", + "nameserver" => [ + "ns.example.com.", + "ns.example.net." + ], + "mail_exchange" => [ + { + "host" => "ASPMX.L.GOOGLE.COM.", + "priority" => 10 + }, + { + "host" => "ALT1.ASPMX.L.GOOGLE.COM.", + "priority" => 20 + }, + { + "host" => "ALT2.ASPMX.L.GOOGLE.COM.", + "priority" => 20 + }, + { + "host" => "ASPMX2.GOOGLEMAIL.COM.", + "priority" => 30 + }, + { + "host" => "ASPMX3.GOOGLEMAIL.COM.", + "priority" => 30 + }, + { + "host" => "ASPMX4.GOOGLEMAIL.COM.", + "priority" => 30 + }, + { + "host" => "ASPMX5.GOOGLEMAIL.COM.", + "priority" => 30 + } + ], + "records" => [ + { + "name" => "www", + "type" => "A", + "ip" => "127.0.0.1" + }, + { + "name" => "img", + "ttl" => 30, + "type" => "A", + "ip" => "127.0.0.1" + }, + { + "name" => "mail", + "type" => "CNAME", + "ip" => "ghs.google.com." + } + ] + } + ] } -An example of a data bag with mail records and specific IPs. - - { - "id": "exampleDOTcom", - "domain": "example.com", - "type": "master", - "allow_transfer": [], - "zone_info": { - "global_ttl": 300, - "soa": "ns.example.com.", - "contact": "user.example.com.", - "nameserver": [ "ns.example.com.", - "ns.example.net." ], - "mail_exchange": [], - "records": [{ - "name": "www", - "type": "A" - },{ - "name": "img", - "ttl": 30, - "type": "A" - },{ - "name": "mail", - "type": "CNAME" - }] +An example of node attributes with mail records and specific IPs. + + node[:bind9][:zones] = [ + { + "domain" => "example.com", + "type" => "master", + "allow_transfer" => [], + "zone_info" => { + "global_ttl": 300, + "soa": "ns.example.com.", + "contact": "user.example.com.", + "nameserver": [ + "ns.example.com.", + "ns.example.net." + ], + "mail_exchange": [], + "records": [ + { + "name": "www", + "type": "A" + }, + { + "name": "img", + "ttl": 30, + "type": "A" + }, + { + "name": "mail", + "type": "CNAME" + } + ] + } } - } + ] #Contributions -This cookbook is derived from [Mike Adolphs's](https://github.com/fooforge/chef-cookbook_bind9), and specific contributions can be tracked via git. \ No newline at end of file +This cookbook is derived from [Mike Adolphs's](https://github.com/fooforge/chef-cookbook_bind9), and specific contributions can be tracked via git. diff --git a/Strainerfile b/Strainerfile new file mode 100644 index 0000000..61d536d --- /dev/null +++ b/Strainerfile @@ -0,0 +1,5 @@ +# Strainerfile +knife test: bundle exec knife cookbook test $COOKBOOK +foodcritic: bundle exec foodcritic -f any $SANDBOX/$COOKBOOK +chefspec: bundle exec rspec --color +kitchen: bundle exec kitchen test -d always diff --git a/Thorfile b/Thorfile new file mode 100644 index 0000000..b23ee16 --- /dev/null +++ b/Thorfile @@ -0,0 +1,12 @@ +# encoding: utf-8 + +require 'bundler' +require 'bundler/setup' +require 'berkshelf/thor' + +begin + require 'kitchen/thor_tasks' + Kitchen::ThorTasks.new +rescue LoadError + puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV['CI'] +end diff --git a/attributes/default.rb b/attributes/default.rb index 0fee4c3..9eca6c4 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -1,3 +1,4 @@ +default[:bind9][:zones] = [] default[:bind9][:enable_ipv6] = true # Allow all clients to query the nameserver, no recursion @@ -34,7 +35,7 @@ default[:bind9][:defaults_file] = "/etc/default/bind9" default[:bind9][:data_path] = "/var/cache/bind" default[:bind9][:zones_path] = "/etc/bind/zones" - default[:bind9][:log_file] = "/var/log/bind/bind.log" + default[:bind9][:log_file] = "/var/log/named/bind.log" default[:bind9][:user] = "bind" default[:bind9][:openssl] = "/usr/lib/x86_64-linux-gnu/openssl-1.0.0" diff --git a/chefignore b/chefignore index a6de142..138a808 100644 --- a/chefignore +++ b/chefignore @@ -69,8 +69,6 @@ Procfile # Berkshelf # ############# -Berksfile -Berksfile.lock cookbooks/* tmp diff --git a/metadata.json b/metadata.json deleted file mode 100644 index 6b62e35..0000000 --- a/metadata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "bind9-chroot", - "description": "Installs/Configures bind9 with chroot and hiding CHAOS INFORMATION", - "long_description": "#Description\n\nThis cookbook takes care of the installation and configuration of BIND9. You're able to define some global variables and manage your zonefiles via data bags (json example below).\nIt also supports automatic serial number generation and automatic resource records for chef nodes (see optional json in example below)\nNo DNSSEC, no configurable logging, no rndc shell operations or other safety checks (named-checkconf, etc.).\n\nIf you want to help feel free to contribute (either here or at [Mike Adolphs's cookbook](https://github.com/fooforge/chef-cookbook_bind9), which this is based on)!\n\n**DISCLAIMER**: \nIt works on my setup!\n\n#Requirements\n\nPlatform:\n\n* Debian\n* Ubuntu\n* Centos\n\n#Attributes\n\nIt's so much better if you take a look at the ```attributes/default.rb``` file for the full list, but this is a brief summary:\n\n* **node[:bind9][:enable_ipv6]** - Enables BIND to listen on an IPv6 address. Default is: On\n* **node[:bind9][:allow_query]** - Allow clients to query the nameserver. Default is: none\n* **node[:bind9][:allow_recursion]** - Allow recursive name resolution. Default is: none (to prevent DNS cache poisoning)\n* **node[:bind9][:allow_update]** - Allow dynamic DNS updates. Default is: none\n* **node[:bind9][:allow_transfer]** - Allow zone transfers globally. Default is: none\n* **node[:bind9][:enable_forwarding]** - Enables forwarding of requests. Default is: No forwarding\n* **node[:bind9][:forwarders]** - Array for forwarding DNS. Default is: 8.8.4.4 and 8.8.8.8 (Google DNS)\n* **node[:bind9][:chroot_dir]** - Enables running in a chroot'ed environment. Default is: no chroot'ing\n* **node[:bind9][:disclose]** - Enables disclosing CHAOS information. Default is: false\n\n\n#Usage\n\nAdd ```\"recipe[bind9-chroot]\"``` to your run list. If you want to use BIND9 for serving domains you need add the appropriate data via data bags (example below).\nPlease note that the data bag's structure is mandatory except: \n\n* TTL for DNS records (if you decide to leave it empty, the global TTL will take over).\n* IP for DNS records (if not available, ```node['ipaddress']``` will be used).\n\nIn order to run a a chroot'ed Bind9 server, set the ```node[:bind9][:chroot_dir]``` to the desired chroot path and optionally the ```node[:bind9][:disclose]``` attributes.\n\nTo use this cookbook with Chef Solo, add ```\"recipe[chef-solo-search]\"``` to your run list, and create the data bags either manually or using the ```knife-solo_data_bag``` gem.\n\n#Examples\n\nTo create and view the data bags:\n\n $ knife data bag create zones\n $ knife data bag create zones exampleDOTcom\n $ ... do something ...\n $ knife data bag from file zones exampleDOTcom.json\n\nAn example of a data bag with mail records and specific IPs.\n\n {\n \"id\": \"exampleDOTcom\",\n \"domain\": \"example.com\",\n \"type\": \"master\",\n \"allow_transfer\": [ \"8.8.4.4\",\n \"8.8.8.8\" ],\n \"zone_info\": {\n \"global_ttl\": 300,\n \"soa\": \"ns.example.com.\",\n \"contact\": \"user.example.com.\",\n \"nameserver\": [ \"ns.example.com.\",\n \"ns.example.net.\" ],\n \"mail_exchange\": [{\n \"host\": \"ASPMX.L.GOOGLE.COM.\",\n \"priority\": 10\n },{\n \"host\": \"ALT1.ASPMX.L.GOOGLE.COM.\",\n \"priority\": 20\n },{\n \"host\": \"ALT2.ASPMX.L.GOOGLE.COM.\",\n \"priority\": 20\n },{\n \"host\": \"ASPMX2.GOOGLEMAIL.COM.\",\n \"priority\": 30\n },{\n \"host\": \"ASPMX3.GOOGLEMAIL.COM.\",\n \"priority\": 30\n },{\n \"host\": \"ASPMX4.GOOGLEMAIL.COM.\",\n \"priority\": 30\n },{\n \"host\": \"ASPMX5.GOOGLEMAIL.COM.\",\n \"priority\": 30\n }],\n \"records\": [{\n \"name\": \"www\",\n \"type\": \"A\",\n \"ip\": \"127.0.0.1\"\n },{\n \"name\": \"img\",\n \"ttl\": 30,\n \"type\": \"A\",\n \"ip\": \"127.0.0.1\"\n },{\n \"name\": \"mail\",\n \"type\": \"CNAME\",\n \"ip\": \"ghs.google.com.\"\n }]\n }\n }\n\nAn example of a data bag with mail records and specific IPs.\n\n {\n \"id\": \"exampleDOTcom\",\n \"domain\": \"example.com\",\n \"type\": \"master\",\n \"allow_transfer\": [],\n \"zone_info\": {\n \"global_ttl\": 300,\n \"soa\": \"ns.example.com.\",\n \"contact\": \"user.example.com.\",\n \"nameserver\": [ \"ns.example.com.\",\n \"ns.example.net.\" ],\n \"mail_exchange\": [],\n \"records\": [{\n \"name\": \"www\",\n \"type\": \"A\"\n },{\n \"name\": \"img\",\n \"ttl\": 30,\n \"type\": \"A\"\n },{\n \"name\": \"mail\",\n \"type\": \"CNAME\"\n }]\n }\n }\n \n#Contributions\n\nThis cookbook is derived from [Mike Adolphs's](https://github.com/fooforge/chef-cookbook_bind9), and specific contributions can be tracked via git.", - "maintainer": "Tnarik Innael", - "maintainer_email": "tnarik@lecafeautomatique.co.uk", - "license": "Apache 2.0", - "platforms": { - "ubuntu": ">= 0.0.0", - "debian": ">= 0.0.0", - "centos": ">= 0.0.0" - }, - "dependencies": { - }, - "recommendations": { - }, - "suggestions": { - }, - "conflicting": { - }, - "providing": { - }, - "replacing": { - }, - "attributes": { - }, - "groupings": { - }, - "recipes": { - }, - "version": "0.4.1" -} diff --git a/metadata.rb b/metadata.rb index e640113..74e5a2d 100644 --- a/metadata.rb +++ b/metadata.rb @@ -11,5 +11,5 @@ end %w{resolvconf}.each do |cookbook| - depends(cookbook) + depends cookbook end diff --git a/recipes/chroot.rb b/recipes/chroot.rb index a3df796..1d99d2c 100644 --- a/recipes/chroot.rb +++ b/recipes/chroot.rb @@ -17,34 +17,50 @@ # case node[:platform] - when "ubuntu" - if node[:platform_version].to_f >= 12.04 - ruby_block "copy_openssl_dependencies" do - block do - FileUtils.mkdir_p File.dirname(File.join(node[:bind9][:chroot_dir], node[:bind9][:openssl])) - FileUtils.cp_r node[:bind9][:openssl], File.dirname(File.join(node[:bind9][:chroot_dir], node[:bind9][:openssl])) - end - not_if { ::File.directory?(File.join(node[:bind9][:chroot_dir], node[:bind9][:openssl])) or - !::File.directory?(node[:bind9][:openssl]) } +when "ubuntu" + if node[:platform_version].to_f >= 12.04 + ruby_block "copy_openssl_dependencies" do + block do + FileUtils.mkdir_p File.dirname(File.join(node[:bind9][:chroot_dir], node[:bind9][:openssl])) + FileUtils.cp_r node[:bind9][:openssl], File.dirname(File.join(node[:bind9][:chroot_dir], node[:bind9][:openssl])) end + not_if { ::File.directory?(File.join(node[:bind9][:chroot_dir], node[:bind9][:openssl])) or + !::File.directory?(node[:bind9][:openssl]) } end + end + + ruby_block "modify_init_script" do + block do + rc = Chef::Util::FileEdit.new("/etc/init.d/bind9") + rc.search_file_replace(Regexp.new("/var/run/named"), "${PIDDIR}") + rc.write_file + end + not_if { ::File.readlines('/etc/init.d/bind9').grep(Regexp.new("/var/run/named")).empty? } + end + + service 'apparmor' do + supports :status => true, :restart => true, :reload => true + action [:enable] + only_if { ::File.exists?("/etc/init.d/apparmor") } + end + + template "/etc/apparmor.d/local/usr.sbin.named" do + source "local.usr.sbin.named.erb" + owner 'root' + group 'root' + mode '0644' + notifies :restart, "service[apparmor]", :immediately + only_if { ::File.exists?("/etc/apparmor.d/local/usr.sbin.named") } + end + end -directory File.join(node[:bind9][:chroot_dir].to_s, "/var/run/named") do +directory "#{node[:bind9][:chroot_dir].to_s}/var/run/named" do owner node[:bind9][:user] group node[:bind9][:user] mode 0744 recursive true - not_if { ::File.directory?(File.join(node[:bind9][:chroot_dir].to_s, "/var/run/named")) } -end - -ruby_block "modify_init_script" do - block do - rc = Chef::Util::FileEdit.new("/etc/init.d/bind9") - rc.search_file_replace(Regexp.new("/var/run/named"), "${PIDDIR}") - rc.write_file - end - not_if { ::File.readlines('/etc/init.d/bind9').grep(Regexp.new("/var/run/named")).empty? } + not_if { ::File.directory?("#{node[:bind9][:chroot_dir].to_s}/var/run/named") } end chroot_config_dir = File.join(node[:bind9][:chroot_dir].to_s, node[:bind9][:config_path]) @@ -64,13 +80,6 @@ not_if { ::File.symlink?(node[:bind9][:config_path]) } end -directory chroot_config_dir do - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0744 - recursive true -end - link "bind_config_from_chroot" do target_file node[:bind9][:config_path] to chroot_config_dir @@ -79,21 +88,13 @@ chroot_zones_dir = File.join(node[:bind9][:chroot_dir].to_s, node[:bind9][:zones_path]) -directory chroot_zones_dir do - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0744 - recursive true - not_if { chroot_zones_dir.start_with?(chroot_config_dir) } -end - link "bind_zones_from_chroot" do target_file node[:bind9][:zones_path] to chroot_zones_dir not_if { ::File.symlink?(node[:bind9][:zones_path]) or chroot_zones_dir.start_with?(chroot_config_dir) } end -directory File.join(node[:bind9][:chroot_dir].to_s, "/dev") do +directory "#{node[:bind9][:chroot_dir].to_s}/dev" do owner node[:bind9][:user] group node[:bind9][:user] mode 0744 diff --git a/recipes/default.rb b/recipes/default.rb index c617d41..3acf1a7 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -17,150 +17,4 @@ # limitations under the License. # -package "bind9" do - case node[:platform] - when "centos", "redhat", "suse", "fedora" - package_name "bind" - end - action :install -end - -service "bind9" do - case node[:platform] - when "centos", "redhat" - service_name "named" - end - supports :status => true, :reload => true, :restart => true - action [ :enable ] -end - -directory File.join(node[:bind9][:chroot_dir].to_s, node[:bind9][:data_path]) do - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0755 - recursive true -end - -directory File.dirname(File.join(node[:bind9][:chroot_dir].to_s, node[:bind9][:log_file])) do - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0755 - recursive true -end - -directory File.join(node[:bind9][:chroot_dir].to_s, node[:bind9][:zones_path]) do - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0744 - recursive true -end - -if !node[:bind9][:chroot_dir].nil? - include_recipe "bind9-chroot::chroot" -end - -class Chef::Recipe::NameServer - include LeCafeAutomatique::Bind9::NameServer -end - -if node[:bind9][:resolvconf] - include_recipe "resolvconf" - # file "/etc/resolvconf/resolv.conf.d/tail" do - # content NameServer.nameserver_proxy("/etc/resolv.conf", /nameserver.*/) - # only_if { !::File.exists?("/etc/resolvconf/resolv.conf.d/tail") } - # end -end - - -template File.join(node[:bind9][:config_path], node[:bind9][:options_file]) do - source "named.conf.options.erb" - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0644 - notifies :restart, "service[bind9]" -end - -template File.join(node[:bind9][:config_path], node[:bind9][:config_file]) do - source "named.conf.erb" - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0644 - notifies :restart, "service[bind9]" -end - -template File.join(node[:bind9][:config_path], node[:bind9][:local_file]) do - source "named.conf.local.erb" - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0644 - variables({ - :zonefiles => search(:zones) - }) - notifies :restart, "service[bind9]" -end - - -template node[:bind9][:defaults_file] do - source "bind9.erb" - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0644 - notifies :restart, "service[bind9]" - not_if { node[:bind9][:defaults_file].nil? } -end - - -directory node[:bind9][:zones_path] do - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0744 - recursive true - not_if { ::File.directory?(node[:bind9][:zones_path]) or ::File.symlink?(node[:bind9][:zones_path]) } -end - -search(:zones).each do |zone| - #unless zone['autodomain'].nil? || zone['autodomain'] == '' - # search(:node, "domain:#{zone['autodomain']}").each do |host| - # next if host['ipaddress'] == '' || host['ipaddress'].nil? - # zone['zone_info']['records'].push( { - # "name" => host['hostname'], - # "type" => "A", - # "ip" => host['ipaddress'] - # }) - # end - #end - - template File.join(node[:bind9][:zones_path], zone['domain']) do - source File.join(node[:bind9][:zones_path], "#{zone['domain']}.erb") - local true - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0644 - notifies :restart, "service[bind9]" - variables({ - :serial => zone['zone_info']['serial'] || Time.new.strftime("%Y%m%d%H%M%S") - }) - action :nothing - end - - template File.join(node[:bind9][:zones_path], "#{zone['domain']}.erb") do - source "zonefile.erb" - owner node[:bind9][:user] - group node[:bind9][:user] - mode 0644 - variables({ - :domain => zone['domain'], - :soa => zone['zone_info']['soa'], - :contact => zone['zone_info']['contact'], - :global_ttl => zone['zone_info']['global_ttl'], - :nameserver => zone['zone_info']['nameserver'], - :mail_exchange => zone['zone_info']['mail_exchange'], - :records => zone['zone_info']['records'] - }) - notifies :create, resources(:template => File.join(node[:bind9][:zones_path], zone['domain'])), :immediately - end -end - -service "bind9" do - action [ :start ] -end +include_recipe 'bind9-chroot::server' diff --git a/recipes/server.rb b/recipes/server.rb new file mode 100644 index 0000000..4edc796 --- /dev/null +++ b/recipes/server.rb @@ -0,0 +1,152 @@ +# Cookbook Name:: bind9-chroot +# Recipe:: server +# +# Copyright 2011, Mike Adolphs +# Copyright 2013, tnarik +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package "bind9" do + case node[:platform] + when "centos", "redhat", "suse", "fedora" + package_name "bind" + end + action :install +end + +service "bind9" do + case node[:platform] + when "centos", "redhat" + service_name "named" + end + supports :status => true, :reload => true, :restart => true + action [ :enable ] +end + +directory File.join(node[:bind9][:chroot_dir].to_s, node[:bind9][:data_path]) do + owner node[:bind9][:user] + group node[:bind9][:user] + mode 0755 + recursive true +end + +directory File.dirname(File.join(node[:bind9][:chroot_dir].to_s, node[:bind9][:log_file])) do + owner node[:bind9][:user] + group node[:bind9][:user] + mode 0755 + recursive true +end + +directory File.join(node[:bind9][:chroot_dir].to_s, node[:bind9][:zones_path]) do + owner node[:bind9][:user] + group node[:bind9][:user] + mode 0744 + recursive true +end + +if !node[:bind9][:chroot_dir].nil? + include_recipe "bind9-chroot::chroot" +end + +class Chef::Recipe::NameServer + include LeCafeAutomatique::Bind9::NameServer +end + +if node[:bind9][:resolvconf] + include_recipe "resolvconf" + # file "/etc/resolvconf/resolv.conf.d/tail" do + # content NameServer.nameserver_proxy("/etc/resolv.conf", /nameserver.*/) + # only_if { !::File.exists?("/etc/resolvconf/resolv.conf.d/tail") } + # end +end + +template File.join(node[:bind9][:config_path], node[:bind9][:options_file]) do + source "named.conf.options.erb" + owner node[:bind9][:user] + group node[:bind9][:user] + mode 0644 + notifies :restart, "service[bind9]" +end + +template File.join(node[:bind9][:config_path], node[:bind9][:config_file]) do + source "named.conf.erb" + owner node[:bind9][:user] + group node[:bind9][:user] + mode 0644 + notifies :restart, "service[bind9]" +end + +template File.join(node[:bind9][:config_path], node[:bind9][:local_file]) do + source "named.conf.local.erb" + owner node[:bind9][:user] + group node[:bind9][:user] + mode 0644 + variables( + :zonefiles => node[:bind9][:zones] + ) + notifies :restart, "service[bind9]" +end + +template 'defaults_file' do + path node[:bind9][:defaults_file] + source "bind9.erb" + owner node[:bind9][:user] + group node[:bind9][:user] + mode 0644 + notifies :restart, "service[bind9]" + not_if { node[:bind9][:defaults_file].nil? } +end + +directory 'recreate zones path' do + path node[:bind9][:zones_path] + owner node[:bind9][:user] + group node[:bind9][:user] + mode 0744 + recursive true + not_if { ::File.directory?(node[:bind9][:zones_path]) or ::File.symlink?(node[:bind9][:zones_path]) } +end + +node[:bind9][:zones].each do |zone| + + if zone[:type] == "master" and zone[:zone_info].is_a?(Hash) + template File.join(node[:bind9][:zones_path], zone[:domain]) do + source File.join(node[:bind9][:zones_path], "#{zone[:domain]}.erb") + local true + owner node[:bind9][:user] + group node[:bind9][:user] + mode 0644 + notifies :restart, "service[bind9]" + variables( + :serial => zone[:zone_info][:serial] || Time.new.strftime("%Y%m%d%H%M%S") + ) + action :nothing + end + + template File.join(node[:bind9][:zones_path], "#{zone[:domain]}.erb") do + source "zonefile.erb" + owner node[:bind9][:user] + group node[:bind9][:user] + mode 0644 + variables( + :soa => zone[:zone_info][:soa], + :contact => zone[:zone_info][:contact], + :global_ttl => zone[:zone_info][:global_ttl], + :nameserver => zone[:zone_info][:nameserver], + :mail_exchange => zone[:zone_info][:mail_exchange], + :records => zone[:zone_info][:records] + ) + notifies :create, "template[#{node[:bind9][:zones_path]}/#{zone[:domain]}]", :immediately + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..113437f --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,60 @@ +# spec_helper.rb +require 'chefspec' +require 'chefspec/berkshelf' + +Zones = [ + { + 'domain' => 'example.com', + 'type' => 'master', + 'allow_transfer' => [ + '192.168.1.2', + '192.168.1.3' + ], + 'also_notify' => [ + '192.168.1.2', + '192.168.1.3' + ], + 'zone_info' => { + 'serial' =>'00000', + 'soa' => 'ns.example.com', + 'contact' => 'root.example.com', + 'global_ttl' => 300, + 'nameserver' => [ + 'ns1.example.com', + 'ns2.example.com' + ], + 'mail_exchange' => [ + { + 'host' => 'ASPMX.L.GOOGLE.COM', + 'priority' => 10, + } + ], + 'records' => [ + { + 'name' => 'www', + 'type' => 'A', + 'ip' => '127.0.0.1' + } + ] + } + }, + { + 'domain' => 'example.net', + 'type' => 'slave', + 'masters' => [ + '192.168.1.1' + ] + } + ] + +RSpec.configure do |config| + config.color_enabled = true + config.tty = true + config.formatter = :documentation + config.treat_symbols_as_metadata_keys_with_true_values = true + config.filter_run :focus => true + config.run_all_when_everything_filtered = true + config.expect_with :rspec do |c| + c.syntax = :expect + end +end diff --git a/spec/unit/recipes_spec/chroot_spec.rb b/spec/unit/recipes_spec/chroot_spec.rb new file mode 100644 index 0000000..2316a96 --- /dev/null +++ b/spec/unit/recipes_spec/chroot_spec.rb @@ -0,0 +1,146 @@ +require 'spec_helper' + +ChefSpec::Coverage.start! + +describe 'bind9-chroot::chroot' do + platforms = { + 'ubuntu' => { + 'versions' => ['12.04'], + 'user' => 'bind', + 'group' => 'bind', + 'config_dir' => '/etc/bind', + 'zones_dir' => '/etc/bind/zones' + } + } + platforms.each do |platform,vals| + vals['versions'].each do |version| + context "On #{platform} #{version}" do + let(:chef_run) do + ChefSpec::Runner.new(:platform=>platform,:version=>version) do |node| + node.set[:bind9][:chroot_dir] = '/var/chroot/named' + end.converge(described_recipe) + end + + before(:each) do + File.stub(:readlines).with(anything).and_call_original + File.stub(:readlines).with('/etc/init.d/bind9').and_return(['/var/run/named']) + File.stub(:directory?).with(anything).and_call_original + File.stub(:directory?).with('/var/chroot/named/usr/lib/x86_64-linux-gnu/openssl-1.0.0').and_return(true) + end + + if platform == 'ubuntu' + it 'does not run ruby_block copy_openssl_dependencies when /var/chroot/named/usr/lib/x86_64-linux-gnu/openssl-1.0.0 exists' do + expect(chef_run).to_not run_ruby_block('copy_openssl_dependencies') + end + + it 'does not run ruby_block copy_openssl_dependencies when /usr/lib/x86_64-linux-gnu/openssl-1.0.0 does not exist' do + File.stub(:directory?).with('/usr/lib/x86_64-linux-gnu/openssl-1.0.0').and_return(false) + expect(chef_run).to_not run_ruby_block('copy_openssl_dependencies') + end + + it 'runs ruby_block copy_openssl_dependencies' do + File.stub(:directory?).with('/var/chroot/named/usr/lib/x86_64-linux-gnu/openssl-1.0.0').and_return(false) + expect(chef_run).to run_ruby_block('copy_openssl_dependencies') + end + + it 'runs ruby_block modify_init_script' do + expect(chef_run).to run_ruby_block('modify_init_script') + end + + it 'does not run ruby_block modify_init_script' do + File.stub(:readlines).with(anything).and_call_original + File.stub(:readlines).with('/etc/init.d/bind9').and_return(['']) + expect(chef_run).to_not run_ruby_block('modify_init_script') + end + + it 'enables apparmor service' do + expect(chef_run).to enable_service('apparmor') + end + + it 'creates /etc/apparmor.d/local/usr.sbin.named' do + File.stub(:exists?).with(anything).and_call_original + File.stub(:exists?).with('/etc/apparmor.d/local/usr.sbin.named').and_return(true) + expect(chef_run).to create_template('/etc/apparmor.d/local/usr.sbin.named').with( + user: 'root', + group: 'root', + mode: '0644' + ) + end + + it 'does not create /etc/apparmor.d/local/usr.sbin.named' do + File.stub(:exists?).with(anything).and_call_original + File.stub(:exists?).with('/etc/apparmor.d/local/usr.sbin.named').and_return(false) + expect(chef_run).to_not create_template('/etc/apparmor.d/local/usr.sbin.named') + end + + it '/etc/apparmor.d/local/usr.sbin.named restarts apparmor service' do + expect(chef_run.template('/etc/apparmor.d/local/usr.sbin.named')).to notify('service[apparmor]').immediately + end + end + + it 'creates directory /var/chroot/named/var/run/named' do + expect(chef_run).to create_directory('/var/chroot/named/var/run/named').with( + user: vals['user'], + group: vals['group'], + mode: 0744, + recursive: true + ) + end + + it "creates directory /var/chroot/named#{vals['config_dir']}" do + expect(chef_run).to create_directory("/var/chroot/named#{vals['config_dir']}").with( + user: 'bind', + group: 'bind', + mode: 0744, + recursive: true + ) + end + + it 'runs ruby_block move_config_to_chroot' do + File.stub(:symlink?).with(anything).and_call_original + File.stub(:symlink?).with(vals['config_dir']).and_return(false) + expect(chef_run).to run_ruby_block('move_config_to_chroot') + end + + it 'does not run ruby_block move_config_to_chroot' do + File.stub(:symlink?).with(anything).and_call_original + File.stub(:symlink?).with(vals['config_dir']).and_return(true) + expect(chef_run).to_not run_ruby_block('move_config_to_chroot') + end + + it 'links bind config from chroot' do + expect(chef_run).to create_link(vals['config_dir']).with( + to: "/var/chroot/named#{vals['config_dir']}" + ) + end + + it 'does not link bind zones from chroot' do + expect(chef_run).to_not create_link(vals['zones_dir']).with( + to: "/var/chroot/named#{vals['zones_dir']}" + ) + end + + it 'creates directory /var/chroot/named/dev' do + expect(chef_run).to create_directory('/var/chroot/named/dev').with( + user: vals['user'], + group: vals['group'], + mode: 0744, + recursive: true + ) + end + + it 'creates special device files' do + expect(chef_run).to run_execute('create_special_device_files') + end + + it 'does not create special device files' do + File.stub(:exists?).with(anything).and_call_original + File.stub(:exists?).with('/var/chroot/named/dev/null').and_return(true) + expect(chef_run).to_not run_execute('create_special_device_files') + end + + end + end + end +end + diff --git a/spec/unit/recipes_spec/server_spec.rb b/spec/unit/recipes_spec/server_spec.rb new file mode 100644 index 0000000..a4b9a93 --- /dev/null +++ b/spec/unit/recipes_spec/server_spec.rb @@ -0,0 +1,393 @@ +require 'spec_helper' + +ChefSpec::Coverage.start! do + add_filter '*/bind9-chroot/recipes/chroot.rb' +end + +describe 'bind9-chroot::server' do + platforms = { + 'ubuntu' => { + 'versions' => ['12.04'], + 'package' => 'bind9', + 'service' => 'bind9', + 'user' => 'bind', + 'group' => 'bind', + 'data_dir' => '/var/cache/bind', + 'log_dir' => '/var/log/named', + 'zones_dir' => '/etc/bind/zones', + 'named_options' => '/etc/bind/named.conf.options', + 'named_conf' => '/etc/bind/named.conf', + 'named_local' => '/etc/bind/named.conf.local' + + }, + 'centos' => { + 'versions' => ['6.5'], + 'package' => 'bind', + 'service' => 'named', + 'user' => 'named', + 'group' => 'named', + 'data_dir' => '/var/named', + 'log_dir' => '/var/log/named', + 'zones_dir' => '/var/named/zones', + 'named_options' => '/etc/named/named.conf.options', + 'named_conf' => '/etc/named/named.conf', + 'named_local' => '/etc/named/named.conf.local' + + } + } + let(:zones) { [ + { + 'domain' => 'example.com', + 'type' => 'master', + 'allow_transfer' => [ + '192.168.1.2', + '192.168.1.3' + ], + 'also_notify' => [ + '192.168.1.2', + '192.168.1.3' + ], + 'zone_info' => { + 'serial' =>'00000', + 'soa' => 'ns.example.com', + 'contact' => 'root.example.com', + 'global_ttl' => 300, + 'nameserver' => [ + 'ns1.example.com', + 'ns2.example.com' + ], + 'mail_exchange' => [ + { + 'host' => 'ASPMX.L.GOOGLE.COM', + 'priority' => 10, + } + ], + 'records' => [ + { + 'name' => 'www', + 'type' => 'A', + 'ip' => '127.0.0.1' + } + ] + } + }, + { + 'domain' => 'example.net', + 'type' => 'slave', + 'masters' => [ + '192.168.1.1' + ] + } + ] + } + let(:zone_file) { '$TTL 300 +@ IN SOA ns.example.com. root.example.com. ( + <%= @serial %> ; serial [yyyyMMddNN] + 4H ; refresh + 30M ; retry + 1W ; expiry + 1D ; minimum +) + + IN NS ns.example.com. + IN NS ns1.example.com. + IN NS ns2.example.com. + + IN MX 10 ASPMX.L.GOOGLE.COM. + +www IN A 127.0.0.1 +' + } + + platforms.each do |platform,vals| + vals['versions'].each do |version| + context "On #{platform} #{version}" do + let(:chef_run) do + ChefSpec::Runner.new(:platform=>platform,:version=>version) do |node| + node.set[:bind9][:zones] = zones + end.converge(described_recipe) + end + let(:node) { chef_run } + + let(:named_conf_local) { "// +// Do any local configuration here +// + +// Consider adding the 1918 zones here, if they are not used in your +// organization +//include \"/etc/bind/zones.rfc1918\"; + +zone \"example.com\" { + type master; + file \"#{vals['zones_dir']}/example.com\"; + allow-transfer { + 192.168.1.2; + 192.168.1.3; + }; + also-notify { + 192.168.1.2; + 192.168.1.3; + }; +}; + +zone \"example.net\" { + type slave; + file \"example.net\"; + masters { + 192.168.1.1; + }; +};" + } + + before(:each) do + File.stub(:readlines).with(anything).and_call_original + File.stub(:readlines).with('/etc/init.d/bind9').and_return(['/var/run/named']) + end + + it "installs #{vals['package']}" do + expect(chef_run).to install_package(vals['package']) + end + + it "enables #{vals['service']} service" do + expect(chef_run).to enable_service(vals['service']) + end + + it "creates directory #{vals['data_dir']} owned by #{vals['user']} user" do + expect(chef_run).to create_directory(vals['data_dir']).with( + user: vals['user'], + group: vals['group'], + mode: 0755, + recursive: true + ) + end + + it "creates directory /var/chroot/named#{vals['data_dir']} owned by #{vals['user']} user" do + chef_run.node.set[:bind9][:chroot_dir] = '/var/chroot/named' + chef_run.converge(described_recipe) + expect(chef_run).to create_directory("/var/chroot/named#{vals['data_dir']}").with( + user: vals['user'], + group: vals['group'], + mode: 0755, + recursive: true + ) + end + + it "creates directory #{vals['log_dir']} owned by #{vals['user']} user" do + expect(chef_run).to create_directory(vals['log_dir']).with( + user: vals['user'], + group: vals['group'], + mode: 0755, + recursive: true + ) + end + + it "creates directory /var/chroot/named#{vals['log_dir']} owned by #{vals['user']} user" do + chef_run.node.set[:bind9][:chroot_dir] = '/var/chroot/named' + chef_run.converge(described_recipe) + expect(chef_run).to create_directory("/var/chroot/named#{vals['log_dir']}").with( + user: vals['user'], + group: vals['group'], + mode: 0755, + recursive: true + ) + end + + it "creates directory #{vals['zones_dir']} owned by #{vals['user']} user" do + expect(chef_run).to create_directory(vals['zones_dir']).with( + user: vals['user'], + group: vals['group'], + mode: 0744, + recursive: true + ) + end + + it "creates directory /var/chroot/named#{vals['zones_dir']} owned by #{vals['user']} user" do + chef_run.node.set[:bind9][:chroot_dir] = '/var/chroot/named' + chef_run.converge(described_recipe) + expect(chef_run).to create_directory("/var/chroot/named#{vals['zones_dir']}").with( + user: vals['user'], + group: vals['group'], + mode: 0744, + recursive: true + ) + end + + it 'does not include bind9-chroot::chroot recipe' do + expect(chef_run).to_not include_recipe('bind9-chroot::chroot') + end + + it 'includes bind9-chroot::chroot recipe' do + chef_run.node.set[:bind9][:chroot_dir] = '/var/chroot/named' + chef_run.converge(described_recipe) + expect(chef_run).to include_recipe('bind9-chroot::chroot') + end + + it 'does not include resolvconf recipe' do + expect(chef_run).to_not include_recipe('resolvconf') + end + + it 'includes resolvconf recipe' do + chef_run.node.set[:bind9][:resolvconf] = true + chef_run.converge(described_recipe) + expect(chef_run).to include_recipe('resolvconf') + end + + it "creates template #{vals['named_options']}" do + expect(chef_run).to create_template(vals['named_options']).with( + user: vals['user'], + group: vals['group'], + mode: 0644, + ) + end + + it "creates template #{vals['named_conf']}" do + expect(chef_run).to create_template(vals['named_conf']).with( + user: vals['user'], + group: vals['group'], + mode: 0644, + ) + end + + it "#{vals['named_conf']} notifies #{vals['service']} to restart" do + expect(chef_run.template(vals['named_conf'])).to notify("service[#{vals['service']}]").to(:restart) + end + + it "creates #{vals['named_local']}" do + expect(chef_run).to create_template(vals['named_local']).with( + user: vals['user'], + group: vals['group'], + mode: 0644, + variables: { :zonefiles => Zones } + ) + end + + it "fills #{vals['named_local']} with correct content" do + expect(chef_run).to render_file(vals['named_local']).with_content(named_conf_local) + end + + it "#{vals['named_local']} notifies bind9 to restart" do + expect(chef_run.template(vals['named_local'])).to notify("service[#{vals['service']}]").to(:restart) + end + + it 'creates /etc/default/bind9' do + if chef_run.node[:bind9][:defaults_file] + expect(chef_run).to create_template('defaults_file').with( + user: vals['user'], + group: vals['group'], + mode: 0644, + ) + end + end + + it '/etc/default/bind9 notifies bind9 to restart' do + expect(chef_run.template('defaults_file')).to notify("service[#{vals['service']}]").to(:restart) + end + + it 'does not create /etc/default/bind9' do + if chef_run.node[:bind9][:defaults_file].nil? + expect(chef_run).to_not create_template('defaults_file').with( + user: vals['user'], + group: vals['group'], + mode: 0644, + ) + end + end + + it "recreates directory #{vals['data_dir']} owned by #{vals['user']} user" do + File.stub(:directory?).with(anything).and_call_original + File.stub(:directory?).with(vals['zones_dir']).and_return(false) + File.stub(:symlink?).with(anything).and_call_original + File.stub(:symlink?).with(vals['zones_dir']).and_return(false) + expect(chef_run).to create_directory('recreate zones path').with( + user: vals['user'], + group: vals['group'], + mode: 0744, + recursive: true + ) + end + + it "does not recreate directory #{vals['data_dir']} owned by #{vals['user']} user" do + File.stub(:directory?).with(anything).and_call_original + File.stub(:directory?).with(vals['zones_dir']).and_return(true) + File.stub(:symlink?).with(anything).and_call_original + File.stub(:symlink?).with(vals['zones_dir']).and_return(true) + expect(chef_run).to_not create_directory('recreate zones path').with( + user: vals['user'], + group: vals['group'], + mode: 0744, + recursive: true + ) + end + + it "does not create #{vals['zones_dir']}/example.com" do + expect(chef_run).to_not create_template("#{vals['zones_dir']}/example.com").with( + source: "#{vals['zones_dir']}/example.com.erb", + local: true, + user: vals['user'], + group: vals['group'], + mode: 0644, + variables: { :serial => '00000' } + ) + end + + it "does not create /var/chroot/named#{vals['zones_dir']}/example.com" do + chef_run.node.set[:bind9][:chroot_dir] = '/var/chroot/named' + chef_run.converge(described_recipe) + expect(chef_run).to_not create_template("/var/chroot/named#{vals['zones_dir']}/example.com").with( + source: "/var/chroot/named#{vals['zones_dir']}/example.com.erb", + local: true, + user: vals['user'], + group: vals['group'], + mode: 0644, + variables: { :serial => '00000' } + ) + end + + it "creates #{vals['zones_dir']}/example.com.erb" do + expect(chef_run).to create_template("#{vals['zones_dir']}/example.com.erb").with( + source: 'zonefile.erb', + user: vals['user'], + group: vals['group'], + mode: 0644, + variables: { + :soa => 'ns.example.com', + :contact => 'root.example.com', + :global_ttl => 300, + :nameserver => [ + 'ns1.example.com', + 'ns2.example.com' + ], + :mail_exchange => [ + { + 'host' => 'ASPMX.L.GOOGLE.COM', + 'priority' => 10 + } + ], + :records => [ + { + 'name' => 'www', + 'type' => 'A', + 'ip' => '127.0.0.1' + } + ] + } + ) + end + + + it "fills #{vals['zones_dir']}/example.com.erb with correct content" do + expect(chef_run).to render_file("#{vals['zones_dir']}/example.com.erb").with_content(zone_file) + end + + it "notifies #{vals['zones_dir']}/example.com immediately" do + expect(chef_run.template("#{vals['zones_dir']}/example.com.erb")).to notify("template[#{vals['zones_dir']}/example.com]").to(:create).immediately + end + + it "does not create #{vals['zones_dir']}/example.net.erb" do + expect(chef_run).to_not create_template("#{vals['zones_dir']}/example.net.erb") + end + + end + end + end +end diff --git a/templates/default/local.usr.sbin.named.erb b/templates/default/local.usr.sbin.named.erb new file mode 100644 index 0000000..f9856bb --- /dev/null +++ b/templates/default/local.usr.sbin.named.erb @@ -0,0 +1,16 @@ +# Site-specific additions and overrides for usr.sbin.named. +# For more details, please see /etc/apparmor.d/local/README. + +# named chroot +/var/chroot/named/** r, +/var/chroot/named/etc/bind/** r, +/var/chroot/named/var/lib/bind/ rw, +/var/chroot/named/var/lib/bind/** rw, +/var/chroot/named/var/cache/bind/ rw, +/var/chroot/named/var/cache/bind/** rw, +/var/chroot/named/var/log/named/** rw, + +/var/chroot/named/var/run/named/named.pid w, +/var/chroot/named/var/run/named/session.key w, + +/var/chroot/named/usr/lib/x86_64-linux-gnu/openssl-1.0.0/engines/libgost.so rm, diff --git a/templates/default/named.conf.local.erb b/templates/default/named.conf.local.erb index 082faa0..2b06f98 100644 --- a/templates/default/named.conf.local.erb +++ b/templates/default/named.conf.local.erb @@ -7,14 +7,34 @@ //include "/etc/bind/zones.rfc1918"; <% @zonefiles.each do |conf| -%> -zone "<%= conf["domain"] %>" IN { - type <%= conf["type"] %>; - file "<%= node[:bind9][:zones_path] %>/<%= conf["domain"] %>"; + <% if conf['type'] %> +zone "<%= conf['domain'] %>" { + type <%= conf['type'] %>; + <% if conf['type'] == "master" %> + file "<%= node[:bind9][:chroot_dir] %><%= node[:bind9][:zones_path] %>/<%= conf["domain"] %>"; + <% if conf['allow_transfer'].is_a?(Array) %> allow-transfer { - <% conf["allow_transfer"].each do |ip| -%> + <% conf['allow_transfer'].each do |ip| -%> <%= ip %>; - <% end %> + <% end %> + }; + <% end %> + <% if conf['also_notify'].is_a?(Array) -%> + also-notify { + <% conf['also_notify'].each do |ip| -%> + <%= ip %>; + <% end %> }; + <% end %> + <% elsif conf['type'] == "slave" -%> + file "<%= conf["domain"] %>"; + masters { + <% conf['masters'].each do |ip| -%> + <%= ip %>; + <% end %> + }; + <% end %> }; + <% end %> <% end %> diff --git a/templates/default/zonefile.erb b/templates/default/zonefile.erb index 511c071..1f6469a 100644 --- a/templates/default/zonefile.erb +++ b/templates/default/zonefile.erb @@ -1,5 +1,5 @@ $TTL <%= @global_ttl %> -@ IN SOA <%= @soa %> <%= @contact %> ( +@ IN SOA <%= @soa %>. <%= @contact %>. ( <%%= @serial %> ; serial [yyyyMMddNN] 4H ; refresh 30M ; retry @@ -7,16 +7,15 @@ $TTL <%= @global_ttl %> 1D ; minimum ) - IN NS <%= @soa %> + IN NS <%= @soa %>. <% @nameserver.each do |ns| -%> - IN NS <%= ns %> + IN NS <%= ns %>. <% end %> <% @mail_exchange.each do |mx| -%> - IN MX <%= mx['priority'] %> <%= mx['host'] %> + IN MX <%= mx['priority'] %> <%= mx['host'] %>. <% end %> <% @records.each do |record| -%> -<%= "%-20s %5s IN %5s %s" % [record['name'],record['ttl'],record['type'], - (record['ip'].nil? || record['ip'].empty?)? node['ipaddress']: record['ip'] ] %> +<%= "%-20s %5s IN %5s %s" % [record['name'],record['ttl'],record['type'],(record['ip'].nil? || record['ip'].empty?)? node['ipaddress']: record['ip'] ] %> <% end %> diff --git a/test/integration/chroot/serverspec/bind9_service_spec.rb b/test/integration/chroot/serverspec/bind9_service_spec.rb new file mode 100644 index 0000000..30a7fc6 --- /dev/null +++ b/test/integration/chroot/serverspec/bind9_service_spec.rb @@ -0,0 +1,139 @@ +require 'spec_helper' + +describe port(53) do + it { should be_listening } +end + +case RSpec.configure.os[:family] +when 'Ubuntu' + + describe package('bind9') do + it { should be_installed } + end + + describe service('bind9') do + it { should be_enabled } + it { should be_running } + end + + describe file('/var/chroot/named/etc/bind/named.conf.local') do + contents = '// +// Do any local configuration here +// + +// Consider adding the 1918 zones here, if they are not used in your +// organization +//include "/etc/bind/zones.rfc1918"; + +zone "example.com" { + type master; + file "/var/chroot/named/etc/bind/zones/example.com"; + allow-transfer { + 192.168.1.2; + 192.168.1.3; + }; + also-notify { + 192.168.1.2; + 192.168.1.3; + }; +}; + +zone "example.net" { + type slave; + file "example.net"; + masters { + 192.168.1.1; + }; +};' + it { should be_file } + its(:content) {should match contents } + end + + describe file('/var/chroot/named/etc/bind/zones/example.com') do + contents = '\$TTL 300 +@ IN SOA ns.example.com. root.example.com. \( + 00000 \; serial \[yyyyMMddNN\] + 4H \; refresh + 30M \; retry + 1W \; expiry + 1D \; minimum +\) + + IN NS ns.example.com. + IN NS ns1.example.com. + IN NS ns2.example.com. + + IN MX 10 ASPMX.L.GOOGLE.COM. + +www IN A 127.0.0.1' + + it { should be_file } + its(:content) { should match contents } + end + +else + + describe package('bind') do + it { should be_installed } + end + + describe service('named') do + it { should be_enabled } + it { should be_running } + end + + describe file('/var/chroot/named/etc/named/named.conf.local') do + contents = '// +// Do any local configuration here +// + +// Consider adding the 1918 zones here, if they are not used in your +// organization +//include "/etc/bind/zones.rfc1918"; + +zone "example.com" { + type master; + file "/var/chroot/named/var/named/zones/example.com"; + allow-transfer { + 192.168.1.2; + 192.168.1.3; + }; + also-notify { + 192.168.1.2; + 192.168.1.3; + }; +}; + +zone "example.net" { + type slave; + file "example.net"; + masters { + 192.168.1.1; + }; +};' + it { should be_file } + its(:content) {should match contents } + end + + describe file('/var/chroot/named/var/named/zones/example.com') do + content = '\$TTL 300 +@ IN SOA ns.example.com. root.example.com. \( + 00000 \; serial \[yyyyMMddNN\] + 4H \; refresh + 30M \; retry + 1W \; expiry + 1D \; minimum +\) + + IN NS ns.example.com. + IN NS ns1.example.com. + IN NS ns2.example.com. + + IN MX 10 ASPMX.L.GOOGLE.COM. + +www IN A 127.0.0.1' + it { should be_file } + its(:content) { should match content } + end + +end diff --git a/test/integration/chroot/serverspec/spec_helper.rb b/test/integration/chroot/serverspec/spec_helper.rb new file mode 100644 index 0000000..6b0a55d --- /dev/null +++ b/test/integration/chroot/serverspec/spec_helper.rb @@ -0,0 +1,11 @@ +require 'serverspec' + +include Serverspec::Helper::Exec +include Serverspec::Helper::DetectOS + +RSpec.configure do |c| + c.before :all do + c.path = '/sbin:/usr/sbin' + c.os = backend.check_os + end +end diff --git a/test/integration/default/serverspec/bind9_service_spec.rb b/test/integration/default/serverspec/bind9_service_spec.rb new file mode 100644 index 0000000..79ce154 --- /dev/null +++ b/test/integration/default/serverspec/bind9_service_spec.rb @@ -0,0 +1,139 @@ +require 'spec_helper' + +describe port(53) do + it { should be_listening } +end + +case RSpec.configure.os[:family] +when 'Ubuntu' + + describe package('bind9') do + it { should be_installed } + end + + describe service('bind9') do + it { should be_enabled } + it { should be_running } + end + + describe file('/etc/bind/named.conf.local') do + contents = '// +// Do any local configuration here +// + +// Consider adding the 1918 zones here, if they are not used in your +// organization +//include "/etc/bind/zones.rfc1918"; + +zone "example.com" { + type master; + file "/etc/bind/zones/example.com"; + allow-transfer { + 192.168.1.2; + 192.168.1.3; + }; + also-notify { + 192.168.1.2; + 192.168.1.3; + }; +}; + +zone "example.net" { + type slave; + file "example.net"; + masters { + 192.168.1.1; + }; +};' + it { should be_file } + its(:content) {should match contents } + end + + describe file('/etc/bind/zones/example.com') do + contents = '\$TTL 300 +@ IN SOA ns.example.com. root.example.com. \( + 00000 \; serial \[yyyyMMddNN\] + 4H \; refresh + 30M \; retry + 1W \; expiry + 1D \; minimum +\) + + IN NS ns.example.com. + IN NS ns1.example.com. + IN NS ns2.example.com. + + IN MX 10 ASPMX.L.GOOGLE.COM. + +www IN A 127.0.0.1' + + it { should be_file } + its(:content) { should match contents } + end + +else + + describe package('bind') do + it { should be_installed } + end + + describe service('named') do + it { should be_enabled } + it { should be_running } + end + + describe file('/etc/named/named.conf.local') do + contents = '// +// Do any local configuration here +// + +// Consider adding the 1918 zones here, if they are not used in your +// organization +//include "/etc/bind/zones.rfc1918"; + +zone "example.com" { + type master; + file "/var/named/zones/example.com"; + allow-transfer { + 192.168.1.2; + 192.168.1.3; + }; + also-notify { + 192.168.1.2; + 192.168.1.3; + }; +}; + +zone "example.net" { + type slave; + file "example.net"; + masters { + 192.168.1.1; + }; +};' + it { should be_file } + its(:content) {should match contents } + end + + describe file('/var/named/zones/example.com') do + content = '\$TTL 300 +@ IN SOA ns.example.com. root.example.com. \( + 00000 \; serial \[yyyyMMddNN\] + 4H \; refresh + 30M \; retry + 1W \; expiry + 1D \; minimum +\) + + IN NS ns.example.com. + IN NS ns1.example.com. + IN NS ns2.example.com. + + IN MX 10 ASPMX.L.GOOGLE.COM. + +www IN A 127.0.0.1' + it { should be_file } + its(:content) { should match content } + end + +end diff --git a/test/integration/default/serverspec/spec_helper.rb b/test/integration/default/serverspec/spec_helper.rb new file mode 100644 index 0000000..6b0a55d --- /dev/null +++ b/test/integration/default/serverspec/spec_helper.rb @@ -0,0 +1,11 @@ +require 'serverspec' + +include Serverspec::Helper::Exec +include Serverspec::Helper::DetectOS + +RSpec.configure do |c| + c.before :all do + c.path = '/sbin:/usr/sbin' + c.os = backend.check_os + end +end