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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcaa697157000cc1bf04fd72aee445d2ecd8b8b34352eb99d8e5544903c92146
4
- data.tar.gz: 453b90bf06d3555904e3d60177979a09cdaca5370a63aa5634fc3322f8527ea2
3
+ metadata.gz: ce291e6fbcef26df114b7bbf86e5e2ebd21bfdccf8734f5a9c68976ef92bdf2b
4
+ data.tar.gz: 2138a7322d533f1de25e2bd7dc69a5046acfda2f352520a64cbccf209f8a7b35
5
5
  SHA512:
6
- metadata.gz: 1386094b965dc6309249f816dc509fcfb6810e5dc2ae3601709a3ccddfb9a6ab1e6315cc8e5b5ac62c0edc09ad02cdfa428d2a593df8c5975bd6d35929254235
7
- data.tar.gz: b927d38959ed6496c65e6fcb98525b9a189c96621b159ece23ec06b382b6df87c9b5b78da996d2fbe6524cad03c95e8d0cb6376b4627bf83c0be6c17a7d0c441
6
+ metadata.gz: ec4b883bb2bde7c4803ddd623a4ff95866a7d0505e1493f76af61abd6d904f2f81f40791a7902bbb380def97e82320cc0d1c57ba54fe02d87c726f41e6cb7f51
7
+ data.tar.gz: 5f2e9b36f150961bbb40df71c3aa917e8572d509ecb28aa547b75442adacba8c2f3cc7d642469a0b63402b09334d747c7bdf3245b264b233dae37daea22fc96c
@@ -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@v5
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
@@ -1,3 +1,3 @@
1
1
  module RecordStore
2
- VERSION = '8.0.10'.freeze
2
+ VERSION = '9.0.0'.freeze
3
3
  end
@@ -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
- "#{RecordStore.implicit_records_templates_path}/#{filename}"
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
 
@@ -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 = RecordStore::Zone::Config.new(**config.deep_symbolize_keys)
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
- return if unrooted_name.casecmp?(Array(authority).first.to_s)
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', '< 11.2'
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.81.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: 8.0.10
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: '11.2'
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: '11.2'
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.81.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.81.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: 3.7.2
464
+ rubygems_version: 4.0.21
451
465
  specification_version: 4
452
466
  summary: Manage DNS using git
453
467
  test_files: []