inspec-core 4.38.9 → 4.49.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) 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 +11 -1
  5. data/lib/inspec/cached_fetcher.rb +2 -2
  6. data/lib/inspec/cli.rb +14 -4
  7. data/lib/inspec/control_eval_context.rb +64 -17
  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/plugin/v1/registry.rb +6 -2
  12. data/lib/inspec/profile.rb +146 -6
  13. data/lib/inspec/resources/apache_conf.rb +8 -6
  14. data/lib/inspec/resources/cassandra.rb +64 -0
  15. data/lib/inspec/resources/cassandradb_conf.rb +47 -0
  16. data/lib/inspec/resources/cassandradb_session.rb +68 -0
  17. data/lib/inspec/resources/chrony_conf.rb +55 -0
  18. data/lib/inspec/resources/csv.rb +26 -3
  19. data/lib/inspec/resources/groups.rb +22 -3
  20. data/lib/inspec/resources/ibmdb2_conf.rb +57 -0
  21. data/lib/inspec/resources/ibmdb2_session.rb +69 -0
  22. data/lib/inspec/resources/mongodb_session.rb +88 -0
  23. data/lib/inspec/resources/mssql_sys_conf.rb +48 -0
  24. data/lib/inspec/resources/opa.rb +26 -0
  25. data/lib/inspec/resources/opa_api.rb +39 -0
  26. data/lib/inspec/resources/opa_cli.rb +43 -0
  27. data/lib/inspec/resources/oracle.rb +66 -0
  28. data/lib/inspec/resources/oracledb_conf.rb +40 -0
  29. data/lib/inspec/resources/oracledb_listener_conf.rb +123 -0
  30. data/lib/inspec/resources/oracledb_session.rb +23 -6
  31. data/lib/inspec/resources/postgres_session.rb +15 -10
  32. data/lib/inspec/resources/registry_key.rb +1 -1
  33. data/lib/inspec/resources/security_identifier.rb +8 -14
  34. data/lib/inspec/resources/security_policy.rb +4 -3
  35. data/lib/inspec/resources/service.rb +7 -1
  36. data/lib/inspec/resources/sybase_conf.rb +37 -0
  37. data/lib/inspec/resources/sybase_session.rb +111 -0
  38. data/lib/inspec/resources/users.rb +16 -2
  39. data/lib/inspec/resources/windows_firewall.rb +1 -1
  40. data/lib/inspec/resources/wmi.rb +1 -1
  41. data/lib/inspec/resources.rb +12 -0
  42. data/lib/inspec/run_data/profile.rb +0 -2
  43. data/lib/inspec/runner.rb +2 -0
  44. data/lib/inspec/utils/filter.rb +1 -1
  45. data/lib/inspec/version.rb +1 -1
  46. data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +1 -1
  47. data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +1 -1
  48. data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +1 -1
  49. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +16 -15
  50. metadata +18 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17449ad4c9680511a8fc11c6fdb11d9ece550a7942c9e734c95eac0d41913d9f
4
- data.tar.gz: ae5055ccc9bebd1aed4f22da4ad4dcd1be31e1bd2b5707e7b5fb088c916eda08
3
+ metadata.gz: 31dcab9ba9621f43755fc390ad061c08b7e6ab19466d26a38921d33960e1bbf5
4
+ data.tar.gz: eecdf0ec772ef012bfbf5185b379c47dd008fe902b9e91d4feee6979b0294c0f
5
5
  SHA512:
6
- metadata.gz: 6cec299ca48d7ca4c3fb9b3eecc79c8687541fbd83fc79e837ed13d2abb4bcb861f747782a68bf90f7e1083443a671079a1368a97e9f552e249e456616a92059
7
- data.tar.gz: 287e2d79dbc494c83d6f8b8046e0f9c54632c5a13ec75ac69b603bdf9fe9b6a89ff86c9c8f025f7e04372490adb1ffa5a5a7fc10f3ecab1e7fabd70f71f6767d
6
+ metadata.gz: 1059703ad59c2bf7c213a0914f94a8852cc550b1d305f634e07a08b364edae5c0f550927a8bcec9e712cd313949388082fdebf5c3164147ef12c21df952281b9
7
+ data.tar.gz: 1d4e6e64b8851c40349b7d696d46904fa73c0683d19c70afaa942a4c4bef4591a3ed4d7ec2c89aa5f7717da70617d70f182a7e9b894338da77592231b4c62234
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"
@@ -136,6 +140,8 @@ module Inspec
136
140
  profile_options
137
141
  option :controls, type: :array,
138
142
  desc: "A list of control names to run, or a list of /regexes/ to match against control names. Ignore all other tests."
143
+ option :tags, type: :array,
144
+ desc: "A list of tags names that are part of controls to filter and run controls, or a list of /regexes/ to match against tags names of controls. Ignore all other tests."
139
145
  option :reporter, type: :array,
140
146
  banner: "one two:/output/file/path",
141
147
  desc: "Enable one or more output reporters: cli, documentation, html, progress, json, json-min, json-rspec, junit, yaml"
@@ -168,6 +174,10 @@ module Inspec
168
174
  desc: "After normal execution order, results are sorted by control ID, or by file (default), or randomly. None uses legacy unsorted mode."
169
175
  option :filter_empty_profiles, type: :boolean, default: false,
170
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"
171
181
  option :command_timeout, type: :numeric,
172
182
  desc: "Maximum seconds to allow commands to run during execution.",
173
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
@@ -65,6 +65,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
65
65
  desc: "Save the created profile to a path"
66
66
  option :controls, type: :array,
67
67
  desc: "A list of controls to include. Ignore all other tests."
68
+ option :tags, type: :array,
69
+ desc: "A list of tags to filter controls and include only those. Ignore all other tests."
68
70
  profile_options
69
71
  def json(target)
70
72
  require "json" unless defined?(JSON)
@@ -91,7 +93,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
91
93
  end
92
94
 
93
95
  desc "check PATH", "verify all tests at the specified PATH"
94
- 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."
95
98
  profile_options
96
99
  def check(path) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
97
100
  o = config
@@ -119,8 +122,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
119
122
  end
120
123
  puts
121
124
 
122
- if result[:errors].empty? && result[:warnings].empty?
123
- ui.plain_line("No errors or warnings")
125
+ if result[:errors].empty? && result[:warnings].empty? && result[:offenses].empty?
126
+ ui.plain_line("No errors, warnings, or offenses")
124
127
  else
125
128
  item_msg = lambda { |item|
126
129
  pos = [item[:file], item[:line], item[:column]].compact.join(":")
@@ -132,11 +135,18 @@ class Inspec::InspecCLI < Inspec::BaseCLI
132
135
 
133
136
  puts
134
137
 
138
+ unless result[:offenses].empty?
139
+ puts "Offenses:\n"
140
+ result[:offenses].each { |item| ui.cyan(" #{Inspec::UI::GLYPHS[:script_x]} #{item_msg.call(item)}\n\n") }
141
+ end
142
+
143
+ offenses = ui.cyan("#{result[:offenses].length} offenses", print: false)
135
144
  errors = ui.red("#{result[:errors].length} errors", print: false)
136
145
  warnings = ui.yellow("#{result[:warnings].length} warnings", print: false)
137
- ui.plain_line("Summary: #{errors}, #{warnings}")
146
+ ui.plain_line("Summary: #{errors}, #{warnings}, #{offenses}")
138
147
  end
139
148
  end
149
+
140
150
  ui.exit Inspec::UI::EXIT_USAGE_ERROR unless result[:summary][:valid]
141
151
  rescue StandardError => e
142
152
  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,12 +54,30 @@ module Inspec
53
54
 
54
55
  def control(id, opts = {}, &block)
55
56
  opts[:skip_only_if_eval] = @skip_only_if_eval
56
- if control_exist_in_controls_list?(id) || controls_list_empty?
57
+ if (controls_list_empty? && tags_list_empty?) || control_exist_in_controls_list?(id)
57
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)
58
65
  end
59
66
  end
67
+
60
68
  alias rule control
61
69
 
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)
77
+ rescue
78
+ []
79
+ end
80
+
62
81
  # Describe allows users to write rspec-like bare describe
63
82
  # blocks without declaring an inclosing control. Here, we
64
83
  # generate a control for them automatically and then execute
@@ -74,7 +93,7 @@ module Inspec
74
93
  res = describe(*args, &block)
75
94
  end
76
95
 
77
- if control_exist_in_controls_list?(id) || controls_list_empty?
96
+ if controls_list_empty? || control_exist_in_controls_list?(id)
78
97
  register_control(rule, &block)
79
98
  end
80
99
 
@@ -171,6 +190,47 @@ module Inspec
171
190
  @skip_file = true
172
191
  end
173
192
 
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
209
+ end
210
+ tag_option_matches_with_list
211
+ end
212
+
213
+ def tags_list_empty?
214
+ !@conf.empty? && @conf.key?("profile") && @conf["profile"].include_tags_list.empty? || @conf.empty?
215
+ end
216
+
217
+ # Check if the given control exist in the --controls option
218
+ def control_exist_in_controls_list?(id)
219
+ id_exist_in_list = false
220
+ if profile_config_exist?
221
+ id_exist_in_list = @conf["profile"].include_controls_list.any? do |inclusion|
222
+ # Try to see if the inclusion is a regex, and if it matches
223
+ inclusion == id || (inclusion.is_a?(Regexp) && inclusion =~ id)
224
+ end
225
+ end
226
+ id_exist_in_list
227
+ end
228
+
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
+
174
234
  private
175
235
 
176
236
  def block_location(block, alternate_caller)
@@ -187,21 +247,8 @@ module Inspec
187
247
  !@conf.empty? && @conf.key?("profile") && !@conf["profile"].include_controls_list.empty?
188
248
  end
189
249
 
190
- # Returns true if configuration hash is empty or configuration hash does not have the list of controls that needs to be included
191
- def controls_list_empty?
192
- !@conf.empty? && @conf.key?("profile") && @conf["profile"].include_controls_list.empty? || @conf.empty?
193
- end
194
-
195
- # Check if the given control exist in the --controls option
196
- def control_exist_in_controls_list?(id)
197
- id_exist_in_list = false
198
- if profile_config_exist?
199
- id_exist_in_list = @conf["profile"].include_controls_list.any? do |inclusion|
200
- # Try to see if the inclusion is a regex, and if it matches
201
- inclusion == id || (inclusion.is_a?(Regexp) && inclusion =~ id)
202
- end
203
- end
204
- id_exist_in_list
250
+ def profile_tag_config_exist?
251
+ !@conf.empty? && @conf.key?("profile") && !@conf["profile"].include_tags_list.empty?
205
252
  end
206
253
  end
207
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
 
@@ -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
 
@@ -87,6 +91,7 @@ module Inspec
87
91
  @logger = options[:logger] || Logger.new(nil)
88
92
  @locked_dependencies = options[:dependencies]
89
93
  @controls = options[:controls] || []
94
+ @tags = options[:tags] || []
90
95
  @writable = options[:writable] || false
91
96
  @profile_id = options[:id]
92
97
  @profile_name = options[:profile_name]
@@ -206,12 +211,15 @@ module Inspec
206
211
  @params ||= load_params
207
212
  end
208
213
 
209
- def collect_tests(include_list = @controls)
214
+ def collect_tests
210
215
  unless @tests_collected || failed?
211
216
  return unless supports_platform?
212
217
 
213
218
  locked_dependencies.each(&:collect_tests)
214
219
 
220
+ tests = filter_waived_controls
221
+
222
+ # Collect tests
215
223
  tests.each do |path, content|
216
224
  next if content.nil? || content.empty?
217
225
 
@@ -228,6 +236,65 @@ module Inspec
228
236
  @runner_context.all_rules
229
237
  end
230
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
+
231
298
  # This creates the list of controls provided in the --controls options which need to be include
232
299
  # for evaluation.
233
300
  def include_controls_list
@@ -253,6 +320,30 @@ module Inspec
253
320
  included_controls
254
321
  end
255
322
 
323
+ # This creates the list of controls to be filtered by tag values provided in the --tags options
324
+ def include_tags_list
325
+ return [] if @tags.nil? || @tags.empty?
326
+
327
+ included_tags = @tags
328
+ # Check for anything that might be a regex in the list, and make it official
329
+ included_tags.each_with_index do |inclusion, index|
330
+ next if inclusion.is_a?(Regexp)
331
+ # Insist the user wrap the regex in slashes to demarcate it as a regex
332
+ next unless inclusion.start_with?("/") && inclusion.end_with?("/")
333
+
334
+ inclusion = inclusion[1..-2] # Trim slashes
335
+ begin
336
+ re = Regexp.new(inclusion)
337
+ included_tags[index] = re
338
+ rescue RegexpError => e
339
+ warn "Ignoring unparseable regex '/#{inclusion}/' in --control CLI option: #{e.message}"
340
+ included_tags[index] = nil
341
+ end
342
+ end
343
+ included_tags.compact!
344
+ included_tags
345
+ end
346
+
256
347
  def load_libraries
257
348
  return @runner_context if @libraries_loaded
258
349
 
@@ -357,6 +448,43 @@ module Inspec
357
448
  res
358
449
  end
359
450
 
451
+ def cookstyle_linting_check
452
+ msgs = []
453
+ output = cookstyle_rake_output.split("Offenses:").last
454
+ msgs = output.split("\n").select { |x| x =~ /[A-Z]:/ } unless output.nil?
455
+ msgs
456
+ end
457
+
458
+ # Cookstyle linting rake run output
459
+ def cookstyle_rake_output
460
+ require "cookstyle"
461
+ require "rubocop/rake_task"
462
+ begin
463
+ RuboCop::RakeTask.new(:cookstyle_lint) do |spec|
464
+ spec.options += [
465
+ "--display-cop-names",
466
+ "--parallel",
467
+ "--only=InSpec/Deprecations",
468
+ ]
469
+ spec.patterns += Dir.glob("#{@target}/**/*.rb").reject { |f| File.directory?(f) }
470
+ spec.fail_on_error = false
471
+ end
472
+ rescue LoadError
473
+ puts "Rubocop is not available. Install the rubocop gem to run the lint tests."
474
+ end
475
+ begin
476
+ stdout = StringIO.new
477
+ $stdout = stdout
478
+ Rake::Task["cookstyle_lint"].invoke
479
+ $stdout = STDOUT
480
+ Rake.application["cookstyle_lint"].reenable
481
+ stdout.string
482
+ rescue => e
483
+ puts "Cookstyle lint checks could not be performed. Error while running cookstyle - #{e}"
484
+ ""
485
+ end
486
+ end
487
+
360
488
  # Check if the profile is internally well-structured. The logger will be
361
489
  # used to print information on errors and warnings which are found.
362
490
  #
@@ -373,6 +501,7 @@ module Inspec
373
501
  },
374
502
  errors: [],
375
503
  warnings: [],
504
+ offenses: [],
376
505
  }
377
506
 
378
507
  entry = lambda { |file, line, column, control, msg|
@@ -395,6 +524,10 @@ module Inspec
395
524
  result[:errors].push(entry.call(file, line, column, control, msg))
396
525
  }
397
526
 
527
+ offense = lambda { |file, line, column, control, msg|
528
+ result[:offenses].push(entry.call(file, line, column, control, msg))
529
+ }
530
+
398
531
  @logger.info "Checking profile in #{@target}"
399
532
  meta_path = @source_reader.target.abs_path(@source_reader.metadata.ref)
400
533
 
@@ -457,8 +590,15 @@ module Inspec
457
590
  warn.call(sfile, sline, nil, id, "Control #{id} has no tests defined") if control[:checks].nil? || control[:checks].empty?
458
591
  end
459
592
 
460
- # profile is valid if we could not find any error
461
- result[:summary][:valid] = result[:errors].empty?
593
+ # Running cookstyle to check for code offenses
594
+ cookstyle_linting_check.each do |lint_output|
595
+ data = lint_output.split(":")
596
+ msg = "#{data[-2]}:#{data[-1]}"
597
+ offense.call(data[0], data[1], data[2], nil, msg)
598
+ end
599
+
600
+ # profile is valid if we could not find any error & offenses
601
+ result[:summary][:valid] = result[:errors].empty? && result[:offenses].empty?
462
602
 
463
603
  @logger.info "Control definitions OK." if result[:warnings].empty?
464
604
  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