inspec 0.9.5 → 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -8
- data/README.md +8 -39
- data/Rakefile +74 -9
- data/bin/inspec +66 -10
- data/docs/ctl_inspec.rst +7 -1
- data/docs/inspec_and_friends.rst +1 -1
- data/docs/resources.rst +51 -45
- data/examples/README.md +7 -0
- data/examples/kitchen-ansible/.kitchen.yml +25 -0
- data/examples/kitchen-ansible/Gemfile +20 -0
- data/examples/kitchen-ansible/README.md +53 -0
- data/examples/kitchen-ansible/files/nginx.repo +6 -0
- data/examples/kitchen-ansible/tasks/main.yml +16 -0
- data/examples/kitchen-ansible/test/integration/default/default.yml +5 -0
- data/examples/{test-kitchen → kitchen-ansible}/test/integration/default/web_spec.rb +0 -0
- data/examples/{test-kitchen → kitchen-chef}/.kitchen.yml +1 -1
- data/examples/{test-kitchen → kitchen-chef}/Berksfile +0 -0
- data/examples/{test-kitchen → kitchen-chef}/Gemfile +1 -2
- data/examples/{test-kitchen → kitchen-chef}/README.md +1 -1
- data/examples/{test-kitchen → kitchen-chef}/metadata.rb +0 -0
- data/examples/{test-kitchen → kitchen-chef}/recipes/default.rb +0 -0
- data/examples/{test-kitchen → kitchen-chef}/recipes/nginx.rb +0 -0
- data/examples/kitchen-chef/test/integration/default/web_spec.rb +28 -0
- data/examples/kitchen-puppet/.kitchen.yml +22 -0
- data/examples/kitchen-puppet/Gemfile +21 -0
- data/examples/kitchen-puppet/Puppetfile +25 -0
- data/examples/kitchen-puppet/README.md +53 -0
- data/examples/kitchen-puppet/manifests/site.pp +33 -0
- data/examples/kitchen-puppet/metadata.json +11 -0
- data/examples/kitchen-puppet/test/integration/default/web_spec.rb +28 -0
- data/inspec.gemspec +2 -0
- data/lib/inspec/plugins/resource.rb +21 -0
- data/lib/inspec/shell.rb +73 -11
- data/lib/inspec/version.rb +1 -1
- data/lib/matchers/matchers.rb +43 -0
- data/lib/resources/apache_conf.rb +12 -9
- data/lib/resources/apt.rb +7 -0
- data/lib/resources/audit_policy.rb +6 -6
- data/lib/resources/auditd_conf.rb +6 -7
- data/lib/resources/auditd_rules.rb +9 -8
- data/lib/resources/bond.rb +6 -6
- data/lib/resources/bridge.rb +7 -0
- data/lib/resources/command.rb +10 -8
- data/lib/resources/csv.rb +6 -5
- data/lib/resources/directory.rb +6 -0
- data/lib/resources/etc_group.rb +9 -1
- data/lib/resources/file.rb +72 -61
- data/lib/resources/gem.rb +6 -4
- data/lib/resources/group.rb +7 -0
- data/lib/resources/host.rb +6 -0
- data/lib/resources/inetd_conf.rb +8 -8
- data/lib/resources/ini.rb +6 -6
- data/lib/resources/interface.rb +8 -8
- data/lib/resources/iptables.rb +6 -0
- data/lib/resources/json.rb +6 -5
- data/lib/resources/kernel_module.rb +6 -5
- data/lib/resources/kernel_parameter.rb +6 -4
- data/lib/resources/limits_conf.rb +6 -6
- data/lib/resources/login_def.rb +6 -0
- data/lib/resources/mysql_conf.rb +6 -0
- data/lib/resources/mysql_session.rb +7 -0
- data/lib/resources/npm.rb +6 -4
- data/lib/resources/ntp_conf.rb +7 -7
- data/lib/resources/oneget.rb +6 -0
- data/lib/resources/os.rb +8 -0
- data/lib/resources/os_env.rb +6 -0
- data/lib/resources/package.rb +8 -1
- data/lib/resources/parse_config.rb +14 -0
- data/lib/resources/passwd.rb +7 -0
- data/lib/resources/pip.rb +6 -0
- data/lib/resources/port.rb +22 -11
- data/lib/resources/postgres_conf.rb +6 -0
- data/lib/resources/postgres_session.rb +8 -0
- data/lib/resources/processes.rb +17 -1
- data/lib/resources/registry_key.rb +7 -0
- data/lib/resources/script.rb +11 -0
- data/lib/resources/security_policy.rb +6 -1
- data/lib/resources/service.rb +10 -0
- data/lib/resources/ssh_conf.rb +6 -0
- data/lib/resources/user.rb +9 -2
- data/lib/resources/windows_feature.rb +6 -0
- data/lib/resources/yaml.rb +6 -0
- data/lib/resources/yum.rb +7 -0
- data/lib/utils/find_files.rb +15 -7
- data/test/helper.rb +9 -0
- data/test/integration/.kitchen.yml +3 -0
- data/test/integration/test/integration/default/compare_matcher_spec.rb +19 -0
- data/test/integration/test/integration/default/etc_group.rb +13 -0
- data/test/integration/test/integration/default/os_spec.rb +13 -0
- data/test/integration/test/integration/default/port_spec.rb +1 -1
- data/test/unit/mock/cmd/find-apache2-conf-enabled +1 -0
- data/test/unit/mock/cmd/find-apache2-ports-conf +1 -0
- data/test/unit/mock/cmd/ps-aux +2 -0
- data/test/unit/mock/files/apache2.conf +14 -0
- data/test/unit/mock/files/ports.conf +6 -0
- data/test/unit/mock/files/serve-cgi-bin.conf +20 -0
- data/test/unit/resources/apache_conf_test.rb +31 -0
- data/test/unit/resources/file_test.rb +181 -0
- data/test/unit/resources/package_test.rb +9 -0
- data/test/unit/resources/port_test.rb +33 -13
- data/test/unit/resources/processes_test.rb +6 -0
- data/test/unit/resources/service_test.rb +10 -0
- data/test/unit/resources/user_test.rb +12 -0
- data/test/unit/utils/find_files_test.rb +23 -0
- metadata +61 -16
- data/bin/inspec.orig +0 -115
- data/lib/resources/.service.rb.swp +0 -0
- data/test/unit/mock/profiles/rules/metadata.rb +0 -2
- data/test/unit/mock/profiles/rules/test/test.rb +0 -6
data/lib/inspec/shell.rb
CHANGED
@@ -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 '
|
28
|
-
that.
|
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
|
43
|
-
puts "To find out how to use it, type: #{mark '
|
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
|
48
|
-
|
49
|
-
|
66
|
+
def help(resource = nil)
|
67
|
+
if resource.nil?
|
68
|
+
|
69
|
+
ctx = @runner.backend
|
70
|
+
puts <<EOF
|
50
71
|
|
51
|
-
|
72
|
+
Available commands:
|
52
73
|
|
53
|
-
|
54
|
-
|
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
|
data/lib/inspec/version.rb
CHANGED
data/lib/matchers/matchers.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
95
|
-
end
|
100
|
+
files = find_files(id, depth: 1, type: 'file')
|
96
101
|
|
97
|
-
|
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
|
-
|
105
|
+
# [].flatten! == nil
|
106
|
+
includes.flatten! || []
|
104
107
|
end
|
105
108
|
|
106
109
|
def read_file(path)
|
data/lib/resources/apt.rb
CHANGED
@@ -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
|
data/lib/resources/bond.rb
CHANGED
@@ -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
|
data/lib/resources/bridge.rb
CHANGED
@@ -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
|
data/lib/resources/command.rb
CHANGED
@@ -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)
|
data/lib/resources/csv.rb
CHANGED
@@ -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)
|
data/lib/resources/directory.rb
CHANGED
@@ -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
|
data/lib/resources/etc_group.rb
CHANGED
@@ -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)
|
data/lib/resources/file.rb
CHANGED
@@ -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
|
-
|
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?(
|
33
|
-
|
34
|
-
|
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?(
|
49
|
-
|
50
|
-
|
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?(
|
65
|
-
|
66
|
-
|
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 #{
|
62
|
+
"File #{path}"
|
82
63
|
end
|
83
64
|
|
84
65
|
private
|
85
66
|
|
86
|
-
def
|
87
|
-
|
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
|
-
|
93
|
-
|
94
|
-
if
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
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
|