smart_proxy_dns_powerdns 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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