dns_one 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4925039fe22a770c8d76ed2f8a0ed4bd55e1e047
4
+ data.tar.gz: 140362fa6f7990264bdbd010c45407cbbbdefec1
5
+ SHA512:
6
+ metadata.gz: cb274f8459e0c1673927af11c10798388bf68b6987aed1e918ba3b9a1439ae23a7c36aa1efbc491e9da820c01b18db1c31a3d99e38ca29e50386e02d2be48d33
7
+ data.tar.gz: f97d08684e282a3498834c38f717bca6771ba39d31d43d2001f2e65a4282ca32aa4b9af5d3732647c7f92b3a3bb49268523f8e03b8df95e534561d5e85cd1701
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+
14
+ dev_conf.yml
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dns_one.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Tom Lobato
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # DnsOne
2
+
3
+ Instead having a complex data schema to assign record sets to individual DNS zones, dns_one assigns one or few record to many zones.
4
+
5
+ Configure your zones in YML files and fetch your domains from a database or YML backend.
6
+
7
+ ## Installation
8
+
9
+ # gem install dns_one
10
+
11
+ ## Usage
12
+
13
+ # dns_one install
14
+
15
+ Configure ```/etc/dns_one/conf.yml```. Then:
16
+
17
+ # dns_one start
18
+
19
+ Also:
20
+
21
+ # dns_one status
22
+ # dns_one stop
23
+ # dns_one uninstall
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
+
29
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
+
31
+ ## Contributing
32
+
33
+ Bug reports and pull requests are welcome on GitHub at https://github.com/tomlobato/dns_one.
34
+
35
+
36
+ ## License
37
+
38
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
39
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dns_one"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/dns_one.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dns_one/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dns_one"
8
+ spec.version = DnsOne::VERSION
9
+ spec.authors = ["Tom Lobato"]
10
+ spec.email = ["tomlobato@gmail.com"]
11
+
12
+ spec.summary = %q{DNS server for many zones sharing only one or few records, written in Ruby.}
13
+ spec.description = %q{Instead having a complex data schema to assign record sets to individual DNS zones, dns_one assigns one or few record to many zones. Configure your zones in YML files and fetch your domains from a database or YML backend.}
14
+ spec.homepage = "https://tomlobato.github.io/dns_one/"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.14"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+
28
+ spec.add_runtime_dependency "thor"
29
+ spec.add_runtime_dependency "rubydns"
30
+ spec.add_runtime_dependency "activerecord"
31
+ spec.add_runtime_dependency "rexec"
32
+
33
+ end
data/exe/dns_one ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "dns_one/cli"
4
+
5
+ DnsOne::CLI.start
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "set"
4
+ require "benchmark"
5
+
6
+ def test_sorted(max_idx)
7
+ puts "preparing..."
8
+ arr_orig = (0..max_idx).to_a
9
+ arr_1 = arr_orig.dup
10
+ new_elm = arr_1.delete(arr_1.sample)
11
+ arr_2 = arr_1.dup
12
+ arr_3 = arr_1.dup
13
+ set_1 = SortedSet.new(arr_1)
14
+ hash_orig={}; arr_1.each{|i| hash_orig[i] = 'asd' }
15
+ hash = hash_orig.dup
16
+ hash.delete new_elm
17
+
18
+ puts "testing insert..."
19
+
20
+ Benchmark.bm do |x|
21
+ x.report('index ') { arr_1.insert( arr_1.index { |x| x > new_elm } , new_elm) }
22
+ x.report('each_with_index') { arr_2.insert( [*arr_2.each_with_index].bsearch{|x, _| x > new_elm}.last , new_elm) }
23
+ x.report('bsearch_index ') { arr_3.insert( arr_3.bsearch_index{|x, _| x > new_elm} , new_elm) }
24
+ x.report('sortedset ') { set_1 << new_elm }
25
+ x.report('hash ') { hash[new_elm] = 'asd' }
26
+ end
27
+ # puts arr_1.join(" ")
28
+ puts (arr_orig == arr_1).to_s
29
+
30
+ # puts arr_2.join(" ")
31
+ puts (arr_orig == arr_2).to_s
32
+
33
+ # puts arr_3.join(" ")
34
+ puts (arr_orig == arr_3).to_s
35
+
36
+ # puts set_1.to_a.join(" ")
37
+ puts (arr_orig == set_1.to_a).to_s
38
+
39
+ # puts set_1.to_a.join(" ")
40
+ puts (hash_orig == hash).to_s
41
+
42
+ puts "search..."
43
+
44
+ Benchmark.bm do |x|
45
+ x.report('index ') { puts arr_1.index { |x| x > new_elm } }
46
+ x.report('each_with_index') { puts [*arr_2.each_with_index].bsearch{|x, _| x > new_elm}.last }
47
+ x.report('bsearch_index ') { puts arr_3.bsearch_index{|x, _| x > new_elm} }
48
+ x.report('sortedset ') { puts set_1.find_index new_elm }
49
+ x.report('hash ') { puts hash[new_elm] }
50
+ end
51
+
52
+ end
53
+
54
+ test_sorted eval(ARGV[0])
55
+
56
+ # WINNER 25/Jun/2017: HASH
57
+
58
+
@@ -0,0 +1,35 @@
1
+ module Backend; class DB
2
+ def initialize conf
3
+ @query = conf.delete :query
4
+ @db_conf = conf
5
+ setup_db
6
+ end
7
+
8
+ def find dom_name
9
+ sql = build_query dom_name
10
+ # http://jakeyesbeck.com/2016/02/14/ruby-threads-and-active-record-connections/
11
+ res = nil
12
+ begin
13
+ ActiveRecord::Base.connection_pool.with_connection do
14
+ res = ActiveRecord::Base.connection.execute sql
15
+ end
16
+ rescue => e
17
+ Log.exc e
18
+ end
19
+ first_record = res&.first
20
+ record_values = first_record&.values
21
+ record_values&.first
22
+ end
23
+
24
+ private
25
+
26
+ def build_query dom_name
27
+ @query.sub '$domain', dom_name
28
+ end
29
+
30
+ def setup_db
31
+ ActiveRecord::Base.logger = Log.logger
32
+ ActiveRecord::Base.establish_connection @db_conf
33
+ end
34
+
35
+ end; end
@@ -0,0 +1,22 @@
1
+
2
+ module Backend; class File
3
+ def initialize file
4
+ @domain_map = {}
5
+ load_file file
6
+ end
7
+
8
+ def find dom_name
9
+ @domain_map[dom_name.downcase]
10
+ end
11
+
12
+ private
13
+
14
+ def load
15
+ File.open(file).each_line do |line|
16
+ domain_name, rec_set_name = line
17
+ .strip
18
+ .split(/[,\s]+/)
19
+ @domain_map[domain_name.strip.downcase] = rec_set_name&.strip || ''
20
+ end
21
+ end
22
+ end; end
@@ -0,0 +1,22 @@
1
+
2
+ module DnsOne; class Cache
3
+ DEFAULT_MAX_SIZE = 10000
4
+
5
+ def initialize max_size = nil
6
+ @max_size = max_size || DEFAULT_MAX_SIZE
7
+ @cache = {}
8
+ end
9
+
10
+ def add k, v
11
+ @cache[k] = v
12
+ if @cache.length > @max_size
13
+ @cache.delete @cache.keys.first
14
+ end
15
+ v
16
+ end
17
+
18
+ def find k
19
+ @cache[k]
20
+ end
21
+
22
+ end; end
@@ -0,0 +1,49 @@
1
+ require "thor"
2
+
3
+ require "dns_one"
4
+ require "dns_one/setup"
5
+
6
+ class DnsOne::CLI < Thor
7
+
8
+ # RUN
9
+
10
+ desc "run", "run server"
11
+ option :conf
12
+ option :log
13
+ def run_srv
14
+ DnsOne::DnsOne.new(conf_file: options[:conf], log_file: options[:log]).start
15
+ end
16
+ default_task :run_srv
17
+
18
+ # INSTALL
19
+
20
+ desc "install", "install dns_one"
21
+ def install
22
+ DnsOne::Setup.new.install
23
+ end
24
+
25
+ desc "uninstall", "uninstall dns_one"
26
+ def uninstall
27
+ DnsOne::Setup.new.uninstall
28
+ end
29
+
30
+ # MANAGE
31
+
32
+ desc "start", "start dns_one"
33
+ def start
34
+ Util.ensure_sytemd
35
+ run_cmd "systemctl start #{DnsOne::Setup::SERVICE_NAME}"
36
+ end
37
+
38
+ desc "stop", "stop dns_one"
39
+ def stop
40
+ Util.ensure_sytemd
41
+ Util.run "systemctl stop #{DnsOne::Setup::SERVICE_NAME}"
42
+ end
43
+
44
+ desc "status", "check dns_one status"
45
+ def status
46
+ Util.ensure_sytemd
47
+ Util.run "systemctl status #{DnsOne::Setup::SERVICE_NAME}"
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+
2
+ class String
3
+ def to_a
4
+ [self]
5
+ end
6
+ def strip_text
7
+ split("\n")
8
+ .map{|l| l.strip}
9
+ .join("\n")
10
+ end
11
+ end
12
+
13
+ class Exception
14
+ def desc
15
+ "#{ message } #{ backtrace&.join "\n" }"
16
+ end
17
+ def puts_stderr
18
+ STDERR.puts e.desc
19
+ end
20
+ end
21
+
22
+ def match_root stat
23
+ stat.uid == 0 && stat.gid == 0
24
+ end
@@ -0,0 +1,51 @@
1
+
2
+ class Log < Logger
3
+ class << self
4
+
5
+ Logger::Severity::constants.each_with_index do |severity, severity_num|
6
+ next if severity == :UNKNOWN
7
+ method_name = severity.to_s[0].downcase
8
+ define_method(method_name) do |msg|
9
+ log severity_num, msg
10
+ end
11
+ end
12
+
13
+ def setup file, syslog_name
14
+ @syslog = Syslog::Logger.new syslog_name
15
+ @log_file = setfile file
16
+ @logger = Logger.new @log_file
17
+ end
18
+
19
+ def setfile file
20
+ if File.exists? file and File.writable? file
21
+ file
22
+ elsif File.writable? File.dirname(file)
23
+ file
24
+ else
25
+ STDOUT
26
+ end
27
+ end
28
+
29
+ def log severity, msg
30
+ met_name = Logger::Severity::constants[severity].downcase
31
+ @logger.send met_name, msg
32
+ if severity >= Logger::WARN
33
+ @syslog.send met_name, msg
34
+ end
35
+ if severity == Logger::FATAL and
36
+ @log_file != STDOUT and
37
+ @log_file != STDERR
38
+ STDERR.puts msg
39
+ end
40
+ end
41
+
42
+ def exc e
43
+ e "#{e.class}: #{e.desc}"
44
+ end
45
+
46
+ def logger
47
+ @logger
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,59 @@
1
+
2
+ module DnsOne; class Server # < RExec::Daemon::Base
3
+
4
+ DNS_DAEMON_RUN_AS = "dnsserver"
5
+ DNS_DAEMON_INTERFACES = [
6
+ [:udp, "0.0.0.0", 153],
7
+ [:tcp, "0.0.0.0", 153],
8
+ [:udp, "::", 15300],
9
+ [:tcp, "::", 15300]
10
+ ]
11
+
12
+ def initialize conf
13
+ ZoneSearch.instance.setup conf
14
+ end
15
+
16
+ def run
17
+ RubyDNS::run_server(listen: dns_daemon_interfaces, logger: Log.logger) do
18
+ on(:start) do
19
+ if RExec.current_user == 'root' and @conf.config.run_as
20
+ RExec.change_user @conf.config.run_as
21
+ end
22
+ Log.i "Running as #{RExec.current_user}"
23
+ end
24
+
25
+ match(/(.+)/) do |t| # transaction
26
+ domain_name = t.question.to_s
27
+ answer, other_records = ZoneSearch.instance.query domain_name, t.resource_class
28
+ if answer or other_records
29
+ t.respond! *answer if answer
30
+ other_records.each do |rec|
31
+ t.add rec.obj, {section: rec.section}
32
+ end
33
+ else
34
+ t.fail! :NXDomain
35
+ end
36
+ end
37
+
38
+ otherwise do |t|
39
+ t.fail! :NXDomain
40
+ end
41
+ end
42
+ end
43
+
44
+ def dns_daemon_interfaces
45
+ if Process.pid == 0
46
+ DNS_DAEMON_INTERFACES
47
+ else
48
+ ports = DNS_DAEMON_INTERFACES.map do |port|
49
+ if port[2] <= 1024
50
+ Log.w "Changing listening port #{port[2]} to #{port[2] + 10000} for non-root process."
51
+ port[2] += 10000
52
+ end
53
+ port
54
+ end
55
+ ports
56
+ end
57
+ end
58
+
59
+ end; end
@@ -0,0 +1,77 @@
1
+ module DnsOne; class Setup
2
+ SYSTEMD_SERVICES_DIR = "/lib/systemd/system/"
3
+ SERVICE_NAME = 'dns_one'
4
+ SYSTEMD_SERVICE_FILE = "#{SYSTEMD_SERVICES_DIR}/#{SERVICE_NAME}.service"
5
+
6
+ def initialize
7
+ @thisdir = File.join File.dirname(__FILE__)
8
+ end
9
+
10
+ def install
11
+ check_root
12
+ unless Util.has_systemd?
13
+ STDERR.puts "DnsOne requires systemd. Aborting install."
14
+ exit 1
15
+ end
16
+ mkdirs
17
+ copy_sample_conf
18
+ install_systemd_service
19
+ setup_finished_msg
20
+ end
21
+
22
+ def uninstall
23
+ stop
24
+ # File.delete DnsOne::DEFAULT_CONF_FILE
25
+ if File.exist?(SYSTEMD_SERVICE_FILE)
26
+ Util.run "systemctl disable #{SERVICE_NAME}"
27
+ File.delete SYSTEMD_SERVICE_FILE
28
+ end
29
+ FileUtils.rm_rf DnsOne::WORKING_DIR
30
+ puts "Uninstall complete."
31
+ end
32
+
33
+ private
34
+
35
+ def mkdirs
36
+ FileUtils.mkdir_p DnsOne::CONF_DIR
37
+ FileUtils.mkdir_p DnsOne::WORK_DIR
38
+ end
39
+
40
+ def setup_finished_msg
41
+ puts "Installed.\n"
42
+ puts "Now:"
43
+ puts "1) Edit #{DnsOne::DEFAULT_CONF_FILE}. You can run 'ruby -Ilib/ exe/dns_one --conf util/dev_conf.yml' to test and adjust your configuration."
44
+ puts "2) After configure run 'dns_one start'."
45
+ end
46
+
47
+ def is_setup?
48
+ File.exist?(DnsOne::DEFAULT_CONF_FILE) &&
49
+ File.exist?(SYSTEMD_SERVICE_FILE)
50
+ end
51
+
52
+ def check_root
53
+ unless Process.uid == 0
54
+ STDERR.puts "Install requires root privileges. Run with sudo or login as root. Aborting."
55
+ exit 1
56
+ end
57
+ end
58
+
59
+ def install_systemd_service
60
+ copy "#{@thisdir}/../../util/dns_one.service",
61
+ SYSTEMD_SERVICE_FILE
62
+
63
+ Util.run "systemctl enable #{SERVICE_NAME}"
64
+ end
65
+
66
+ def copy_sample_conf
67
+ copy "#{@thisdir}/../../util/sample_conf.yml",
68
+ DnsOne::DEFAULT_CONF_FILE
69
+ end
70
+
71
+ def copy from, to, mod = 0600
72
+ puts "Copying #{from} to #{to}..."
73
+ FileUtils.cp from, to
74
+ FileUtils.chmod mod, to
75
+ end
76
+
77
+ end; end
@@ -0,0 +1,25 @@
1
+ module DnsOne; class Util; class << self
2
+
3
+ def die msg
4
+ Log.f msg
5
+ exit 1
6
+ end
7
+
8
+ def run cmd
9
+ puts "Running #{cmd}..."
10
+ system cmd
11
+ end
12
+
13
+ def has_systemd?
14
+ File.exist?(`which systemctl`.strip) &&
15
+ File.writable?('/lib/systemd/system')
16
+ end
17
+
18
+ def ensure_sytemd
19
+ unless has_systemd?
20
+ STDERR.puts "Systemd not available. Aborting."
21
+ exit 1
22
+ end
23
+ end
24
+
25
+ end; end; end
@@ -0,0 +1,3 @@
1
+ module DnsOne
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,106 @@
1
+
2
+ module DnsOne; class ZoneSearch
3
+ include Singleton
4
+
5
+ Name = Resolv::DNS::Name
6
+ IN = Resolv::DNS::Resource::IN
7
+
8
+ def setup conf
9
+ @conf = conf
10
+ check_record_sets
11
+ @backend = set_backend
12
+ @cache = Cache.new @conf.config[:cache_max]
13
+
14
+ @ignore_subdomains_re = nil
15
+ if ignore_subdomains = @conf.config[:ignore_subdomains]
16
+ unless ignore_subdomains.empty?
17
+ subdoms = ignore_subdomains.strip.split(/\s+/).map(&:downcase).join('|')
18
+ @ignore_subdomains_re = /^(#{ subdoms })\./i
19
+ end
20
+ end
21
+ end
22
+
23
+ def query dom_name, res_class
24
+ dom_name = dom_name.dup
25
+
26
+ Log.d "searching #{ dom_name }..."
27
+
28
+ rec_set_name = find_record_set dom_name
29
+ Log.d "record set name #{ rec_set_name ? 'found' : 'not_found' }"
30
+ rec_set_name or return
31
+
32
+ rec_set = @conf.record_sets[rec_set_name.to_sym]
33
+ Log.d "record set #{ rec_set ? 'found' : 'not found' }"
34
+ rec_set or return
35
+
36
+ answer = nil
37
+
38
+ unless res_class == IN::NS
39
+ answer = rec_set[ res_class.to_s.split('::').last.to_sym ]
40
+ answer = [answer] unless answer.is_a? Array
41
+ end
42
+
43
+ other_records = []
44
+
45
+ # NS
46
+ ns_list = rec_set[:NS].map{|ns| IN::NS.new(Name.create ns)}
47
+ ns_section = res_class == IN::NS ? :answer : :authority
48
+ other_records << OpenStruct.new(obj: ns_list, section: ns_section)
49
+
50
+ [answer, other_records]
51
+ end
52
+
53
+ private
54
+
55
+ def set_backend
56
+ if file = @conf.backend[:file]
57
+ unless File.exists? file
58
+ Util.die "Configuration file #{file} not found."
59
+ end
60
+ Backend::File.new file
61
+ else
62
+ Backend::DB.new @conf.backend
63
+ end
64
+ end
65
+
66
+ def find_record_set dom_name
67
+ use_cache = dom_name !~ /^NC/
68
+ dom_name.sub! /^NC/, ''
69
+
70
+ dom_name.downcase!
71
+
72
+ dom_name.sub! /\.home$/i, ''
73
+
74
+ if @ignore_subdomains_re
75
+ dom_name.sub! @ignore_subdomains_re, ''
76
+ end
77
+
78
+ if use_cache and rec_set = @cache.find(dom_name)
79
+ Log.d "found in cache"
80
+ rec_set
81
+ else
82
+ if rec_set = @backend.find(dom_name)
83
+ @cache.add dom_name, rec_set if use_cache
84
+ rec_set
85
+ end
86
+ end
87
+ end
88
+
89
+ def check_record_sets
90
+ unless @conf.record_sets and not @conf.record_sets.empty?
91
+ Util.die "Record sets cannot be empty. Check file."
92
+ end
93
+
94
+ @conf.record_sets.each_pair do |rec_set_name, records|
95
+ unless records[:NS] and records[:NS].length >= 1
96
+ Util.die "Record set #{rec_set_name} is invalid. It must have at least 1 NS record."
97
+ end
98
+ unless records[:SOA] and records[:SOA].length == 7
99
+ Util.die "Record set #{rec_set_name} is invalid. It must have a valid SOA record."
100
+ end
101
+ end
102
+ end
103
+
104
+ end; end
105
+
106
+
data/lib/dns_one.rb ADDED
@@ -0,0 +1,77 @@
1
+
2
+ require 'rubydns'
3
+ require 'active_record'
4
+ require 'yaml'
5
+ require 'rexec'
6
+ require 'syslog'
7
+ require 'syslog/logger'
8
+
9
+ require "dns_one/core_extensions"
10
+ require "dns_one/log"
11
+ require "dns_one/util"
12
+
13
+ require "dns_one/server"
14
+ require "dns_one/setup"
15
+ require "dns_one/cache"
16
+ require "dns_one/zone_search"
17
+
18
+ require 'dns_one/backend/file'
19
+ require 'dns_one/backend/db'
20
+
21
+ module DnsOne; class DnsOne
22
+
23
+ DEFAULT_LOG_FILE = "/var/log/dns_server.log"
24
+ DEFAULT_CONF_FILE = '/etc/dns_one/conf.yml'
25
+ WORK_DIR = "/var/local/dnsserver"
26
+
27
+ CONF_DIR = "/etc/dns_one"
28
+ SYSLOG_NAME = 'dns_one'
29
+
30
+ def initialize conf_file: nil, log_file: nil
31
+ log_file ||= DEFAULT_LOG_FILE
32
+ conf_file ||= DEFAULT_CONF_FILE
33
+
34
+ Log.setup log_file, SYSLOG_NAME
35
+ @conf = parse_conf conf_file
36
+
37
+ # check_root
38
+ begin
39
+ Dir.chdir WORK_DIR
40
+ rescue => e
41
+ Log.w "Cannot change working dir to #{WORK_DIR}. Will continue in #{Dir.pwd}."
42
+ end
43
+ end
44
+
45
+ def start
46
+ Server.new(@conf).run
47
+ end
48
+
49
+ private
50
+
51
+ def parse_conf conf_file
52
+ check_conf_file conf_file
53
+
54
+ conf = YAML.load_file conf_file
55
+ conf.deep_symbolize_keys!
56
+
57
+ OpenStruct.new conf
58
+ end
59
+
60
+ def check_conf_file conf_file
61
+ unless File.readable? conf_file
62
+ Util.die "Conf file #{conf_file} not found or unreadable. Aborting."
63
+ end
64
+
65
+ conf_stat = File.stat conf_file
66
+
67
+ unless conf_stat.mode.to_s(8) =~ /0600$/
68
+ # Util.die "Conf file #{conf_file} must have mode 0600. Aborting."
69
+ end
70
+
71
+ unless match_root conf_stat
72
+ # Util.die "Conf file #{conf_file} must have uid/gid set to root. Aborting."
73
+ end
74
+ end
75
+
76
+ end; end
77
+
@@ -0,0 +1,12 @@
1
+
2
+ [Unit]
3
+ Description=dns_one supervisor
4
+
5
+ [Service]
6
+ User=root
7
+ Group=root
8
+ Restart=on-failure
9
+ WorkingDirectory=/var/local/dns_one
10
+ ExecStart=/bin/bash -lc '/root/.rbenv/shims/dns_one'
11
+ StandardOutput=syslog
12
+ StandardError=syslog
@@ -0,0 +1,72 @@
1
+
2
+ config:
3
+ run_as: dnsserver # required (adduser dnsserver)
4
+ # cache_max: 100000 # optional, defaults to 10000
5
+ ignore_subdomains: www en it es pt ru fr at
6
+
7
+ backend:
8
+ ##############
9
+ # DB backend #
10
+ ##############
11
+ database: db_name
12
+ username: db_user
13
+ password: db_pass
14
+ pool: 20
15
+ adapter: postgresql
16
+ host: my-pg-db.com
17
+ port: 5432
18
+ # adapter: mysql2
19
+ # host: my-mysql-db.com
20
+ # port: 3306
21
+ # # socket: /var/run/mysqld/mysqld.sock"
22
+ query: SELECT 'set1' FROM domains WHERE url = $domain LIMIT 1
23
+ # query: SELECT domains.record_set FROM domains WHERE domain_name = $domain LIMIT 1
24
+
25
+ # or...
26
+
27
+ ################
28
+ # File backend #
29
+ ################
30
+
31
+ # file: /etc/dns_one/domain.csv
32
+
33
+ # domain.csv example (set is optional, if missing the first record_set will be used)):
34
+ # mydomain.com set1
35
+ # myotherdomain.com
36
+ # myotherdomain2.com
37
+ # myotherdomain3.com set2
38
+
39
+ record_sets:
40
+
41
+ set1:
42
+ A: 123.234.345.456
43
+ AAAA: 1234:3c00::f03c:91fa:fec2:15df
44
+ NS:
45
+ - ns1.mynsserver.com
46
+ - ns2.mynsserver.com
47
+ SOA:
48
+ - ns1.mynsserver.com # mname Name of the host where the master zone file for this zone resides.
49
+ - www.mycompany.com # rname The person responsible for this domain name.
50
+ - 2016042600 # serial The version number of the zone file.
51
+ - 900 # refresh How often, in seconds, a secondary name server is to check for updates from the primary name server.
52
+ - 600 # retry How often, in seconds, a secondary name server is to retry after a failure to check for a refresh.
53
+ - 300 # expire Time in seconds that a secondary name server is to use the data before refreshing from the primary name server.
54
+ - 200 # minimum The minimum number of seconds to be used for TTL values in RRs.
55
+ # More about SOA fields on http://ruby-doc.org/stdlib-2.0.0/libdoc/resolv/rdoc/Resolv/DNS/Resource/SOA.html
56
+
57
+ set2:
58
+ A: 32.34.54.56
59
+ AAAA: 9034:3c00::f03c:91fb:fec2:15d0
60
+ NS:
61
+ - ns1.myothernsserver.com
62
+ - ns2.myothernsserver.com
63
+ - ns3.myothernsserver.com
64
+ - ns4.myothernsserver.com
65
+ SOA:
66
+ - ns1.mynsserver.com
67
+ - www.mycompany.com
68
+ - 2016042601
69
+ - 900
70
+ - 600
71
+ - 300
72
+ - 200
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dns_one
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Lobato
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-06-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubydns
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activerecord
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rexec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Instead having a complex data schema to assign record sets to individual
112
+ DNS zones, dns_one assigns one or few record to many zones. Configure your zones
113
+ in YML files and fetch your domains from a database or YML backend.
114
+ email:
115
+ - tomlobato@gmail.com
116
+ executables:
117
+ - dns_one
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - ".gitignore"
122
+ - ".rspec"
123
+ - ".travis.yml"
124
+ - Gemfile
125
+ - LICENSE.txt
126
+ - README.md
127
+ - Rakefile
128
+ - bin/console
129
+ - bin/setup
130
+ - dns_one.gemspec
131
+ - exe/dns_one
132
+ - experiments/cache_store.rb
133
+ - lib/dns_one.rb
134
+ - lib/dns_one/backend/db.rb
135
+ - lib/dns_one/backend/file.rb
136
+ - lib/dns_one/cache.rb
137
+ - lib/dns_one/cli.rb
138
+ - lib/dns_one/core_extensions.rb
139
+ - lib/dns_one/log.rb
140
+ - lib/dns_one/server.rb
141
+ - lib/dns_one/setup.rb
142
+ - lib/dns_one/util.rb
143
+ - lib/dns_one/version.rb
144
+ - lib/dns_one/zone_search.rb
145
+ - util/dns_one.service
146
+ - util/sample_conf.yml
147
+ homepage: https://tomlobato.github.io/dns_one/
148
+ licenses:
149
+ - MIT
150
+ metadata: {}
151
+ post_install_message:
152
+ rdoc_options: []
153
+ require_paths:
154
+ - lib
155
+ required_ruby_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ requirements: []
166
+ rubyforge_project:
167
+ rubygems_version: 2.5.1
168
+ signing_key:
169
+ specification_version: 4
170
+ summary: DNS server for many zones sharing only one or few records, written in Ruby.
171
+ test_files: []