record_store 8.0.10 → 9.0.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 +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +6 -0
- data/README.md +2 -0
- data/lib/record_store/version.rb +1 -1
- data/lib/record_store/zone/config/implicit_record_template.rb +21 -1
- data/lib/record_store/zone.rb +38 -2
- data/record_store.gemspec +3 -2
- metadata +20 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ce291e6fbcef26df114b7bbf86e5e2ebd21bfdccf8734f5a9c68976ef92bdf2b
|
|
4
|
+
data.tar.gz: 2138a7322d533f1de25e2bd7dc69a5046acfda2f352520a64cbccf209f8a7b35
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ec4b883bb2bde7c4803ddd623a4ff95866a7d0505e1493f76af61abd6d904f2f81f40791a7902bbb380def97e82320cc0d1c57ba54fe02d87c726f41e6cb7f51
|
|
7
|
+
data.tar.gz: 5f2e9b36f150961bbb40df71c3aa917e8572d509ecb28aa547b75442adacba8c2f3cc7d642469a0b63402b09334d747c7bdf3245b264b233dae37daea22fc96c
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -14,7 +14,7 @@ jobs:
|
|
|
14
14
|
ruby: [ 3.2, 3.3, 3.4 ]
|
|
15
15
|
name: Test Ruby ${{ matrix.ruby }}
|
|
16
16
|
steps:
|
|
17
|
-
- uses: actions/checkout@
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
18
|
- name: Install dependencies
|
|
19
19
|
run: |
|
|
20
20
|
sudo apt-get update && sudo apt-get install build-essential libcurl4-openssl-dev
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 9.0.0
|
|
4
|
+
- Only load implicit record templates by plain file name from directly inside `implicit_records_templates_path`. Template names containing path separators, `.` or `..`, and symlinks that resolve outside that directory now raise an error. Zone definitions that referenced templates this way must move them into the templates directory and use the plain file name.
|
|
5
|
+
|
|
6
|
+
## 8.0.11
|
|
7
|
+
- Modified Zone#fetch_authority to handle SOA answer sections from authoritative servers
|
|
8
|
+
|
|
3
9
|
## 8.0.10
|
|
4
10
|
- Fix Cloudflare provider to skip empty changesets
|
|
5
11
|
- Add TXT record denormalization for DNSimple provider
|
data/README.md
CHANGED
|
@@ -135,6 +135,8 @@ Record Store supports injecting implicit records into a zone based on templates.
|
|
|
135
135
|
|
|
136
136
|
Templates can help reduce zone file bloat where instead of defining many generic literals within the zone file for a given criteria zone record, these generic records can be implicitly injected into the `Zone` at the time of initialization based on information provided within the template.
|
|
137
137
|
|
|
138
|
+
Zone definitions reference templates by plain file name (for example `implicit_records_templates: [acme-challenge.yml.erb]`), and only files directly inside `implicit_records_templates_path` can be loaded. Names containing path separators, `.` or `..`, and symlinks that resolve outside that directory are rejected. Templates are rendered as ERB, so treat the templates directory as code: restrict who can change it to people you would trust to change Record Store itself.
|
|
139
|
+
|
|
138
140
|
----
|
|
139
141
|
|
|
140
142
|
# Development
|
data/lib/record_store/version.rb
CHANGED
|
@@ -25,6 +25,8 @@ module RecordStore
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
class ImplicitRecordTemplate
|
|
28
|
+
class InvalidTemplateFilename < ArgumentError; end
|
|
29
|
+
|
|
28
30
|
class << self
|
|
29
31
|
def from_file(filename:)
|
|
30
32
|
filepath = template_filepath_for(filename: filename)
|
|
@@ -43,8 +45,26 @@ module RecordStore
|
|
|
43
45
|
|
|
44
46
|
private
|
|
45
47
|
|
|
48
|
+
# Templates are rendered as ERB, so only files directly inside the
|
|
49
|
+
# configured templates directory may be loaded. Zone definitions name a
|
|
50
|
+
# template by its plain file name; anything that could resolve elsewhere
|
|
51
|
+
# (path separators, `.`/`..`, or a symlink out of the directory) is rejected.
|
|
46
52
|
def template_filepath_for(filename:)
|
|
47
|
-
|
|
53
|
+
name = filename.to_s
|
|
54
|
+
base = Pathname.new(RecordStore.implicit_records_templates_path).realpath
|
|
55
|
+
|
|
56
|
+
if name.empty? || name == '.' || name == '..' || name != File.basename(name)
|
|
57
|
+
raise InvalidTemplateFilename,
|
|
58
|
+
"implicit record template #{filename.inspect} must be a plain file name in #{base}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
path = base.join(name).realpath
|
|
62
|
+
unless path.dirname == base
|
|
63
|
+
raise InvalidTemplateFilename,
|
|
64
|
+
"implicit record template #{filename.inspect} resolves outside #{base}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
path.to_s
|
|
48
68
|
end
|
|
49
69
|
end
|
|
50
70
|
|
data/lib/record_store/zone.rb
CHANGED
|
@@ -80,7 +80,7 @@ module RecordStore
|
|
|
80
80
|
|
|
81
81
|
def initialize(name:, records: [], config: {}, abstract_syntax_trees: {})
|
|
82
82
|
@name = Record.ensure_ends_with_dot(name)
|
|
83
|
-
@config =
|
|
83
|
+
@config = build_config(config)
|
|
84
84
|
@records = build_records(records)
|
|
85
85
|
@abstract_syntax_trees = abstract_syntax_trees
|
|
86
86
|
end
|
|
@@ -143,6 +143,12 @@ module RecordStore
|
|
|
143
143
|
|
|
144
144
|
def fetch_authority(nameserver = ROOT_SERVERS.sample)
|
|
145
145
|
authority = fetch_soa(nameserver) do |reply, _name|
|
|
146
|
+
# If we get a valid SOA answer, the nameserver is authoritative, so fetch NS records directly
|
|
147
|
+
if reply.answer.any? && reply.answer.first[0].to_s == unrooted_name
|
|
148
|
+
break fetch_ns_records(nameserver)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# For NXDOMAIN or referral responses, continue with authority section
|
|
146
152
|
break if reply.answer.any?
|
|
147
153
|
|
|
148
154
|
raise "No authority found (#{name})" if reply.authority.none?
|
|
@@ -151,7 +157,16 @@ module RecordStore
|
|
|
151
157
|
end
|
|
152
158
|
|
|
153
159
|
# candidate DNS name is returned instead when NXDomain or other error
|
|
154
|
-
|
|
160
|
+
# In this case, query the parent domain's NS records
|
|
161
|
+
if authority.is_a?(Array) && authority.first.is_a?(Resolv::DNS::Name) &&
|
|
162
|
+
unrooted_name.casecmp?(authority.first.to_s)
|
|
163
|
+
# Extract parent domain from unrooted_name (e.g., "sub.example.com" -> "example.com")
|
|
164
|
+
parts = unrooted_name.split('.')
|
|
165
|
+
return if parts.length <= 1 # No parent domain available (TLD)
|
|
166
|
+
|
|
167
|
+
parent_domain = parts[1..-1].join('.') + '.'
|
|
168
|
+
return fetch_ns_records_for_domain(parent_domain, nameserver)
|
|
169
|
+
end
|
|
155
170
|
|
|
156
171
|
authority
|
|
157
172
|
end
|
|
@@ -164,6 +179,21 @@ module RecordStore
|
|
|
164
179
|
end
|
|
165
180
|
end
|
|
166
181
|
|
|
182
|
+
def fetch_ns_records(nameserver)
|
|
183
|
+
fetch_ns_records_for_domain(name, nameserver)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def fetch_ns_records_for_domain(domain, nameserver)
|
|
187
|
+
Resolv::DNS.open(nameserver: nameserver) do |resolv|
|
|
188
|
+
resources = resolv.getresources(domain, Resolv::DNS::Resource::IN::NS)
|
|
189
|
+
return nil if resources.empty?
|
|
190
|
+
|
|
191
|
+
resources.map.with_index do |ns, index|
|
|
192
|
+
Record::NS.new(ttl: ns.ttl, fqdn: domain, nsdname: ns.name.to_s, record_id: index)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
167
197
|
def resolve_authority(authority)
|
|
168
198
|
nameservers = authority.map { |a| a.last.name.to_s }
|
|
169
199
|
|
|
@@ -194,6 +224,12 @@ module RecordStore
|
|
|
194
224
|
end
|
|
195
225
|
end
|
|
196
226
|
|
|
227
|
+
def build_config(config)
|
|
228
|
+
RecordStore::Zone::Config.new(**config.deep_symbolize_keys)
|
|
229
|
+
rescue Config::ImplicitRecordTemplate::InvalidTemplateFilename => e
|
|
230
|
+
raise e.exception("zone #{unrooted_name}: #{e.message}")
|
|
231
|
+
end
|
|
232
|
+
|
|
197
233
|
def build_records(records)
|
|
198
234
|
all_records = records.map { |record| Record.build_from_yaml_definition(record) }
|
|
199
235
|
|
data/record_store.gemspec
CHANGED
|
@@ -28,10 +28,11 @@ Gem::Specification.new do |spec|
|
|
|
28
28
|
|
|
29
29
|
spec.add_runtime_dependency 'activemodel', '>= 4.2'
|
|
30
30
|
spec.add_runtime_dependency 'activesupport', '>= 4.2'
|
|
31
|
+
spec.add_runtime_dependency 'csv'
|
|
31
32
|
spec.add_runtime_dependency 'ejson'
|
|
32
33
|
spec.add_runtime_dependency 'thor', '>= 1.4.0'
|
|
33
34
|
|
|
34
|
-
spec.add_runtime_dependency 'dnsimple', '>= 4.4', '<
|
|
35
|
+
spec.add_runtime_dependency 'dnsimple', '>= 4.4', '< 12.3'
|
|
35
36
|
spec.add_runtime_dependency 'fog-dynect', '>= 0.4', '< 0.6'
|
|
36
37
|
spec.add_runtime_dependency 'fog-json'
|
|
37
38
|
spec.add_runtime_dependency 'fog-xml'
|
|
@@ -46,7 +47,7 @@ Gem::Specification.new do |spec|
|
|
|
46
47
|
spec.add_development_dependency 'mocha'
|
|
47
48
|
spec.add_development_dependency 'pry'
|
|
48
49
|
spec.add_development_dependency 'rake'
|
|
49
|
-
spec.add_development_dependency 'rubocop', '~> 1.
|
|
50
|
+
spec.add_development_dependency 'rubocop', '~> 1.88.0'
|
|
50
51
|
spec.add_development_dependency 'rubocop-shopify', '~> 2.15.1'
|
|
51
52
|
spec.add_development_dependency 'vcr'
|
|
52
53
|
spec.add_development_dependency 'webmock'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: record_store
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 9.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Willem van Bergen
|
|
@@ -38,6 +38,20 @@ dependencies:
|
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '4.2'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: csv
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: ejson
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -75,7 +89,7 @@ dependencies:
|
|
|
75
89
|
version: '4.4'
|
|
76
90
|
- - "<"
|
|
77
91
|
- !ruby/object:Gem::Version
|
|
78
|
-
version: '
|
|
92
|
+
version: '12.3'
|
|
79
93
|
type: :runtime
|
|
80
94
|
prerelease: false
|
|
81
95
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -85,7 +99,7 @@ dependencies:
|
|
|
85
99
|
version: '4.4'
|
|
86
100
|
- - "<"
|
|
87
101
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '
|
|
102
|
+
version: '12.3'
|
|
89
103
|
- !ruby/object:Gem::Dependency
|
|
90
104
|
name: fog-dynect
|
|
91
105
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -298,14 +312,14 @@ dependencies:
|
|
|
298
312
|
requirements:
|
|
299
313
|
- - "~>"
|
|
300
314
|
- !ruby/object:Gem::Version
|
|
301
|
-
version: 1.
|
|
315
|
+
version: 1.88.0
|
|
302
316
|
type: :development
|
|
303
317
|
prerelease: false
|
|
304
318
|
version_requirements: !ruby/object:Gem::Requirement
|
|
305
319
|
requirements:
|
|
306
320
|
- - "~>"
|
|
307
321
|
- !ruby/object:Gem::Version
|
|
308
|
-
version: 1.
|
|
322
|
+
version: 1.88.0
|
|
309
323
|
- !ruby/object:Gem::Dependency
|
|
310
324
|
name: rubocop-shopify
|
|
311
325
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -447,7 +461,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
447
461
|
- !ruby/object:Gem::Version
|
|
448
462
|
version: '0'
|
|
449
463
|
requirements: []
|
|
450
|
-
rubygems_version:
|
|
464
|
+
rubygems_version: 4.0.21
|
|
451
465
|
specification_version: 4
|
|
452
466
|
summary: Manage DNS using git
|
|
453
467
|
test_files: []
|