inspec-core 5.22.3 → 5.22.36

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.
@@ -0,0 +1,372 @@
1
+ require "ast"
2
+ require "rubocop-ast"
3
+ module Inspec
4
+ class Profile
5
+ class AstHelper
6
+ class CollectorBase
7
+ include Parser::AST::Processor::Mixin
8
+ include RuboCop::AST::Traversal
9
+
10
+ attr_reader :memo
11
+ def initialize(memo)
12
+ @memo = memo
13
+ end
14
+ end
15
+
16
+ class InputCollectorBase < CollectorBase
17
+ VALID_INPUT_OPTIONS = %i{name value type required priority pattern profile sensitive}.freeze
18
+
19
+ REQUIRED_VALUES_MAP = {
20
+ true: true,
21
+ false: false,
22
+ }.freeze
23
+
24
+ def initialize(memo)
25
+ @memo = memo
26
+ end
27
+
28
+ def collect_input(input_children)
29
+ input_name = input_children.children[2].value
30
+
31
+ # Check if memo[:inputs] already has a value for the input_name, if yes, then skip adding it to the array
32
+ unless memo[:inputs].any? { |input| input[:name] == input_name }
33
+ # The value will be updated if available in the input_children
34
+ opts = {
35
+ value: "Input '#{input_name}' does not have a value. Skipping test.",
36
+ }
37
+
38
+ if input_children.children[3]&.type == :hash
39
+ input_children.children[3].children.each do |child_node|
40
+ if VALID_INPUT_OPTIONS.include?(child_node.key.value)
41
+ if child_node.value.class == RuboCop::AST::Node && REQUIRED_VALUES_MAP.key?(child_node.value.type)
42
+ opts.merge!(child_node.key.value => REQUIRED_VALUES_MAP[child_node.value.type])
43
+ elsif child_node.value.class == RuboCop::AST::HashNode
44
+ # Here value will be a hash
45
+ values = {}
46
+ child_node.value.children.each do |grand_child_node|
47
+ values.merge!(grand_child_node.key.value => grand_child_node.value.value)
48
+ end
49
+ opts.merge!(child_node.key.value => values)
50
+ else
51
+ opts.merge!(child_node.key.value => child_node.value.value)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # TODO: Add rules for handling the input options or use existing rules if available
58
+ # 1. Handle pattern matching for the given input value
59
+ # 2. Handle data-type matching for the given input value
60
+ # 3. Handle required flag for the given input value
61
+ # 4. Handle sensitive flag for the given input value
62
+ memo[:inputs] ||= []
63
+ input_hash = {
64
+ name: input_name,
65
+ options: opts,
66
+ }
67
+ memo[:inputs] << input_hash
68
+ end
69
+ end
70
+
71
+ def check_and_collect_input(node)
72
+ if input_pattern_match?(node)
73
+ collect_input(node)
74
+ else
75
+ node.children.each do |child_node|
76
+ check_and_collect_input(child_node) if input_pattern_match?(child_node)
77
+ end
78
+ end
79
+ end
80
+
81
+ def input_pattern_match?(node)
82
+ RuboCop::AST::NodePattern.new("(send nil? :input ...)").match(node)
83
+ end
84
+ end
85
+
86
+ class ImpactCollector < CollectorBase
87
+ def on_send(node)
88
+ if RuboCop::AST::NodePattern.new("(send nil? :impact ...)").match(node)
89
+ memo[:impact] = node.children[2].value
90
+ end
91
+ end
92
+ end
93
+
94
+ class DescCollector < CollectorBase
95
+ def on_send(node)
96
+ if RuboCop::AST::NodePattern.new("(send nil? :desc ...)").match(node)
97
+ memo[:descriptions] ||= {}
98
+ if node.children[2] && node.children[3]
99
+ # NOTE: This assumes the description is as below
100
+ # desc 'label', 'An optional description with a label' # Pair a part of the description with a label
101
+ memo[:descriptions] = memo[:descriptions].merge(node.children[2].value => node.children[3].value)
102
+ else
103
+ memo[:desc] = node.children[2].value
104
+ memo[:descriptions] = memo[:descriptions].merge(default: node.children[2].value)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ class TitleCollector < CollectorBase
111
+ def on_send(node)
112
+ if RuboCop::AST::NodePattern.new("(send nil? :title ...)").match(node)
113
+ # TODO - title may not be a simple string
114
+ memo[:title] = node.children[2].value
115
+ end
116
+ end
117
+ end
118
+
119
+ class TagCollector < CollectorBase
120
+
121
+ ACCPETABLE_TAG_TYPE_TO_VALUES = {
122
+ false: false,
123
+ true: true,
124
+ nil: nil,
125
+ }.freeze
126
+
127
+ def on_send(node)
128
+ if RuboCop::AST::NodePattern.new("(send nil? :tag ...)").match(node)
129
+ memo[:tags] ||= {}
130
+
131
+ node.children[2..-1].each do |tag_node|
132
+ collect_tags(tag_node)
133
+ end
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ def collect_tags(tag_node)
140
+ if tag_node.type == :str || tag_node.type == :sym
141
+ memo[:tags] = memo[:tags].merge(tag_node.value => nil)
142
+ elsif tag_node.type == :hash
143
+ tags_coll = {}
144
+ tag_node.children.each do |child_tag|
145
+ key = child_tag.key.value
146
+ if child_tag.value.type == :array
147
+ value = child_tag.value.children.map { |child_node| child_node.type == :str ? child_node.children.first : nil }
148
+ elsif ACCPETABLE_TAG_TYPE_TO_VALUES.key?(child_tag.value.type)
149
+ value = ACCPETABLE_TAG_TYPE_TO_VALUES[child_tag.value.type]
150
+ else
151
+ if child_tag.value.children.first.class == RuboCop::AST::SendNode
152
+ # Cases like this: (where there is no assignment of the value to a variable like gcp_project_id)
153
+ # tag project: gcp_project_id.to_s
154
+ #
155
+ # Lecacy evaluates gcp_project_id.to_s and then passes the value to the tag
156
+ # We are not evaluating the value here, so we are just passing the value as it is
157
+ #
158
+ # TODO: Do we need to evaluate the value here?
159
+ # (byebug) child_tag.value
160
+ # s(:send,
161
+ # s(:send, nil, :gcp_project_id), :to_s)
162
+ value = child_tag.value.children.first.children[1]
163
+ elsif child_tag.value.children.first.class == RuboCop::AST::Node
164
+ # Cases like this:
165
+ # control_id = '1.1'
166
+ # tag cis_gcp: control_id.to_s
167
+ value = child_tag.value.children.first.children[0]
168
+ else
169
+ value = child_tag.value.value
170
+ end
171
+ end
172
+ tags_coll.merge!(key => value)
173
+ end
174
+ memo[:tags] = memo[:tags].merge(tags_coll)
175
+ end
176
+ end
177
+ end
178
+
179
+ class RefCollector < CollectorBase
180
+ def on_send(node)
181
+ if RuboCop::AST::NodePattern.new("(send nil? :ref ...)").match(node)
182
+ # Construct the array of refs hash as below
183
+
184
+ # "refs": [
185
+ # {
186
+ # "url": "http://",
187
+ # "ref": "Some ref"
188
+ # },
189
+ # {
190
+ # "ref": "https://",
191
+ # }
192
+ # ]
193
+
194
+ # node.children[1] && node.children[1] == :ref - we don't need this check as the pattern match above will take care of it
195
+ return unless node.children[2]
196
+
197
+ references = {}
198
+
199
+ if node.children[2].type == :begin
200
+ # Case for: ref ({:ref=>"Some ref", :url=>"https://"})
201
+ # find the hash node
202
+ iterate_child_and_collect_ref(node.children[2].children, references)
203
+ elsif node.children[2].type == :str
204
+ # Case for: ref "ref1", url: "http://",
205
+ references.merge!(ref: node.children[2].value)
206
+ iterate_child_and_collect_ref(node.children[3..-1], references)
207
+ end
208
+
209
+ memo[:refs] ||= []
210
+ memo[:refs] << references
211
+ end
212
+ end
213
+
214
+ private
215
+
216
+ def iterate_child_and_collect_ref(child_node, references = {})
217
+ child_node.each do |ref_node|
218
+ if ref_node.type == :hash
219
+ iterate_hash_node(ref_node, references)
220
+ elsif ref_node.type == :str
221
+ references.merge!(ref_node.value => nil)
222
+ end
223
+ end
224
+ end
225
+
226
+ def iterate_hash_node(hash_node, references = {})
227
+ # hash node like this:
228
+ # s(:hash,
229
+ # s(:pair,
230
+ # s(:sym, :url),
231
+ # s(:str, "https://")))
232
+ #
233
+ # or like this:
234
+ # (byebug) hash_node
235
+ # s(:hash,
236
+ # s(:pair,
237
+ # s(:sym, :url),
238
+ # s(:send,
239
+ # s(:send, nil, :cis_url), :to_s)))
240
+ hash_node.children.each do |child_node|
241
+ if child_node.type == :pair
242
+ if child_node.value.children.first.class == RuboCop::AST::SendNode
243
+ # Case like this (where there is no assignment of the value to a variable like cis_url)
244
+ # ref 'CIS Benchmark', url: cis_url.to_s
245
+ # Lecacy evaluates cis_url.to_s and then passes the value to the ref
246
+ # We are not evaluating the value here, so we are just passing the value as it is
247
+ #
248
+ # TODO: Do we need to evaluate the value here?
249
+ #
250
+ # (byebug) child_node.value.children.first
251
+ # s(:send, nil, :cis_url)
252
+ value = child_node.value.children.first.children[1]
253
+ elsif child_node.value.class == RuboCop::AST::SendNode
254
+ # Cases like this:
255
+ # cis_url = attribute('cis_url')
256
+ # ref 'CIS Benchmark', url: cis_url.to_s
257
+ value = child_node.value.children.first.children[0]
258
+ else
259
+ # Cases like this: ref 'CIS Benchmark - 2', url: "https://"
260
+ # require 'byebug'; byebug
261
+ value = child_node.value.value
262
+ end
263
+ references.merge!(child_node.key.value => value)
264
+ end
265
+ end
266
+ end
267
+ end
268
+
269
+ class ControlIDCollector < CollectorBase
270
+ attr_reader :seen_control_ids, :source_location_ref, :include_tests
271
+ def initialize(memo, source_location_ref, include_tests: false)
272
+ @memo = memo
273
+ @seen_control_ids = {}
274
+ @source_location_ref = source_location_ref
275
+ @include_tests = include_tests
276
+ end
277
+
278
+ def on_block(block_node)
279
+ if RuboCop::AST::NodePattern.new("(block (send nil? :control ...) ...)").match(block_node)
280
+ # NOTE: Assuming begin block is at the index 2
281
+ begin_block = block_node.children[2]
282
+ control_node = block_node.children[0]
283
+
284
+ # TODO - This assumes the control ID is always a plain string, which we know it is often not!
285
+ control_id = control_node.children[2].value
286
+ # TODO - BUG - this keeps seeing the same nodes over and over againa, and so repeating control IDs. We are ignoring duplicate control IDs, which is incorrect.
287
+ return if seen_control_ids[control_id]
288
+
289
+ seen_control_ids[control_id] = true
290
+
291
+ control_data = {
292
+ id: control_id,
293
+ code: block_node.source,
294
+ source_location: {
295
+ line: block_node.first_line,
296
+ ref: source_location_ref,
297
+ },
298
+ title: nil,
299
+ desc: nil,
300
+ descriptions: {},
301
+ impact: 0.5,
302
+ refs: [],
303
+ tags: {},
304
+ }
305
+ control_data[:checks] = [] if include_tests
306
+
307
+ # Scan the code block for per-control metadata
308
+ collectors = []
309
+ collectors.push ImpactCollector.new(control_data)
310
+ collectors.push DescCollector.new(control_data)
311
+ collectors.push TitleCollector.new(control_data)
312
+ collectors.push TagCollector.new(control_data)
313
+ collectors.push RefCollector.new(control_data)
314
+ collectors.push InputCollectorWithinControlBlock.new(@memo)
315
+ collectors.push TestsCollector.new(control_data) if include_tests
316
+
317
+ begin_block.each_node do |node_within_control|
318
+ collectors.each { |collector| collector.process(node_within_control) }
319
+ end
320
+
321
+ memo[:controls].push control_data
322
+ end
323
+ end
324
+ end
325
+
326
+ class InputCollectorWithinControlBlock < InputCollectorBase
327
+ def initialize(memo)
328
+ @memo = memo
329
+ end
330
+
331
+ def on_send(node)
332
+ check_and_collect_input(node)
333
+ end
334
+ end
335
+
336
+ class InputCollectorOutsideControlBlock < InputCollectorBase
337
+ def initialize(memo)
338
+ @memo = memo
339
+ end
340
+
341
+ # TODO: There is scope to refactor InputCollectorOutsideControlBlock and InputCollectorWithinControlBlock
342
+ # 1. We can have a single class for both the collectors
343
+ # 2. We can have a on_send and on_lvasgn method in the same class
344
+ # :lvasgn in ast stands for "local variable assignment"
345
+ def on_lvasgn(node)
346
+ # We are looking for the following pattern in the AST
347
+ # (lvasgn :var_name (send nil? :input ...))
348
+ # example: a = input('a') or a = input('a', value: 'b')
349
+ # and not this: a = 1
350
+ if RuboCop::AST::NodePattern.new("(lvasgn _ (send nil? :input ...))").match(node)
351
+ input_children = node.children[1]
352
+ collect_input(input_children)
353
+ end
354
+ end
355
+
356
+ def on_send(node)
357
+ check_and_collect_input(node)
358
+ end
359
+ end
360
+
361
+ class TestsCollector < CollectorBase
362
+
363
+ def on_block(node)
364
+ if RuboCop::AST::NodePattern.new("(block (send nil? :describe ...) ...)").match(node) ||
365
+ RuboCop::AST::NodePattern.new("(block (send nil? :expect ...) ...)").match(node)
366
+ memo[:checks] << node.source
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "5.22.3".freeze
2
+ VERSION = "5.22.36".freeze
3
3
  end
@@ -19,15 +19,17 @@ module Inspec
19
19
  data = nil
20
20
  if [".yaml", ".yml"].include? file_extension
21
21
  data = Secrets::YAML.resolve(file_path)
22
- data = data.inputs unless data.nil?
23
- validate_json_yaml(data)
22
+ unless data.nil?
23
+ data = data.inputs
24
+ validate_json_yaml(data)
25
+ end
24
26
  elsif file_extension == ".csv"
25
27
  data = Waivers::CSVFileReader.resolve(file_path)
26
28
  headers = Waivers::CSVFileReader.headers
27
29
  validate_headers(headers)
28
30
  elsif file_extension == ".json"
29
31
  data = Waivers::JSONFileReader.resolve(file_path)
30
- validate_json_yaml(data)
32
+ validate_json_yaml(data) unless data.nil?
31
33
  end
32
34
  output.merge!(data) if !data.nil? && data.is_a?(Hash)
33
35
 
@@ -168,10 +168,10 @@ module InspecPlugins
168
168
  end
169
169
 
170
170
  # read profile name from inspec.yml
171
- profile_name = profile.params[:name]
171
+ profile_name = profile.name
172
172
 
173
173
  # read profile version from inspec.yml
174
- profile_version = profile.params[:version]
174
+ profile_version = profile.version
175
175
 
176
176
  # check that the profile is not uploaded already,
177
177
  # confirm upload to the user (overwrite with --force)
@@ -1,4 +1,5 @@
1
- <% slugged_id = control.id.tr(" ", "_") %>
1
+ <% slugged_control_id = control.id.tr(" ", "_") %>
2
+ <% slugged_profile_id = profile.name.gsub(/\W/, "_") %>
2
3
  <%
3
4
  if enhanced_outcomes
4
5
  status = control.status
@@ -13,7 +14,7 @@
13
14
  end
14
15
  %>
15
16
 
16
- <div class="control control-status-<%= status %>" id="control-<%= slugged_id %>">
17
+ <div class="control control-status-<%= status %>" id="profile-<%= slugged_profile_id %>-control-<%= slugged_control_id %>">
17
18
 
18
19
  <%
19
20
  # Determine range of impact
@@ -29,7 +30,7 @@
29
30
  %>
30
31
 
31
32
  <h3 class="control-title">Control <code><%= control.id %></code></h3>
32
- <table class="control-metadata info" id="control-metadata-<%= slugged_id %>">
33
+ <table class="control-metadata info" id="profile-<%= slugged_profile_id %>-control-metadata-<%= slugged_control_id %>">
33
34
  <caption>Control Table</caption>
34
35
  <tr class="status status-<%= status %>"><th>Status:</th><td><div><%= status.capitalize %></div></td></tr>
35
36
  <% if control.title %><tr class="title"><th>Title:</th><td><%= control.title %></td></tr> <% end %>
@@ -64,9 +65,9 @@
64
65
  <tr class="code">
65
66
  <th>Source Code:</th>
66
67
  <td>
67
- <input type="button" class="show-source-code" id="show-code-<%= slugged_id %>" value="Show Source"/>
68
- <input type="button" class="hide-source-code hidden" id="hide-code-<%= slugged_id %>" value="Hide Source"/>
69
- <pre class="source-code hidden" id="source-code-<%= slugged_id %>">
68
+ <input type="button" class="show-source-code" id="show-code-<%= slugged_profile_id %>-<%= slugged_control_id %>" value="Show Source"/>
69
+ <input type="button" class="hide-source-code hidden" id="hide-code-<%= slugged_profile_id %>-<%= slugged_control_id %>" value="Hide Source"/>
70
+ <pre class="source-code hidden" id="source-code-<%= slugged_profile_id %>-<%= slugged_control_id %>">
70
71
  <code>
71
72
  <%= control.code %>
72
73
  </code>
@@ -11,17 +11,17 @@ function removeCssClass(id, cls) {
11
11
  }
12
12
 
13
13
  function handleShowSource(evt) {
14
- var control_id = evt.srcElement.id.replace("show-code-", "")
14
+ var slugged_id = evt.srcElement.id.replace("show-code-", "")
15
15
  addCssClass(evt.srcElement.id, "hidden")
16
- removeCssClass("hide-code-" + control_id, "hidden")
17
- removeCssClass("source-code-" + control_id, "hidden")
16
+ removeCssClass("hide-code-" + slugged_id, "hidden")
17
+ removeCssClass("source-code-" + slugged_id, "hidden")
18
18
  }
19
19
 
20
20
  function handleHideSource(evt) {
21
- var control_id = evt.srcElement.id.replace("hide-code-", "")
21
+ var slugged_id = evt.srcElement.id.replace("hide-code-", "")
22
22
  addCssClass(evt.srcElement.id, "hidden")
23
- addCssClass("source-code-" + control_id, "hidden")
24
- removeCssClass("show-code-" + control_id, "hidden")
23
+ addCssClass("source-code-" + slugged_id, "hidden")
24
+ removeCssClass("show-code-" + slugged_id, "hidden")
25
25
  }
26
26
 
27
27
  function handleSelectorChange(evt) {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.22.3
4
+ version: 5.22.36
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef InSpec Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-16 00:00:00.000000000 Z
11
+ date: 2023-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef-telemetry
@@ -59,7 +59,7 @@ dependencies:
59
59
  version: '0.20'
60
60
  - - "<"
61
61
  - !ruby/object:Gem::Version
62
- version: '2.0'
62
+ version: 1.3.0
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
@@ -69,7 +69,7 @@ dependencies:
69
69
  version: '0.20'
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
- version: '2.0'
72
+ version: 1.3.0
73
73
  - !ruby/object:Gem::Dependency
74
74
  name: method_source
75
75
  requirement: !ruby/object:Gem::Requirement
@@ -119,7 +119,7 @@ dependencies:
119
119
  version: '3.9'
120
120
  - - "<="
121
121
  - !ruby/object:Gem::Version
122
- version: '3.11'
122
+ version: '3.12'
123
123
  type: :runtime
124
124
  prerelease: false
125
125
  version_requirements: !ruby/object:Gem::Requirement
@@ -129,7 +129,7 @@ dependencies:
129
129
  version: '3.9'
130
130
  - - "<="
131
131
  - !ruby/object:Gem::Version
132
- version: '3.11'
132
+ version: '3.12'
133
133
  - !ruby/object:Gem::Dependency
134
134
  name: rspec-its
135
135
  requirement: !ruby/object:Gem::Requirement
@@ -167,7 +167,7 @@ dependencies:
167
167
  version: '3.4'
168
168
  - - "<"
169
169
  - !ruby/object:Gem::Version
170
- version: '5.0'
170
+ version: '6.0'
171
171
  type: :runtime
172
172
  prerelease: false
173
173
  version_requirements: !ruby/object:Gem::Requirement
@@ -177,7 +177,7 @@ dependencies:
177
177
  version: '3.4'
178
178
  - - "<"
179
179
  - !ruby/object:Gem::Version
180
- version: '5.0'
180
+ version: '6.0'
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: mixlib-log
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -325,7 +325,7 @@ dependencies:
325
325
  version: '1.5'
326
326
  - - "<"
327
327
  - !ruby/object:Gem::Version
328
- version: '2.0'
328
+ version: '3.0'
329
329
  type: :runtime
330
330
  prerelease: false
331
331
  version_requirements: !ruby/object:Gem::Requirement
@@ -335,7 +335,7 @@ dependencies:
335
335
  version: '1.5'
336
336
  - - "<"
337
337
  - !ruby/object:Gem::Version
338
- version: '2.0'
338
+ version: '3.0'
339
339
  - !ruby/object:Gem::Dependency
340
340
  name: semverse
341
341
  requirement: !ruby/object:Gem::Requirement
@@ -716,6 +716,7 @@ files:
716
716
  - lib/inspec/utils/parser.rb
717
717
  - lib/inspec/utils/pkey_reader.rb
718
718
  - lib/inspec/utils/podman.rb
719
+ - lib/inspec/utils/profile_ast_helpers.rb
719
720
  - lib/inspec/utils/run_data_filters.rb
720
721
  - lib/inspec/utils/simpleconfig.rb
721
722
  - lib/inspec/utils/spdx.rb