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