inspec-core 4.38.9 → 4.49.0
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.
- checksums.yaml +4 -4
- data/Gemfile +5 -1
- data/etc/deprecations.json +1 -1
- data/lib/inspec/base_cli.rb +11 -1
- data/lib/inspec/cached_fetcher.rb +2 -2
- data/lib/inspec/cli.rb +14 -4
- data/lib/inspec/control_eval_context.rb +64 -17
- data/lib/inspec/dsl.rb +18 -3
- data/lib/inspec/fetcher/url.rb +45 -3
- data/lib/inspec/fetcher.rb +3 -3
- data/lib/inspec/plugin/v1/registry.rb +6 -2
- data/lib/inspec/profile.rb +146 -6
- data/lib/inspec/resources/apache_conf.rb +8 -6
- data/lib/inspec/resources/cassandra.rb +64 -0
- data/lib/inspec/resources/cassandradb_conf.rb +47 -0
- data/lib/inspec/resources/cassandradb_session.rb +68 -0
- data/lib/inspec/resources/chrony_conf.rb +55 -0
- data/lib/inspec/resources/csv.rb +26 -3
- data/lib/inspec/resources/groups.rb +22 -3
- data/lib/inspec/resources/ibmdb2_conf.rb +57 -0
- data/lib/inspec/resources/ibmdb2_session.rb +69 -0
- data/lib/inspec/resources/mongodb_session.rb +88 -0
- data/lib/inspec/resources/mssql_sys_conf.rb +48 -0
- data/lib/inspec/resources/opa.rb +26 -0
- data/lib/inspec/resources/opa_api.rb +39 -0
- data/lib/inspec/resources/opa_cli.rb +43 -0
- data/lib/inspec/resources/oracle.rb +66 -0
- data/lib/inspec/resources/oracledb_conf.rb +40 -0
- data/lib/inspec/resources/oracledb_listener_conf.rb +123 -0
- data/lib/inspec/resources/oracledb_session.rb +23 -6
- data/lib/inspec/resources/postgres_session.rb +15 -10
- data/lib/inspec/resources/registry_key.rb +1 -1
- data/lib/inspec/resources/security_identifier.rb +8 -14
- data/lib/inspec/resources/security_policy.rb +4 -3
- data/lib/inspec/resources/service.rb +7 -1
- data/lib/inspec/resources/sybase_conf.rb +37 -0
- data/lib/inspec/resources/sybase_session.rb +111 -0
- data/lib/inspec/resources/users.rb +16 -2
- data/lib/inspec/resources/windows_firewall.rb +1 -1
- data/lib/inspec/resources/wmi.rb +1 -1
- data/lib/inspec/resources.rb +12 -0
- data/lib/inspec/run_data/profile.rb +0 -2
- data/lib/inspec/runner.rb +2 -0
- data/lib/inspec/utils/filter.rb +1 -1
- data/lib/inspec/version.rb +1 -1
- data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +1 -1
- data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +1 -1
- data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +1 -1
- data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +16 -15
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31dcab9ba9621f43755fc390ad061c08b7e6ab19466d26a38921d33960e1bbf5
|
4
|
+
data.tar.gz: eecdf0ec772ef012bfbf5185b379c47dd008fe902b9e91d4feee6979b0294c0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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"
|
data/etc/deprecations.json
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
"groups": {
|
5
5
|
"attrs_value_replaces_default": {
|
6
6
|
"action": "warn",
|
7
|
-
"prefix": "The 'default' option for
|
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",
|
data/lib/inspec/base_cli.rb
CHANGED
@@ -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
|
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
|
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)
|
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
|
-
|
191
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/inspec/fetcher/url.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
|
data/lib/inspec/fetcher.rb
CHANGED
@@ -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
|
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
|
data/lib/inspec/profile.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
-
#
|
461
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
110
|
+
includes.push(files) if files
|
111
|
+
end
|
110
112
|
end
|
111
113
|
|
112
114
|
# [].flatten! == nil
|