redzone 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZGIzOTRjZmY0MmE1YmNmYTA0YjFlODAxYzY5NzgxNDg0YmI5ODc3MA==
5
+ data.tar.gz: !binary |-
6
+ YWY5OGI0YzMyOWNjMGNjOWY4ZThkOGJkMmJjMzBmNzYzYzRjNWQyMQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NTI0Y2ZhZjhmMTg3Y2VkODc3MDBhMDY0N2VlZmRjODhiMWJkODgxOTU3MmZk
10
+ NzBkZTYwNzk0ODVlYzBlNzYxNGViN2EzZjQwOTlhZTI2NTgzNmY2MzczM2My
11
+ ZDg4NjFmYTM5NzE0YmQ4MDRiZjI5NzdjOWM1ZWM4N2E1YTgwNWE=
12
+ data.tar.gz: !binary |-
13
+ MTYzMTc3YjRkNjA2OTA3YjVhY2Q4ZDRjOTk2ZDkyYzJmZGFhNzY2ZTkzY2Qx
14
+ NTVjMzk5NTU5ODAxODQ1N2Y3ZjVmZTg0N2VlMGI5OWI2MmUzODM3NmVlNzcy
15
+ MDQzOGY4MTAzZDRmZTE0YmU1NGNlODYzNjlmYTViOTE2ZTYxZDk=
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.idea/
2
+ /.yardoc/
3
+ /.bundle/
4
+ /pkg/
5
+ Gemfile.lock
6
+ .DS_Store
7
+ /tmp/
8
+ /doc/
9
+ /features/reports
10
+ /spec/reports
11
+ /vendor/
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'rspec'
7
+ gem 'simplecov'
8
+ gem 'cucumber'
9
+ gem 'mocha', :require => 'mocha/api'
10
+ gem 'ci_reporter'
11
+ gem 'aruba'
12
+ end
13
+
14
+ group :development do
15
+ gem 'yard'
16
+ gem 'redcarpet', '~> 2.3.0'
17
+ end
data/LICENSE.md ADDED
@@ -0,0 +1,10 @@
1
+ # @markup markdown
2
+ # @title MIT License
3
+
4
+ Copyright (C) 2014 Justen Walker
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # RedZone - Generate BIND zone files from simple YAML syntax
2
+
3
+ Introduction
4
+ ------------
5
+
6
+ RedZone is a command-line utility for generating BIND DNS zone files from a YAML file. The tools follows the
7
+ UNIX philosophy and therefore does not attempt to do anything besides this.
8
+
9
+ RedZone was inspired by Daniel P. Berrange's [NoZone](http://search.cpan.org/~danberr/NoZone-1.0/lib/NoZone.pm) i
10
+ script which does something very similar in Perl.
11
+ The notable differences are:
12
+ - Added support for reverse-dns generation
13
+ - Removed zone inheritance, opting instead to just
14
+ use a common section from which all zones inherit details.
15
+ - No generation of named.conf.
16
+
17
+ Disclaimer
18
+ ----------
19
+
20
+ This gem was just released (pre 1.0) and probably doesn't have everything (or anything) you want yet.
21
+
22
+ During pre 1.0, things may change that break backwards compatibility between releases. Most likely these breaking
23
+ changes would be related to the YAML file syntax.
24
+
25
+ ### Current TODO List
26
+
27
+ - More test coverage for existing features
28
+
29
+ Installation
30
+ ------------
31
+
32
+ TODO: Publish to RubyGems.org
33
+
34
+ Usage
35
+ -----
36
+
37
+ ```
38
+ RedZone commands:
39
+ redzone generate DIR # Generates a bind database files into the given directory.
40
+ redzone help [COMMAND] # Describe available commands or one specific command
41
+ redzone version # Shows the current version of redzone
42
+
43
+ Options:
44
+ [--zones=ZONES] # RedZone zone file. (Default: /etc/redzone/zones.yml)
45
+ ```
46
+
47
+ ### Generating zone databases
48
+
49
+ - Create a zones.yml file (See [zones.yml.example]) in `/etc/redzone`
50
+ - Execute `redzone generate /var/named`
51
+ - Each zone and reverse record database should be present in /var/named
52
+
53
+ For example, if your zone.yml file is like the zones.yml.example file
54
+ then the resulting db files are:
55
+ - `0.0.0.0.0.0.0.0.0.0.c.f.ip6.arpa.db`
56
+ - `1.168.192.in-addr.arpa.db`
57
+ - `32.12.in-addr.arpa.db`
58
+ - `9.8.7.6.4.3.2.1.1.0.0.2.ip6.arpa.db`
59
+ - `qa.redzone.com.db`
60
+ - `redzone.com.db`
61
+
62
+ How to Contribute
63
+ -----------------
64
+
65
+ ### Running the tests
66
+
67
+ $ bundle
68
+ $ bundle exec rake spec
69
+
70
+ ### Installing locally
71
+
72
+ $ bundle
73
+ $ [bundle exec] rake install
74
+
75
+ ### Reporting Issues
76
+
77
+ Please include a reproducible test case.
78
+
79
+ License
80
+ -------
81
+
82
+ Copyright (c) 2013 Justen Walker.
83
+
84
+ Released under the terms of the MIT License. For further information, please see the file [LICENSE.md].
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ require "yard"
4
+
5
+ yard_files = ['lib/**/*.rb','-','LICENSE.md']
6
+ yard_opts = [
7
+ '--markup', 'markdown',
8
+ '--markup-provider', 'redcarpet',
9
+ '--readme', 'README.md',
10
+ '--no-private',
11
+ '--exclude', 'lib/*/cli.rb'
12
+ ]
13
+
14
+ YARD::Rake::YardocTask.new do |t|
15
+ t.files = yard_files
16
+ t.options = yard_opts
17
+ end
18
+ task :yard_server => [:yard] do
19
+ system "yard server --reload"
20
+ end
21
+ begin
22
+ require 'ci/reporter/rake/rspec'
23
+ rescue LoadError
24
+ end
25
+
26
+ RSpec::Core::RakeTask.new(:spec) do |t|
27
+ t.pattern = "spec/unit/**/*_spec.rb"
28
+ end
29
+
30
+ task :default do
31
+ sh %{rake -T}
32
+ end
data/bin/redzone ADDED
@@ -0,0 +1,7 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib',__FILE__)
4
+ $:.unshift(lib) unless $:.include?(lib)
5
+
6
+ require 'redzone/cli'
7
+ RedZone::Cli.start(ARGV)
@@ -0,0 +1,66 @@
1
+ require 'ipaddr'
2
+
3
+ module RedZone
4
+ # Arpa definition
5
+ class Arpa
6
+ # Reverse DNS name
7
+ # @return [String] dns name
8
+ attr_reader :name
9
+
10
+ # Network
11
+ # @return [IPAddr]
12
+ attr_reader :network
13
+
14
+ # Get the list of PTR records
15
+ # @return [Array<Record>] PTR records
16
+ attr_reader :records
17
+
18
+ # Constructs a new MailExchange entry
19
+ # @param [Hash<String, SOA>] opt
20
+ # @option opt [String] :name Arpa DNS name (Required)
21
+ # @option opt [String] :network IP address with network mask (Required)
22
+ # @option opt [SOA,String] :soa SOA record (Required)
23
+ def initialize(opt)
24
+ raise ArgumentError, ':name is required' unless opt.has_key?(:name)
25
+ raise ArgumentError, ':network is required' unless opt.has_key?(:network)
26
+ raise ArgumentError, ':soa is required' unless opt.has_key?(:soa)
27
+ @name = opt[:name]
28
+ @network = IPAddr.new(opt[:network])
29
+ @soa = opt[:soa]
30
+ @records = []
31
+ end
32
+
33
+ # Writes the Arpa to the given IO stream
34
+ # @param [IO] io IO Stream
35
+ def write(io)
36
+ io << @soa
37
+ @records.each do |r|
38
+ io << r
39
+ end
40
+ io << "\n"
41
+ end
42
+
43
+ # Adds a machine to the arpa network for reverse-address lookup
44
+ # only if the machine is in this network.
45
+ # @param [Machine] machine
46
+ # @param [String] domain name
47
+ def add(machine,domain)
48
+ fqdn = "#{machine.name}.#{domain}."
49
+ substr = ".#{@name}"
50
+ if @network.ipv4? and machine.ipv4? and @network.include?(machine.ipv4)
51
+ ip = machine.ipv4.reverse
52
+ ip.slice!(substr)
53
+ records << Record.new(:name => ip, :type => "PTR", :data => fqdn, :comment => "Machine #{machine.name}")
54
+ end
55
+ if @network.ipv6? and machine.ipv6? and @network.include?(machine.ipv6)
56
+ ip = machine.ipv6.ip6_arpa
57
+ ip.slice!(substr)
58
+ records << Record.new(:name => ip, :type => "PTR", :data => fqdn, :comment => "Machine #{machine.name}")
59
+ end
60
+ end
61
+
62
+ def add_record(record)
63
+ records << record
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,33 @@
1
+ require 'thor'
2
+
3
+ require 'redzone/zone_config'
4
+ require 'redzone/zonefile_writer'
5
+ require 'redzone/environment'
6
+ module RedZone
7
+ # RedZone Command-line actions
8
+ class Cli < Thor
9
+ package_name 'RedZone'
10
+ class_option :zones, :type => :string, :desc => <<-eos.strip
11
+ RedZone zone file. (Default: #{RedZone::Environment.default_zonefile})
12
+ eos
13
+ #class_option :config, :type => :string, :desc => <<-eos.strip
14
+ # RedZone configuration file. (Default: #{RedZone::Environment.default_configfile})
15
+ #eos
16
+
17
+ desc 'generate DIR', <<-eos.strip
18
+ Generates a bind database files into the given directory.
19
+ eos
20
+ # Generates a bind database files into the given directory.
21
+ def generate(dir)
22
+ c = ZoneConfig.new(options[:zones])
23
+ writer = ZonefileWriter.new(c)
24
+ writer.write_zones(Pathname.new(dir))
25
+ end
26
+
27
+ desc 'version', 'Shows the current version of redzone'
28
+ # Prints the current RedZone version to the console
29
+ def version
30
+ say "redzone v#{RedZone::VERSION}"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ module RedZone
2
+ # RedZone environment properties
3
+ module Environment
4
+ # Get the default etc path
5
+ # @return [Pathname] default etc path
6
+ def self.default_etc
7
+ Pathname.new('/etc/redzone')
8
+ end
9
+ # Get the default location of the zone file
10
+ # @return [Pathname] default zones.yml path
11
+ def self.default_zonefile
12
+ self.default_etc.join('zones.yml')
13
+ end
14
+ # Get the default location of the config file
15
+ # @return [Pathname] default config.yml path
16
+ def self.default_configfile
17
+ self.default_etc.join('config.yml')
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,55 @@
1
+ module RedZone
2
+ # Simple time parser for TTL/SOA lifetimes
3
+ #
4
+ # The simple time format format is a number followed by a time unit.
5
+ #
6
+ # ie: `<Number> [Unit]`
7
+ #
8
+ # Where the `Unit` is one of
9
+ #
10
+ # - `M`: Minute(s)
11
+ # - `H`: Hour(s)
12
+ # - `D`: Day(s)
13
+ # - `W`: Week(s)
14
+ #
15
+ # If the units are missing, it is assumed to be in seconds
16
+ #
17
+ class Lifetime
18
+ # Constructs a lifetime object from a string
19
+ # @param [String] str time stirng
20
+ def initialize(str)
21
+ if str.upcase =~ /([0-9]+)\s*([HDWM]?)/
22
+ i = $1.to_i
23
+ pl = "s" if i > 1
24
+ pl ||= ""
25
+ time = case $2
26
+ when 'H' then [i * 3600,"#{i} Hour#{pl}"]
27
+ when 'D' then [i * 86400,"#{i} Day#{pl}"]
28
+ when 'M' then [i * 60,"#{i} Minute#{pl}"]
29
+ when 'W' then [i * 604800,"#{i} Week#{pl}"]
30
+ else [i,"#{i} Second#{pl}"]
31
+ end
32
+ @time = time.first
33
+ @str = time.last
34
+ end
35
+ end
36
+ # Returns the lifetime as seconds
37
+ # @return [Integer] seconds
38
+ def seconds
39
+ @time
40
+ end
41
+ # Returns the string representaton of the lifetime
42
+ #
43
+ # Examples:
44
+ #
45
+ # - 300 Seconds
46
+ # - 1 Minute
47
+ # - 2 Hours
48
+ # - 1 Day
49
+ # - 2 Weeks
50
+ # @return [String]
51
+ def to_s
52
+ @str
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,70 @@
1
+ require 'ipaddr'
2
+
3
+ module RedZone
4
+ # Machine entry
5
+ # @attr [String] name Relative domain name
6
+ # @attr [IPAddr] ipv4 IPV4 Address
7
+ # @attr [IPAddr] ipv6 IPV6 Address
8
+ class Machine
9
+ attr_reader :name,:ipv4,:ipv6
10
+ # Construct a new machine entry
11
+ # @param [String] name Relative domain name
12
+ # @param [Hash] config Machine configuration
13
+ # @option config [String] :ipv4 IPV4 Address
14
+ # @option config [String] :ipv6 IPV6 Address
15
+ def initialize(name,config)
16
+ @name = name
17
+ if config.is_a? Hash
18
+ @alias = nil
19
+ @ipv4 = IPAddr.new(config[:ipv4]) if config.has_key?(:ipv4)
20
+ @ipv6 = IPAddr.new(config[:ipv6]) if config.has_key?(:ipv6)
21
+ elsif config.is_a? Machine
22
+ @alias = config
23
+ @ipv4 = config.ipv4
24
+ @ipv6 = config.ipv6
25
+ end
26
+ end
27
+ # Returns true if this machine is an alias of another
28
+ # @return [Boolean] true if the machine is an alias
29
+ def alias?
30
+ not @alias.nil?
31
+ end
32
+ # Returns a new machine that is an alias of this machine.
33
+ # If this machine is already an alias, it delegates this call
34
+ # to the aliased machine rather than this one.
35
+ # @return [Machine]
36
+ def alias(name)
37
+ if @alias.nil?
38
+ Machine.new(name,self)
39
+ else
40
+ @alias.alias(name)
41
+ end
42
+ end
43
+ # Test if the machine has an ipv4 address
44
+ # @return [Boolean] if the machine has an ipv4 address
45
+ def ipv4?
46
+ not @ipv4.nil?
47
+ end
48
+ # Test if the machine has an ipv6 address
49
+ # @return [Boolean] if the machine has an ipv6 address
50
+ def ipv6?
51
+ not @ipv6.nil?
52
+ end
53
+ # Get the list of A/AAAA records
54
+ # @return [Array<Record>]
55
+ def records
56
+ r = []
57
+ comment = "Machine #{@alias.name}" if not @alias.nil?
58
+ if ipv4?
59
+ ipv4opt = {:name => @name, :data => @ipv4.to_s, :type => 'A', :comment => comment }
60
+ r << Record.new(ipv4opt)
61
+ end
62
+ if ipv6?
63
+
64
+ ipv6opt = {:name => @name, :data => @ipv6.to_s, :type => 'AAAA', :comment => comment }
65
+ r << Record.new(ipv6opt)
66
+ end
67
+ r
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,28 @@
1
+ module RedZone
2
+ # A mail server record
3
+ class MailExchange
4
+ # MX Server name / alias
5
+ attr_reader :name
6
+
7
+ # Get the target machine hosting the mail exchange
8
+ attr_reader :machine
9
+
10
+ # MX Priority
11
+ attr_reader :priority
12
+
13
+ # Constructs a new MailExchange entry
14
+ # @param [String] name Server name / alias
15
+ # @param [Machine] machine Target machine
16
+ # @param [Integer] priority MX priority setting
17
+ def initialize(name,machine,priority)
18
+ @name = name
19
+ @machine = machine.alias(@name)
20
+ @priority = priority
21
+ end
22
+ # Get the list of MX records
23
+ # @return [Array<Record>]
24
+ def records
25
+ [Record.new(:name => "@", :type => "MX", :data => "#{@priority} #{@name}")]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module RedZone
2
+ # NameServer record
3
+ class NameServer
4
+ # Name Server name / alias
5
+ attr_reader :name
6
+
7
+ # Get the target machine hosting the name server
8
+ attr_reader :machine
9
+
10
+ # Constructs a new NameServer
11
+ # @param [String] name Server name / alias
12
+ # @param [Machine] machine Target machine
13
+ def initialize(name,machine)
14
+ @name = name
15
+ @machine = machine.alias(@name)
16
+ end
17
+
18
+ # Get the list of NS records
19
+ # @return [Array<Record>]
20
+ def records
21
+ [Record.new(:name => "@", :type => "NS", :data => "#{@name}")]
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ require 'redzone/lifetime'
2
+ module RedZone
3
+ # DNS Record
4
+ class Record
5
+ # Returns a new instance of a domain record
6
+ # @param [Hash] record
7
+ # @option record [String] :name The record name (Required)
8
+ # @option record [String] :class ('IN') The record class. (Optional)
9
+ # @option record [String] :ttl The ttl for the record (Optional)
10
+ # @option record [String] :type The type of record, eg: CNAME, A, AAAA. (Required)
11
+ # @option record [String] :data The record data (Required)
12
+ # @option record [String] :comment A comment for the record
13
+ def initialize(record)
14
+ raise ArgumentError, ':name is required' unless record.has_key?(:name)
15
+ raise ArgumentError, ':type is required' unless record.has_key?(:type)
16
+ raise ArgumentError, ':data is required' unless record.has_key?(:data)
17
+ @name = record[:name]
18
+ @class = record[:class] || 'IN'
19
+ @type = record[:type]
20
+ if record.has_key?(:ttl) and not record[:ttl].nil?
21
+ @ttl = Lifetime.new(record[:ttl]).seconds
22
+ end
23
+ @data = record[:data]
24
+ if @type == "TXT"
25
+ @data = '"%s"' % [@data.gsub(/^\s*"?|"?\s*$/,'').gsub(/\n/,'')]
26
+ end
27
+ if record.has_key?(:comment) and not record[:comment].nil?
28
+ @comment = " ; %s" % [record[:comment]]
29
+ end
30
+ end
31
+ # Returns the domain record as a string to be written in the zone file
32
+ # @return [String] record line
33
+ def to_s
34
+ "%-20s %-8s %s %-8s %-20s%s\n" % [@name, @ttl, @class, @type, @data, @comment || '']
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,64 @@
1
+ require 'redzone/lifetime'
2
+ module RedZone
3
+ # A DNS Start of Authority (SOA) record
4
+ #
5
+ # Example
6
+ # -------
7
+ #
8
+ #
9
+ # $ORIGIN example.com.
10
+ # $TTL 3600 ; TTL = 1 Hour
11
+ # @ IN SOA ns1 hostmaster.example.com. (
12
+ # 20140104 ; sn = serial number
13
+ # 3600 ; ref = refresh = 1 Hour
14
+ # 600 ; rt = update retry = 10 Minutes
15
+ # 86400 ; ex = expiry = 1 Day
16
+ # )
17
+ #
18
+ class SOA
19
+ # Returns a new instance of a zone SOA record
20
+ # @param [Hash] opts
21
+ # @option opts [String] :domain Zone (domain) name
22
+ # @option opts [String] :ns Primary name server
23
+ # @option opts [String] :hostmaster Email address of the zone file maintainer ('@' replaced by '.')
24
+ # @option opts [String] :refresh Time between refreshes from slave servers
25
+ # @option opts [String] :retry Time between retrying failed zone transfers
26
+ # @option opts [String] :expire The maximum time that a secondary server will keep trying to complete a zone transfer.
27
+ # @option opts [String] :ttl The minimum time-to-live that applies to all resource records in the zone file
28
+ def initialize(opts)
29
+ @domain = opts[:domain]
30
+ @ns = opts[:ns]
31
+ @hostmaster = escape(opts[:hostmaster] || "hostmaster.#{@domain}")
32
+ @ttl = Lifetime.new(opts[:ttl] || '1H')
33
+ @refresh = Lifetime.new(opts[:refresh] || '1H')
34
+ @retry = Lifetime.new(opts[:retry] || '10M')
35
+ @expire = Lifetime.new(opts[:expire] || '1D')
36
+ end
37
+ # Returns the SOA record with the serial set to the current unix timestamp
38
+ # @return [String] SOA Record
39
+ def to_s
40
+ to_soa(Time.now.to_i)
41
+ end
42
+
43
+ # Returns the SOA record with the given serial number
44
+ # @param [Integer] serial Serial number (Usually YYYYMMDDnn)
45
+ # @return [String] SOA Record
46
+ def to_soa(serial)
47
+ io = StringIO.new
48
+ io << "$ORIGIN #{@domain}.\n"
49
+ io << "$TTL #{@ttl.seconds} ; TTL = #{@ttl}\n"
50
+ io << "@ IN SOA #{@ns} #{@hostmaster} (\n"
51
+ io << " %-10s ; sn = serial number\n" % [serial]
52
+ io << " %-10s ; ref = refresh = %s\n" % [@refresh.seconds,@refresh]
53
+ io << " %-10s ; rt = retry = %s\n" % [@retry.seconds,@retry]
54
+ io << " %-10s ; ex = expiry = %s\n" % [@expire.seconds,@expire]
55
+ io << " %-10s ; ttl = %s\n" % [@ttl.seconds,@ttl]
56
+ io << ")\n\n"
57
+ io.string
58
+ end
59
+ private
60
+ def escape(email)
61
+ email.gsub(/@/,'.')
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,6 @@
1
+ # Red Zone module
2
+ #
3
+ module RedZone
4
+ # Current version of RedZone
5
+ VERSION = '0.0.1'
6
+ end