inspec-core 5.10.5 → 5.17.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d96c590b3ff91ce5db5eaf8ee6ef68721fd17d103f250c8c3106941784f8f336
4
- data.tar.gz: 50b56c3506f186b51fcf07df23a59050461132e0c03245bed088961ef0e854fb
3
+ metadata.gz: b31dbb074483f274162eeea0fde1b234cd19e1c65257e19f7d0bc2f46c375b70
4
+ data.tar.gz: 31756fedad66edc248e5ae7b1ca5ab08408f6d7d6e6ce6dfb9053d1323c1acac
5
5
  SHA512:
6
- metadata.gz: 0eeb703b52391323d79b9ff99ce9bdc4b4aac5903fd6e10087b7ae92372b733b7a5779672fb67a3dc7aa6cfb2e3c859ca35ab81a6e49c0331ae623487e6ea241
7
- data.tar.gz: 7f076f8c2bef5080a73b6fb8886657137a558bf9c624dd11906c91ef9f555e8c6713af8f62282f9a2fa20e650624fbcd3172fe0e9557f59dd8eb539684192ff1
6
+ metadata.gz: e765163668a3799ae45fbe2a5ddd219832341c32b01b62422506324a62c44b99ad771f47c662be5abb1198fdd65969e2a038f54c5059841561edda42170f7631
7
+ data.tar.gz: bbbadea0724f15d1a67c895eab750eea1a660bfc2e65143af703f8b5a88eb9e0c9fde1f9c08356fd9e4b32d202d620902fbad8784618cc01eec866c20f5247c7
@@ -0,0 +1,9 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8FWKhVwT5ilFdk5/schY
3
+ J3X6DkDbZPul7MAKYuicmyvrk4VZFqFJYzUo9G+ZvtWCjCMZF3JLDbg0VJ+V3j2b
4
+ A5MRUDMH1MtbQZS8u0AIUAitHsSiMgu4w/EHUSHODoDmbdaHT1xkFe9IxMSM1/AV
5
+ /s5u2uAMuiayo+dGW5i9xf/LMZN1JCeX8Yqw85CpS01gMC2XxoUu5wGLwBwXkdql
6
+ +H3SaWRdDTtccgtu0Rxt9dzRtHAPNWKSLl9TScW6Qt/+7bgb3M6+7od/FuPziAS9
7
+ 1NGUiKL9vajpafxUJF8863q0l64dAXGWXVFed7/7GFiNVuLxBksPzK8VrkyCS214
8
+ kQIDAQAB
9
+ -----END PUBLIC KEY-----
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
@@ -50,7 +50,11 @@ module Inspec
50
50
  end
51
51
 
52
52
  def gem_version_installed?(name, version)
53
- list_installed_gems.any? { |s| s.name == name && Gem::Requirement.new(version.split(",")) =~ s.version }
53
+ if version.nil?
54
+ list_installed_gems.any? { |s| s.name == name }
55
+ else
56
+ list_installed_gems.any? { |s| s.name == name && Gem::Requirement.new(version.split(",")) =~ s.version }
57
+ end
54
58
  end
55
59
 
56
60
  private
@@ -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
@@ -409,7 +410,7 @@ module Inspec
409
410
  else
410
411
  ui = Inspec::UI.new
411
412
  gem_dependencies.each { |gem_dependency| ui.list_item("#{gem_dependency[:name]} #{gem_dependency[:version]}") }
412
- choice = ui.prompt.select("Would you like to install profile gem dependencies listed above?", %w{Yes No})
413
+ choice = ui.prompt.select("The above listed gem dependencies are required to run the profile. Would you like to install them ?", %w{Yes No})
413
414
  if choice == "Yes"
414
415
  Inspec::Config.cached[:auto_install_gems] = true
415
416
  load_gem_dependencies
@@ -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
 
@@ -848,7 +850,12 @@ module Inspec
848
850
  f = load_rule_filepath(prefix, rule)
849
851
  load_rule(rule, f, controls, groups)
850
852
  end
851
- params[:inputs] = Inspec::InputRegistry.list_inputs_for_profile(@profile_id)
853
+ if @profile_id.nil?
854
+ # identifying inputs using profile name
855
+ params[:inputs] = Inspec::InputRegistry.list_inputs_for_profile(params[:name])
856
+ else
857
+ params[:inputs] = Inspec::InputRegistry.list_inputs_for_profile(@profile_id)
858
+ end
852
859
  params
853
860
  end
854
861
 
@@ -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
@@ -113,6 +113,16 @@ module Inspec::Resources
113
113
  resolve.nil? || resolve.empty? ? nil : resolve
114
114
  end
115
115
 
116
+ # returns an array of the ipv4 addresses
117
+ def ipv4_address
118
+ ipaddress.select { |ip| ip.match(Resolv::IPv4::Regex) }
119
+ end
120
+
121
+ # returns an array of the ipv6 addresses
122
+ def ipv6_address
123
+ ipaddress.select { |ip| ip.match(Resolv::IPv6::Regex) }
124
+ end
125
+
116
126
  def to_s
117
127
  resource_name = "Host #{hostname}"
118
128
  resource_name += " port #{port} proto #{protocol}" if port
@@ -296,15 +306,44 @@ module Inspec::Resources
296
306
  end
297
307
 
298
308
  def resolve(hostname)
309
+ addresses = []
310
+ # -Type A is the DNS query for IPv4 server Address.
299
311
  cmd = inspec.command("Resolve-DnsName –Type A #{hostname} | ConvertTo-Json")
300
312
  begin
301
- resolv = JSON.parse(cmd.stdout)
313
+ resolve_ipv4 = JSON.parse(cmd.stdout)
302
314
  rescue JSON::ParserError => _e
303
315
  return nil
304
316
  end
305
317
 
306
- resolv = [resolv] unless resolv.is_a?(Array)
307
- resolv.map { |entry| entry["IPAddress"] }
318
+ resolve_ipv4 = resolve_ipv4.inject(:merge) if resolve_ipv4.is_a?(Array)
319
+
320
+ # Append the ipv4 addresses
321
+ resolve_ipv4.each_value do |ip|
322
+ matched = ip.to_s.chomp.match(Resolv::IPv4::Regex)
323
+ next if matched.nil? || addresses.include?(matched.to_s)
324
+
325
+ addresses << matched.to_s
326
+ end
327
+
328
+ # -Type AAAA is the DNS query for IPv6 server Address.
329
+ cmd = inspec.command("Resolve-DnsName –Type AAAA #{hostname} | ConvertTo-Json")
330
+ begin
331
+ resolve_ipv6 = JSON.parse(cmd.stdout)
332
+ rescue JSON::ParserError => _e
333
+ return nil
334
+ end
335
+
336
+ resolve_ipv6 = resolve_ipv6.inject(:merge) if resolve_ipv6.is_a?(Array)
337
+
338
+ # Append the ipv6 addresses
339
+ resolve_ipv6.each_value do |ip|
340
+ matched = ip.to_s.chomp.match(Resolv::IPv6::Regex)
341
+ next if matched.nil? || addresses.include?(matched.to_s)
342
+
343
+ addresses << matched.to_s
344
+ end
345
+
346
+ addresses
308
347
  end
309
348
  end
310
349
  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
@@ -0,0 +1,46 @@
1
+ require "inspec/resources/command"
2
+ module Inspec::Resources
3
+ class Mailalias < Inspec.resource(1)
4
+ # resource internal name.
5
+ name "mail_alias"
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: "unix"
10
+
11
+ desc "Use the mail_alias InSpec audit resource to test mail alias present in the aliases file"
12
+
13
+ example <<~EXAMPLE
14
+ describe mail_alias("toor") do
15
+ it { should be_aliased_to "root" }
16
+ end
17
+ EXAMPLE
18
+
19
+ def initialize(alias_key)
20
+ skip_resource "The `mail_alias` resource is not yet available on your OS." unless inspec.os.unix?
21
+ @alias_key = alias_key
22
+ end
23
+
24
+ # resource_id is used in reporting engines to uniquely identify the individual resource.
25
+ def resource_id
26
+ "#{@alias_key}"
27
+ end
28
+
29
+ # resource appearance in test reports.
30
+ def to_s
31
+ "mail_alias #{resource_id}"
32
+ end
33
+
34
+ # aliased_to matcher checks if the given alias_value is set to the initialized alias_key
35
+ def aliased_to?(alias_value)
36
+ # /etc/aliases if the file where the alias and its value(s) are stored
37
+ cmd = inspec.command("cat /etc/aliases | grep '^#{@alias_key}:'")
38
+ raise Inspec::Exceptions::ResourceFailed, "#{@alias_key} is not a valid key in the aliases" if cmd.exit_status.to_i != 0
39
+
40
+ # in general aliases file contains : separated values like alias_key : alias_value1, alias_value2
41
+ alias_values_combined = cmd.stdout.split(":").map(&:strip)[1]
42
+ alias_values_splitted = alias_values_combined.split(",").map(&:strip)
43
+ alias_values_splitted.include?(alias_value)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,72 @@
1
+ require "inspec/resources/command"
2
+
3
+ module Inspec::Resources
4
+ class PhpConfig < Inspec.resource(1)
5
+ # Resource's internal name.
6
+ name "php_config"
7
+ supports platform: "unix"
8
+ supports platform: "windows"
9
+ desc "Use the php_config InSpec audit resource to test PHP config parameters"
10
+
11
+ example <<~EXAMPLE
12
+ describe php_config("config_param") do
13
+ its("value") { should eq "some_value" }
14
+ end
15
+
16
+ describe php_config("config_param", { "ini" => "path_to_ini_file" }) do
17
+ its("value") { should eq "some_value" }
18
+ end
19
+ EXAMPLE
20
+
21
+ # Resource initialization.
22
+ attr_reader :config_param, :config_file_or_path
23
+ def initialize(config_param, config_file_or_path = {})
24
+ @config_param = config_param
25
+ @config_file_or_path = config_file_or_path
26
+ end
27
+
28
+ # Unique resource id
29
+ def resource_id
30
+ config_param
31
+ end
32
+
33
+ # Resource appearance in test reports.
34
+ def to_s
35
+ "php_config #{resource_id}"
36
+ end
37
+
38
+ # Returns the value evaluated for the initialized config parameter
39
+ def value
40
+ php_utility = find_utility_or_error
41
+
42
+ # The keys in the hash provided by user can be string or symbols.
43
+ # Converting the key to symbols to handle scenario when "ini" key is provided as string.
44
+ config_file_or_path.transform_keys(&:to_sym)
45
+
46
+ # Assign the path with -c option for ini file provided by the user if any.
47
+ php_ini_file = !config_file_or_path.empty? && config_file_or_path.key?(:ini) ? "-c #{config_file_or_path[:ini]}" : ""
48
+
49
+ # The below command `get_cfg_var` is used to fetch the value for any config parameter.
50
+ php_cmd = "#{php_utility} #{php_ini_file} -r 'echo get_cfg_var(\"#{config_param}\");'"
51
+ config_value_cmd = inspec.command(php_cmd)
52
+
53
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{php_cmd} failed: #{config_value_cmd.stderr}" if config_value_cmd.exit_status.to_i != 0
54
+
55
+ config_value = config_value_cmd.stdout.strip
56
+
57
+ # Convert value to integer if the config value are digits.
58
+ config_value.match(/^(\d)+$/) ? config_value.to_i : config_value
59
+ end
60
+
61
+ private
62
+
63
+ # Method to check if php is present or not on the system.
64
+ def find_utility_or_error
65
+ %w{/usr/sbin/php /sbin/php php}.each do |cmd|
66
+ return cmd if inspec.command(cmd).exist?
67
+ end
68
+
69
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `php` on your system."
70
+ end
71
+ end
72
+ end
@@ -60,6 +60,17 @@ module Inspec::Resources
60
60
  @list
61
61
  end
62
62
 
63
+ # Matcher to check if the process is running
64
+ def running?
65
+ # A process is considered running if:
66
+ # unix: it is in running(R) state or either of sleep state(D: Uninterruptible or S: Interruptible)
67
+ # windows: it is responding i.e. state is True.
68
+
69
+ # Other codes like <(high priorty), N(low priority), +(foreground process group) etc. may appear after the state code in unix.
70
+ # Hence the regex used is /^statecode+/ where statecode is either R, S, or D.
71
+ states.any? and !!(states[0] =~ /True/ || states[0] =~ /^R+/ || states[0] =~ /^D+/ || states[0] =~ /^S+/)
72
+ end
73
+
63
74
  filter = FilterTable.create
64
75
  filter.register_column(:labels, field: "label")
65
76
  .register_column(:pids, field: "pid")