inspec-core 5.7.9 → 5.14.0

Sign up to get free protection for your applications and to get access to all the features.
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