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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dns_one.gemspec +33 -0
- data/exe/dns_one +5 -0
- data/experiments/cache_store.rb +58 -0
- data/lib/dns_one/backend/db.rb +35 -0
- data/lib/dns_one/backend/file.rb +22 -0
- data/lib/dns_one/cache.rb +22 -0
- data/lib/dns_one/cli.rb +49 -0
- data/lib/dns_one/core_extensions.rb +24 -0
- data/lib/dns_one/log.rb +51 -0
- data/lib/dns_one/server.rb +59 -0
- data/lib/dns_one/setup.rb +77 -0
- data/lib/dns_one/util.rb +25 -0
- data/lib/dns_one/version.rb +3 -0
- data/lib/dns_one/zone_search.rb +106 -0
- data/lib/dns_one.rb +77 -0
- data/util/dns_one.service +12 -0
- data/util/sample_conf.yml +72 -0
- metadata +171 -0
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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
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
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,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
|
data/lib/dns_one/cli.rb
ADDED
@@ -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
|
data/lib/dns_one/log.rb
ADDED
@@ -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
|
data/lib/dns_one/util.rb
ADDED
@@ -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,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,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: []
|