inspec-core 5.7.9 → 5.14.0

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/etc/deprecations.json +0 -5
  3. data/lib/inspec/cli.rb +2 -0
  4. data/lib/inspec/plugin/v2/installer.rb +9 -2
  5. data/lib/inspec/plugin/v2/loader.rb +13 -0
  6. data/lib/inspec/plugin/v2/status.rb +2 -1
  7. data/lib/inspec/profile.rb +7 -5
  8. data/lib/inspec/resources/apt.rb +12 -6
  9. data/lib/inspec/resources/cgroup.rb +101 -0
  10. data/lib/inspec/resources/default_gateway.rb +61 -0
  11. data/lib/inspec/resources/docker_container.rb +21 -0
  12. data/lib/inspec/resources/docker_image.rb +53 -0
  13. data/lib/inspec/resources/file.rb +91 -0
  14. data/lib/inspec/resources/groups.rb +5 -0
  15. data/lib/inspec/resources/linux_audit_system.rb +81 -0
  16. data/lib/inspec/resources/lxc.rb +57 -0
  17. data/lib/inspec/resources/mail_alias.rb +46 -0
  18. data/lib/inspec/resources/oracledb_session.rb +7 -3
  19. data/lib/inspec/resources/postgres_session.rb +4 -2
  20. data/lib/inspec/resources/routing_table.rb +137 -0
  21. data/lib/inspec/resources/service.rb +87 -1
  22. data/lib/inspec/resources/user.rb +12 -0
  23. data/lib/inspec/resources/users.rb +79 -14
  24. data/lib/inspec/resources/virtualization.rb +9 -3
  25. data/lib/inspec/ui.rb +9 -0
  26. data/lib/inspec/version.rb +1 -1
  27. data/lib/plugins/inspec-artifact/inspec-artifact.gemspec +9 -0
  28. data/lib/plugins/inspec-compliance/inspec-compliance.gemspec +9 -0
  29. data/lib/plugins/inspec-habitat/inspec-habitat.gemspec +9 -0
  30. data/lib/plugins/inspec-init/inspec-init.gemspec +9 -0
  31. data/lib/plugins/inspec-plugin-manager-cli/inspec-plugin-manager-cli.gemspec +10 -0
  32. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +15 -11
  33. data/lib/plugins/inspec-reporter-html2/inspec-reporter-html2.gemspec +9 -0
  34. data/lib/plugins/inspec-reporter-json-min/inspec-reporter-json-min.gemspec +9 -0
  35. data/lib/plugins/inspec-reporter-junit/inspec-reporter-junit.gemspec +9 -0
  36. data/lib/plugins/inspec-streaming-reporter-progress-bar/inspec-streaming-reporter-progress-bar.gemspec +9 -0
  37. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +32 -21
  38. metadata +17 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c3af1b1fd71ded2286e8aa90c46c74dd20f7fb31c525e56ae03e52952c26b0f
4
- data.tar.gz: 507db51585f790cc26af852626b3bb52f8a35f36870ee563902234f1c2664a2a
3
+ metadata.gz: 3cb92e0f21964ecac43d5d9f208e7a2b07a57c2daa0fef4aad06515f933b9072
4
+ data.tar.gz: 6c4d5f59ed6dde73193198f4ad32c801515cec12d9c04924765d1ac7ee4bd5bc
5
5
  SHA512:
6
- metadata.gz: 00447ba26c980417a501be959fb8ca85268db4e600417c11a912181da2c75c41169d419f1177fac484a12c1c405cae3aab5912ec9cab24f9a9e4adf355d40faa
7
- data.tar.gz: f007b4bc1998187a7313f34f8739145f5e511500e2c9ca280b7163495a6909646be25fed9b4307a27f370e5e36b94b36b558c9e8d5c67079dc6cf24fb93029ea
6
+ metadata.gz: 8397f679eea47dead2cb1952026b74c800adf7ea650120597b714c3ccdef1c7e0eeb6af387458a31c028173a6bc332b07de103d4d545dbd37257573b77c6e0b1
7
+ data.tar.gz: 96d8ac046aa00ec95fc173e81c28999d744ed823af7a4a6401e9d1776817e303e451e7c8d46450d2c39c02e6ef4ff4c82d1c425683e82299effa2f3384f9f93e
@@ -83,11 +83,6 @@
83
83
  "suffix": "This resource was removed in InSpec 4.0.",
84
84
  "comment": "Needed for ServerSpec compatibility"
85
85
  },
86
- "resource_ppa": {
87
- "action": "exit",
88
- "suffix": "This resource was removed in InSpec 4.0.",
89
- "comment": "Needed for ServerSpec compatibility"
90
- },
91
86
  "resource_script": {
92
87
  "action": "exit",
93
88
  "suffix": "This resource will be removed in InSpec 4.0"
data/lib/inspec/cli.rb CHANGED
@@ -95,6 +95,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
95
95
  desc "check PATH", "verify all tests at the specified PATH"
96
96
  option :format, type: :string,
97
97
  desc: "The output format to use doc (default), json. If valid format is not provided then it will use the default."
98
+ option :with_cookstyle, type: :boolean,
99
+ desc: "Enable or disable cookstyle checks.", default: false
98
100
  profile_options
99
101
  def check(path) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
100
102
  o = config
@@ -149,12 +149,19 @@ module Inspec::Plugin::V2
149
149
 
150
150
  gem_info = {}
151
151
  matched_tuples.each do |tuple|
152
- gem_info[tuple.first.name] ||= []
153
- gem_info[tuple.first.name] << tuple.first.version.to_s
152
+ gem_info[tuple.first.name] ||= {}
153
+ gem_info[tuple.first.name]["versions"] ||= []
154
+ gem_info[tuple.first.name]["versions"] << tuple.first.version.to_s
155
+ gem_info[tuple.first.name]["description"] ||= fetch_plugin_specs(fetcher, tuple.first.name)&.summary
154
156
  end
155
157
  gem_info
156
158
  end
157
159
 
160
+ def fetch_plugin_specs(fetcher, gem_name)
161
+ plugin_dependency = Gem::Dependency.new(gem_name)
162
+ fetcher.spec_for_dependency(plugin_dependency).flatten.first
163
+ end
164
+
158
165
  # Testing API. Performs a hard reset on the installer and registry, and reloads the loader.
159
166
  # Not for public use.
160
167
  # TODO: bad timing coupling in tests
@@ -259,10 +259,15 @@ module Inspec::Plugin::V2
259
259
  status.entry_point = File.join(plugin_dir, "lib", status.name.to_s + ".rb")
260
260
  status.installation_type = :core
261
261
  status.loaded = false
262
+ status.description = fetch_gemspec(File.join(plugin_dir, status.name.to_s + ".gemspec"))&.summary
262
263
  registry[status.name.to_sym] = status
263
264
  end
264
265
  end
265
266
 
267
+ def fetch_gemspec(spec_file)
268
+ Gem::Specification.load(spec_file)
269
+ end
270
+
266
271
  def read_conf_file_into_registry
267
272
  conf_file.each do |plugin_entry|
268
273
  status = Inspec::Plugin::V2::Status.new
@@ -273,6 +278,7 @@ module Inspec::Plugin::V2
273
278
  when :user_gem
274
279
  status.entry_point = status.name.to_s
275
280
  status.version = plugin_entry[:version]
281
+ status.description = fetch_plugin_specs(status.name.to_s)&.summary
276
282
  when :path
277
283
  status.entry_point = plugin_entry[:installation_path]
278
284
  end
@@ -281,6 +287,12 @@ module Inspec::Plugin::V2
281
287
  end
282
288
  end
283
289
 
290
+ def fetch_plugin_specs(plugin_name)
291
+ fetcher = Gem::SpecFetcher.fetcher
292
+ plugin_dependency = Gem::Dependency.new(plugin_name)
293
+ fetcher.spec_for_dependency(plugin_dependency).flatten.first
294
+ end
295
+
284
296
  def fixup_train_plugin_status(status)
285
297
  status.api_generation = :'train-1'
286
298
  if status.installation_type == :user_gem
@@ -327,6 +339,7 @@ module Inspec::Plugin::V2
327
339
  status.version = plugin_spec.version.to_s
328
340
  status.loaded = false
329
341
  status.installation_type = :system_gem
342
+ status.description = plugin_spec.summary
330
343
 
331
344
  if train_plugin_name?(status[:name])
332
345
  # Train plugins are not true InSpec plugins; we need to decorate them a
@@ -14,7 +14,8 @@ module Inspec::Plugin::V2
14
14
  :loaded, # true, false False could mean not attempted or failed
15
15
  :load_exception, # Exception class if it failed to load
16
16
  :name, # String name
17
- :version # three-digit version. Core / bundled plugins use InSpec version here.
17
+ :version, # three-digit version. Core / bundled plugins use InSpec version here.
18
+ :description # Description of plugin.
18
19
  ) do
19
20
  def initialize(*)
20
21
  super
@@ -105,6 +105,7 @@ module Inspec
105
105
  @check_mode = options[:check_mode] || false
106
106
  @parent_profile = options[:parent_profile]
107
107
  @legacy_profile_path = options[:profiles_path] || false
108
+ @check_cookstyle = options[:with_cookstyle]
108
109
  Metadata.finalize(@source_reader.metadata, @profile_id, options)
109
110
 
110
111
  # if a backend has already been created, clone it so each profile has its own unique backend object
@@ -655,12 +656,13 @@ module Inspec
655
656
  end
656
657
 
657
658
  # Running cookstyle to check for code offenses
658
- cookstyle_linting_check.each do |lint_output|
659
- data = lint_output.split(":")
660
- msg = "#{data[-2]}:#{data[-1]}"
661
- offense.call(data[0], data[1], data[2], nil, msg)
659
+ if @check_cookstyle
660
+ cookstyle_linting_check.each do |lint_output|
661
+ data = lint_output.split(":")
662
+ msg = "#{data[-2]}:#{data[-1]}"
663
+ offense.call(data[0], data[1], data[2], nil, msg)
664
+ end
662
665
  end
663
-
664
666
  # profile is valid if we could not find any error & offenses
665
667
  result[:summary][:valid] = result[:errors].empty? && result[:offenses].empty?
666
668
 
@@ -135,19 +135,25 @@ module Inspec::Resources
135
135
 
136
136
  class PpaRepository < AptRepository
137
137
  name "ppa"
138
+ desc "Use the ppa InSpec audit resource to verify PPA repositories on the Debian-based linux platforms."
139
+ example <<~EXAMPLE
140
+ describe ppa('ubuntu-wine/ppa') do
141
+ it { should exist }
142
+ it { should be_enabled }
143
+ end
144
+
145
+ describe ppa('ppa:ubuntu-wine/ppa') do
146
+ it { should exist }
147
+ it { should be_enabled }
148
+ end
149
+ EXAMPLE
138
150
 
139
151
  def exists?
140
- deprecated
141
152
  super()
142
153
  end
143
154
 
144
155
  def enabled?
145
- deprecated
146
156
  super()
147
157
  end
148
-
149
- def deprecated
150
- Inspec.deprecate(:resource_ppa, "The `ppa` resource is deprecated. Please use `apt`")
151
- end
152
158
  end
153
159
  end
@@ -0,0 +1,101 @@
1
+ require "inspec/resources/command"
2
+ module Inspec::Resources
3
+ class Cgroup < Inspec.resource(1)
4
+ name "cgroup"
5
+ # Restrict to only run on the below platform
6
+ supports platform: "linux"
7
+ desc "Use the cgroup InSpec audit resource to test cgroup subsytem's parameters."
8
+
9
+ example <<~EXAMPLE
10
+ describe cgroup("foo") do
11
+ its("cpuset.cpus") { should eq 0 }
12
+ its("memory.limit_in_bytes") { should eq 499712 }
13
+ its("memory.limit_in_bytes") { should be <= 500000 }
14
+ its("memory.numa_stat") { should match /total=0/ }
15
+ end
16
+ EXAMPLE
17
+
18
+ # Resource initialization.
19
+ def initialize(cgroup_name)
20
+ raise Inspec::Exceptions::ResourceSkipped, "The `cgroup` resource is not supported on your OS yet." unless inspec.os.linux?
21
+
22
+ @cgroup_name = cgroup_name
23
+ @valid_queries, @valid_queries_split = [], []
24
+ find_valid_queries
25
+ # Used to track the method calls in an "its" query
26
+ @cgroup_info_query = []
27
+ end
28
+
29
+ def resource_id
30
+ @cgroup_name
31
+ end
32
+
33
+ def to_s
34
+ "cgroup #{resource_id}"
35
+ end
36
+
37
+ def method_missing(param)
38
+ # Add the latest param we've seen to the list and form the query with all the params we've seen so far.
39
+ @cgroup_info_query << param.to_s
40
+ query = @cgroup_info_query.join(".")
41
+
42
+ # The ith level param must match with atleast one row's ith column of @valid_queries_split
43
+ # Else there is no way, we would find any valid query in further iteration, so raise exception.
44
+ if @valid_queries_split.map { |e| e[@cgroup_info_query.length - 1] }.include?(param.to_s)
45
+ # If the query form so far is part of @valid_queries, we are good to trigger find_cgroup_info
46
+ # else go for next level of param
47
+ if @valid_queries.include?(query)
48
+ @cgroup_info_query = []
49
+ find_cgroup_info(query)
50
+ else
51
+ self
52
+ end
53
+ else
54
+ @cgroup_info_query = []
55
+
56
+ raise Inspec::Exceptions::ResourceFailed, "The query #{query} does not appear to be valid."
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # Method to find cgget tool
63
+ def find_cgget_or_error
64
+ %w{/usr/sbin/cgget /sbin/cgget cgget}.each do |cmd|
65
+ return cmd if inspec.command(cmd).exist?
66
+ end
67
+
68
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `cgget`"
69
+ end
70
+
71
+ # find the cgroup info of the query which is given as input by the user
72
+ def find_cgroup_info(query)
73
+ bin = find_cgget_or_error
74
+ cgget_cmd = "#{bin} -n -r #{query} #{@cgroup_name}"
75
+ cmd = inspec.command(cgget_cmd)
76
+
77
+ raise Inspec::Exceptions::ResourceFailed, "Executing cgget failed: #{cmd.stderr}" if cmd.exit_status.to_i != 0
78
+
79
+ # For complex returns the user must use match /the_regex/
80
+ param_value = cmd.stdout.split(":")
81
+ return nil if param_value.nil? || param_value.empty?
82
+
83
+ param_value = param_value[1].strip.split("\t").join
84
+ param_value.match(/^\d+$/) ? param_value.to_i : param_value
85
+ end
86
+
87
+ # find all the information about all relevant controllers for the current cgroup
88
+ def find_valid_queries
89
+ bin = find_cgget_or_error
90
+ cgget_all_cmd = "#{bin} -n -a #{@cgroup_name}"
91
+ cmd = inspec.command(cgget_all_cmd)
92
+
93
+ raise Inspec::Exceptions::ResourceFailed, "Executing cgget failed: #{cmd.stderr}" if cmd.exit_status.to_i != 0
94
+
95
+ queries = cmd.stdout.to_s.gsub(/:.*/, "").gsub(/^\s+.*/, "").split("\n")
96
+ # store the relevant controller parameters in @valid_queries and the dot splitted paramters into @valid_queries_split
97
+ @valid_queries = queries.map { |q| q if q.length > 0 }.compact
98
+ @valid_queries_split = @valid_queries.map { |q| q.split(".") }.compact
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,61 @@
1
+ require "inspec/resources/command"
2
+ require_relative "routing_table"
3
+
4
+ module Inspec::Resources
5
+ class Defaultgateway < Routingtable
6
+ # resource internal name.
7
+ name "default_gateway"
8
+
9
+ # Restrict to only run on the below platforms (if none were given,
10
+ # all OS's and cloud API's supported)
11
+ supports platform: "unix"
12
+ supports platform: "windows"
13
+
14
+ desc "Use the `default_gateway` Chef InSpec audit resource to test the assigned ip address and interface for the default route."
15
+
16
+ example <<~EXAMPLE
17
+ describe default_gateway do
18
+ its(:ipaddress) { should eq '172.31.80.1' }
19
+ end
20
+ describe default_gateway do
21
+ its("interface") { should eq 'eth0' }
22
+ end
23
+ EXAMPLE
24
+
25
+ def initialize
26
+ skip_resource "The `default_gateway` resource is not yet available on your OS." unless inspec.os.unix? || inspec.os.windows?
27
+ # invoke the routing_table initialize; which populates the @routing_info
28
+ super()
29
+ end
30
+
31
+ # resource appearance in test reports.
32
+ def to_s
33
+ "default_gateway"
34
+ end
35
+
36
+ # fetches the ipaddress assigned to the default gateway
37
+ # default gateway's destination is either `default` or `0.0.0.0`
38
+ def ipaddress
39
+ # @routing_info is the hash populated in routing_table resource
40
+ # @routing_info contain values as:
41
+ # {
42
+ # destination1: [ [gateway1x, interface1x], [gateway1y, interface1y] ],
43
+ # destination2: [gateway2, interface2]
44
+ # }
45
+ %w{default 0.0.0.0}.each do |destination|
46
+ return @routing_info[destination][0][0] if @routing_info.key?(destination)
47
+ end
48
+ # raise exception because no destination with value default or 0.0.0.0 is found in the routing table
49
+ raise Inspec::Exceptions::ResourceFailed, "No routing found as part of default gateway"
50
+ end
51
+
52
+ # fetches the interface assigned to the default gateway
53
+ def interface
54
+ %w{default 0.0.0.0}.each do |destination|
55
+ return @routing_info[destination][0][1] if @routing_info.key?(destination)
56
+ end
57
+ # raise exception because no destination with value default or 0.0.0.0 is found in the routing table
58
+ raise Inspec::Exceptions::ResourceFailed, "No routing found as part of default gateway"
59
+ end
60
+ end
61
+ end
@@ -43,6 +43,19 @@ module Inspec::Resources
43
43
  status.downcase.start_with?("up") if object_info.entries.length == 1
44
44
  end
45
45
 
46
+ # has_volume? matcher checks if the volume specified in source path of host is mounted in destination path of docker
47
+ def has_volume?(destination, source)
48
+ # volume_info is the hash which contains the low-level information about the container
49
+ # if Mounts key is not present or is nil; raise exception
50
+ raise Inspec::Exceptions::ResourceFailed, "Could not find any mounted volumes for your container" unless volume_info.Mounts[0]
51
+
52
+ # Iterate through the list of mounted volumes and check if it matches with the given destination and source
53
+ # is_mounted flag is used to handle to return explict boolean values of true or false
54
+ is_mounted = false
55
+ volume_info.Mounts.detect { |mount| is_mounted = mount.Destination == destination && mount.Source == source }
56
+ is_mounted
57
+ end
58
+
46
59
  def status
47
60
  object_info.status[0] if object_info.entries.length == 1
48
61
  end
@@ -87,5 +100,13 @@ module Inspec::Resources
87
100
  opts = @opts
88
101
  @info = inspec.docker.containers.where { names == opts[:name] || (!id.nil? && !opts[:id].nil? && (id == opts[:id] || id.start_with?(opts[:id]))) }
89
102
  end
103
+
104
+ # volume_info returns the low-level information obtained on docker inspect [container_name/id]
105
+ def volume_info
106
+ return @mount_info if defined?(@mount_info)
107
+
108
+ # Check for either docker inspect [container_name] or docker inspect [container_id]
109
+ @mount_info = inspec.docker.object(@opts[:name] || @opts[:id])
110
+ end
90
111
  end
91
112
  end
@@ -48,6 +48,25 @@ module Inspec::Resources
48
48
  object_info.tags[0] if object_info.entries.size == 1
49
49
  end
50
50
 
51
+ # method_missing handles when hash_keys are invoked to check information obtained on docker inspect [image_name]
52
+ def method_missing(*hash_keys)
53
+ # User can test the low-level inspect information in three ways:
54
+ # Way 1: Serverspec style: its(['Config.Cmd']) { should include some_value }
55
+ # here, the value for hash_keys recieved is [:[], "Config.Cmd"]
56
+ # Way 2: InSpec style: its(['Config','Cmd']) { should include some_value }
57
+ # here, the value for hash_keys recieved is [:[], "Config", "Cmd"]
58
+ # Way 3: Mix of both: its(['GraphDriver.Data','MergedDir']) { should include some_value }
59
+ # here, the value for hash_keys recieved is [:[], "GraphDriver.Data", "MergedDir"]
60
+
61
+ # hash_keys are passed to this method to evaluate the value
62
+ image_hash_inspection(hash_keys)
63
+ end
64
+
65
+ # inspection property allows to test any of the hash key-value pairs as part of the image_inspect_info
66
+ def inspection
67
+ image_inspect_info
68
+ end
69
+
51
70
  def to_s
52
71
  img = @opts[:image] || @opts[:id]
53
72
  "Docker Image #{img}"
@@ -80,5 +99,39 @@ module Inspec::Resources
80
99
  (repository == opts[:repo] && tag == opts[:tag]) || (!id.nil? && !opts[:id].nil? && (id == opts[:id] || id.start_with?(opts[:id])))
81
100
  end
82
101
  end
102
+
103
+ # image_inspect_info returns the complete inspect hash_values of the image
104
+ def image_inspect_info
105
+ return @inspect_info if defined?(@inspect_info)
106
+
107
+ @inspect_info = inspec.docker.object(@opts[:image] || (!@opts[:id].nil? && @opts[:id]))
108
+ end
109
+
110
+ # image_hash_inspection formats the input hash_keys and checks if any value exists for such keys in @inspect_info(image_inspect_info)
111
+ def image_hash_inspection(hash_keys)
112
+ # The hash_keys recieved are in three formats as mentioned in method_missing
113
+ # The hash_keys recieved must be in array format [] and the zeroth index must be :[]
114
+ # Check for the conditions and remove the zeroth element from the hash_keys
115
+
116
+ hash_keys.shift if hash_keys.is_a?(Array) && hash_keys[0] == :[]
117
+
118
+ # When received hash_keys in Serverspec style or mix of both
119
+ # The hash_keys are to be splitted at '.' (dot) and flatten it so that it doesn't become array of arrays
120
+ # After splitting and flattening is done, hash_keys is now an array with individual keys
121
+ hash_keys = hash_keys.map { |key| key.split(".") }.flatten
122
+
123
+ # image_inspect_info returns the complete inspect hash_values of the image
124
+ # dig() finds the nested value specified by the sequence of the key object by calling dig at each step.
125
+ # hash_keys is the key object. If one of the key is bad, value will be nil.
126
+ hash_value = image_inspect_info.dig(*hash_keys)
127
+
128
+ # If one of the key is bad, hash_value will be nil, so raise exception which throws it in rescue block
129
+ # else return hash_value
130
+ raise Inspec::Exceptions::ResourceFailed if hash_value.nil?
131
+
132
+ hash_value
133
+ rescue
134
+ raise Inspec::Exceptions::ResourceFailed, "#{hash_keys.join(".")} is not a valid key for your image or has nil value."
135
+ end
83
136
  end
84
137
  end
@@ -181,6 +181,34 @@ module Inspec::Resources
181
181
  inv_mode & file.mode != 0
182
182
  end
183
183
 
184
+ def immutable?
185
+ raise Inspec::Exceptions::ResourceSkipped, "The `be_immutable` matcher is not supported on your OS yet." unless inspec.os.unix?
186
+
187
+ if inspec.os.linux?
188
+ file_info = LinuxImmutableFlagCheck.new(inspec, file)
189
+ else
190
+ file_info = UnixImmutableFlagCheck.new(inspec, file)
191
+ end
192
+
193
+ file_info.is_immutable?
194
+ end
195
+
196
+ # parse the json file content and returns the content
197
+ def content_as_json
198
+ require "json" unless defined?(JSON)
199
+ JSON.parse(file.content)
200
+ rescue => e
201
+ raise Inspec::Exceptions::ResourceFailed, "Unable to parse the given JSON file: #{e.message}"
202
+ end
203
+
204
+ # parse the yaml file content and returns the content
205
+ def content_as_yaml
206
+ require "yaml" unless defined?(YAML)
207
+ YAML.load(file.content)
208
+ rescue => e
209
+ raise Inspec::Exceptions::ResourceFailed, "Unable to parse the given YAML file: #{e.message}"
210
+ end
211
+
184
212
  def to_s
185
213
  if file
186
214
  "File #{source_path}"
@@ -373,4 +401,67 @@ module Inspec::Resources
373
401
  end
374
402
  end
375
403
  end
404
+
405
+ # Helper class for immutable matcher.
406
+ class ImmutableFlagCheck
407
+ attr_reader :inspec, :file_path
408
+ def initialize(inspec, file)
409
+ @inspec = inspec
410
+ @file_path = file.path
411
+ end
412
+
413
+ def find_utility_or_error(utility_name)
414
+ [
415
+ "/usr/sbin/#{utility_name}",
416
+ "/sbin/#{utility_name}",
417
+ "/usr/bin/#{utility_name}",
418
+ "/bin/#{utility_name}",
419
+ "#{utility_name}",
420
+ ].each do |cmd|
421
+ return cmd if inspec.command(cmd).exist?
422
+ end
423
+
424
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `#{utility_name}`"
425
+ end
426
+ end
427
+
428
+ class LinuxImmutableFlagCheck < ImmutableFlagCheck
429
+ def is_immutable?
430
+ # Check if lsattr is available. In general, all linux system has lsattr & chattr
431
+ # This logic check is valid for immutable flag set with chattr
432
+ utility = find_utility_or_error("lsattr")
433
+ utility_cmd = inspec.command("#{utility} #{file_path}")
434
+
435
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{utility} #{file_path} failed: #{utility_cmd.stderr}" if utility_cmd.exit_status.to_i != 0
436
+
437
+ # General output for lsattr file_name is:
438
+ # ----i---------e----- file_name
439
+ # The fifth char resembles the immutable flag. Total 20 flags are allowed.
440
+ lsattr_info = utility_cmd.stdout.strip.squeeze(" ")
441
+ lsattr_info =~ /^.{4}i.{15} .*/
442
+ end
443
+ end
444
+
445
+ class UnixImmutableFlagCheck < ImmutableFlagCheck
446
+ def is_immutable?
447
+ # Check if chflags is available on the system. Most unix-like system comes with chflags.
448
+ # This logic check is valid for immutable flag set with chflags
449
+ find_utility_or_error("chflags")
450
+
451
+ # In general ls -lO is used to check immutable flag set by chflags
452
+ utility_cmd = inspec.command("ls -lO #{file_path}")
453
+
454
+ # But on some bsd system (eg: freebsd) ls -lo is used instead of ls -lO
455
+ utility_cmd = inspec.command("ls -lo #{file_path}") if utility_cmd.exit_status.to_i != 0
456
+
457
+ raise Inspec::Exceptions::ResourceFailed, "Executing ls -lo #{file_path} and ls -lO #{file_path} failed: #{utility_cmd.stderr}" if utility_cmd.exit_status.to_i != 0
458
+
459
+ # General output for ls -lO file_name is:
460
+ # -rw-r--r-- 1 current_user 1083951318 uchg 0 Apr 6 12:45 file_name
461
+ # The schg flag and the uchg flag represents the immutable flags
462
+ # uchg => user immutable flag, schg => system immutable flag.
463
+ file_info = utility_cmd.stdout.strip.split
464
+ file_info.include?("uchg") || file_info.include?("schg")
465
+ end
466
+ end
376
467
  end
@@ -145,6 +145,11 @@ module Inspec::Resources
145
145
  true
146
146
  end
147
147
 
148
+ # matcher equivalent to gid property.
149
+ def has_gid?(gid_value)
150
+ gid_value == gid
151
+ end
152
+
148
153
  def to_s
149
154
  "Group #{@group}"
150
155
  end
@@ -0,0 +1,81 @@
1
+ require "inspec/resources/command"
2
+ module Inspec::Resources
3
+ class LinuxAuditSystem < Inspec.resource(1)
4
+ # Resource's internal name.
5
+ name "linux_audit_system"
6
+
7
+ # Restrict to only run on the below platforms (if none were given,
8
+ # all OS's and cloud API's supported)
9
+ supports platform: "linux"
10
+
11
+ desc "Use the `linux_audit_system` Chef InSpec audit resource to test the configuration of linux audit system."
12
+
13
+ example <<~EXAMPLE
14
+ describe linux_audit_system do
15
+ it { should be_enabled }
16
+ it { should be_running }
17
+ its("rules") { should include "-w /etc -p wa" }
18
+ its("rules") { should include %r{-w /etc -p wa} }
19
+ its("rules") { should include %r!-w /etc -p wa! }
20
+ end
21
+ EXAMPLE
22
+
23
+ attr_reader :auditctl_utility
24
+
25
+ # Resource initialization.
26
+ def initialize
27
+ skip_resource "The `linux_audit_system` resource is not yet available on your OS." unless inspec.os.linux?
28
+ @auditctl_utility = find_auditctl_or_error
29
+ end
30
+
31
+ # Resource appearance in test reports.
32
+ def to_s
33
+ "linux_audit_system"
34
+ end
35
+
36
+ # The be_enabled matcher checks if the auditing is enabled.
37
+ # The enabled flag 1 indicates that the auditing is enabled.
38
+ def enabled?
39
+ auditctl_cmd = inspec.command("#{auditctl_utility} -s | grep enabled")
40
+
41
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{auditctl_utility} -s | grep enabled failed: #{auditctl_cmd.stderr}" if auditctl_cmd.exit_status.to_i != 0
42
+
43
+ # Sample stdout: enabled 1
44
+ auditctl_enabled_status = auditctl_cmd.stdout.strip.split
45
+ auditctl_enabled_status[1].to_i == 1
46
+ end
47
+
48
+ # The be_running matcher checks if the audit daemon is running.
49
+ # A pid of 0 indicates that the audit daemon is not running.
50
+ def running?
51
+ auditctl_cmd = inspec.command("#{auditctl_utility} -s | grep pid")
52
+
53
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{auditctl_utility} -s | grep enabled failed: #{auditctl_cmd.stderr}" if auditctl_cmd.exit_status.to_i != 0
54
+
55
+ # Sample stdout: pid 682462
56
+ auditctl_running_status = auditctl_cmd.stdout.strip.split
57
+ !auditctl_running_status[1].nil? && auditctl_running_status[1].to_i != 0
58
+ end
59
+
60
+ # The rules property returns the array of audit rules obtained on auditctl -l.
61
+ # The auditctl -l list all rules, 1 per line.
62
+ def rules
63
+ auditctl_cmd = inspec.command("#{auditctl_utility} -l")
64
+
65
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{auditctl_utility} -l: #{auditctl_cmd.stderr}" if auditctl_cmd.exit_status.to_i != 0
66
+
67
+ auditctl_cmd.stdout.strip.split("\n")
68
+ end
69
+
70
+ private
71
+
72
+ # Check if auditctl is available on the system.
73
+ def find_auditctl_or_error
74
+ %w{/usr/sbin/auditctl /sbin/auditctl auditctl}.each do |cmd|
75
+ return cmd if inspec.command(cmd).exist?
76
+ end
77
+
78
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `auditctl`. This resource requires `auditctl` utility to be available on the system."
79
+ end
80
+ end
81
+ end