inspec-core 4.41.2 → 4.50.3

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