inspec-core 4.41.2 → 4.50.3

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -1
  3. data/etc/deprecations.json +1 -1
  4. data/lib/inspec/base_cli.rb +9 -1
  5. data/lib/inspec/cached_fetcher.rb +2 -2
  6. data/lib/inspec/cli.rb +21 -4
  7. data/lib/inspec/control_eval_context.rb +54 -46
  8. data/lib/inspec/dsl.rb +18 -3
  9. data/lib/inspec/fetcher/url.rb +45 -3
  10. data/lib/inspec/fetcher.rb +3 -3
  11. data/lib/inspec/globals.rb +5 -0
  12. data/lib/inspec/plugin/v1/registry.rb +6 -2
  13. data/lib/inspec/profile.rb +122 -5
  14. data/lib/inspec/resources/apache_conf.rb +8 -6
  15. data/lib/inspec/resources/cassandra.rb +64 -0
  16. data/lib/inspec/resources/cassandradb_conf.rb +47 -0
  17. data/lib/inspec/resources/cassandradb_session.rb +68 -0
  18. data/lib/inspec/resources/chrony_conf.rb +55 -0
  19. data/lib/inspec/resources/csv.rb +26 -3
  20. data/lib/inspec/resources/groups.rb +22 -3
  21. data/lib/inspec/resources/http.rb +107 -55
  22. data/lib/inspec/resources/ibmdb2_conf.rb +57 -0
  23. data/lib/inspec/resources/ibmdb2_session.rb +69 -0
  24. data/lib/inspec/resources/mssql_sys_conf.rb +48 -0
  25. data/lib/inspec/resources/opa.rb +4 -1
  26. data/lib/inspec/resources/oracle.rb +66 -0
  27. data/lib/inspec/resources/oracledb_conf.rb +40 -0
  28. data/lib/inspec/resources/oracledb_listener_conf.rb +123 -0
  29. data/lib/inspec/resources/oracledb_session.rb +23 -6
  30. data/lib/inspec/resources/postgres_session.rb +15 -10
  31. data/lib/inspec/resources/registry_key.rb +1 -1
  32. data/lib/inspec/resources/security_identifier.rb +8 -14
  33. data/lib/inspec/resources/security_policy.rb +4 -3
  34. data/lib/inspec/resources/service.rb +7 -1
  35. data/lib/inspec/resources/sybase_conf.rb +37 -0
  36. data/lib/inspec/resources/sybase_session.rb +111 -0
  37. data/lib/inspec/resources/users.rb +16 -2
  38. data/lib/inspec/resources/windows_firewall.rb +1 -1
  39. data/lib/inspec/resources/wmi.rb +1 -1
  40. data/lib/inspec/resources.rb +9 -0
  41. data/lib/inspec/run_data/profile.rb +0 -2
  42. data/lib/inspec/utils/filter.rb +1 -1
  43. data/lib/inspec/version.rb +1 -1
  44. data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +1 -1
  45. data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +1 -1
  46. data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +1 -1
  47. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +16 -15
  48. metadata +14 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ccadabfe4c0ecceecb305842e1874255ce4797a8df92e9d1d940bfe52f4b2dbb
4
- data.tar.gz: 8bed2e86c15c05fcd6ee5974e1167aab1f2ab6c2d321a6f802af738594fafe5b
3
+ metadata.gz: 506be4d9918c8af46f6b3784e8a18550cee990ad73c7c731bb1afea7197c5370
4
+ data.tar.gz: b48fa274325e96ac07185653b2eeb997ed5ea6fa39f570b0400706331a3b4d52
5
5
  SHA512:
6
- metadata.gz: ac9a9e7a80179eaa6f1a77ff4fd4aa4505a39ce389a655bdf853caeece477c58ea7ef46db749cf54a11c2e5dff5e62b09fdd950caac396ffbab4640183549924
7
- data.tar.gz: 613c20973b4b10202fe0d0aac0e53b65cb0cb201150b93665304f0a5f7d4341981768cc799865bb3d7b9ad6b9afa93f782a9651d88001001b899d85b4521e1b6
6
+ metadata.gz: 4eb19bed92e35c49395e513e263f3afdb3f59abcf873b88f0c7dc07775a64c4e6aea3db57f069b19be73e9aa92de106db95ecb4b60fc3fc4c5b080f4fc97df6c
7
+ data.tar.gz: e8eac56320e4c0e36078bdcef95dde1b6ec710d7bfdba1bbd4f8301ee0f5799c5a913f96edc5c31ba78c2dcc3647e671454b30ac430729943a0dca071191aa63
data/Gemfile CHANGED
@@ -30,7 +30,11 @@ end
30
30
  group :test do
31
31
  gem "chefstyle", "~> 2.0.3"
32
32
  gem "concurrent-ruby", "~> 1.0"
33
- gem "html-proofer", platforms: :ruby # do not attempt to run proofer on windows
33
+ if Gem.ruby_version.to_s.start_with?("2.5")
34
+ gem "html-proofer", "= 3.19.1" , platforms: :ruby # do not attempt to run proofer on windows
35
+ else
36
+ gem "html-proofer", platforms: :ruby # do not attempt to run proofer on windows
37
+ end
34
38
  gem "json_schemer", ">= 0.2.1", "< 0.2.19"
35
39
  gem "m"
36
40
  gem "minitest-sprint", "~> 1.0"
@@ -4,7 +4,7 @@
4
4
  "groups": {
5
5
  "attrs_value_replaces_default": {
6
6
  "action": "warn",
7
- "prefix": "The 'default' option for attributes is being replaced by 'value' - please use it instead."
7
+ "prefix": "The 'default' option for inputs is being replaced by 'value' - please use it instead."
8
8
  },
9
9
  "attrs_dsl": {
10
10
  "action": "ignore",
@@ -43,11 +43,15 @@ module Inspec
43
43
  begin
44
44
  if (allowed_commands & ARGV.map(&:downcase)).empty? && # Did they use a non-exempt command?
45
45
  !ARGV.empty? # Did they supply at least one command?
46
- LicenseAcceptance::Acceptor.check_and_persist(
46
+ license_acceptor_output = LicenseAcceptance::Acceptor.check_and_persist(
47
47
  Inspec::Dist::EXEC_NAME,
48
48
  Inspec::VERSION,
49
49
  logger: Inspec::Log
50
50
  )
51
+ if license_acceptor_output && ARGV.count == 1 && (ARGV.first.include? "--chef-license")
52
+ Inspec::UI.new.exit
53
+ end
54
+ license_acceptor_output
51
55
  end
52
56
  rescue LicenseAcceptance::LicenseNotAcceptedError
53
57
  Inspec::Log.error "#{Inspec::Dist::PRODUCT_NAME} cannot execute without accepting the license"
@@ -170,6 +174,10 @@ module Inspec
170
174
  desc: "After normal execution order, results are sorted by control ID, or by file (default), or randomly. None uses legacy unsorted mode."
171
175
  option :filter_empty_profiles, type: :boolean, default: false,
172
176
  desc: "Filter empty profiles (profiles without controls) from the report."
177
+ option :filter_waived_controls, type: :boolean,
178
+ desc: "Do not execute waived controls in InSpec at all. Must use with --waiver-file. Ignores `run` setting of waiver file."
179
+ option :retain_waiver_data, type: :boolean,
180
+ desc: "EXPERIMENTAL: Only works in conjunction with --filter-waived-controls, retains waiver data about controls that were skipped"
173
181
  option :command_timeout, type: :numeric,
174
182
  desc: "Maximum seconds to allow commands to run during execution.",
175
183
  long_desc: "Maximum seconds to allow commands to run during execution. A timed out command is considered an error."
@@ -6,9 +6,9 @@ module Inspec
6
6
  extend Forwardable
7
7
 
8
8
  attr_reader :cache, :target, :fetcher
9
- def initialize(target, cache)
9
+ def initialize(target, cache, opts = {})
10
10
  @target = target
11
- @fetcher = Inspec::Fetcher::Registry.resolve(target)
11
+ @fetcher = Inspec::Fetcher::Registry.resolve(target, opts)
12
12
 
13
13
  if @fetcher.nil?
14
14
  raise("Could not fetch inspec profile in #{target.inspect}.")
data/lib/inspec/cli.rb CHANGED
@@ -93,7 +93,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
93
93
  end
94
94
 
95
95
  desc "check PATH", "verify all tests at the specified PATH"
96
- option :format, type: :string
96
+ option :format, type: :string,
97
+ desc: "The output format to use doc (default), json. If valid format is not provided then it will use the default."
97
98
  profile_options
98
99
  def check(path) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
99
100
  o = config
@@ -121,8 +122,13 @@ class Inspec::InspecCLI < Inspec::BaseCLI
121
122
  end
122
123
  puts
123
124
 
124
- if result[:errors].empty? && result[:warnings].empty?
125
- ui.plain_line("No errors or warnings")
125
+ enable_offenses = !Inspec.locally_windows? # See 5723
126
+ if result[:errors].empty? && result[:warnings].empty? && result[:offenses].empty?
127
+ if enable_offenses
128
+ ui.plain_line("No errors, warnings, or offenses")
129
+ else
130
+ ui.plain_line("No errors or warnings")
131
+ end
126
132
  else
127
133
  item_msg = lambda { |item|
128
134
  pos = [item[:file], item[:line], item[:column]].compact.join(":")
@@ -134,11 +140,22 @@ class Inspec::InspecCLI < Inspec::BaseCLI
134
140
 
135
141
  puts
136
142
 
143
+ if enable_offenses && !result[:offenses].empty?
144
+ puts "Offenses:\n"
145
+ result[:offenses].each { |item| ui.cyan(" #{Inspec::UI::GLYPHS[:script_x]} #{item_msg.call(item)}\n\n") }
146
+ end
147
+
148
+ offenses = ui.cyan("#{result[:offenses].length} offenses", print: false)
137
149
  errors = ui.red("#{result[:errors].length} errors", print: false)
138
150
  warnings = ui.yellow("#{result[:warnings].length} warnings", print: false)
139
- ui.plain_line("Summary: #{errors}, #{warnings}")
151
+ if enable_offenses
152
+ ui.plain_line("Summary: #{errors}, #{warnings}, #{offenses}")
153
+ else
154
+ ui.plain_line("Summary: #{errors}, #{warnings}")
155
+ end
140
156
  end
141
157
  end
158
+
142
159
  ui.exit Inspec::UI::EXIT_USAGE_ERROR unless result[:summary][:valid]
143
160
  rescue StandardError => e
144
161
  pretty_handle_exception(e)
@@ -18,6 +18,7 @@ module Inspec
18
18
  attr_accessor :skip_file
19
19
  attr_accessor :profile_context
20
20
  attr_accessor :resources_dsl
21
+ attr_accessor :conf
21
22
 
22
23
  def initialize(profile_context, resources_dsl, backend, conf, dependencies, require_loader, skip_only_if_eval)
23
24
  @profile_context = profile_context
@@ -53,19 +54,26 @@ module Inspec
53
54
 
54
55
  def control(id, opts = {}, &block)
55
56
  opts[:skip_only_if_eval] = @skip_only_if_eval
56
- tag_ids = control_tags(&block)
57
- if (controls_list_empty? && tags_list_empty?) || control_exist_in_controls_list?(id) || tag_exist_in_control_tags?(tag_ids)
57
+ if (controls_list_empty? && tags_list_empty?) || control_exist_in_controls_list?(id)
58
58
  register_control(Inspec::Rule.new(id, profile_id, resources_dsl, opts, &block))
59
+ elsif !tags_list_empty?
60
+ # Inside elsif rule is initialised before registering it because it enables fetching of control tags
61
+ # This condition is only true when --tags option is used
62
+ inspec_rule = Inspec::Rule.new(id, profile_id, resources_dsl, opts, &block)
63
+ tag_ids = control_tags(inspec_rule)
64
+ register_control(inspec_rule) if tag_exist_in_control_tags?(tag_ids)
59
65
  end
60
66
  end
61
67
 
62
68
  alias rule control
63
69
 
64
- def control_tags(&block)
65
- tag_source = block.source.split("\n").select { |src| src.split.first.eql?("tag") }
66
- tag_source = tag_source.map { |src| src.sub("tag", "").strip }.map { |src| src.split(",").map { |final_src| final_src.sub(/([^:]*):/, "") } }.flatten
67
- output = tag_source.map { |src| src.sub(/\[|\]/, "") }.map { |src| instance_eval(src) }
68
- output.compact.uniq
70
+ def control_tags(inspec_rule)
71
+ all_tags = []
72
+ inspec_rule.tag.each do |key, value|
73
+ all_tags.push(key)
74
+ all_tags.push(value) unless value.nil?
75
+ end
76
+ all_tags.flatten.compact.uniq.map(&:to_s)
69
77
  rescue
70
78
  []
71
79
  end
@@ -182,29 +190,24 @@ module Inspec
182
190
  @skip_file = true
183
191
  end
184
192
 
185
- private
186
-
187
- def block_location(block, alternate_caller)
188
- if block.nil?
189
- alternate_caller[/^(.+:\d+):in .+$/, 1] || "unknown"
190
- else
191
- path, line = block.source_location
192
- "#{File.basename(path)}:#{line}"
193
+ # Check if the given control exist in the --tags option
194
+ def tag_exist_in_control_tags?(tag_ids)
195
+ tag_option_matches_with_list = false
196
+ if !tag_ids.empty? && !tag_ids.nil? && profile_tag_config_exist?
197
+ tag_option_matches_with_list = !(tag_ids & @conf["profile"].include_tags_list).empty?
198
+ unless tag_option_matches_with_list
199
+ @conf["profile"].include_tags_list.any? do |inclusion|
200
+ # Try to see if the inclusion is a regex, and if it matches
201
+ if inclusion.is_a?(Regexp)
202
+ tag_ids.each do |id|
203
+ tag_option_matches_with_list = (inclusion =~ id)
204
+ break if tag_option_matches_with_list
205
+ end
206
+ end
207
+ end
208
+ end
193
209
  end
194
- end
195
-
196
- # Returns true if configuration hash is not empty and it contains the list of controls is not empty
197
- def profile_config_exist?
198
- !@conf.empty? && @conf.key?("profile") && !@conf["profile"].include_controls_list.empty?
199
- end
200
-
201
- def profile_tag_config_exist?
202
- !@conf.empty? && @conf.key?("profile") && !@conf["profile"].include_tags_list.empty?
203
- end
204
-
205
- # Returns true if configuration hash is empty or configuration hash does not have the list of controls that needs to be included
206
- def controls_list_empty?
207
- !@conf.empty? && @conf.key?("profile") && @conf["profile"].include_controls_list.empty? || @conf.empty?
210
+ tag_option_matches_with_list
208
211
  end
209
212
 
210
213
  def tags_list_empty?
@@ -223,24 +226,29 @@ module Inspec
223
226
  id_exist_in_list
224
227
  end
225
228
 
226
- # Check if the given control exist in the --tags option
227
- def tag_exist_in_control_tags?(tag_ids)
228
- tag_option_matches_with_list = false
229
- if !tag_ids.empty? && !tag_ids.nil? && profile_tag_config_exist?
230
- tag_option_matches_with_list = !(tag_ids & @conf["profile"].include_tags_list).empty?
231
- unless tag_option_matches_with_list
232
- @conf["profile"].include_tags_list.any? do |inclusion|
233
- # Try to see if the inclusion is a regex, and if it matches
234
- if inclusion.is_a?(Regexp)
235
- tag_ids.each do |id|
236
- tag_option_matches_with_list = (inclusion =~ id)
237
- break if tag_option_matches_with_list
238
- end
239
- end
240
- end
241
- end
229
+ # Returns true if configuration hash is empty or configuration hash does not have the list of controls that needs to be included
230
+ def controls_list_empty?
231
+ !@conf.empty? && @conf.key?("profile") && @conf["profile"].include_controls_list.empty? || @conf.empty?
232
+ end
233
+
234
+ private
235
+
236
+ def block_location(block, alternate_caller)
237
+ if block.nil?
238
+ alternate_caller[/^(.+:\d+):in .+$/, 1] || "unknown"
239
+ else
240
+ path, line = block.source_location
241
+ "#{File.basename(path)}:#{line}"
242
242
  end
243
- tag_option_matches_with_list
243
+ end
244
+
245
+ # Returns true if configuration hash is not empty and it contains the list of controls is not empty
246
+ def profile_config_exist?
247
+ !@conf.empty? && @conf.key?("profile") && !@conf["profile"].include_controls_list.empty?
248
+ end
249
+
250
+ def profile_tag_config_exist?
251
+ !@conf.empty? && @conf.key?("profile") && !@conf["profile"].include_tags_list.empty?
244
252
  end
245
253
  end
246
254
  end
data/lib/inspec/dsl.rb CHANGED
@@ -93,23 +93,38 @@ module Inspec::DSL
93
93
  context = dep_entry.profile.runner_context
94
94
  # if we don't want all the rules, then just make 1 pass to get all rule_IDs
95
95
  # that we want to keep from the original
96
- filter_included_controls(context, dep_entry.profile, &block) unless opts[:include_all]
96
+ if !opts[:include_all] || !(opts[:conf]["profile"].include_tags_list.empty?) || !opts[:conf]["profile"].include_controls_list.empty?
97
+ filter_included_controls(context, dep_entry.profile, opts, &block)
98
+ end
97
99
  # interpret the block and skip/modify as required
98
100
  context.load(block) if block_given?
99
101
  bind_context.add_subcontext(context)
100
102
  end
101
103
 
102
- def self.filter_included_controls(context, profile, &block)
104
+ def self.filter_included_controls(context, profile, opts, &block)
103
105
  mock = Inspec::Backend.create(Inspec::Config.mock)
104
106
  include_ctx = Inspec::ProfileContext.for_profile(profile, mock)
105
107
  include_ctx.load(block) if block_given?
108
+ include_ctx.control_eval_context.conf = opts[:conf]
109
+ control_eval_ctx = include_ctx.control_eval_context
106
110
  # remove all rules that were not registered
107
111
  context.all_rules.each do |r|
108
112
  id = Inspec::Rule.rule_id(r)
109
113
  fid = Inspec::Rule.profile_id(r) + "/" + id
110
- unless include_ctx.rules[id] || include_ctx.rules[fid]
114
+ if !opts[:include_all] && !(include_ctx.rules[id] || include_ctx.rules[fid])
111
115
  context.remove_rule(fid)
112
116
  end
117
+
118
+ unless control_eval_ctx.controls_list_empty?
119
+ # filter the dependent profile controls which are not in the --controls options list
120
+ context.remove_rule(fid) unless control_eval_ctx.control_exist_in_controls_list?(id)
121
+ end
122
+
123
+ unless control_eval_ctx.tags_list_empty?
124
+ # filter included controls using --tags
125
+ tag_ids = control_eval_ctx.control_tags(r)
126
+ context.remove_rule(fid) unless control_eval_ctx.tag_exist_in_control_tags?(tag_ids)
127
+ end
113
128
  end
114
129
  end
115
130
  end
@@ -44,11 +44,17 @@ module Inspec::Fetcher
44
44
  # - Branch URL
45
45
  # - Commit URL
46
46
  #
47
- # master url:
47
+ # master url(default branch master):
48
48
  # https://github.com/nathenharvey/tmp_compliance_profile/ is transformed to
49
49
  # https://github.com/nathenharvey/tmp_compliance_profile/archive/master.tar.gz
50
50
  # https://bitbucket.org/username/repo is transformed to
51
51
  # https://bitbucket.org/username/repo/get/master.tar.gz
52
+
53
+ # main url(default branch main):
54
+ # https://github.com/nathenharvey/tmp_compliance_profile/ is transformed to
55
+ # https://github.com/nathenharvey/tmp_compliance_profile/archive/main.tar.gz
56
+ # https://bitbucket.org/username/repo is transformed to
57
+ # https://bitbucket.org/username/repo/get/main.tar.gz
52
58
  #
53
59
  # branch:
54
60
  # https://github.com/hardening-io/tests-os-hardening/tree/2.0 is transformed to
@@ -68,14 +74,18 @@ module Inspec::Fetcher
68
74
  BITBUCKET_URL_REGEX = %r{^https?://(www\.)?bitbucket\.org/(?<user>[\w-]+)/(?<repo>[\w-]+)(\.git)?(/)?$}.freeze
69
75
  BITBUCKET_URL_BRANCH_REGEX = %r{^https?://(www\.)?bitbucket\.org/(?<user>[\w-]+)/(?<repo>[\w-]+)/branch/(?<branch>[\w\.]+)(/)?$}.freeze
70
76
  BITBUCKET_URL_COMMIT_REGEX = %r{^https?://(www\.)?bitbucket\.org/(?<user>[\w-]+)/(?<repo>[\w-]+)/commits/(?<commit>[\w\.]+)(/)?$}.freeze
77
+ GITHUB_URL = "https://github.com".freeze
78
+ BITBUCKET_URL = "https://bitbucket.org".freeze
71
79
 
72
80
  def self.transform(target)
73
81
  transformed_target = if m = GITHUB_URL_REGEX.match(target) # rubocop:disable Lint/AssignmentInCondition
74
- "https://github.com/#{m[:user]}/#{m[:repo]}/archive/master.tar.gz"
82
+ default_branch = default_ref(m, GITHUB_URL)
83
+ "https://github.com/#{m[:user]}/#{m[:repo]}/archive/#{default_branch}.tar.gz"
75
84
  elsif m = GITHUB_URL_WITH_TREE_REGEX.match(target) # rubocop:disable Lint/AssignmentInCondition
76
85
  "https://github.com/#{m[:user]}/#{m[:repo]}/archive/#{m[:commit]}.tar.gz"
77
86
  elsif m = BITBUCKET_URL_REGEX.match(target) # rubocop:disable Lint/AssignmentInCondition
78
- "https://bitbucket.org/#{m[:user]}/#{m[:repo]}/get/master.tar.gz"
87
+ default_branch = default_ref(m, BITBUCKET_URL)
88
+ "https://bitbucket.org/#{m[:user]}/#{m[:repo]}/get/#{default_branch}.tar.gz"
79
89
  elsif m = BITBUCKET_URL_BRANCH_REGEX.match(target) # rubocop:disable Lint/AssignmentInCondition
80
90
  "https://bitbucket.org/#{m[:user]}/#{m[:repo]}/get/#{m[:branch]}.tar.gz"
81
91
  elsif m = BITBUCKET_URL_COMMIT_REGEX.match(target) # rubocop:disable Lint/AssignmentInCondition
@@ -120,6 +130,38 @@ module Inspec::Fetcher
120
130
 
121
131
  private
122
132
 
133
+ class << self
134
+ def default_ref(match_data, repo_url)
135
+ remote_url = "#{repo_url}/#{match_data[:user]}/#{match_data[:repo]}.git"
136
+ command_string = "git remote show #{remote_url}"
137
+ cmd = shellout(command_string)
138
+ unless cmd.exitstatus == 0
139
+ raise(Inspec::FetcherFailure, "Profile git dependency failed with default reference - #{remote_url} - error running '#{command_string}': #{cmd.stderr}")
140
+ else
141
+ ref = cmd.stdout.lines.detect { |l| l.include? "HEAD branch:" }&.split(":")&.last&.strip
142
+ unless ref
143
+ raise(Inspec::FetcherFailure, "Profile git dependency failed with default reference - #{remote_url} - error running '#{command_string}': NULL reference")
144
+ end
145
+
146
+ ref
147
+ end
148
+ end
149
+
150
+ def shellout(cmd, opts = {})
151
+ Inspec::Log.debug("Running external command: #{cmd} (#{opts})")
152
+ cmd = Mixlib::ShellOut.new(cmd, opts)
153
+ cmd.run_command
154
+ Inspec::Log.debug("External command: completed with exit status: #{cmd.exitstatus}")
155
+ Inspec::Log.debug("External command: STDOUT BEGIN")
156
+ Inspec::Log.debug(cmd.stdout)
157
+ Inspec::Log.debug("External command: STDOUT END")
158
+ Inspec::Log.debug("External command: STDERR BEGIN")
159
+ Inspec::Log.debug(cmd.stderr)
160
+ Inspec::Log.debug("External command: STDERR END")
161
+ cmd
162
+ end
163
+ end
164
+
123
165
  def parse_uri(target)
124
166
  return URI.parse(target) if target.is_a?(String)
125
167
 
@@ -2,12 +2,12 @@ require "inspec/plugin/v1"
2
2
 
3
3
  module Inspec
4
4
  class FetcherRegistry < PluginRegistry
5
- def resolve(target)
5
+ def resolve(target, opts = {})
6
6
  if fetcher_specified?(target)
7
- super(target)
7
+ super(target, opts)
8
8
  else
9
9
  Inspec::Log.debug("Assuming default supermarket source for #{target}")
10
- super(with_default_fetcher(target))
10
+ super(with_default_fetcher(target), opts)
11
11
  end
12
12
  end
13
13
 
@@ -18,4 +18,9 @@ module Inspec
18
18
  require "etc" unless defined?(Etc)
19
19
  Etc.getpwuid.dir
20
20
  end
21
+
22
+ def self.locally_windows?
23
+ require "rbconfig" unless defined?(RbConfig)
24
+ RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
25
+ end
21
26
  end
@@ -9,9 +9,13 @@ class PluginRegistry
9
9
  #
10
10
  # @param [String] target to resolve
11
11
  # @return [Plugin] plugin instance if it can be resolved, nil otherwise
12
- def resolve(target)
12
+ def resolve(target, opts = {})
13
13
  modules.each do |m|
14
- res = m.resolve(target)
14
+ res = if Inspec::Fetcher::Url == m
15
+ m.resolve(target, opts)
16
+ else
17
+ m.resolve(target)
18
+ end
15
19
  return res unless res.nil?
16
20
  end
17
21
  nil
@@ -18,9 +18,9 @@ module Inspec
18
18
  class Profile
19
19
  extend Forwardable
20
20
 
21
- def self.resolve_target(target, cache)
21
+ def self.resolve_target(target, cache, opts = {})
22
22
  Inspec::Log.debug "Resolve #{target} into cache #{cache.path}"
23
- Inspec::CachedFetcher.new(target, cache)
23
+ Inspec::CachedFetcher.new(target, cache, opts)
24
24
  end
25
25
 
26
26
  # Check if the profile contains a vendored cache, move content into global cache
@@ -70,7 +70,11 @@ module Inspec
70
70
 
71
71
  def self.for_target(target, opts = {})
72
72
  opts[:vendor_cache] ||= Cache.new
73
- fetcher = resolve_target(target, opts[:vendor_cache])
73
+ config = {}
74
+ unless opts[:runner_conf].nil?
75
+ config = opts[:runner_conf].respond_to?(:final_options) ? opts[:runner_conf].final_options : opts[:runner_conf]
76
+ end
77
+ fetcher = resolve_target(target, opts[:vendor_cache], config)
74
78
  for_fetcher(fetcher, opts)
75
79
  end
76
80
 
@@ -213,6 +217,9 @@ module Inspec
213
217
 
214
218
  locked_dependencies.each(&:collect_tests)
215
219
 
220
+ tests = filter_waived_controls
221
+
222
+ # Collect tests
216
223
  tests.each do |path, content|
217
224
  next if content.nil? || content.empty?
218
225
 
@@ -229,6 +236,65 @@ module Inspec
229
236
  @runner_context.all_rules
230
237
  end
231
238
 
239
+ # Wipe out waived controls
240
+ def filter_waived_controls
241
+ cfg = Inspec::Config.cached
242
+ return tests unless cfg["filter_waived_controls"]
243
+
244
+ ## Find the waivers file
245
+ # - TODO: cli_opts and instance_variable_get could be exposed
246
+ waiver_paths = cfg.instance_variable_get(:@cli_opts)["waiver_file"]
247
+ if waiver_paths.blank?
248
+ Inspec::Log.error "Must use --waiver-file with --filter-waived-controls"
249
+ Inspec::UI.new.exit(:usage_error)
250
+ end
251
+
252
+ # # Pull together waiver
253
+ waived_control_ids = []
254
+ waiver_paths.each do |waiver_path|
255
+ waiver_content = YAML.load_file(waiver_path)
256
+ unless waiver_content
257
+ # Note that we will have already issued a detailed warning
258
+ Inspec::Log.error "YAML parsing error in #{waiver_path}"
259
+ Inspec::UI.new.exit(:usage_error)
260
+ end
261
+ waived_control_ids << waiver_content.keys
262
+ end
263
+ waived_control_id_regex = "(#{waived_control_ids.join("|")})"
264
+
265
+ ## Purge tests (this could be doone in next block for performance)
266
+ ## TODO: implement earlier with pure AST and pure autocorrect AST
267
+ filtered_tests = {}
268
+ if cfg["retain_waiver_data"]
269
+ # VERY EXPERIMENTAL, but an empty describe block at the top level
270
+ # of the control blocks evaluation of ruby code until later-term
271
+ # waivers (behind the scenes this tells RSpec to hold on and use its internals to lazy load the code). This allows current waiver-data (e.g. skips) to still
272
+ # be processed and rendered
273
+ tests.each do |control_filename, source_code|
274
+ cleared_tests = source_code.scan(/control\s+['"].+?['"].+?(?=(?:control\s+['"].+?['"])|\z)/m).collect do |element|
275
+ next if element.blank?
276
+
277
+ if element&.match?(waived_control_id_regex)
278
+ splitlines = element.split("\n")
279
+ splitlines[0] + "\ndescribe '---' do\n" + splitlines[1..-1].join("\n") + "\nend\n"
280
+ else
281
+ element
282
+ end
283
+ end.join("")
284
+ filtered_tests[control_filename] = cleared_tests
285
+ end
286
+ else
287
+ tests.each do |control_filename, source_code|
288
+ cleared_tests = source_code.scan(/control\s+['"].+?['"].+?(?=(?:control\s+['"].+?['"])|\z)/m).select do |control_code|
289
+ !control_code.match?(waived_control_id_regex)
290
+ end.join("")
291
+
292
+ filtered_tests[control_filename] = cleared_tests
293
+ end
294
+ end
295
+ filtered_tests
296
+ end
297
+
232
298
  # This creates the list of controls provided in the --controls options which need to be include
233
299
  # for evaluation.
234
300
  def include_controls_list
@@ -382,6 +448,45 @@ module Inspec
382
448
  res
383
449
  end
384
450
 
451
+ def cookstyle_linting_check
452
+ msgs = []
453
+ return msgs if Inspec.locally_windows? # See #5723
454
+
455
+ output = cookstyle_rake_output.split("Offenses:").last
456
+ msgs = output.split("\n").select { |x| x =~ /[A-Z]:/ } unless output.nil?
457
+ msgs
458
+ end
459
+
460
+ # Cookstyle linting rake run output
461
+ def cookstyle_rake_output
462
+ require "cookstyle"
463
+ require "rubocop/rake_task"
464
+ begin
465
+ RuboCop::RakeTask.new(:cookstyle_lint) do |spec|
466
+ spec.options += [
467
+ "--display-cop-names",
468
+ "--parallel",
469
+ "--only=InSpec/Deprecations",
470
+ ]
471
+ spec.patterns += Dir.glob("#{@target}/**/*.rb").reject { |f| File.directory?(f) }
472
+ spec.fail_on_error = false
473
+ end
474
+ rescue LoadError
475
+ puts "Rubocop is not available. Install the rubocop gem to run the lint tests."
476
+ end
477
+ begin
478
+ stdout = StringIO.new
479
+ $stdout = stdout
480
+ Rake::Task["cookstyle_lint"].invoke
481
+ $stdout = STDOUT
482
+ Rake.application["cookstyle_lint"].reenable
483
+ stdout.string
484
+ rescue => e
485
+ puts "Cookstyle lint checks could not be performed. Error while running cookstyle - #{e}"
486
+ ""
487
+ end
488
+ end
489
+
385
490
  # Check if the profile is internally well-structured. The logger will be
386
491
  # used to print information on errors and warnings which are found.
387
492
  #
@@ -398,6 +503,7 @@ module Inspec
398
503
  },
399
504
  errors: [],
400
505
  warnings: [],
506
+ offenses: [],
401
507
  }
402
508
 
403
509
  entry = lambda { |file, line, column, control, msg|
@@ -420,6 +526,10 @@ module Inspec
420
526
  result[:errors].push(entry.call(file, line, column, control, msg))
421
527
  }
422
528
 
529
+ offense = lambda { |file, line, column, control, msg|
530
+ result[:offenses].push(entry.call(file, line, column, control, msg))
531
+ }
532
+
423
533
  @logger.info "Checking profile in #{@target}"
424
534
  meta_path = @source_reader.target.abs_path(@source_reader.metadata.ref)
425
535
 
@@ -482,8 +592,15 @@ module Inspec
482
592
  warn.call(sfile, sline, nil, id, "Control #{id} has no tests defined") if control[:checks].nil? || control[:checks].empty?
483
593
  end
484
594
 
485
- # profile is valid if we could not find any error
486
- result[:summary][:valid] = result[:errors].empty?
595
+ # Running cookstyle to check for code offenses
596
+ cookstyle_linting_check.each do |lint_output|
597
+ data = lint_output.split(":")
598
+ msg = "#{data[-2]}:#{data[-1]}"
599
+ offense.call(data[0], data[1], data[2], nil, msg)
600
+ end
601
+
602
+ # profile is valid if we could not find any error & offenses
603
+ result[:summary][:valid] = result[:errors].empty? && result[:offenses].empty?
487
604
 
488
605
  @logger.info "Control definitions OK." if result[:warnings].empty?
489
606
  result
@@ -82,7 +82,7 @@ module Inspec::Resources
82
82
  end
83
83
  end
84
84
 
85
- @params.merge!(params)
85
+ @params.merge!(params) { |key, current_val, new_val| [*current_val].to_a + [*new_val].to_a }
86
86
 
87
87
  to_read = to_read.drop(1)
88
88
  to_read += include_files(params).find_all do |fp|
@@ -101,12 +101,14 @@ module Inspec::Resources
101
101
  include_files_optional = params["IncludeOptional"] || []
102
102
 
103
103
  includes = []
104
- (include_files + include_files_optional).each do |f|
105
- id = Pathname.new(f).absolute? ? f : File.join(conf_dir, f)
106
- files = find_files(id, depth: 1, type: "file")
107
- files += find_files(id, depth: 1, type: "link")
104
+ unless conf_dir.nil?
105
+ (include_files + include_files_optional).each do |f|
106
+ id = Pathname.new(f).absolute? ? f : File.join(conf_dir, f)
107
+ files = find_files(id, depth: 1, type: "file")
108
+ files += find_files(id, depth: 1, type: "link")
108
109
 
109
- includes.push(files) if files
110
+ includes.push(files) if files
111
+ end
110
112
  end
111
113
 
112
114
  # [].flatten! == nil