inspec-core 4.18.51 → 4.18.85
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +61 -0
- data/README.md +3 -3
- data/inspec-core.gemspec +51 -0
- data/lib/bundles/inspec-supermarket/cli.rb +1 -0
- data/lib/inspec/backend.rb +49 -47
- data/lib/inspec/base_cli.rb +2 -2
- data/lib/inspec/cached_fetcher.rb +4 -0
- data/lib/inspec/cli.rb +5 -0
- data/lib/inspec/config.rb +1 -1
- data/lib/inspec/control_eval_context.rb +131 -199
- data/lib/inspec/dependencies/requirement.rb +1 -1
- data/lib/inspec/dependencies/resolver.rb +46 -0
- data/lib/inspec/dsl_shared.rb +25 -3
- data/lib/inspec/fetcher.rb +0 -3
- data/lib/inspec/fetcher/git.rb +4 -0
- data/lib/inspec/fetcher/url.rb +1 -2
- data/lib/inspec/file_provider.rb +4 -2
- data/lib/inspec/library_eval_context.rb +37 -37
- data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +27 -0
- data/lib/inspec/plugin/v1/plugins.rb +0 -1
- data/lib/inspec/profile.rb +8 -6
- data/lib/inspec/profile_context.rb +74 -9
- data/lib/inspec/profile_vendor.rb +48 -3
- data/lib/inspec/resource.rb +192 -41
- data/lib/inspec/resources/aide_conf.rb +1 -1
- data/lib/inspec/resources/apache_conf.rb +15 -31
- data/lib/inspec/resources/command.rb +1 -1
- data/lib/inspec/resources/crontab.rb +56 -56
- data/lib/inspec/resources/etc_fstab.rb +1 -1
- data/lib/inspec/resources/etc_group.rb +1 -1
- data/lib/inspec/resources/etc_hosts.rb +2 -3
- data/lib/inspec/resources/etc_hosts_allow_deny.rb +1 -1
- data/lib/inspec/resources/file.rb +2 -2
- data/lib/inspec/resources/filesystem.rb +4 -4
- data/lib/inspec/resources/groups.rb +16 -2
- data/lib/inspec/resources/iis_app.rb +1 -1
- data/lib/inspec/resources/ini.rb +1 -2
- data/lib/inspec/resources/mount.rb +2 -2
- data/lib/inspec/resources/oracledb_session.rb +1 -1
- data/lib/inspec/resources/package.rb +22 -0
- data/lib/inspec/resources/passwd.rb +1 -1
- data/lib/inspec/resources/platform.rb +36 -36
- data/lib/inspec/resources/port.rb +1 -1
- data/lib/inspec/resources/postfix_conf.rb +1 -1
- data/lib/inspec/resources/service.rb +23 -15
- data/lib/inspec/resources/users.rb +3 -3
- data/lib/inspec/resources/virtualization.rb +15 -11
- data/lib/inspec/resources/x509_certificate.rb +18 -4
- data/lib/inspec/resources/xinetd_conf.rb +1 -1
- data/lib/inspec/resources/xml.rb +1 -2
- data/lib/inspec/rspec_extensions.rb +12 -0
- data/lib/inspec/rule.rb +63 -22
- data/lib/inspec/utils/filter.rb +2 -0
- data/lib/inspec/utils/parser.rb +244 -240
- data/lib/inspec/utils/simpleconfig.rb +1 -1
- data/lib/inspec/version.rb +1 -1
- data/lib/matchers/matchers.rb +11 -10
- data/lib/plugins/inspec-compliance/lib/inspec-compliance.rb +3 -0
- data/lib/plugins/inspec-habitat/lib/inspec-habitat/profile.rb +2 -2
- data/lib/plugins/inspec-init/templates/profiles/aws/README.md +192 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/attributes.yml +2 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/controls/example.rb +39 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +22 -0
- data/lib/plugins/inspec-init/templates/profiles/azure/README.md +56 -0
- data/lib/plugins/inspec-init/templates/profiles/azure/controls/example.rb +14 -0
- data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +14 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/README.md +66 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/attributes.yml +2 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/controls/example.rb +27 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +19 -0
- data/lib/source_readers/inspec.rb +1 -1
- metadata +87 -74
- data/lib/inspec/plugin/v1/plugin_types/resource.rb +0 -176
- data/lib/plugins/inspec-init/templates/profiles/os/libraries/.gitkeep +0 -0
@@ -9,8 +9,8 @@ module Inspec
|
|
9
9
|
@profile_path = Pathname.new(File.expand_path(path))
|
10
10
|
end
|
11
11
|
|
12
|
-
def vendor!
|
13
|
-
vendor_dependencies
|
12
|
+
def vendor!(opts)
|
13
|
+
vendor_dependencies!(opts)
|
14
14
|
end
|
15
15
|
|
16
16
|
# The URL fetcher uses a Tempfile to retrieve the vendored
|
@@ -51,8 +51,10 @@ module Inspec
|
|
51
51
|
}
|
52
52
|
end
|
53
53
|
|
54
|
-
def vendor_dependencies
|
54
|
+
def vendor_dependencies!(opts)
|
55
|
+
# This deletes any existing vendor/ directory
|
55
56
|
delete_vendored_data
|
57
|
+
warm_vendor_cache_from_archives if opts[:airgap]
|
56
58
|
File.write(lockfile, profile.generate_lockfile.to_yaml)
|
57
59
|
extract_archives
|
58
60
|
end
|
@@ -80,5 +82,48 @@ module Inspec
|
|
80
82
|
File.delete(filepath)
|
81
83
|
end
|
82
84
|
end
|
85
|
+
|
86
|
+
# Check top-level profile metadata for any local archives
|
87
|
+
# if present, go ahead and inflate it into the vendor cache.
|
88
|
+
# This will cause subsequent fetchers to hit on the vendor cache,
|
89
|
+
# avoiding a possibly failing fetch under airgapped conditions.
|
90
|
+
def warm_vendor_cache_from_archives
|
91
|
+
# We need to determine the cwd of the profile, to expand the (likely)
|
92
|
+
# relative path of the dependencies. There is no non-invasive way of doing that.
|
93
|
+
profile_cwd = profile.source_reader.target.parent.path
|
94
|
+
|
95
|
+
# list dependencies in profile
|
96
|
+
profile.metadata.dependencies.each do |dep_info|
|
97
|
+
next unless dep_info.key?(:path) # "Local"-type fetchers are identified by the :path key
|
98
|
+
|
99
|
+
# Locate local dep
|
100
|
+
dep_profile_path = File.expand_path(dep_info[:path], profile_cwd)
|
101
|
+
raise(Inspec::FetcherFailure, "Profile dependency local path '#{dep_profile_path}' does not exist") unless File.exist?(dep_profile_path)
|
102
|
+
|
103
|
+
# Determine if it is an archive
|
104
|
+
file_provider = FileProvider.for_path(dep_profile_path)
|
105
|
+
next unless file_provider.is_a?(ZipProvider) || file_provider.is_a?(TarProvider)
|
106
|
+
|
107
|
+
# Place the local dependency in the vendor cache.
|
108
|
+
# Fetchers are in charge of calculating cache keys.
|
109
|
+
fetcher = Inspec::Fetcher::Local.resolve(dep_profile_path)
|
110
|
+
# Use a shorter name here in hopes of dodging windows filesystems path length restrictions
|
111
|
+
actual_dep_profile_cache_dir = "#{cache_path}/#{fetcher.cache_key}"
|
112
|
+
short_dep_profile_cache_dir = "#{cache_path}/short"
|
113
|
+
file_provider.extract(short_dep_profile_cache_dir)
|
114
|
+
|
115
|
+
# The archive (probably) contained a vendor cache of its own.
|
116
|
+
# Since we are warming the fetcher cache, and fetcher caches are one-level
|
117
|
+
# while archive caches are potentially deep, we must flatten the archive cache,
|
118
|
+
# so that all vendor cache entries are top-level.
|
119
|
+
Dir["#{short_dep_profile_cache_dir}/vendor/*"].each do |sub_dep_cache_dir|
|
120
|
+
FileUtils.mv(sub_dep_cache_dir, cache_path)
|
121
|
+
end
|
122
|
+
|
123
|
+
# And finally correctly name the dependency itself.
|
124
|
+
FileUtils.mv(short_dep_profile_cache_dir, actual_dep_profile_cache_dir)
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
83
128
|
end
|
84
129
|
end
|
data/lib/inspec/resource.rb
CHANGED
@@ -1,11 +1,149 @@
|
|
1
1
|
# copyright: 2015, Vulcano Security GmbH
|
2
|
-
require "inspec/plugin/v1"
|
3
|
-
require "inspec/utils/deprecation/global_method" # for resources
|
4
2
|
|
5
3
|
module Inspec
|
6
4
|
class ProfileNotFound < StandardError; end
|
7
5
|
|
8
6
|
class Resource
|
7
|
+
def self.name(value = nil)
|
8
|
+
return @name unless value
|
9
|
+
|
10
|
+
@name = value
|
11
|
+
|
12
|
+
__register(value, self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.desc(value = nil)
|
16
|
+
return find_class_instance_variable(:@description) unless value
|
17
|
+
|
18
|
+
@description = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.example(value = nil)
|
22
|
+
return find_class_instance_variable(:@example) unless value
|
23
|
+
|
24
|
+
@example = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.supports(criteria = nil)
|
28
|
+
return if criteria.nil?
|
29
|
+
|
30
|
+
key = @name.to_sym
|
31
|
+
|
32
|
+
# HACK: this is broken!!! this is global where the rest are localized to registry
|
33
|
+
Inspec::Resource.support_registry[key] ||= []
|
34
|
+
Inspec::Resource.support_registry[key].push(criteria)
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO: this is pretty terrible and is only here to work around
|
38
|
+
# the idea that we've trained resource authors to make initialize
|
39
|
+
# methods w/o calling super.
|
40
|
+
|
41
|
+
def supersuper_initialize(backend, name)
|
42
|
+
@resource_skipped = false
|
43
|
+
@resource_failed = false
|
44
|
+
# TODO remove this (or the support_registry) (again?)
|
45
|
+
@supports = Inspec::Resource.support_registry[name.to_sym]
|
46
|
+
@resource_exception_message = nil
|
47
|
+
|
48
|
+
# attach the backend to this instance
|
49
|
+
@__backend_runner__ = backend
|
50
|
+
@__resource_name__ = name
|
51
|
+
|
52
|
+
check_supported!
|
53
|
+
|
54
|
+
yield
|
55
|
+
rescue Inspec::Exceptions::ResourceSkipped => e
|
56
|
+
skip_resource(e.message)
|
57
|
+
rescue Inspec::Exceptions::ResourceFailed => e
|
58
|
+
fail_resource(e.message)
|
59
|
+
rescue NotImplementedError => e
|
60
|
+
fail_resource(e.message) unless @resource_failed
|
61
|
+
rescue NoMethodError => e
|
62
|
+
skip_resource(e.message) unless @resource_failed
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_supported!
|
66
|
+
backend = @__backend_runner__
|
67
|
+
name = @__resource_name__
|
68
|
+
|
69
|
+
supported = @supports ? check_supports : true # check_supports has side effects!
|
70
|
+
test_backend = defined?(Train::Transports::Mock::Connection) && backend.backend.class == Train::Transports::Mock::Connection
|
71
|
+
# raise unless we are supported or in test
|
72
|
+
unless supported || test_backend
|
73
|
+
msg = "Unsupported resource/backend combination: %s / %s. Exiting." %
|
74
|
+
[name, backend.platform.name]
|
75
|
+
raise ArgumentError, msg
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Support for Resource DSL plugins.
|
80
|
+
# This is called when an unknown method is encountered
|
81
|
+
# within a resource class definition.
|
82
|
+
# Even tho this is defined as an instance method, it gets added to
|
83
|
+
# the class via `extend`, so this is actually a class defintion.
|
84
|
+
def self.method_missing(method_name, *arguments, &block)
|
85
|
+
require "inspec/plugin/v2"
|
86
|
+
# Check to see if there is a resource_dsl plugin activator hook with the method name
|
87
|
+
registry = Inspec::Plugin::V2::Registry.instance
|
88
|
+
hook = registry.find_activators(plugin_type: :resource_dsl, activator_name: method_name).first
|
89
|
+
if hook
|
90
|
+
# OK, load the hook if it hasn't been already. We'll then know a module,
|
91
|
+
# which we can then inject into the resource
|
92
|
+
hook.activate
|
93
|
+
# Inject the module's methods into the resource as class methods.
|
94
|
+
# implementation_class is the field name, but this is actually a module.
|
95
|
+
extend(hook.implementation_class)
|
96
|
+
# Now that the module is loaded, it defined one or more methods
|
97
|
+
# (presumably the one we were looking for.)
|
98
|
+
# We still haven't called it, so do so now.
|
99
|
+
send(method_name, *arguments, &block)
|
100
|
+
else
|
101
|
+
# If we couldn't find a plugin to match, maybe something up above has it,
|
102
|
+
# or maybe it is just a unknown method error.
|
103
|
+
super
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
############################################################
|
108
|
+
# Infrastructure / Bookkeeping
|
109
|
+
|
110
|
+
def self.__register(name, resource_klass)
|
111
|
+
cl = Class.new(resource_klass) do # TODO: remove
|
112
|
+
# As best I can figure out, this anonymous class only exists
|
113
|
+
# because we're trying to avoid having resources with
|
114
|
+
# initialize methods from having to call super, which is,
|
115
|
+
# quite frankly, dumb. Avoidable even with some simple
|
116
|
+
# documentation.
|
117
|
+
def initialize(backend, name, *args)
|
118
|
+
supersuper_initialize(backend, name) do
|
119
|
+
super(*args)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
reg = __resource_registry rescue nil
|
125
|
+
reg = self.__resource_registry = Inspec::Resource.registry unless reg
|
126
|
+
|
127
|
+
# Warn if a resource pack is overwriting a core resource.
|
128
|
+
# Suppress warning if the resource is an AWS resource, see #3822
|
129
|
+
|
130
|
+
if reg.key?(name) && !name.start_with?("aws_")
|
131
|
+
Inspec::Log.warn("Overwriting resource #{name}. To reference a specific version of #{name} use the resource() method")
|
132
|
+
end
|
133
|
+
|
134
|
+
reg[name] = cl
|
135
|
+
end # __register
|
136
|
+
|
137
|
+
def self.__resource_registry
|
138
|
+
# rubocop:disable Style/AndOr
|
139
|
+
find_class_instance_variable(:@__resource_registry) or
|
140
|
+
raise("__resource_registry not set!")
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.__resource_registry=(o)
|
144
|
+
@__resource_registry = o
|
145
|
+
end
|
146
|
+
|
9
147
|
def self.default_registry
|
10
148
|
@default_registry ||= {}
|
11
149
|
end
|
@@ -16,8 +154,8 @@ module Inspec
|
|
16
154
|
end
|
17
155
|
|
18
156
|
# TODO: these are keyed off of symbols
|
19
|
-
def self.
|
20
|
-
@
|
157
|
+
def self.support_registry
|
158
|
+
@support_registry ||= {}
|
21
159
|
end
|
22
160
|
|
23
161
|
def self.new_registry
|
@@ -47,48 +185,51 @@ module Inspec
|
|
47
185
|
end
|
48
186
|
end
|
49
187
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
188
|
+
def to_s
|
189
|
+
@__resource_name__
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.toggle_inspect
|
193
|
+
has_inspect = instance_method(:inspect).source_location
|
194
|
+
unless has_inspect
|
195
|
+
define_method :inspect do
|
196
|
+
to_s
|
197
|
+
end
|
198
|
+
else
|
199
|
+
undef_method :inspect
|
200
|
+
end
|
201
|
+
end
|
59
202
|
|
60
|
-
|
61
|
-
define_method :resource_class do |profile_name, resource_name|
|
62
|
-
inner_context = if profile_name == profile_context.profile_id
|
63
|
-
profile_context
|
64
|
-
else
|
65
|
-
profile_context.subcontext_by_name(profile_name)
|
66
|
-
end
|
203
|
+
attr_reader :resource_exception_message
|
67
204
|
|
68
|
-
|
205
|
+
def skip_resource(message)
|
206
|
+
@resource_skipped = true
|
207
|
+
@resource_exception_message = message
|
208
|
+
end
|
69
209
|
|
70
|
-
|
71
|
-
|
210
|
+
def resource_skipped?
|
211
|
+
@resource_skipped
|
212
|
+
end
|
72
213
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
214
|
+
def fail_resource(message)
|
215
|
+
@resource_failed = true
|
216
|
+
@resource_exception_message = message
|
217
|
+
end
|
77
218
|
|
78
|
-
|
79
|
-
|
219
|
+
def resource_failed?
|
220
|
+
@resource_failed
|
221
|
+
end
|
80
222
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
223
|
+
def check_supports
|
224
|
+
require "inspec/resources/platform"
|
225
|
+
status = inspec.platform.supported?(@supports)
|
226
|
+
fail_msg = "Resource `#{@__resource_name__}` is not supported on platform #{inspec.platform.name}/#{inspec.platform.release}."
|
227
|
+
fail_resource(fail_msg) unless status
|
228
|
+
status
|
229
|
+
end
|
85
230
|
|
86
|
-
|
87
|
-
|
88
|
-
define_method :inspec do
|
89
|
-
backend
|
90
|
-
end
|
91
|
-
end
|
231
|
+
def inspec
|
232
|
+
@__backend_runner__
|
92
233
|
end
|
93
234
|
end
|
94
235
|
|
@@ -99,8 +240,7 @@ module Inspec
|
|
99
240
|
# @return [Resource] base class for creating a new resource
|
100
241
|
def self.resource(version)
|
101
242
|
validate_resource_dsl_version!(version)
|
102
|
-
|
103
|
-
Inspec::Plugins::Resource
|
243
|
+
Inspec::Resource
|
104
244
|
end
|
105
245
|
|
106
246
|
def self.validate_resource_dsl_version!(version)
|
@@ -108,6 +248,17 @@ module Inspec
|
|
108
248
|
end
|
109
249
|
end
|
110
250
|
|
251
|
+
class Module
|
252
|
+
# Any use of this is an anti-pattern caused by our use of negligent
|
253
|
+
# design and metaprogramming nonsense. We should work it out... but
|
254
|
+
# there are many hurdles in our way.
|
255
|
+
def find_class_instance_variable(k)
|
256
|
+
(ancestors - Object.ancestors)
|
257
|
+
.find { |cls| cls.instance_variable_defined?(k) }
|
258
|
+
&.instance_variable_get(k)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
111
262
|
# Many resources use FilterTable.
|
112
263
|
require "inspec/utils/filter"
|
113
264
|
# conflicts with global `gem` method so we need to pre-load this.
|
@@ -7,9 +7,9 @@ require "inspec/utils/file_reader"
|
|
7
7
|
module Inspec::Resources
|
8
8
|
class ApacheConf < Inspec.resource(1)
|
9
9
|
name "apache_conf"
|
10
|
-
supports platform: "
|
11
|
-
|
12
|
-
desc "Use the apache_conf InSpec audit resource to test the configuration settings for Apache.
|
10
|
+
supports platform: "unix"
|
11
|
+
|
12
|
+
desc "Use the apache_conf InSpec audit resource to test the configuration settings for Apache. The configuration settings may vary significantly from platform to platform."
|
13
13
|
example <<~EXAMPLE
|
14
14
|
describe apache_conf do
|
15
15
|
its('setting_name') { should eq 'value' }
|
@@ -26,6 +26,7 @@ module Inspec::Resources
|
|
26
26
|
@files_contents = {}
|
27
27
|
@content = nil
|
28
28
|
@params = nil
|
29
|
+
|
29
30
|
read_content
|
30
31
|
end
|
31
32
|
|
@@ -34,7 +35,6 @@ module Inspec::Resources
|
|
34
35
|
end
|
35
36
|
|
36
37
|
def params(*opts)
|
37
|
-
@params || read_content
|
38
38
|
res = @params
|
39
39
|
opts.each do |opt|
|
40
40
|
res = res[opt] unless res.nil?
|
@@ -43,21 +43,11 @@ module Inspec::Resources
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def method_missing(name)
|
46
|
-
|
47
|
-
@params || read_content
|
48
|
-
|
49
|
-
# extract values
|
50
|
-
@params[name.to_s] unless @params.nil?
|
46
|
+
@params[name.to_s]
|
51
47
|
end
|
52
48
|
|
53
49
|
def filter_comments(data)
|
54
|
-
|
55
|
-
data.each_line do |line|
|
56
|
-
unless line.match(/^\s*#/)
|
57
|
-
content << line
|
58
|
-
end
|
59
|
-
end
|
60
|
-
content
|
50
|
+
data.lines.grep_v(/^\s*#/).join
|
61
51
|
end
|
62
52
|
|
63
53
|
def read_content
|
@@ -86,7 +76,7 @@ module Inspec::Resources
|
|
86
76
|
).params
|
87
77
|
|
88
78
|
# Capture any characters between quotes that are not escaped in values
|
89
|
-
params.values.
|
79
|
+
params.values.each do |value|
|
90
80
|
value.map! do |sub_value|
|
91
81
|
sub_value[/(?<=["|'])(?:\\.|[^"'\\])*(?=["|'])/] || sub_value
|
92
82
|
end
|
@@ -128,15 +118,8 @@ module Inspec::Resources
|
|
128
118
|
end
|
129
119
|
|
130
120
|
def conf_dir
|
131
|
-
|
132
|
-
|
133
|
-
else
|
134
|
-
# On RHEL-based systems, the configuration is usually in a /conf directory
|
135
|
-
# that contains the primary config file. We assume the "config path" is the
|
136
|
-
# directory that contains the /conf directory, such as /etc/httpd, so that
|
137
|
-
# the conf.d directory can be properly located.
|
138
|
-
Pathname.new(File.dirname(conf_path)).parent.to_s
|
139
|
-
end
|
121
|
+
# apparently apache conf keys are case insensitive
|
122
|
+
@params["ServerRoot"] || @params["serverroot"]
|
140
123
|
end
|
141
124
|
|
142
125
|
def to_s
|
@@ -145,12 +128,13 @@ module Inspec::Resources
|
|
145
128
|
|
146
129
|
private
|
147
130
|
|
131
|
+
PATHS = ["/etc/apache2/apache2.conf",
|
132
|
+
"/etc/httpd/conf/httpd.conf"]
|
133
|
+
.map(&:freeze)
|
134
|
+
.freeze
|
135
|
+
|
148
136
|
def default_conf_path
|
149
|
-
|
150
|
-
"/etc/apache2/apache2.conf"
|
151
|
-
else
|
152
|
-
"/etc/httpd/conf/httpd.conf"
|
153
|
-
end
|
137
|
+
@default ||= PATHS.find { |path| inspec.file(path).exist? }
|
154
138
|
end
|
155
139
|
end
|
156
140
|
end
|