inspec-core 5.22.3 → 5.22.36

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