inspec-core 4.18.51 → 4.18.85

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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +61 -0
  3. data/README.md +3 -3
  4. data/inspec-core.gemspec +51 -0
  5. data/lib/bundles/inspec-supermarket/cli.rb +1 -0
  6. data/lib/inspec/backend.rb +49 -47
  7. data/lib/inspec/base_cli.rb +2 -2
  8. data/lib/inspec/cached_fetcher.rb +4 -0
  9. data/lib/inspec/cli.rb +5 -0
  10. data/lib/inspec/config.rb +1 -1
  11. data/lib/inspec/control_eval_context.rb +131 -199
  12. data/lib/inspec/dependencies/requirement.rb +1 -1
  13. data/lib/inspec/dependencies/resolver.rb +46 -0
  14. data/lib/inspec/dsl_shared.rb +25 -3
  15. data/lib/inspec/fetcher.rb +0 -3
  16. data/lib/inspec/fetcher/git.rb +4 -0
  17. data/lib/inspec/fetcher/url.rb +1 -2
  18. data/lib/inspec/file_provider.rb +4 -2
  19. data/lib/inspec/library_eval_context.rb +37 -37
  20. data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +27 -0
  21. data/lib/inspec/plugin/v1/plugins.rb +0 -1
  22. data/lib/inspec/profile.rb +8 -6
  23. data/lib/inspec/profile_context.rb +74 -9
  24. data/lib/inspec/profile_vendor.rb +48 -3
  25. data/lib/inspec/resource.rb +192 -41
  26. data/lib/inspec/resources/aide_conf.rb +1 -1
  27. data/lib/inspec/resources/apache_conf.rb +15 -31
  28. data/lib/inspec/resources/command.rb +1 -1
  29. data/lib/inspec/resources/crontab.rb +56 -56
  30. data/lib/inspec/resources/etc_fstab.rb +1 -1
  31. data/lib/inspec/resources/etc_group.rb +1 -1
  32. data/lib/inspec/resources/etc_hosts.rb +2 -3
  33. data/lib/inspec/resources/etc_hosts_allow_deny.rb +1 -1
  34. data/lib/inspec/resources/file.rb +2 -2
  35. data/lib/inspec/resources/filesystem.rb +4 -4
  36. data/lib/inspec/resources/groups.rb +16 -2
  37. data/lib/inspec/resources/iis_app.rb +1 -1
  38. data/lib/inspec/resources/ini.rb +1 -2
  39. data/lib/inspec/resources/mount.rb +2 -2
  40. data/lib/inspec/resources/oracledb_session.rb +1 -1
  41. data/lib/inspec/resources/package.rb +22 -0
  42. data/lib/inspec/resources/passwd.rb +1 -1
  43. data/lib/inspec/resources/platform.rb +36 -36
  44. data/lib/inspec/resources/port.rb +1 -1
  45. data/lib/inspec/resources/postfix_conf.rb +1 -1
  46. data/lib/inspec/resources/service.rb +23 -15
  47. data/lib/inspec/resources/users.rb +3 -3
  48. data/lib/inspec/resources/virtualization.rb +15 -11
  49. data/lib/inspec/resources/x509_certificate.rb +18 -4
  50. data/lib/inspec/resources/xinetd_conf.rb +1 -1
  51. data/lib/inspec/resources/xml.rb +1 -2
  52. data/lib/inspec/rspec_extensions.rb +12 -0
  53. data/lib/inspec/rule.rb +63 -22
  54. data/lib/inspec/utils/filter.rb +2 -0
  55. data/lib/inspec/utils/parser.rb +244 -240
  56. data/lib/inspec/utils/simpleconfig.rb +1 -1
  57. data/lib/inspec/version.rb +1 -1
  58. data/lib/matchers/matchers.rb +11 -10
  59. data/lib/plugins/inspec-compliance/lib/inspec-compliance.rb +3 -0
  60. data/lib/plugins/inspec-habitat/lib/inspec-habitat/profile.rb +2 -2
  61. data/lib/plugins/inspec-init/templates/profiles/aws/README.md +192 -0
  62. data/lib/plugins/inspec-init/templates/profiles/aws/attributes.yml +2 -0
  63. data/lib/plugins/inspec-init/templates/profiles/aws/controls/example.rb +39 -0
  64. data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +22 -0
  65. data/lib/plugins/inspec-init/templates/profiles/azure/README.md +56 -0
  66. data/lib/plugins/inspec-init/templates/profiles/azure/controls/example.rb +14 -0
  67. data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +14 -0
  68. data/lib/plugins/inspec-init/templates/profiles/gcp/README.md +66 -0
  69. data/lib/plugins/inspec-init/templates/profiles/gcp/attributes.yml +2 -0
  70. data/lib/plugins/inspec-init/templates/profiles/gcp/controls/example.rb +27 -0
  71. data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +19 -0
  72. data/lib/source_readers/inspec.rb +1 -1
  73. metadata +87 -74
  74. data/lib/inspec/plugin/v1/plugin_types/resource.rb +0 -176
  75. 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
@@ -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.supports
20
- @supports ||= {}
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
- # Creates the inner DSL which includes all resources for
51
- # creating tests. It is always connected to one target,
52
- # which is specified via the backend argument.
53
- #
54
- # @param backend [BackendRunner] exposing the target to resources
55
- # @return [ResourcesDSL]
56
- def self.create_dsl(profile_context)
57
- backend = profile_context.backend
58
- my_registry = profile_context.resource_registry
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
- Module.new do
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
- raise ProfileNotFound, "Cannot find profile named: #{profile_name}" if inner_context.nil?
205
+ def skip_resource(message)
206
+ @resource_skipped = true
207
+ @resource_exception_message = message
208
+ end
69
209
 
70
- inner_context.resource_registry[resource_name]
71
- end
210
+ def resource_skipped?
211
+ @resource_skipped
212
+ end
72
213
 
73
- my_registry.each do |id, r|
74
- define_method id.to_sym do |*args|
75
- r.new(backend, id.to_s, *args)
76
- end
214
+ def fail_resource(message)
215
+ @resource_failed = true
216
+ @resource_exception_message = message
217
+ end
77
218
 
78
- # confirm backend custom resources have access to other custom resources
79
- next if backend.respond_to?(id)
219
+ def resource_failed?
220
+ @resource_failed
221
+ end
80
222
 
81
- backend.class.send(:define_method, id.to_sym) do |*args|
82
- r.new(backend, id.to_s, *args)
83
- end
84
- end
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
- # attach backend so we have access to all resources and
87
- # the train connection object
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
- require "inspec/plugin/v1/plugin_types/resource"
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.
@@ -24,7 +24,7 @@ module Inspec::Resources
24
24
 
25
25
  attr_reader :params
26
26
 
27
- include CommentParser
27
+ include Inspec::Utils::CommentParser
28
28
  include FileReader
29
29
 
30
30
  def initialize(aide_conf_path = nil)
@@ -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: "linux"
11
- supports platform: "debian"
12
- desc "Use the apache_conf InSpec audit resource to test the configuration settings for Apache. This file is typically located under /etc/apache2 on the Debian and Ubuntu platforms and under /etc/httpd on the Fedora, CentOS, Red Hat Enterprise Linux, and Arch Linux platforms. The configuration settings may vary significantly from platform to platform."
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
- # ensure params are loaded
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
- content = ""
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.map! do |value|
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
- if inspec.os.debian?
132
- File.dirname(conf_path)
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
- if inspec.os.debian?
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