smart_proxy_dns_powerdns 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6aef9bd6276d31444fab893bb884ccc663820cba
4
- data.tar.gz: e17f14a05adef3c63ec53c0d8f131a3668f962dd
2
+ SHA256:
3
+ metadata.gz: 91fabd73d9aa9962f5117f82375dbf50ebd42f1535ab1162b18f5af6eccb20a1
4
+ data.tar.gz: ebcaed367e7e65a911d8711e881ff9471e7ef731015cf4c7935ba330b8f464ea
5
5
  SHA512:
6
- metadata.gz: ebf9237f705434eec15aa56008f4819270e7134816433fba310da2b5daa80574a7065f249fd848b32f612dc12a87917f14f6e9eb0743751b83dfa79c16252ca7
7
- data.tar.gz: 4aae6514df432f499f87998e9c84a0ec1b4964146073bb9b59d60770d3f058b7d0cc8636762d0c7868bde48c674d1797c474c87a54069e2c1ac15938adbcb386
6
+ metadata.gz: 49e9f311cc142d0daf1ff414d715c52950e4ff3d599c267fae33932648aa8578fbd2b1ce8e298c4d4425e7d60e6dcc69b9c63a98d3fff2c3107558914af87c53
7
+ data.tar.gz: d21fc640d1f82569fa90718912ebca748c17a66caef9ba8966cb1c1e65a01179a0eea692e9f4f727e215577bb1143b58ad32a49f82b6cb2086b2573a9571fbee
data/README.md CHANGED
@@ -8,10 +8,10 @@ This plugin adds a new DNS provider for managing records in PowerDNS.
8
8
 
9
9
  ## Installation
10
10
 
11
- See [How_to_Install_a_Smart-Proxy_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Smart-Proxy_Plugin)
11
+ See [How\_to\_Install\_a\_Smart-Proxy\_Plugin](https://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Smart-Proxy_Plugin)
12
12
  for how to install Smart Proxy plugins
13
13
 
14
- This plugin is compatible with Smart Proxy 1.13 or higher.
14
+ This plugin is compatible with Smart Proxy 1.15 or higher.
15
15
 
16
16
  When installing using "gem", make sure to install the bundle file:
17
17
 
@@ -19,6 +19,11 @@ When installing using "gem", make sure to install the bundle file:
19
19
 
20
20
  ## Upgrading
21
21
 
22
+ ### 0.4.0
23
+
24
+ * The minimum Smart Proxy version is now 1.15
25
+ * The MySQL and PostgreSQL backends are officially deprecated and will be removed in the next release.
26
+
22
27
  ### 0.3.0
23
28
 
24
29
  * The minimum Smart Proxy version is now 1.13
@@ -44,7 +49,11 @@ To use the REST backend, set the following parameters:
44
49
  :powerdns_rest_url: 'http://localhost:8081/api/v1/servers/localhost'
45
50
  :powerdns_rest_api_key: 'apikey'
46
51
 
47
- Note the API is only tested with 4.x. Older versions may work, but they can also break.
52
+ **Note** only API v1 from PowerDNS 4.x is supported. The v0 API from 3.x is unsupported.
53
+
54
+ ### DNSSEC with REST
55
+
56
+ Domains in PowerDNS need a rectify action after modification. In the past this was done using pdnsutil (which can still be set) but since PowerDNS 4.1.0 the API can do this automatically. The [domain metadata API-RECTIFY](https://doc.powerdns.com/authoritative/domainmetadata.html#metadata-api-rectify) needs to be set to `1`. When it's unset, the config variable [default-api-rectify](https://doc.powerdns.com/authoritative/settings.html#setting-default-api-rectify) will be used. PowerDNS 4.2.0 started to default to true. When this is used, the value for `:powerdns_pdnssec` in this plugin should be empty (default).
48
57
 
49
58
  ### MySQL
50
59
 
@@ -56,6 +65,8 @@ To use MySQL, set the following parameters:
56
65
  :powerdns_mysql_password: ''
57
66
  :powerdns_mysql_database: 'powerdns'
58
67
 
68
+ **Note** use of this backend is deprecated. REST should be used.
69
+
59
70
  ### PostgreSQL
60
71
 
61
72
  To use PostgreSQL, set the following parameters:
@@ -63,17 +74,45 @@ To use PostgreSQL, set the following parameters:
63
74
  :powerdns_backend: 'postgresql'
64
75
  :powerdns_postgresql_connection: 'host=localhost user=powerdns password=mypassword dbname=powerdns'
65
76
 
77
+ **Note** use of this backend is deprecated. REST should be used.
78
+
66
79
  ### DNSSEC with MySQL and PostgreSQL
67
80
 
68
81
  In case you've enabled DNSSEC (as you should), all database backends require a rectify-zone after every zone change. The REST backend ignores this setting. The pdnssec command is configurable:
69
82
 
70
- :powerdns_pdnssec: 'pdnssec'
83
+ :powerdns_pdnssec: 'pdnsutil'
71
84
 
72
85
  Or a more complex example:
73
86
 
74
- :powerdns_pdnssec: 'sudo pdnssec --config-name=myconfig'
87
+ :powerdns_pdnssec: 'sudo pdnsutil --config-name=myconfig'
88
+
89
+ Note that PowerDNS 3.x used `pdnssec` rather than `pdnsutil` which explains the naming of the option.
90
+
91
+ ### SOA autoserial with MySQL and PostgreSQL
92
+
93
+ PowerDNS (>= 3.3) provides a feature called `autoserial` that takes care of managing the serial of `SOA` records.
94
+
95
+ There are many options available regarding how PowerDNS generates the serial and details can be found looking for the `SOA-EDIT` option in PowerDNS.
96
+
97
+ One option is to let the PowerDNS backend determine the `SOA` serial using the biggest `change_date` of the records associated with the DNS domain.
98
+ `smart_proxy_dns_powerdns` uses this approach and updates the `change_date` field of changed records, setting them to the current timestamp of the database server, represented as **the number of seconds since EPOCH**.
99
+
100
+ * when a new record is created, its `change_date` is set accordingly
101
+ * when a record is deleted, the `change_date` of the `SOA` record for the domain is updated
102
+
103
+ ### Updating the SOA serial when using the REST backend
104
+
105
+ When using the REST backend, the `change_date` of records isn't modified by this plugin. To automatically increment the serial number of a zone, you can configure the [SOA-EDIT-API](https://doc.powerdns.com/authoritative/domainmetadata.html#soa-edit-api) zone metadata. For example:
106
+
107
+ ```shell
108
+ pdnsutil set-meta example.com SOA-EDIT-API DEFAULT
109
+ ```
110
+
111
+ Other methods for managing the serial number are also available. Alternatives to `SOA-EDIT-API` you might want to investigate include:
112
+ * Installing database triggers that update the SOA record.
113
+ * Reconfiguring powerdns's prepared statements such that the `change\_date` column gets updated when records are updated.
75
114
 
76
- Note that PowerDNS 4.x now uses `pdnsutil` rather than `pdnssec`.
115
+ Full discussion of these methods is beyond the scope of this README.
77
116
 
78
117
  ## Contributing
79
118
 
@@ -94,7 +133,7 @@ Then run the tests:
94
133
 
95
134
  ## Copyright
96
135
 
97
- Copyright (c) 2015 - 2016 Ewoud Kohl van Wijngaarden
136
+ Copyright (c) 2015 - 2019 Ewoud Kohl van Wijngaarden
98
137
 
99
138
  This program is free software: you can redistribute it and/or modify
100
139
  it under the terms of the GNU General Public License as published by
@@ -107,5 +146,5 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
107
146
  GNU General Public License for more details.
108
147
 
109
148
  You should have received a copy of the GNU General Public License
110
- along with this program. If not, see <http://www.gnu.org/licenses/>.
149
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
111
150
 
@@ -26,7 +26,7 @@ module Proxy::Dns::Powerdns::Backend
26
26
  domain = row
27
27
  end
28
28
 
29
- raise Proxy::Dns::Error, "Unable to determine zone. Zone must exist in PowerDNS." unless domain
29
+ raise Proxy::Dns::Error, "Unable to determine zone for #{name}. Zone must exist in PowerDNS." unless domain
30
30
 
31
31
  domain
32
32
  end
@@ -35,7 +35,7 @@ module Proxy::Dns::Powerdns::Backend
35
35
  name = connection.escape(name)
36
36
  content = connection.escape(content)
37
37
  type = connection.escape(type)
38
- connection.query("INSERT INTO records (domain_id, name, ttl, content, type) VALUES (#{domain_id}, '#{name}', #{ttl.to_i}, '#{content}', '#{type}')")
38
+ connection.query("INSERT INTO records (domain_id, name, ttl, content, type, change_date) VALUES (#{domain_id}, '#{name}', #{ttl.to_i}, '#{content}', '#{type}', UNIX_TIMESTAMP())")
39
39
  connection.affected_rows == 1
40
40
  end
41
41
 
@@ -43,7 +43,16 @@ module Proxy::Dns::Powerdns::Backend
43
43
  name = connection.escape(name)
44
44
  type = connection.escape(type)
45
45
  connection.query("DELETE FROM records WHERE domain_id=#{domain_id} AND name='#{name}' AND type='#{type}'")
46
- connection.affected_rows >= 1
46
+ return false if connection.affected_rows == 0
47
+
48
+ connection.query("UPDATE records SET change_date=UNIX_TIMESTAMP() WHERE domain_id=#{domain_id} AND type='SOA'")
49
+ affected_rows = connection.affected_rows
50
+ if affected_rows > 1
51
+ logger.warning("Updated multiple SOA records (host=#{name}, domain_id=#{domain_id}). Check your zone records for duplicate SOA entries.")
52
+ elsif affected_rows == 0
53
+ logger.info("No SOA record updated (host=#{name}, domain_id=#{domain_id}). This can be caused by either a missing SOA record for the zone or consecutive updates of the same zone during the same second.")
54
+ end
55
+ true
47
56
  end
48
57
  end
49
58
  end
@@ -24,19 +24,28 @@ module Proxy::Dns::Powerdns::Backend
24
24
  end
25
25
  end
26
26
 
27
- raise Proxy::Dns::Error, "Unable to determine zone. Zone must exist in PowerDNS." unless domain
27
+ raise Proxy::Dns::Error, "Unable to determine zone for #{name}. Zone must exist in PowerDNS." unless domain
28
28
 
29
29
  domain
30
30
  end
31
31
 
32
32
  def create_record domain_id, name, type, content
33
- result = connection.exec_params("INSERT INTO records (domain_id, name, ttl, content, type) VALUES ($1::int, $2, $3::int, $4, $5)", [domain_id, name, ttl, content, type])
33
+ result = connection.exec_params("INSERT INTO records (domain_id, name, ttl, content, type, change_date) VALUES ($1::int, $2, $3::int, $4, $5, extract(epoch from now()))", [domain_id, name, ttl, content, type])
34
34
  result.cmdtuples == 1
35
35
  end
36
36
 
37
37
  def delete_record domain_id, name, type
38
38
  result = connection.exec_params("DELETE FROM records WHERE domain_id=$1::int AND name=$2 AND type=$3", [domain_id, name, type])
39
- result.cmdtuples >= 1
39
+ return false if result.cmdtuples == 0
40
+
41
+ result = connection.exec_params("UPDATE records SET change_date=extract(epoch from now()) WHERE domain_id=$1::int AND type='SOA'", [domain_id])
42
+ affected_rows = result.cmdtuples
43
+ if affected_rows > 1
44
+ logger.warning("Updated multiple SOA records (host=#{name}, domain_id=#{domain_id}). Check your zone records for duplicate SOA entries.")
45
+ elsif affected_rows == 0
46
+ logger.info("No SOA record updated (host=#{name}, domain_id=#{domain_id}). This can be caused by either a missing SOA record for the zone or consecutive updates of the same zone during the same second.")
47
+ end
48
+ true
40
49
  end
41
50
  end
42
51
  end
@@ -33,7 +33,7 @@ module Proxy::Dns::Powerdns::Backend
33
33
  }.max_by { |zone| zone['name'].length }
34
34
  end
35
35
 
36
- raise Proxy::Dns::Error, "Unable to determine zone. Zone must exist in PowerDNS." unless result
36
+ raise Proxy::Dns::Error, "Unable to determine zone for #{name}. Zone must exist in PowerDNS." unless result
37
37
 
38
38
  result
39
39
  end
@@ -13,85 +13,28 @@ module Proxy::Dns::Powerdns
13
13
  super(a_server, a_ttl)
14
14
  end
15
15
 
16
- def create_a_record(fqdn, ip)
17
- case a_record_conflicts(fqdn, ip)
18
- when 1
19
- raise(Proxy::Dns::Collision, "'#{fqdn} 'is already in use")
20
- when 0 then
21
- return nil
22
- else
23
- do_create(fqdn, ip, "A")
24
- end
25
- end
26
-
27
- def create_aaaa_record(fqdn, ip)
28
- case aaaa_record_conflicts(fqdn, ip)
29
- when 1
30
- raise(Proxy::Dns::Collision, "'#{fqdn} 'is already in use")
31
- when 0 then
32
- return nil
33
- else
34
- do_create(fqdn, ip, "AAAA")
35
- end
36
- end
37
-
38
- def create_cname_record(fqdn, target)
39
- case cname_record_conflicts(fqdn, target)
40
- when 1 then
41
- raise(Proxy::Dns::Collision, "'#{fqdn} 'is already in use")
42
- when 0 then
43
- return nil
44
- else
45
- do_create(fqdn, target, "CNAME")
46
- end
47
- end
48
-
49
- def create_ptr_record(fqdn, ptr)
50
- case ptr_record_conflicts(fqdn, ptr_to_ip(ptr))
51
- when 1
52
- raise(Proxy::Dns::Collision, "'#{fqdn} 'is already in use")
53
- when 0 then
54
- return nil
55
- else
56
- do_create(ptr, fqdn, "PTR")
57
- end
58
- end
59
-
60
16
  def do_create(name, value, type)
61
17
  zone = get_zone(name)
62
- unless create_record(zone['id'], name, type, value) and rectify_zone(zone['name'])
63
- raise Proxy::Dns::Error.new("Failed to create record #{name} #{type} #{value}")
18
+ if create_record(zone['id'], name, type, value)
19
+ raise Proxy::Dns::Error.new("Failed to rectify zone #{zone['name']}") unless rectify_zone(zone['name'])
20
+ else
21
+ raise Proxy::Dns::Error.new("Failed to insert record #{name} #{type} #{value}")
64
22
  end
65
23
  true
66
24
  end
67
25
 
68
- def remove_a_record(fqdn)
69
- do_remove(fqdn, "A")
70
- end
71
-
72
- def remove_aaaa_record(fqdn)
73
- do_remove(fqdn, "AAAA")
74
- end
75
-
76
- def remove_cname_record(fqdn)
77
- do_remove(fqdn, "CNAME")
78
- end
79
-
80
- def remove_ptr_record(ptr)
81
- do_remove(ptr, "PTR")
82
- end
83
-
84
26
  def do_remove(name, type)
85
27
  zone = get_zone(name)
86
28
  if delete_record(zone['id'], name, type)
87
- raise Proxy::Dns::Error.new("Failed to remove record #{name} #{type}") unless rectify_zone(zone['name'])
29
+ raise Proxy::Dns::Error.new("Failed to rectify zone #{name}") unless rectify_zone(zone['name'])
88
30
  end
89
31
  true
90
32
  end
91
33
 
92
- def get_zone(fqdn)
34
+ # :nocov:
35
+ def get_zone(name)
93
36
  # TODO: backend specific
94
- raise Proxy::Dns::Error, "Unable to determine zone. Zone must exist in PowerDNS."
37
+ raise Proxy::Dns::Error, "Unable to determine zone for #{name}. Zone must exist in PowerDNS."
95
38
  end
96
39
 
97
40
  def create_record(domain_id, name, type, content)
@@ -103,12 +46,19 @@ module Proxy::Dns::Powerdns
103
46
  # TODO: backend specific
104
47
  false
105
48
  end
49
+ # :nocov:
106
50
 
107
51
  def rectify_zone domain
108
52
  if @pdnssec
109
- %x(#{@pdnssec} rectify-zone "#{domain}")
110
-
111
- $?.exitstatus == 0
53
+ logger.debug("running: #{@pdnssec} rectify-zone \"#{domain}\"")
54
+ pdnsout = %x(#{@pdnssec} rectify-zone "#{domain}" 2>&1)
55
+
56
+ if $?.exitstatus != 0
57
+ logger.debug("#{@pdnssec} (exit: #{$?.exitstatus}) says: #{pdnsout}")
58
+ false
59
+ else
60
+ true
61
+ end
112
62
  else
113
63
  true
114
64
  end
@@ -5,7 +5,7 @@ module Proxy::Dns::Powerdns
5
5
  class Plugin < ::Proxy::Provider
6
6
  plugin :dns_powerdns, ::Proxy::Dns::Powerdns::VERSION
7
7
 
8
- requires :dns, '>= 1.13'
8
+ requires :dns, '>= 1.15'
9
9
 
10
10
  validate_presence :powerdns_backend
11
11
 
@@ -1,7 +1,7 @@
1
1
  module Proxy
2
2
  module Dns
3
3
  module Powerdns
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
6
6
  end
7
7
  end
@@ -39,19 +39,76 @@ class DnsPowerdnsBackendMysqlTest < Test::Unit::TestCase
39
39
  @connection.expects(:escape).with('test.example.com').returns('test.example.com')
40
40
  @connection.expects(:escape).with('A').returns('A')
41
41
  @connection.expects(:escape).with('10.1.1.1').returns('10.1.1.1')
42
- @connection.expects(:query).with("INSERT INTO records (domain_id, name, ttl, content, type) VALUES (1, 'test.example.com', 86400, '10.1.1.1', 'A')")
42
+ @connection.expects(:query).with("INSERT INTO records (domain_id, name, ttl, content, type, change_date) VALUES (1, 'test.example.com', 86400, '10.1.1.1', 'A', UNIX_TIMESTAMP())")
43
43
  @connection.expects(:affected_rows).returns(1)
44
44
 
45
45
  assert @provider.create_record(1, 'test.example.com', 'A', '10.1.1.1')
46
46
  end
47
47
 
48
48
  def test_delete_record
49
- @connection.expects(:escape).with('test.example.com').returns('test.example.com')
50
- @connection.expects(:escape).with('A').returns('A')
51
- @connection.expects(:query).with("DELETE FROM records WHERE domain_id=1 AND name='test.example.com' AND type='A'")
52
- @connection.expects(:affected_rows).returns(1)
49
+ mock_escapes(fqdn, 'A')
50
+ @connection.expects(:query).with(query_delete)
51
+ @connection.expects(:query).with(query_update_soa)
52
+ @connection.expects(:affected_rows).twice.returns(1)
53
+ assert @provider.delete_record(domain_id, fqdn, 'A')
54
+ end
55
+
56
+ def test_delete_no_record
57
+ mock_escapes(fqdn, 'A')
58
+ @connection.expects(:query).with(query_delete)
59
+ @connection.expects(:affected_rows).returns(0)
60
+
61
+ assert_false @provider.delete_record(domain_id, fqdn, 'A')
62
+ end
63
+
64
+ def test_delete_record_no_soa
65
+ mock_escapes(fqdn, 'A')
66
+ @connection.expects(:query).with(query_delete)
67
+ @connection.expects(:query).with(query_update_soa)
68
+ @connection.expects(:affected_rows).twice.returns(1, 0)
69
+ logger = mock()
70
+ logger.expects(:info)
71
+ @provider.stubs(:logger).returns(logger)
72
+
73
+ assert @provider.delete_record(domain_id, fqdn, 'A')
74
+ end
75
+
76
+ def test_delete_record_multiple_soa
77
+ mock_escapes(fqdn, 'A')
78
+ @connection.expects(:query).with(query_delete)
79
+ @connection.expects(:query).with(query_update_soa)
80
+ @connection.expects(:affected_rows).twice.returns(1, 2)
81
+ logger = mock()
82
+ logger.expects(:warning)
83
+ @provider.stubs(:logger).returns(logger)
84
+
85
+ assert @provider.delete_record(domain_id, fqdn, 'A')
86
+ end
87
+
88
+ private
89
+
90
+ def mock_escapes(*elts)
91
+ elts.each { |e| @connection.expects(:escape).with(e).returns(e) }
92
+ end
93
+
94
+ def domain
95
+ 'example.com'
96
+ end
97
+
98
+ def fqdn
99
+ "test.#{domain}"
100
+ end
101
+
102
+ def domain_id
103
+ 1
104
+ end
105
+
106
+ def query_delete(type='A')
107
+ "DELETE FROM records WHERE domain_id=#{domain_id} AND name='#{fqdn}' AND type='#{type}'"
108
+ end
53
109
 
54
- assert @provider.delete_record(1, 'test.example.com', 'A')
110
+ def query_update_soa
111
+ "UPDATE records SET change_date=UNIX_TIMESTAMP() WHERE domain_id=#{domain_id} AND type='SOA'"
55
112
  end
56
113
 
57
114
  end
@@ -35,33 +35,91 @@ class DnsPowerdnsBackendPostgresqlTest < Test::Unit::TestCase
35
35
 
36
36
  def test_create_record
37
37
  @connection.expects(:exec_params).
38
- with("INSERT INTO records (domain_id, name, ttl, content, type) VALUES ($1::int, $2, $3::int, $4, $5)", [1, 'test.example.com', 86400, '10.1.1.1', 'A']).
38
+ with("INSERT INTO records (domain_id, name, ttl, content, type, change_date) VALUES ($1::int, $2, $3::int, $4, $5, extract(epoch from now()))", [1, 'test.example.com', 86400, '10.1.1.1', 'A']).
39
39
  returns(mock(:cmdtuples => 1))
40
40
 
41
41
  assert_true @provider.create_record(1, 'test.example.com', 'A', '10.1.1.1')
42
42
  end
43
43
 
44
44
  def test_delete_record_no_records
45
- @connection.expects(:exec_params).
46
- with("DELETE FROM records WHERE domain_id=$1::int AND name=$2 AND type=$3", [1, 'test.example.com', 'A']).
47
- returns(mock(:cmdtuples => 0))
48
-
49
- assert_false @provider.delete_record(1, 'test.example.com', 'A')
45
+ mock_delete_tuples(0)
46
+ assert_false run_delete_record
50
47
  end
51
48
 
52
49
  def test_delete_record_single_record
53
- @connection.expects(:exec_params).
54
- with("DELETE FROM records WHERE domain_id=$1::int AND name=$2 AND type=$3", [1, 'test.example.com', 'A']).
55
- returns(mock(:cmdtuples => 1))
50
+ mock_delete_tuples(1)
51
+ mock_update_soa_tuples(1)
56
52
 
57
- assert_true @provider.delete_record(1, 'test.example.com', 'A')
53
+ assert_true run_delete_record
58
54
  end
59
55
 
60
56
  def test_delete_record_multiple_records
57
+ mock_delete_tuples(2)
58
+ mock_update_soa_tuples(1)
59
+
60
+ assert_true run_delete_record
61
+ end
62
+
63
+ def test_delete_record_no_soa
64
+ mock_delete_tuples(1)
65
+ mock_update_soa_tuples(0)
66
+ logger = mock()
67
+ logger.expects(:info)
68
+ @provider.stubs(:logger).returns(logger)
69
+
70
+ assert_true run_delete_record
71
+ end
72
+
73
+ def test_delete_record_multiple_soa
74
+ mock_delete_tuples(1)
75
+ mock_update_soa_tuples(2)
76
+ logger = mock()
77
+ logger.expects(:warning)
78
+ @provider.stubs(:logger).returns(logger)
79
+
80
+ assert_true run_delete_record
81
+ end
82
+
83
+ private
84
+
85
+ def mock_delete_tuples(cmdtuples)
61
86
  @connection.expects(:exec_params).
62
- with("DELETE FROM records WHERE domain_id=$1::int AND name=$2 AND type=$3", [1, 'test.example.com', 'A']).
63
- returns(mock(:cmdtuples => 2))
87
+ with(query_delete, [domain_id, fqdn, record_type]).
88
+ returns(mock(:cmdtuples => cmdtuples))
89
+ end
64
90
 
65
- assert_true @provider.delete_record(1, 'test.example.com', 'A')
91
+ def mock_update_soa_tuples(cmdtuples)
92
+ @connection.expects(:exec_params).
93
+ with(query_update_soa, [domain_id]).
94
+ returns(mock(:cmdtuples => cmdtuples))
66
95
  end
96
+
97
+ def run_delete_record
98
+ @provider.delete_record(domain_id, fqdn, record_type)
99
+ end
100
+
101
+ def domain
102
+ 'example.com'
103
+ end
104
+
105
+ def fqdn
106
+ "test.#{domain}"
107
+ end
108
+
109
+ def domain_id
110
+ 1
111
+ end
112
+
113
+ def record_type
114
+ 'A'
115
+ end
116
+
117
+ def query_delete
118
+ "DELETE FROM records WHERE domain_id=$1::int AND name=$2 AND type=$3"
119
+ end
120
+
121
+ def query_update_soa
122
+ "UPDATE records SET change_date=extract(epoch from now()) WHERE domain_id=$1::int AND type='SOA'"
123
+ end
124
+
67
125
  end
@@ -4,153 +4,39 @@ require 'smart_proxy_dns_powerdns/dns_powerdns_main'
4
4
 
5
5
  class DnsPowerdnsRecordTest < Test::Unit::TestCase
6
6
  def setup
7
- @provider = Proxy::Dns::Powerdns::Record.new('localhost', 86400, 'sudo pdnssec')
7
+ @provider = Proxy::Dns::Powerdns::Record.new('localhost', 86400, 'echo pdnssec')
8
8
  end
9
9
 
10
10
  def test_initialize
11
11
  assert_equal 86400, @provider.ttl
12
- assert_equal 'sudo pdnssec', @provider.pdnssec
12
+ assert_equal 'echo pdnssec', @provider.pdnssec
13
13
  end
14
14
 
15
- # Test A record creation
16
- def test_create_a
17
- @provider.expects(:a_record_conflicts).with('test.example.com', '10.1.1.1').returns(-1)
15
+ def test_do_create_success
18
16
  @provider.expects(:get_zone).with('test.example.com').returns({'id' => 1, 'name' => 'example.com'})
19
17
  @provider.expects(:create_record).with(1, 'test.example.com', 'A', '10.1.1.1').returns(true)
20
18
  @provider.expects(:rectify_zone).with('example.com').returns(true)
21
19
 
22
- assert @provider.create_a_record(fqdn, ipv4)
23
- end
24
-
25
- # Test A record creation does nothing if the same record exists
26
- def test_create_a_duplicate
27
- @provider.expects(:a_record_conflicts).with('test.example.com', '10.1.1.1').returns(0)
28
-
29
- assert_equal nil, @provider.create_a_record(fqdn, ipv4)
30
- end
31
-
32
- # Test A record creation fails if the record exists
33
- def test_create_a_conflict
34
- @provider.expects(:a_record_conflicts).with('test.example.com', '10.1.1.1').returns(1)
35
-
36
- assert_raise(Proxy::Dns::Collision) { @provider.create_a_record(fqdn, ipv4) }
37
- end
38
-
39
- # Test AAAA record creation
40
- def test_create_aaaa
41
- @provider.expects(:aaaa_record_conflicts).with('test.example.com', '2001:db8:1234:abcd::1').returns(-1)
42
- @provider.expects(:get_zone).with('test.example.com').returns({'id' => 1, 'name' => 'example.com'})
43
- @provider.expects(:create_record).with(1, 'test.example.com', 'AAAA', '2001:db8:1234:abcd::1').returns(true)
44
- @provider.expects(:rectify_zone).with('example.com').returns(true)
45
-
46
- assert @provider.create_aaaa_record(fqdn, ipv6)
47
- end
48
-
49
- # Test AAAA record creation does nothing if the same record exists
50
- def test_create_aaaa_duplicate
51
- @provider.expects(:aaaa_record_conflicts).with('test.example.com', '2001:db8:1234:abcd::1').returns(0)
52
-
53
- assert_equal nil, @provider.create_aaaa_record(fqdn, ipv6)
54
- end
55
-
56
- # Test AAAA record creation fails if the record exists
57
- def test_create_aaaa_conflict
58
- @provider.expects(:aaaa_record_conflicts).with('test.example.com', '2001:db8:1234:abcd::1').returns(1)
59
-
60
- assert_raise(Proxy::Dns::Collision) { @provider.create_aaaa_record(fqdn, ipv6) }
61
- end
62
-
63
- # Test CNAME record creation
64
- def test_create_cname
65
- @provider.expects(:cname_record_conflicts).with('test.example.com', 'something.example.com').returns(-1)
66
- @provider.expects(:get_zone).with('test.example.com').returns({'id' => 1, 'name' => 'example.com'})
67
- @provider.expects(:create_record).with(1, 'test.example.com', 'CNAME', 'something.example.com').returns(true)
68
- @provider.expects(:rectify_zone).with('example.com').returns(true)
69
-
70
- assert @provider.create_cname_record(fqdn, 'something.example.com')
71
- end
72
-
73
- # Test CNAME record creation does nothing if the same record exists
74
- def test_create_cname_duplicate
75
- @provider.expects(:cname_record_conflicts).with('test.example.com', 'something.example.com').returns(0)
76
-
77
- assert_equal nil, @provider.create_cname_record(fqdn, 'something.example.com')
78
- end
79
-
80
- # Test CNAME record creation fails if the record exists
81
- def test_create_cname_conflict
82
- @provider.expects(:cname_record_conflicts).with('test.example.com', 'something.example.com').returns(1)
83
-
84
- assert_raise(Proxy::Dns::Collision) { @provider.create_cname_record(fqdn, 'something.example.com') }
85
- end
86
-
87
- # Test PTR record creation
88
- def test_create_ptr
89
- @provider.expects(:ptr_record_conflicts).with('test.example.com', '10.1.1.1').returns(-1)
90
- @provider.expects(:get_zone).with('1.1.1.10.in-addr.arpa').returns({'id' => 1, 'name' => '1.1.10.in-addr.arpa'})
91
- @provider.expects(:create_record).with(1, '1.1.1.10.in-addr.arpa', 'PTR', 'test.example.com').returns(true)
92
- @provider.expects(:rectify_zone).with('1.1.10.in-addr.arpa').returns(true)
93
-
94
- assert @provider.create_ptr_record(fqdn, reverse_ipv4)
95
- end
96
-
97
- # Test PTR record creation does nothing if the same record exists
98
- def test_create_ptr_duplicate
99
- @provider.expects(:ptr_record_conflicts).with('test.example.com', '10.1.1.1').returns(0)
100
-
101
- assert_equal nil, @provider.create_ptr_record(fqdn, reverse_ipv4)
102
- end
103
-
104
- # Test PTR record creation fails if the record exists
105
- def test_create_ptr_conflict
106
- @provider.expects(:ptr_record_conflicts).with('test.example.com', '10.1.1.1').returns(1)
107
-
108
- assert_raise(Proxy::Dns::Collision) { @provider.create_ptr_record(fqdn, reverse_ipv4) }
109
- end
110
-
111
- # Test PTR record creation
112
- def test_create_ptr_ipv6
113
- @provider.expects(:ptr_record_conflicts).with('test.example.com', '2001:0db8:1234:abcd:0000:0000:0000:0001').returns(-1)
114
- @provider.expects(:get_zone).with('1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.b.a.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa').returns({'id' => 1, 'name' => 'd.c.b.a.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa'})
115
- @provider.expects(:create_record).with(1, '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.b.a.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa', 'PTR', 'test.example.com').returns(true)
116
- @provider.expects(:rectify_zone).with('d.c.b.a.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa').returns(true)
117
-
118
- assert @provider.create_ptr_record(fqdn, reverse_ipv6)
20
+ assert @provider.do_create('test.example.com', '10.1.1.1', 'A')
119
21
  end
120
22
 
121
- # Test A record removal
122
- def test_remove_a
23
+ def test_do_create_failure_in_create
123
24
  @provider.expects(:get_zone).with('test.example.com').returns({'id' => 1, 'name' => 'example.com'})
124
- @provider.expects(:delete_record).with(1, 'test.example.com', 'A').returns(true)
125
- @provider.expects(:rectify_zone).with('example.com').returns(true)
126
-
127
- assert @provider.remove_a_record(fqdn)
128
- end
129
-
130
- # Test PTR record removal
131
- def test_remove_ptr_ipv4
132
- @provider.expects(:get_zone).with('1.1.1.10.in-addr.arpa').returns({'id' => 1, 'name' => '1.1.10.in-addr.arpa'})
133
- @provider.expects(:delete_record).with(1, '1.1.1.10.in-addr.arpa', 'PTR').returns(true)
134
- @provider.expects(:rectify_zone).with('1.1.10.in-addr.arpa').returns(true)
135
-
136
- assert @provider.remove_ptr_record(reverse_ipv4)
137
- end
138
-
139
- # Test PTR record removal
140
- def test_remove_ptr_ipv6
141
- @provider.expects(:get_zone).with('1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.b.a.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa').returns({'id' => 1, 'name' => 'd.c.b.a.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa'})
142
- @provider.expects(:delete_record).with(1, '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.b.a.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa', 'PTR').returns(true)
143
- @provider.expects(:rectify_zone).with('d.c.b.a.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa').returns(true)
25
+ @provider.expects(:create_record).with(1, 'test.example.com', 'A', '10.1.1.1').returns(false)
144
26
 
145
- assert @provider.remove_ptr_record(reverse_ipv6)
27
+ assert_raise(Proxy::Dns::Error) do
28
+ @provider.do_create('test.example.com', '10.1.1.1', 'A')
29
+ end
146
30
  end
147
31
 
148
- def test_do_create
32
+ def test_do_create_failure_in_rectify
149
33
  @provider.expects(:get_zone).with('test.example.com').returns({'id' => 1, 'name' => 'example.com'})
150
34
  @provider.expects(:create_record).with(1, 'test.example.com', 'A', '10.1.1.1').returns(true)
151
- @provider.expects(:rectify_zone).with('example.com').returns(true)
35
+ @provider.expects(:rectify_zone).with('example.com').returns(false)
152
36
 
153
- assert @provider.do_create('test.example.com', '10.1.1.1', 'A')
37
+ assert_raise(Proxy::Dns::Error) do
38
+ @provider.do_create('test.example.com', '10.1.1.1', 'A')
39
+ end
154
40
  end
155
41
 
156
42
  def test_do_remove
@@ -161,25 +47,26 @@ class DnsPowerdnsRecordTest < Test::Unit::TestCase
161
47
  assert @provider.do_remove('test.example.com', 'A')
162
48
  end
163
49
 
164
- private
50
+ def test_rectify_zone_success
51
+ @provider.logger.expects(:debug).with('running: echo pdnssec rectify-zone "example.com"')
165
52
 
166
- def fqdn
167
- 'test.example.com'
53
+ assert_true @provider.rectify_zone 'example.com'
168
54
  end
169
55
 
170
- def ipv4
171
- '10.1.1.1'
172
- end
56
+ def test_rectify_zone_failure
57
+ @provider = Proxy::Dns::Powerdns::Record.new('localhost', 86400, 'false')
173
58
 
174
- def reverse_ipv4
175
- '1.1.1.10.in-addr.arpa'
176
- end
59
+ @provider.logger.expects(:debug).with('running: false rectify-zone "example.com"')
60
+ @provider.logger.expects(:debug).with('false (exit: 1) says: ')
177
61
 
178
- def ipv6
179
- '2001:db8:1234:abcd::1'
62
+ assert_false @provider.rectify_zone 'example.com'
180
63
  end
181
64
 
182
- def reverse_ipv6
183
- '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.b.a.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa'
65
+ def test_rectify_zone_no_pdnssec
66
+ @provider = Proxy::Dns::Powerdns::Record.new('localhost', 86400, nil)
67
+
68
+ @provider.logger.stubs(:debug).raises(Exception)
69
+
70
+ assert_true @provider.rectify_zone 'example.com'
184
71
  end
185
72
  end
@@ -0,0 +1,63 @@
1
+ require 'test_helper'
2
+ require 'dns_common/dns_common'
3
+ require 'smart_proxy_dns_powerdns'
4
+ require 'smart_proxy_dns_powerdns/dns_powerdns_main'
5
+ require "rack/test"
6
+ require 'json'
7
+
8
+ module Proxy::Dns
9
+ module DependencyInjection
10
+ include Proxy::DependencyInjection::Accessors
11
+ def container_instance; end
12
+ end
13
+ end
14
+
15
+ require 'dns/dns_api'
16
+
17
+ ENV['RACK_ENV'] = 'test'
18
+
19
+ class InternalApiTest < Test::Unit::TestCase
20
+ include Rack::Test::Methods
21
+
22
+ def app
23
+ app = Proxy::Dns::Api.new
24
+ app.helpers.server = @server
25
+ app
26
+ end
27
+
28
+ def setup
29
+ @server = Proxy::Dns::Powerdns::Record.new('localhost', 3600)
30
+ end
31
+
32
+ def test_create_a_record
33
+ name = "test.com"
34
+ value = "192.168.33.33"
35
+ type = "A"
36
+ @server.expects(:do_create).with(name, value, type)
37
+ post '/', :fqdn => name, :value => value, :type => type
38
+ assert last_response.ok?, "Last response was not ok: #{last_response.status} #{last_response.body}"
39
+ end
40
+
41
+ def test_create_ptr_record
42
+ name = "test.com"
43
+ value = "33.33.168.192.in-addr.arpa"
44
+ type = "PTR"
45
+ @server.expects(:do_create).with(value, name, type)
46
+ post '/', :fqdn => name, :value => value, :type => type
47
+ assert last_response.ok?, "Last response was not ok: #{last_response.status} #{last_response.body}"
48
+ end
49
+
50
+ def test_delete_a_record
51
+ name = "test.com"
52
+ @server.expects(:do_remove).with(name, "A")
53
+ delete name
54
+ assert last_response.ok?, "Last response was not ok: #{last_response.status} #{last_response.body}"
55
+ end
56
+
57
+ def test_delete_ptr_record
58
+ name = "33.33.168.192.in-addr.arpa"
59
+ @server.expects(:do_remove).with(name, "PTR")
60
+ delete name
61
+ assert last_response.ok?, "Last response was not ok: #{last_response.status} #{last_response.body}"
62
+ end
63
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_dns_powerdns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ewoud Kohl van Wijngaarden
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-01 00:00:00.000000000 Z
11
+ date: 2019-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -86,9 +86,6 @@ files:
86
86
  - lib/smart_proxy_dns_powerdns/dns_powerdns_main.rb
87
87
  - lib/smart_proxy_dns_powerdns/dns_powerdns_plugin.rb
88
88
  - lib/smart_proxy_dns_powerdns/dns_powerdns_version.rb
89
- - test/config/smart-proxy-settings.d/dns.yml
90
- - test/config/smart-proxy-settings.d/dns_powerdns.yml
91
- - test/config/smart-proxy-settings.yml
92
89
  - test/integration/integration_test.rb
93
90
  - test/test_helper.rb
94
91
  - test/unit/dns_powerdns_configuration_test.rb
@@ -97,6 +94,7 @@ files:
97
94
  - test/unit/dns_powerdns_record_postgresql_test.rb
98
95
  - test/unit/dns_powerdns_record_rest_test.rb
99
96
  - test/unit/dns_powerdns_record_test.rb
97
+ - test/unit/internal_api_test.rb
100
98
  homepage: https://github.com/theforeman/smart_proxy_dns_powerdns
101
99
  licenses:
102
100
  - GPL-3.0
@@ -116,20 +114,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
114
  - !ruby/object:Gem::Version
117
115
  version: '0'
118
116
  requirements: []
119
- rubyforge_project:
120
- rubygems_version: 2.6.8
117
+ rubygems_version: 3.0.3
121
118
  signing_key:
122
119
  specification_version: 4
123
120
  summary: PowerDNS DNS provider plugin for Foreman's smart proxy
124
121
  test_files:
125
- - test/integration/integration_test.rb
126
- - test/unit/dns_powerdns_configuration_test.rb
127
- - test/unit/dns_powerdns_record_mysql_test.rb
128
122
  - test/unit/dns_powerdns_record_rest_test.rb
129
123
  - test/unit/dns_powerdns_record_test.rb
130
- - test/unit/dns_powerdns_record_dummy_test.rb
124
+ - test/unit/dns_powerdns_record_mysql_test.rb
125
+ - test/unit/dns_powerdns_configuration_test.rb
126
+ - test/unit/internal_api_test.rb
131
127
  - test/unit/dns_powerdns_record_postgresql_test.rb
132
- - test/config/smart-proxy-settings.d/dns.yml
133
- - test/config/smart-proxy-settings.d/dns_powerdns.yml
134
- - test/config/smart-proxy-settings.yml
128
+ - test/unit/dns_powerdns_record_dummy_test.rb
135
129
  - test/test_helper.rb
130
+ - test/integration/integration_test.rb
@@ -1,3 +0,0 @@
1
- ---
2
- :enabled: true
3
- :use_provider: dns_powerdns
@@ -1,4 +0,0 @@
1
- ---
2
- :powerdns_backend: rest
3
- :powerdns_rest_url: http://localhost:8081/api/v1/servers/localhost
4
- :powerdns_rest_api_key: apikey
@@ -1,4 +0,0 @@
1
- :settings_directory: /home/ekohl/dev/smart_proxy_dns_powerdns/test/config/smart-proxy-settings.d
2
- :http_port: 8000
3
- :log_file: STDOUT
4
- :log_level: DEBUG