inspec-core 4.41.2 → 4.50.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +5 -1
- data/etc/deprecations.json +1 -1
- data/lib/inspec/base_cli.rb +9 -1
- data/lib/inspec/cached_fetcher.rb +2 -2
- data/lib/inspec/cli.rb +21 -4
- data/lib/inspec/control_eval_context.rb +54 -46
- 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/globals.rb +5 -0
- data/lib/inspec/plugin/v1/registry.rb +6 -2
- data/lib/inspec/profile.rb +122 -5
- 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/http.rb +107 -55
- data/lib/inspec/resources/ibmdb2_conf.rb +57 -0
- data/lib/inspec/resources/ibmdb2_session.rb +69 -0
- data/lib/inspec/resources/mssql_sys_conf.rb +48 -0
- data/lib/inspec/resources/opa.rb +4 -1
- 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 +9 -0
- data/lib/inspec/run_data/profile.rb +0 -2
- 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 +14 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 506be4d9918c8af46f6b3784e8a18550cee990ad73c7c731bb1afea7197c5370
|
|
4
|
+
data.tar.gz: b48fa274325e96ac07185653b2eeb997ed5ea6fa39f570b0400706331a3b4d52
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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"
|
|
@@ -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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
193
209
|
end
|
|
194
|
-
|
|
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
|
-
#
|
|
227
|
-
def
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
data/lib/inspec/globals.rb
CHANGED
|
@@ -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
|
|
|
@@ -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
|
-
#
|
|
486
|
-
|
|
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
|
-
|
|
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
|