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.
- 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
|