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 +5 -5
- data/README.md +47 -8
- data/lib/smart_proxy_dns_powerdns/backend/mysql.rb +12 -3
- data/lib/smart_proxy_dns_powerdns/backend/postgresql.rb +12 -3
- data/lib/smart_proxy_dns_powerdns/backend/rest.rb +1 -1
- data/lib/smart_proxy_dns_powerdns/dns_powerdns_main.rb +18 -68
- data/lib/smart_proxy_dns_powerdns/dns_powerdns_plugin.rb +1 -1
- data/lib/smart_proxy_dns_powerdns/dns_powerdns_version.rb +1 -1
- data/test/unit/dns_powerdns_record_mysql_test.rb +63 -6
- data/test/unit/dns_powerdns_record_postgresql_test.rb +71 -13
- data/test/unit/dns_powerdns_record_test.rb +28 -141
- data/test/unit/internal_api_test.rb +63 -0
- metadata +9 -14
- data/test/config/smart-proxy-settings.d/dns.yml +0 -3
- data/test/config/smart-proxy-settings.d/dns_powerdns.yml +0 -4
- data/test/config/smart-proxy-settings.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 91fabd73d9aa9962f5117f82375dbf50ebd42f1535ab1162b18f5af6eccb20a1
|
4
|
+
data.tar.gz: ebcaed367e7e65a911d8711e881ff9471e7ef731015cf4c7935ba330b8f464ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 [
|
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.
|
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
|
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: '
|
83
|
+
:powerdns_pdnssec: 'pdnsutil'
|
71
84
|
|
72
85
|
Or a more complex example:
|
73
86
|
|
74
|
-
:powerdns_pdnssec: 'sudo
|
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
|
-
|
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 -
|
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 <
|
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
|
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
|
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
|
-
|
63
|
-
raise Proxy::Dns::Error.new("Failed to
|
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
|
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
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
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
|
@@ -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
|
-
|
50
|
-
@connection.expects(:
|
51
|
-
@connection.expects(:query).with(
|
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
|
-
|
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
|
-
|
46
|
-
|
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
|
-
|
54
|
-
|
55
|
-
returns(mock(:cmdtuples => 1))
|
50
|
+
mock_delete_tuples(1)
|
51
|
+
mock_update_soa_tuples(1)
|
56
52
|
|
57
|
-
assert_true
|
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(
|
63
|
-
returns(mock(:cmdtuples =>
|
87
|
+
with(query_delete, [domain_id, fqdn, record_type]).
|
88
|
+
returns(mock(:cmdtuples => cmdtuples))
|
89
|
+
end
|
64
90
|
|
65
|
-
|
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, '
|
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 '
|
12
|
+
assert_equal 'echo pdnssec', @provider.pdnssec
|
13
13
|
end
|
14
14
|
|
15
|
-
|
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.
|
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
|
-
|
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(:
|
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
|
-
|
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
|
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(
|
35
|
+
@provider.expects(:rectify_zone).with('example.com').returns(false)
|
152
36
|
|
153
|
-
|
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
|
-
|
50
|
+
def test_rectify_zone_success
|
51
|
+
@provider.logger.expects(:debug).with('running: echo pdnssec rectify-zone "example.com"')
|
165
52
|
|
166
|
-
|
167
|
-
'test.example.com'
|
53
|
+
assert_true @provider.rectify_zone 'example.com'
|
168
54
|
end
|
169
55
|
|
170
|
-
def
|
171
|
-
|
172
|
-
end
|
56
|
+
def test_rectify_zone_failure
|
57
|
+
@provider = Proxy::Dns::Powerdns::Record.new('localhost', 86400, 'false')
|
173
58
|
|
174
|
-
|
175
|
-
|
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
|
-
|
179
|
-
'2001:db8:1234:abcd::1'
|
62
|
+
assert_false @provider.rectify_zone 'example.com'
|
180
63
|
end
|
181
64
|
|
182
|
-
def
|
183
|
-
|
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.
|
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:
|
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
|
-
|
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/
|
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/
|
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
|