inspec 0.9.5 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -8
  3. data/README.md +8 -39
  4. data/Rakefile +74 -9
  5. data/bin/inspec +66 -10
  6. data/docs/ctl_inspec.rst +7 -1
  7. data/docs/inspec_and_friends.rst +1 -1
  8. data/docs/resources.rst +51 -45
  9. data/examples/README.md +7 -0
  10. data/examples/kitchen-ansible/.kitchen.yml +25 -0
  11. data/examples/kitchen-ansible/Gemfile +20 -0
  12. data/examples/kitchen-ansible/README.md +53 -0
  13. data/examples/kitchen-ansible/files/nginx.repo +6 -0
  14. data/examples/kitchen-ansible/tasks/main.yml +16 -0
  15. data/examples/kitchen-ansible/test/integration/default/default.yml +5 -0
  16. data/examples/{test-kitchen → kitchen-ansible}/test/integration/default/web_spec.rb +0 -0
  17. data/examples/{test-kitchen → kitchen-chef}/.kitchen.yml +1 -1
  18. data/examples/{test-kitchen → kitchen-chef}/Berksfile +0 -0
  19. data/examples/{test-kitchen → kitchen-chef}/Gemfile +1 -2
  20. data/examples/{test-kitchen → kitchen-chef}/README.md +1 -1
  21. data/examples/{test-kitchen → kitchen-chef}/metadata.rb +0 -0
  22. data/examples/{test-kitchen → kitchen-chef}/recipes/default.rb +0 -0
  23. data/examples/{test-kitchen → kitchen-chef}/recipes/nginx.rb +0 -0
  24. data/examples/kitchen-chef/test/integration/default/web_spec.rb +28 -0
  25. data/examples/kitchen-puppet/.kitchen.yml +22 -0
  26. data/examples/kitchen-puppet/Gemfile +21 -0
  27. data/examples/kitchen-puppet/Puppetfile +25 -0
  28. data/examples/kitchen-puppet/README.md +53 -0
  29. data/examples/kitchen-puppet/manifests/site.pp +33 -0
  30. data/examples/kitchen-puppet/metadata.json +11 -0
  31. data/examples/kitchen-puppet/test/integration/default/web_spec.rb +28 -0
  32. data/inspec.gemspec +2 -0
  33. data/lib/inspec/plugins/resource.rb +21 -0
  34. data/lib/inspec/shell.rb +73 -11
  35. data/lib/inspec/version.rb +1 -1
  36. data/lib/matchers/matchers.rb +43 -0
  37. data/lib/resources/apache_conf.rb +12 -9
  38. data/lib/resources/apt.rb +7 -0
  39. data/lib/resources/audit_policy.rb +6 -6
  40. data/lib/resources/auditd_conf.rb +6 -7
  41. data/lib/resources/auditd_rules.rb +9 -8
  42. data/lib/resources/bond.rb +6 -6
  43. data/lib/resources/bridge.rb +7 -0
  44. data/lib/resources/command.rb +10 -8
  45. data/lib/resources/csv.rb +6 -5
  46. data/lib/resources/directory.rb +6 -0
  47. data/lib/resources/etc_group.rb +9 -1
  48. data/lib/resources/file.rb +72 -61
  49. data/lib/resources/gem.rb +6 -4
  50. data/lib/resources/group.rb +7 -0
  51. data/lib/resources/host.rb +6 -0
  52. data/lib/resources/inetd_conf.rb +8 -8
  53. data/lib/resources/ini.rb +6 -6
  54. data/lib/resources/interface.rb +8 -8
  55. data/lib/resources/iptables.rb +6 -0
  56. data/lib/resources/json.rb +6 -5
  57. data/lib/resources/kernel_module.rb +6 -5
  58. data/lib/resources/kernel_parameter.rb +6 -4
  59. data/lib/resources/limits_conf.rb +6 -6
  60. data/lib/resources/login_def.rb +6 -0
  61. data/lib/resources/mysql_conf.rb +6 -0
  62. data/lib/resources/mysql_session.rb +7 -0
  63. data/lib/resources/npm.rb +6 -4
  64. data/lib/resources/ntp_conf.rb +7 -7
  65. data/lib/resources/oneget.rb +6 -0
  66. data/lib/resources/os.rb +8 -0
  67. data/lib/resources/os_env.rb +6 -0
  68. data/lib/resources/package.rb +8 -1
  69. data/lib/resources/parse_config.rb +14 -0
  70. data/lib/resources/passwd.rb +7 -0
  71. data/lib/resources/pip.rb +6 -0
  72. data/lib/resources/port.rb +22 -11
  73. data/lib/resources/postgres_conf.rb +6 -0
  74. data/lib/resources/postgres_session.rb +8 -0
  75. data/lib/resources/processes.rb +17 -1
  76. data/lib/resources/registry_key.rb +7 -0
  77. data/lib/resources/script.rb +11 -0
  78. data/lib/resources/security_policy.rb +6 -1
  79. data/lib/resources/service.rb +10 -0
  80. data/lib/resources/ssh_conf.rb +6 -0
  81. data/lib/resources/user.rb +9 -2
  82. data/lib/resources/windows_feature.rb +6 -0
  83. data/lib/resources/yaml.rb +6 -0
  84. data/lib/resources/yum.rb +7 -0
  85. data/lib/utils/find_files.rb +15 -7
  86. data/test/helper.rb +9 -0
  87. data/test/integration/.kitchen.yml +3 -0
  88. data/test/integration/test/integration/default/compare_matcher_spec.rb +19 -0
  89. data/test/integration/test/integration/default/etc_group.rb +13 -0
  90. data/test/integration/test/integration/default/os_spec.rb +13 -0
  91. data/test/integration/test/integration/default/port_spec.rb +1 -1
  92. data/test/unit/mock/cmd/find-apache2-conf-enabled +1 -0
  93. data/test/unit/mock/cmd/find-apache2-ports-conf +1 -0
  94. data/test/unit/mock/cmd/ps-aux +2 -0
  95. data/test/unit/mock/files/apache2.conf +14 -0
  96. data/test/unit/mock/files/ports.conf +6 -0
  97. data/test/unit/mock/files/serve-cgi-bin.conf +20 -0
  98. data/test/unit/resources/apache_conf_test.rb +31 -0
  99. data/test/unit/resources/file_test.rb +181 -0
  100. data/test/unit/resources/package_test.rb +9 -0
  101. data/test/unit/resources/port_test.rb +33 -13
  102. data/test/unit/resources/processes_test.rb +6 -0
  103. data/test/unit/resources/service_test.rb +10 -0
  104. data/test/unit/resources/user_test.rb +12 -0
  105. data/test/unit/utils/find_files_test.rb +23 -0
  106. metadata +61 -16
  107. data/bin/inspec.orig +0 -115
  108. data/lib/resources/.service.rb.swp +0 -0
  109. data/test/unit/mock/profiles/rules/metadata.rb +0 -2
  110. data/test/unit/mock/profiles/rules/test/test.rb +0 -6
@@ -2,6 +2,8 @@
2
2
  # author: Dominik Richter
3
3
  # author: Christoph Hartmann
4
4
 
5
+ require 'rspec/core/formatters/base_text_formatter'
6
+
5
7
  module Inspec
6
8
  class Shell
7
9
  def initialize(runner)
@@ -24,10 +26,14 @@ module Inspec
24
26
  that = self
25
27
 
26
28
  # Add the help command
27
- Pry::Commands.block_command 'usage', 'Show examples' do
28
- that.usage
29
+ Pry::Commands.block_command 'help', 'Show examples' do |resource|
30
+ that.help(resource)
29
31
  end
30
32
 
33
+ # configure pry shell prompt
34
+ Pry.config.prompt_name = 'inspec'
35
+ Pry.prompt = [proc { "\e[0;32m#{Pry.config.prompt_name}>\e[0m " }]
36
+
31
37
  # Add a help menu as the default intro
32
38
  Pry.hooks.add_hook(:before_session, :intro) do
33
39
  intro
@@ -38,23 +44,42 @@ module Inspec
38
44
  "\033[1m#{x}\033[0m"
39
45
  end
40
46
 
47
+ def print_example(example)
48
+ # determine min whitespace that can be removed
49
+ min = nil
50
+ example.lines.each do |line|
51
+ if line.strip.length > 0 # ignore empty lines
52
+ line_whitespace = line.length - line.lstrip.length
53
+ min = line_whitespace if min.nil? || line_whitespace < min
54
+ end
55
+ end
56
+ # remove whitespace from each line
57
+ example.gsub(/\n\s{#{min}}/, "\n")
58
+ end
59
+
41
60
  def intro
42
- puts 'Welcome to the interactive Inspec Shell'
43
- puts "To find out how to use it, type: #{mark 'usage'}"
61
+ puts 'Welcome to the interactive InSpec Shell'
62
+ puts "To find out how to use it, type: #{mark 'help'}"
44
63
  puts
45
64
  end
46
65
 
47
- def usage
48
- ctx = @runner.backend
49
- puts <<EOF
66
+ def help(resource = nil)
67
+ if resource.nil?
68
+
69
+ ctx = @runner.backend
70
+ puts <<EOF
50
71
 
51
- Welcome to the interactive Inspec Shell.
72
+ Available commands:
52
73
 
53
- You can use resources in this environment to test the target machine.
54
- For example:
74
+ `[resource]` - run resource on target machine
75
+ `help resources` - show all available resources that can be used as commands
76
+ `help [resource]` - information about a specific resource
77
+ `exit` - exit the InSpec shell
78
+
79
+ You can use resources in this environment to test the target machine. For example:
55
80
 
56
81
  command('uname -a').stdout
57
- file('/proc/cpuinfo').content
82
+ file('/proc/cpuinfo').content => "value",
58
83
 
59
84
  You are currently running on:
60
85
 
@@ -62,6 +87,43 @@ You are currently running on:
62
87
  OS release: #{mark ctx.os[:release] || 'unknown'}
63
88
 
64
89
  EOF
90
+ elsif resource == 'resources'
91
+ resources
92
+ else
93
+
94
+ if !Inspec::Resource.registry[resource].nil?
95
+ puts <<EOF
96
+ #{mark 'Name:'} #{resource}
97
+
98
+ #{mark 'Description:'}
99
+
100
+ #{Inspec::Resource.registry[resource].desc}
101
+
102
+ #{mark 'Example:'}
103
+ #{print_example(Inspec::Resource.registry[resource].example)}
104
+
105
+ #{mark 'Web Reference:'}
106
+
107
+ https://github.com/chef/inspec/blob/master/docs/resources.rst##{resource}
108
+
109
+ EOF
110
+ else
111
+ puts 'Only the following resources are available:'
112
+ resources
113
+ end
114
+ end
115
+ end
116
+
117
+ def resources
118
+ puts Inspec::Resource.registry.keys.join(' ')
119
+ end
120
+ end
121
+
122
+ class NoSummaryFormatter < RSpec::Core::Formatters::BaseTextFormatter
123
+ RSpec::Core::Formatters.register self, :dump_summary
124
+
125
+ def dump_summary(*_args)
126
+ # output nothing
65
127
  end
66
128
  end
67
129
  end
@@ -3,5 +3,5 @@
3
3
  # author: Christoph Hartmann
4
4
 
5
5
  module Inspec
6
- VERSION = '0.9.5'
6
+ VERSION = '0.9.6'
7
7
  end
@@ -219,3 +219,46 @@ RSpec::Matchers.define :contain do |_rule|
219
219
  fail "[UNSUPPORTED] `contain` matcher. Please use the following syntax `its('content') { should match('value') }`."
220
220
  end
221
221
  end
222
+
223
+ # This matcher implements a compare feature that cannot be covered by the default
224
+ # `eq` matcher
225
+ # You can use it in the following cases:
226
+ # - compare strings case-insensitive
227
+ # - you expect a number (strings will be converted if possible)
228
+ #
229
+ RSpec::Matchers.define :cmp do |expected|
230
+
231
+ def integer?(value)
232
+ return true if value =~ /\A\d+\Z/
233
+ false
234
+ end
235
+
236
+ def float?(value)
237
+ return true if Float(value)
238
+ false
239
+ rescue ArgumentError => _ex
240
+ false
241
+ end
242
+
243
+ match do |actual|
244
+ # if actual and expected are strings
245
+ if actual.is_a?(String) && expected.is_a?(String)
246
+ actual.casecmp(expected) == 0
247
+ elsif expected.is_a?(Integer) && integer?(actual)
248
+ expected == actual.to_i
249
+ elsif expected.is_a?(Float) && float?(actual)
250
+ expected == actual.to_f
251
+ # fallback to equal
252
+ else
253
+ actual == expected
254
+ end
255
+ end
256
+
257
+ failure_message do |actual|
258
+ "\nexpected: #{expected}\n got: #{actual}\n\n(compared using `cmp` matcher)\n"
259
+ end
260
+
261
+ failure_message_when_negated do |actual|
262
+ "\nexpected: value != #{expected}\n got: #{actual}\n\n(compared using `cmp` matcher)\n"
263
+ end
264
+ end
@@ -9,6 +9,12 @@ require 'utils/find_files'
9
9
 
10
10
  class ApacheConf < Inspec.resource(1)
11
11
  name 'apache_conf'
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.'
13
+ example "
14
+ describe apache_conf do
15
+ its('setting_name') { should eq 'value' }
16
+ end
17
+ "
12
18
 
13
19
  include FindFiles
14
20
 
@@ -88,19 +94,16 @@ class ApacheConf < Inspec.resource(1)
88
94
  include_files = params['Include'] || []
89
95
  include_files_optional = params['IncludeOptional'] || []
90
96
 
91
- required = []
92
- include_files.each do |f|
97
+ includes = []
98
+ (include_files + include_files_optional).each do |f|
93
99
  id = File.join(@conf_dir, f)
94
- required.push(find_files(id, depth: 1, type: 'file'))
95
- end
100
+ files = find_files(id, depth: 1, type: 'file')
96
101
 
97
- optional = []
98
- include_files_optional.each do |f|
99
- id = File.join(@conf_dir, f)
100
- optional.push(find_files(id, depth: 1, type: 'file'))
102
+ includes.push(files) if files
101
103
  end
102
104
 
103
- required.flatten! + optional.flatten!
105
+ # [].flatten! == nil
106
+ includes.flatten! || []
104
107
  end
105
108
 
106
109
  def read_file(path)
@@ -30,6 +30,13 @@ require 'uri'
30
30
 
31
31
  class AptRepository < Inspec.resource(1)
32
32
  name 'apt'
33
+ desc 'Use the apt InSpec audit resource to verify Apt repositories on the Debian and Ubuntu platforms, and also PPA repositories on the Ubuntu platform.'
34
+ example "
35
+ describe apt('nginx/stable') do
36
+ it { should exist }
37
+ it { should be_enabled }
38
+ end
39
+ "
33
40
 
34
41
  def initialize(ppa_name)
35
42
  @deb_url = nil
@@ -23,15 +23,15 @@
23
23
  # - "Failure"
24
24
  #
25
25
  # Further information is available at: https://msdn.microsoft.com/en-us/library/dd973859.aspx
26
- #
27
- # Usage:
28
- #
29
- # describe audit_policy do
30
- # its('Other Account Logon Events') { should_not eq 'No Auditing' }
31
- # end
32
26
 
33
27
  class AuditPolicy < Inspec.resource(1)
34
28
  name 'audit_policy'
29
+ desc 'Use the audit_policy InSpec audit resource to test auditing policies on the Microsoft Windows platform. An auditing policy is a category of security-related events to be audited. Auditing is disabled by default and may be enabled for categories like account management, logon events, policy changes, process tracking, privilege use, system events, or object access. For each auditing category property that is enabled, the auditing level may be set to No Auditing, Not Specified, Success, Success and Failure, or Failure.'
30
+ example "
31
+ describe audit_policy do
32
+ its('parameter') { should eq 'value' }
33
+ end
34
+ "
35
35
 
36
36
  def method_missing(method)
37
37
  key = method.to_s
@@ -6,15 +6,14 @@
6
6
 
7
7
  require 'utils/simpleconfig'
8
8
 
9
- # Usage:
10
- # describe audit_daemon_conf do
11
- # its("space_left_action") { should eq "email" }
12
- # its("action_mail_acct") { should eq "root" }
13
- # its("admin_space_left_action") { should eq "halt" }
14
- # end
15
-
16
9
  class AuditDaemonConf < Inspec.resource(1)
17
10
  name 'auditd_conf'
11
+ desc "Use the auditd_conf InSpec audit resource to test the configuration settings for the audit daemon. This file is typically located under /etc/audit/auditd.conf' on UNIX and Linux platforms."
12
+ example "
13
+ describe auditd_conf do
14
+ its('space_left_action') { should eq 'email' }
15
+ end
16
+ "
18
17
 
19
18
  def initialize(path = nil)
20
19
  @conf_path = path || '/etc/audit/auditd.conf'
@@ -4,16 +4,17 @@
4
4
  # author: Dominik Richter
5
5
  # license: All rights reserved
6
6
 
7
- # Usage:
8
- # describe audit_daemon_rules do
9
- # its("LIST_RULES") {should contain_match(/^exit,always arch=.* key=time-change syscall=adjtimex,settimeofday/) }
10
- # its("LIST_RULES") {should contain_match(/^exit,always arch=.* key=time-change syscall=stime,settimeofday,adjtimex/) }
11
- # its("LIST_RULES") {should contain_match(/^exit,always arch=.* key=time-change syscall=clock_settime/)}
12
- # its("LIST_RULES") {should contain_match(/^exit,always watch=\/etc\/localtime perm=wa key=time-change/)}
13
- # end
14
-
15
7
  class AuditDaemonRules < Inspec.resource(1)
16
8
  name 'auditd_rules'
9
+ desc 'Use the auditd_rules InSpec audit resource to test the rules for logging that exist on the system. The audit.rules file is typically located under /etc/audit/ and contains the list of rules that define what is captured in log files.'
10
+ example "
11
+ describe auditd_rules do
12
+ its('LIST_RULES') {should contain_match(/^exit,always arch=.* key=time-change syscall=adjtimex,settimeofday/) }
13
+ its('LIST_RULES') {should contain_match(/^exit,always arch=.* key=time-change syscall=stime,settimeofday,adjtimex/) }
14
+ its('LIST_RULES') {should contain_match(/^exit,always arch=.* key=time-change syscall=clock_settime/)}
15
+ its('LIST_RULES') {should contain_match(/^exit,always watch=\/etc\/localtime perm=wa key=time-change/)}
16
+ end
17
+ "
17
18
 
18
19
  def initialize
19
20
  @content = inspec.command('/sbin/auditctl -l').stdout.chomp
@@ -4,15 +4,15 @@
4
4
 
5
5
  require 'resources/file'
6
6
 
7
- # Usage:
8
- # describe bond('bond0') do
9
- # it { should exist }
10
- # it { should have_interface 'eth0' }
11
- # end
12
-
13
7
  module Inspec::Resources
14
8
  class Bond < File
15
9
  name 'bond'
10
+ desc 'Use the bond InSpec audit resource to test a logical, bonded network interface (i.e. "two or more network interfaces aggregated into a single, logical network interface"). On Linux platforms, any value in the /proc/net/bonding directory may be tested.'
11
+ example "
12
+ describe bond('bond0') do
13
+ it { should exist }
14
+ end
15
+ "
16
16
 
17
17
  def initialize(bond)
18
18
  @bond = bond
@@ -10,6 +10,13 @@
10
10
 
11
11
  class Bridge < Inspec.resource(1)
12
12
  name 'bridge'
13
+ desc 'Use the bridge InSpec audit resource to test basic network bridge properties, such as name, if an interface is defined, and the associations for any defined interface.'
14
+ example "
15
+ describe bridge 'br0' do
16
+ it { should exist }
17
+ it { should have_interface 'eth0' }
18
+ end
19
+ "
13
20
 
14
21
  def initialize(bridge_name)
15
22
  @bridge_name = bridge_name
@@ -4,16 +4,18 @@
4
4
  # author: Christoph Hartmann
5
5
  # license: All rights reserved
6
6
 
7
- # Usage:
8
- # describe command('ls -al /') do
9
- # it { should exist }
10
- # its(:stdout) { should match /bin/ }
11
- # its(:stderr) { should match /No such file or directory/ }
12
- # its(:exit_status) { should eq 0 }
13
- # end
14
-
15
7
  class Cmd < Inspec.resource(1)
16
8
  name 'command'
9
+ desc 'Use the command InSpec audit resource to test an arbitrary command that is run on the system.'
10
+ example "
11
+ describe command('ls -al /') do
12
+ it { should exist }
13
+ its(:stdout) { should match /bin/ }
14
+ its('stderr') { should eq '' }
15
+ its(:exit_status) { should eq 0 }
16
+ end
17
+ "
18
+
17
19
  attr_reader :command
18
20
 
19
21
  def initialize(cmd)
@@ -3,15 +3,16 @@
3
3
  # author: Dominik Richter
4
4
 
5
5
  # Parses a csv document
6
- # Usage:
7
- # describe csv('example.csv') do
8
- # its('name') { should eq(['John', 'Alice']) }
9
- # end
10
- #
11
6
  # This implementation was inspired by a blog post
12
7
  # @see http://technicalpickles.com/posts/parsing-csv-with-ruby
13
8
  class CsvConfig < JsonConfig
14
9
  name 'csv'
10
+ desc 'Use the csv InSpec audit resource to test configuration data in a CSV file.'
11
+ example "
12
+ describe csv('example.csv') do
13
+ its('name') { should eq(['John', 'Alice']) }
14
+ end
15
+ "
15
16
 
16
17
  # override file load and parse hash from csv
17
18
  def parse(content)
@@ -7,6 +7,12 @@ require 'resources/file'
7
7
  module Inspec::Resources
8
8
  class Directory < File
9
9
  name 'directory'
10
+ desc 'Use the directory InSpec audit resource to test if the file type is a directory. This is equivalent to using the file InSpec audit resource and the be_directory matcher, but provides a simpler and more direct way to test directories. All of the matchers available to file may be used with directory.'
11
+ example "
12
+ describe directory('path') do
13
+ it { should be_directory }
14
+ end
15
+ "
10
16
  end
11
17
 
12
18
  def to_s
@@ -29,6 +29,14 @@ class EtcGroup < Inspec.resource(1)
29
29
  include ContentParser
30
30
 
31
31
  name 'etc_group'
32
+ desc 'Use the etc_group InSpec audit resource to test groups that are defined on Linux and UNIX platforms. The /etc/group file stores details about each group---group name, password, group identifier, along with a comma-separate list of users that belong to the group.'
33
+ example "
34
+ describe etc_group do
35
+ its('gids') { should_not contain_duplicates }
36
+ its('groups') { should include 'my_user' }
37
+ its('users') { should include 'my_user' }
38
+ end
39
+ "
32
40
 
33
41
  attr_accessor :gid, :entries
34
42
  def initialize(path = nil)
@@ -37,7 +45,7 @@ class EtcGroup < Inspec.resource(1)
37
45
 
38
46
  # skip resource if it is not supported on current OS
39
47
  return skip_resource 'The `etc_group` resource is not supported on your OS.' \
40
- unless %w{ubuntu debian redhat fedora arch darwin freebsd}.include?(inspec.os[:family])
48
+ unless %w{ubuntu debian redhat fedora centos arch darwin freebsd wrlinux}.include?(inspec.os[:family])
41
49
  end
42
50
 
43
51
  def groups(filter = nil)
@@ -7,8 +7,19 @@
7
7
  module Inspec::Resources
8
8
  class File < Inspec.resource(1)
9
9
  name 'file'
10
+ desc 'Use the file InSpec audit resource to test all system file types, including files, directories, symbolic links, named pipes, sockets, character devices, block devices, and doors.'
11
+ example "
12
+ describe file('path') do
13
+ it { should exist }
14
+ it { should be_file }
15
+ it { should be_readable }
16
+ it { should be_writable }
17
+ it { should be_owned_by 'root' }
18
+ its('mode') { should eq 0644 }
19
+ end
20
+ "
10
21
 
11
- attr_reader :path
22
+ attr_reader :file, :path
12
23
  def initialize(path)
13
24
  @path = path
14
25
  @file = inspec.backend.file(@path)
@@ -21,7 +32,7 @@ module Inspec::Resources
21
32
  product_version file_version version? md5sum sha256sum
22
33
  }.each do |m|
23
34
  define_method m.to_sym do |*args|
24
- @file.method(m.to_sym).call(*args)
35
+ file.method(m.to_sym).call(*args)
25
36
  end
26
37
  end
27
38
 
@@ -29,82 +40,82 @@ module Inspec::Resources
29
40
  fail 'Contain is not supported. Please use standard RSpec matchers.'
30
41
  end
31
42
 
32
- def readable?(by_owner, by_user)
33
- if inspec.os.unix?
34
- by_owner, by_user = check_preconditions(by_owner, by_user)
35
-
36
- if by_user.nil?
37
- m = @file.unix_mode_mask(by_owner, 'r') ||
38
- fail("#{by_owner} is not a valid unix owner.")
39
- (@file.mode & m) != 0
40
- else
41
- check_user_access(by_user, @path, 'r')
42
- end
43
- else
44
- fail "`file(#{@path}).executable?` is not suported on you OS: #{inspec.os['family']}"
45
- end
43
+ def readable?(by_usergroup, by_specific_user)
44
+ return false unless exist?
45
+
46
+ file_permission_granted?('r', by_usergroup, by_specific_user)
46
47
  end
47
48
 
48
- def writable?(by_owner, by_user)
49
- if inspec.os.unix?
50
- by_owner, by_user = check_preconditions(by_owner, by_user)
51
-
52
- if by_user.nil?
53
- m = @file.unix_mode_mask(by_owner, 'w') ||
54
- fail("#{by_owner} is not a valid unix owner.")
55
- (@file.mode & m) != 0
56
- else
57
- check_user_access(by_user, @path, 'w')
58
- end
59
- else
60
- fail "`file(#{@path}).executable?` is not suported on you OS: #{inspec.os['family']}"
61
- end
49
+ def writable?(by_usergroup, by_specific_user)
50
+ return false unless exist?
51
+
52
+ file_permission_granted?('w', by_usergroup, by_specific_user)
62
53
  end
63
54
 
64
- def executable?(by_owner, by_user)
65
- if inspec.os.unix?
66
- by_owner, by_user = check_preconditions(by_owner, by_user)
67
-
68
- if by_user.nil?
69
- m = @file.unix_mode_mask(by_owner, 'x') ||
70
- fail("#{by_owner} is not a valid unix owner.")
71
- return (@file.mode & m) != 0
72
- else
73
- return check_user_access(by_user, @path, 'x')
74
- end
75
- else
76
- fail "`file(#{@path}).executable?` is not suported on you OS: #{inspec.os['family']}"
77
- end
55
+ def executable?(by_usergroup, by_specific_user)
56
+ return false unless exist?
57
+
58
+ file_permission_granted?('x', by_usergroup, by_specific_user)
78
59
  end
79
60
 
80
61
  def to_s
81
- "File #{@path}"
62
+ "File #{path}"
82
63
  end
83
64
 
84
65
  private
85
66
 
86
- def check_preconditions(by_owner, by_user)
87
- by_owner = 'other' if by_owner == 'others'
88
- by_owner = 'all' if (by_owner.nil? || by_owner.empty?) && (by_user.nil?)
89
- [by_owner, by_user]
90
- end
67
+ def file_permission_granted?(flag, by_usergroup, by_specific_user)
68
+ fail 'Checking file permissions is not supported on your os' unless unix?
91
69
 
92
- # check permissions on linux
93
- def check_user_access(user, file, flag)
94
- if inspec.os.linux? == true
95
- # use sh on linux
96
- perm_cmd = "su -s /bin/sh -c \"test -#{flag} #{file}\" #{user}"
97
- elsif inspec.os[:family] == 'freebsd'
98
- # use sudo on freebsd
99
- perm_cmd = "sudo -u #{user} test -#{flag} #{file}"
70
+ usergroup = usergroup_for(by_usergroup, by_specific_user)
71
+
72
+ if by_specific_user.nil?
73
+ check_file_permission_by_mask(usergroup, flag)
74
+ else
75
+ check_file_permission_by_user(by_specific_user, flag)
100
76
  end
77
+ end
101
78
 
102
- if !perm_cmd.nil?
103
- cmd = inspec.command(perm_cmd)
104
- cmd.exit_status == 0 ? true : false
79
+ def check_file_permission_by_mask(usergroup, flag)
80
+ mask = file.unix_mode_mask(usergroup, flag)
81
+ fail 'Invalid usergroup/owner provided' if mask.nil?
82
+
83
+ (file.mode & mask) != 0
84
+ end
85
+
86
+ def check_file_permission_by_user(user, flag)
87
+ if linux?
88
+ perm_cmd = "su -s /bin/sh -c \"test -#{flag} #{path}\" #{user}"
89
+ elsif family == 'freebsd'
90
+ perm_cmd = "sudo -u #{user} test -#{flag} #{path}"
105
91
  else
106
92
  return skip_resource 'The `file` resource does not support `by_user` on your OS.'
107
93
  end
94
+
95
+ cmd = inspec.command(perm_cmd)
96
+ cmd.exit_status == 0 ? true : false
97
+ end
98
+
99
+ def usergroup_for(usergroup, specific_user)
100
+ if usergroup == 'others'
101
+ 'other'
102
+ elsif (usergroup.nil? || usergroup.empty?) && specific_user.nil?
103
+ 'all'
104
+ else
105
+ usergroup
106
+ end
107
+ end
108
+
109
+ def unix?
110
+ inspec.os.unix?
111
+ end
112
+
113
+ def linux?
114
+ inspec.os.linux?
115
+ end
116
+
117
+ def family
118
+ inspec.os[:family]
108
119
  end
109
120
  end
110
121
  end