inspec 1.31.0 → 1.45.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +2599 -1
- data/Gemfile +0 -1
- data/README.md +24 -34
- data/Rakefile +0 -31
- data/docs/dsl_inspec.md +10 -0
- data/docs/matchers.md +29 -0
- data/docs/plugin_kitchen_inspec.md +18 -24
- data/docs/profiles.md +101 -81
- data/docs/resources/aide_conf.md.erb +71 -0
- data/docs/resources/apache_conf.md.erb +19 -33
- data/docs/resources/apt.md.erb +22 -36
- data/docs/resources/audit_policy.md.erb +9 -24
- data/docs/resources/auditd.md.erb +78 -0
- data/docs/resources/auditd_conf.md.erb +20 -34
- data/docs/resources/auditd_rules.md.erb +8 -24
- data/docs/resources/bash.md.erb +4 -26
- data/docs/resources/bond.md.erb +25 -40
- data/docs/resources/bridge.md.erb +5 -25
- data/docs/resources/bsd_service.md.erb +5 -25
- data/docs/resources/command.md.erb +35 -50
- data/docs/resources/cpan.md.erb +62 -0
- data/docs/resources/cran.md.erb +54 -0
- data/docs/resources/crontab.md.erb +9 -23
- data/docs/resources/csv.md.erb +12 -27
- data/docs/resources/dh_params.md +1 -0
- data/docs/resources/directory.md.erb +5 -25
- data/docs/resources/docker.md.erb +61 -58
- data/docs/resources/docker_container.md.erb +23 -19
- data/docs/resources/docker_image.md.erb +20 -16
- data/docs/resources/elasticsearch.md.erb +245 -0
- data/docs/resources/etc_fstab.md.erb +120 -0
- data/docs/resources/etc_group.md.erb +29 -45
- data/docs/resources/etc_hosts.md.erb +68 -0
- data/docs/resources/etc_hosts_allow.md.erb +68 -0
- data/docs/resources/etc_hosts_deny.md.erb +68 -0
- data/docs/resources/file.md.erb +227 -216
- data/docs/resources/firewalld.md.erb +104 -0
- data/docs/resources/gem.md.erb +21 -35
- data/docs/resources/group.md.erb +16 -30
- data/docs/resources/grub_conf.md.erb +9 -24
- data/docs/resources/host.md.erb +32 -49
- data/docs/resources/http.md.erb +38 -44
- data/docs/resources/iis_app.md.erb +25 -35
- data/docs/resources/iis_site.md.erb +26 -40
- data/docs/resources/inetd_conf.md.erb +27 -42
- data/docs/resources/ini.md.erb +9 -23
- data/docs/resources/interface.md.erb +5 -25
- data/docs/resources/iptables.md.erb +15 -29
- data/docs/resources/json.md.erb +12 -27
- data/docs/resources/kernel_module.md.erb +47 -61
- data/docs/resources/kernel_parameter.md.erb +15 -29
- data/docs/resources/key_rsa.md.erb +3 -0
- data/docs/resources/launchd_service.md.erb +5 -25
- data/docs/resources/limits_conf.md.erb +15 -29
- data/docs/resources/login_def.md.erb +15 -30
- data/docs/resources/mount.md.erb +18 -33
- data/docs/resources/mssql_session.md.erb +9 -12
- data/docs/resources/mysql_conf.md.erb +28 -32
- data/docs/resources/mysql_session.md.erb +15 -29
- data/docs/resources/nginx.md.erb +78 -0
- data/docs/resources/nginx_conf.md.erb +127 -0
- data/docs/resources/npm.md.erb +19 -35
- data/docs/resources/ntp_conf.md.erb +20 -37
- data/docs/resources/oneget.md.erb +15 -30
- data/docs/resources/oracledb_session.md.erb +9 -11
- data/docs/resources/os.md.erb +29 -43
- data/docs/resources/os_env.md.erb +23 -44
- data/docs/resources/package.md.erb +34 -36
- data/docs/resources/parse_config.md.erb +5 -25
- data/docs/resources/parse_config_file.md.erb +31 -43
- data/docs/resources/{etc_passwd.md.erb → passwd.md.erb} +24 -39
- data/docs/resources/pip.md.erb +20 -35
- data/docs/resources/port.md.erb +43 -57
- data/docs/resources/postgres_conf.md.erb +17 -31
- data/docs/resources/postgres_hba_conf.md.erb +26 -38
- data/docs/resources/postgres_ident_conf.md.erb +25 -37
- data/docs/resources/postgres_session.md.erb +15 -29
- data/docs/resources/powershell.md.erb +27 -42
- data/docs/resources/processes.md.erb +17 -33
- data/docs/resources/rabbitmq_config.md.erb +9 -24
- data/docs/resources/registry_key.md.erb +27 -42
- data/docs/resources/runit_service.md.erb +5 -25
- data/docs/resources/security_policy.md.erb +12 -27
- data/docs/resources/service.md.erb +27 -42
- data/docs/resources/{etc_shadow.md.erb → shadow.md.erb} +41 -47
- data/docs/resources/ssh_config.md.erb +19 -34
- data/docs/resources/sshd_config.md.erb +19 -34
- data/docs/resources/ssl.md.erb +39 -54
- data/docs/resources/sys_info.md.erb +12 -26
- data/docs/resources/systemd_service.md.erb +5 -25
- data/docs/resources/sysv_service.md.erb +5 -25
- data/docs/resources/upstart_service.md.erb +5 -25
- data/docs/resources/user.md.erb +29 -44
- data/docs/resources/users.md.erb +12 -26
- data/docs/resources/vbscript.md.erb +9 -24
- data/docs/resources/virtualization.md.erb +8 -23
- data/docs/resources/windows_feature.md.erb +15 -30
- data/docs/resources/windows_hotfix.md.erb +50 -0
- data/docs/resources/windows_task.md.erb +12 -26
- data/docs/resources/wmi.md.erb +9 -24
- data/docs/resources/x509_certificate.md.erb +4 -0
- data/docs/resources/xinetd_conf.md.erb +65 -80
- data/docs/resources/xml.md.erb +61 -0
- data/docs/resources/yaml.md.erb +12 -27
- data/docs/resources/yum.md.erb +37 -51
- data/docs/resources/zfs_dataset.md.erb +15 -26
- data/docs/resources/zfs_pool.md.erb +9 -20
- data/docs/ruby_usage.md +0 -1
- data/examples/meta-profile/README.md +1 -1
- data/examples/profile-sensitive/README.md +29 -0
- data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -0
- data/examples/profile-sensitive/controls/sensitive.rb +9 -0
- data/examples/profile-sensitive/inspec.yml +8 -0
- data/inspec.gemspec +3 -3
- data/lib/bundles/inspec-artifact/cli.rb +5 -5
- data/lib/bundles/inspec-compliance/README.md +24 -22
- data/lib/bundles/inspec-compliance/api/login.rb +152 -0
- data/lib/bundles/inspec-compliance/api.rb +46 -16
- data/lib/bundles/inspec-compliance/cli.rb +58 -165
- data/lib/bundles/inspec-compliance/target.rb +2 -3
- data/lib/bundles/inspec-habitat/cli.rb +7 -2
- data/lib/bundles/inspec-habitat/profile.rb +44 -20
- data/lib/bundles/inspec-init/templates/profile/README.md +1 -1
- data/lib/bundles/inspec-init/templates/profile/controls/example.rb +1 -1
- data/lib/bundles/inspec-supermarket/api.rb +1 -1
- data/lib/inspec/backend.rb +8 -0
- data/lib/inspec/cli.rb +1 -1
- data/lib/inspec/control_eval_context.rb +16 -6
- data/lib/inspec/dependencies/requirement.rb +22 -8
- data/lib/inspec/dependencies/resolver.rb +1 -1
- data/lib/inspec/exceptions.rb +4 -0
- data/lib/inspec/file_provider.rb +38 -9
- data/lib/inspec/metadata.rb +17 -3
- data/lib/inspec/objects/attribute.rb +16 -7
- data/lib/inspec/objects/control.rb +10 -2
- data/lib/inspec/plugins/resource.rb +34 -5
- data/lib/inspec/profile.rb +10 -1
- data/lib/inspec/profile_context.rb +3 -1
- data/lib/inspec/resource.rb +17 -5
- data/lib/inspec/rspec_json_formatter.rb +48 -14
- data/lib/inspec/rule.rb +4 -0
- data/lib/inspec/runner.rb +72 -30
- data/lib/inspec/shell.rb +24 -19
- data/lib/inspec/version.rb +1 -1
- data/lib/matchers/matchers.rb +40 -0
- data/lib/resources/aide_conf.rb +162 -0
- data/lib/resources/auditd.rb +231 -0
- data/lib/resources/auditd_rules.rb +3 -1
- data/lib/resources/command.rb +1 -1
- data/lib/resources/cpan.rb +60 -0
- data/lib/resources/cran.rb +66 -0
- data/lib/resources/docker.rb +8 -1
- data/lib/resources/docker_container.rb +19 -2
- data/lib/resources/elasticsearch.rb +172 -0
- data/lib/resources/etc_fstab.rb +102 -0
- data/lib/resources/etc_group.rb +1 -1
- data/lib/resources/etc_hosts.rb +81 -0
- data/lib/resources/etc_hosts_allow_deny.rb +122 -0
- data/lib/resources/file.rb +87 -10
- data/lib/resources/firewalld.rb +144 -0
- data/lib/resources/groups.rb +0 -1
- data/lib/resources/host.rb +30 -15
- data/lib/resources/http.rb +178 -22
- data/lib/resources/mssql_session.rb +3 -3
- data/lib/resources/mysql_conf.rb +10 -0
- data/lib/resources/mysql_session.rb +1 -1
- data/lib/resources/nginx.rb +97 -0
- data/lib/resources/nginx_conf.rb +134 -1
- data/lib/resources/oracledb_session.rb +9 -8
- data/lib/resources/os_env.rb +7 -4
- data/lib/resources/package.rb +14 -2
- data/lib/resources/packages.rb +1 -1
- data/lib/resources/pip.rb +28 -20
- data/lib/resources/port.rb +251 -3
- data/lib/resources/postgres.rb +1 -1
- data/lib/resources/postgres_session.rb +1 -1
- data/lib/resources/processes.rb +106 -20
- data/lib/resources/registry_key.rb +11 -2
- data/lib/resources/service.rb +5 -24
- data/lib/resources/ssh_conf.rb +1 -1
- data/lib/resources/ssl.rb +4 -3
- data/lib/resources/toml.rb +2 -2
- data/lib/resources/windows_hotfix.rb +35 -0
- data/lib/resources/xinetd.rb +4 -6
- data/lib/resources/xml.rb +27 -0
- data/lib/source_readers/inspec.rb +3 -3
- data/lib/utils/find_files.rb +2 -1
- data/lib/utils/nginx_parser.rb +10 -1
- data/lib/utils/object_traversal.rb +35 -10
- data/lib/utils/parser.rb +21 -3
- metadata +48 -18
- data/docs/cli.md +0 -392
data/lib/resources/file.rb
CHANGED
|
@@ -84,6 +84,13 @@ module Inspec::Resources
|
|
|
84
84
|
file_permission_granted?('execute', by_usergroup, by_specific_user)
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
def allowed?(permission, opts = {})
|
|
88
|
+
return false unless exist?
|
|
89
|
+
return skip_resource '`allowed?` is not supported on your OS yet.' if @perms_provider.nil?
|
|
90
|
+
|
|
91
|
+
file_permission_granted?(permission, opts[:by], opts[:by_user])
|
|
92
|
+
end
|
|
93
|
+
|
|
87
94
|
def mounted?(expected_options = nil, identical = false)
|
|
88
95
|
mounted = file.mounted
|
|
89
96
|
|
|
@@ -112,14 +119,20 @@ module Inspec::Resources
|
|
|
112
119
|
(mode & 04000) > 0
|
|
113
120
|
end
|
|
114
121
|
|
|
122
|
+
alias setuid? suid
|
|
123
|
+
|
|
115
124
|
def sgid
|
|
116
125
|
(mode & 02000) > 0
|
|
117
126
|
end
|
|
118
127
|
|
|
128
|
+
alias setgid? sgid
|
|
129
|
+
|
|
119
130
|
def sticky
|
|
120
131
|
(mode & 01000) > 0
|
|
121
132
|
end
|
|
122
133
|
|
|
134
|
+
alias sticky? sticky
|
|
135
|
+
|
|
123
136
|
def to_s
|
|
124
137
|
"File #{source_path}"
|
|
125
138
|
end
|
|
@@ -200,18 +213,82 @@ module Inspec::Resources
|
|
|
200
213
|
end
|
|
201
214
|
|
|
202
215
|
def check_file_permission_by_user(access_type, user, path)
|
|
203
|
-
access_rule =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
when 'write'
|
|
207
|
-
'@(\'FullControl\', \'Modify\', \'Write\')'
|
|
208
|
-
when 'execute'
|
|
209
|
-
'@(\'FullControl\', \'Modify\', \'ReadAndExecute\', \'ExecuteFile\')'
|
|
210
|
-
else
|
|
211
|
-
raise 'Invalid access_type provided'
|
|
212
|
-
end
|
|
216
|
+
access_rule = translate_perm_names(access_type)
|
|
217
|
+
access_rule = convert_to_powershell_array(access_rule)
|
|
218
|
+
|
|
213
219
|
cmd = inspec.command("@(@((Get-Acl '#{path}').access | Where-Object {$_.AccessControlType -eq 'Allow' -and $_.IdentityReference -eq '#{user}' }) | Where-Object {($_.FileSystemRights.ToString().Split(',') | % {$_.trim()} | ? {#{access_rule} -contains $_}) -ne $null}) | measure | % { $_.Count }")
|
|
214
220
|
cmd.stdout.chomp == '0' ? false : true
|
|
215
221
|
end
|
|
222
|
+
|
|
223
|
+
private
|
|
224
|
+
|
|
225
|
+
def convert_to_powershell_array(arr)
|
|
226
|
+
if arr.empty?
|
|
227
|
+
'@()'
|
|
228
|
+
else
|
|
229
|
+
%{@('#{arr.join("', '")}')}
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Translates a developer-friendly string into a list of acceptable
|
|
234
|
+
# FileSystemRights that match it, because Windows has a fun heirarchy
|
|
235
|
+
# of permissions that are able to be noted in multiple ways.
|
|
236
|
+
#
|
|
237
|
+
# See also: https://www.codeproject.com/Reference/871338/AccessControl-FileSystemRights-Permissions-Table
|
|
238
|
+
def translate_perm_names(access_type)
|
|
239
|
+
names = translate_common_perms(access_type)
|
|
240
|
+
names ||= translate_granular_perms(access_type)
|
|
241
|
+
names ||= translate_uncommon_perms(access_type)
|
|
242
|
+
raise 'Invalid access_type provided' unless names
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def translate_common_perms(access_type)
|
|
246
|
+
case access_type
|
|
247
|
+
when 'full-control'
|
|
248
|
+
%w{FullControl}
|
|
249
|
+
when 'modify'
|
|
250
|
+
translate_perm_names('full-control') + %w{Modify}
|
|
251
|
+
when 'read'
|
|
252
|
+
translate_perm_names('modify') + %w{ReadAndExecute Read}
|
|
253
|
+
when 'write'
|
|
254
|
+
translate_perm_names('modify') + %w{Write}
|
|
255
|
+
when 'execute'
|
|
256
|
+
translate_perm_names('modify') + %w{ReadAndExecute ExecuteFile Traverse}
|
|
257
|
+
when 'delete'
|
|
258
|
+
translate_perm_names('modify') + %w{Delete}
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def translate_uncommon_perms(access_type)
|
|
263
|
+
case access_type
|
|
264
|
+
when 'delete-subdirectories-and-files'
|
|
265
|
+
translate_perm_names('full-control') + %w{DeleteSubdirectoriesAndFiles}
|
|
266
|
+
when 'change-permissions'
|
|
267
|
+
translate_perm_names('full-control') + %w{ChangePermissions}
|
|
268
|
+
when 'take-ownership'
|
|
269
|
+
translate_perm_names('full-control') + %w{TakeOwnership}
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def translate_granular_perms(access_type)
|
|
274
|
+
case access_type
|
|
275
|
+
when 'write-data', 'create-files'
|
|
276
|
+
translate_perm_names('write') + %w{WriteData CreateFiles}
|
|
277
|
+
when 'append-data', 'create-directories'
|
|
278
|
+
translate_perm_names('write') + %w{CreateDirectories AppendData}
|
|
279
|
+
when 'write-extended-attributes'
|
|
280
|
+
translate_perm_names('write') + %w{WriteExtendedAttributes}
|
|
281
|
+
when 'write-attributes'
|
|
282
|
+
translate_perm_names('write') + %w{WriteAttributes}
|
|
283
|
+
when 'read-data', 'list-directory'
|
|
284
|
+
translate_perm_names('read') + %w{ReadData ListDirectory}
|
|
285
|
+
when 'read-attributes'
|
|
286
|
+
translate_perm_names('read') + %w{ReadAttributes}
|
|
287
|
+
when 'read-extended-attributes'
|
|
288
|
+
translate_perm_names('read') + %w{ReadExtendedAttributes}
|
|
289
|
+
when 'read-permissions'
|
|
290
|
+
translate_perm_names('read') + %w{ReadPermissions}
|
|
291
|
+
end
|
|
292
|
+
end
|
|
216
293
|
end
|
|
217
294
|
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# author: Matthew
|
|
3
|
+
|
|
4
|
+
module Inspec::Resources
|
|
5
|
+
class FirewallD < Inspec.resource(1)
|
|
6
|
+
###
|
|
7
|
+
# This recourse assumes that the file sudo vim /etc/polkit-1/rules.d/49-nopasswd_global.rules has been
|
|
8
|
+
# set to allow users in group "wheel" to perform any commands without authentication.
|
|
9
|
+
###
|
|
10
|
+
|
|
11
|
+
name 'firewalld'
|
|
12
|
+
desc 'Use the firewalld resource to check and see if firewalld is configured to grand or deny access to specific hosts or services'
|
|
13
|
+
example "
|
|
14
|
+
describe firewalld do
|
|
15
|
+
it { should be_running }
|
|
16
|
+
its('default_zone') { should eq 'public' }
|
|
17
|
+
it { should have_service_enabled_in_zone('ssh', 'public') }
|
|
18
|
+
it { should have_rule_enabled('rule family=ipv4 source address=192.168.0.14 accept', 'public') }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe firewalld.where { zone == 'public' } do
|
|
22
|
+
its('interfaces') { should cmp ['enp0s3', 'eno2'] }
|
|
23
|
+
its('sources') { should cmp ['ssh', 'icmp'] }
|
|
24
|
+
its('services') { should cmp ['192.168.1.0/24', '192.168.1.2'] }
|
|
25
|
+
end
|
|
26
|
+
"
|
|
27
|
+
|
|
28
|
+
attr_reader :params
|
|
29
|
+
|
|
30
|
+
filter = FilterTable.create
|
|
31
|
+
filter.add_accessor(:where)
|
|
32
|
+
.add_accessor(:entries)
|
|
33
|
+
.add(:zone, field: 'zone')
|
|
34
|
+
.add(:interfaces, field: 'interfaces')
|
|
35
|
+
.add(:sources, field: 'sources')
|
|
36
|
+
.add(:services, field: 'services')
|
|
37
|
+
|
|
38
|
+
filter.connect(self, :params)
|
|
39
|
+
|
|
40
|
+
def initialize
|
|
41
|
+
return skip_resource 'The `etc_hosts_deny` resource is not supported on your OS.' unless inspec.os.linux?
|
|
42
|
+
@params = parse_active_zones(active_zones)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def installed?
|
|
46
|
+
inspec.command('firewall-cmd').exist?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def has_zone?(query_zone)
|
|
50
|
+
return false unless installed?
|
|
51
|
+
result = firewalld_command('--get-zones').split(' ')
|
|
52
|
+
result.include?(query_zone)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def running?
|
|
56
|
+
return false unless installed?
|
|
57
|
+
result = firewalld_command('--state')
|
|
58
|
+
result =~ /^running/ ? true : false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def default_zone
|
|
62
|
+
# return: word associated with the name of the default zone
|
|
63
|
+
# example: 'public'
|
|
64
|
+
firewalld_command('--get-default-zone')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def has_service_enabled_in_zone?(query_service, query_zone = default_zone)
|
|
68
|
+
firewalld_command("--zone=#{query_zone} --query-service=#{query_service}") == 'yes'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def service_ports_enabled_in_zone(query_service, query_zone = default_zone)
|
|
72
|
+
# return: String of ports open
|
|
73
|
+
# example: ['22/tcp', '4722/tcp']
|
|
74
|
+
firewalld_command("--zone=#{query_zone} --service=#{query_service} --get-ports --permanent").split(' ')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def service_protocols_enabled_in_zone(query_service, query_zone = default_zone)
|
|
78
|
+
# return: String of protocoals open
|
|
79
|
+
# example: ['icmp', 'ipv4', 'igmp']
|
|
80
|
+
firewalld_command("--zone=#{query_zone} --service=#{query_service} --get-protocols --permanent").split(' ')
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def has_port_enabled_in_zone?(query_port, query_zone = default_zone)
|
|
84
|
+
firewalld_command("--zone=#{query_zone} --query-port=#{query_port}") == 'yes'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def has_rule_enabled?(rule, query_zone = default_zone)
|
|
88
|
+
rule = 'rule ' + rule
|
|
89
|
+
firewalld_command("--zone=#{query_zone} --query-rich-rule=#{rule}") == 'yes'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def active_zones
|
|
95
|
+
# return syntax:
|
|
96
|
+
# [default-zone-name]
|
|
97
|
+
# interfaces: [open interfases]
|
|
98
|
+
#
|
|
99
|
+
# example:
|
|
100
|
+
# public
|
|
101
|
+
# interfaces: enp0s3
|
|
102
|
+
firewalld_command('--get-active-zones')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def parse_active_zones(content)
|
|
106
|
+
# Split by every second line, which contains the zone and the interfaces.
|
|
107
|
+
content = content.split(/\n/).each_slice(2).map { |slice| slice.join("\n") }
|
|
108
|
+
content.map do |line|
|
|
109
|
+
parse_line(line)
|
|
110
|
+
end.compact
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def parse_line(line)
|
|
114
|
+
zone = line.split("\n")[0]
|
|
115
|
+
{
|
|
116
|
+
'zone' => zone,
|
|
117
|
+
'interfaces' => line.split(':')[1].split(' '),
|
|
118
|
+
'services' => services_bound(zone),
|
|
119
|
+
'sources' => sources_bound(zone),
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def sources_bound(query_zone)
|
|
124
|
+
# result: a list containing either an ip address or ip address with a mask, or a ipset or an ipset with the ipset prefix.
|
|
125
|
+
# example: ['192.168.0.4', '192.168.0.0/16', '2111:DB28:ABC:12::', '2111:db89:ab3d:0112::0/64']
|
|
126
|
+
firewalld_command("--zone=#{query_zone} --list-sources").split(' ')
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def services_bound(query_zone)
|
|
130
|
+
# result: a list of services bound to a zone.
|
|
131
|
+
# example: ['ssh', 'dhcpv6-client']
|
|
132
|
+
firewalld_command("--zone=#{query_zone} --list-services").split(' ')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def firewalld_command(command)
|
|
136
|
+
command = "firewall-cmd #{command}"
|
|
137
|
+
result = inspec.command(command)
|
|
138
|
+
if result.stderr != ''
|
|
139
|
+
return "Error on command #{command}: #{result.stderr}"
|
|
140
|
+
end
|
|
141
|
+
result.stdout.strip
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
data/lib/resources/groups.rb
CHANGED
data/lib/resources/host.rb
CHANGED
|
@@ -113,7 +113,10 @@ module Inspec::Resources
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def to_s
|
|
116
|
-
"Host #{hostname}"
|
|
116
|
+
resource_name = "Host #{hostname}"
|
|
117
|
+
resource_name += " port #{port} proto #{protocol}" if port
|
|
118
|
+
|
|
119
|
+
resource_name
|
|
117
120
|
end
|
|
118
121
|
|
|
119
122
|
private
|
|
@@ -148,17 +151,17 @@ module Inspec::Resources
|
|
|
148
151
|
def resolve_with_dig(hostname)
|
|
149
152
|
addresses = []
|
|
150
153
|
|
|
151
|
-
# look for
|
|
152
|
-
cmd = inspec.command("dig +short
|
|
154
|
+
# look for IPv4 addresses
|
|
155
|
+
cmd = inspec.command("dig +short A #{hostname}")
|
|
153
156
|
cmd.stdout.lines.each do |line|
|
|
154
|
-
matched = line.chomp.match(Resolv::
|
|
157
|
+
matched = line.chomp.match(Resolv::IPv4::Regex)
|
|
155
158
|
addresses << matched.to_s unless matched.nil?
|
|
156
159
|
end
|
|
157
160
|
|
|
158
|
-
# look for
|
|
159
|
-
cmd = inspec.command("dig +short
|
|
161
|
+
# look for IPv6 addresses
|
|
162
|
+
cmd = inspec.command("dig +short AAAA #{hostname}")
|
|
160
163
|
cmd.stdout.lines.each do |line|
|
|
161
|
-
matched = line.chomp.match(Resolv::
|
|
164
|
+
matched = line.chomp.match(Resolv::IPv6::Regex)
|
|
162
165
|
addresses << matched.to_s unless matched.nil?
|
|
163
166
|
end
|
|
164
167
|
|
|
@@ -166,13 +169,25 @@ module Inspec::Resources
|
|
|
166
169
|
end
|
|
167
170
|
|
|
168
171
|
def resolve_with_getent(hostname)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
+
cmd = inspec.command("getent ahosts #{hostname}")
|
|
173
|
+
return nil unless cmd.exit_status.to_i.zero?
|
|
174
|
+
|
|
175
|
+
# getent ahosts output is formatted like so:
|
|
176
|
+
# $ getent ahosts www.google.com
|
|
177
|
+
# 172.217.8.4 STREAM www.google.com
|
|
178
|
+
# 172.217.8.4 DGRAM
|
|
179
|
+
# 172.217.8.4 RAW
|
|
180
|
+
# 2607:f8b0:4004:803::2004 STREAM
|
|
181
|
+
# 2607:f8b0:4004:803::2004 DGRAM
|
|
182
|
+
# 2607:f8b0:4004:803::2004 RAW
|
|
183
|
+
addresses = []
|
|
184
|
+
cmd.stdout.lines.each do |line|
|
|
185
|
+
ip, = line.split(/\s+/, 2)
|
|
186
|
+
next unless ip.match(Resolv::IPv4::Regex) || ip.match(Resolv::IPv6::Regex)
|
|
187
|
+
addresses << ip unless addresses.include?(ip)
|
|
188
|
+
end
|
|
172
189
|
|
|
173
|
-
|
|
174
|
-
resolve = /^\s*(?<ip>\S+)\s+(.*)\s*$/.match(cmd.stdout.chomp)
|
|
175
|
-
[resolve[1]] if resolve
|
|
190
|
+
addresses
|
|
176
191
|
end
|
|
177
192
|
end
|
|
178
193
|
|
|
@@ -210,7 +225,7 @@ module Inspec::Resources
|
|
|
210
225
|
def missing_requirements(protocol)
|
|
211
226
|
missing = []
|
|
212
227
|
|
|
213
|
-
if protocol == 'tcp' && (!inspec.command('nc').exist?
|
|
228
|
+
if protocol == 'tcp' && (!inspec.command('nc').exist? && !inspec.command('ncat').exist?)
|
|
214
229
|
missing << 'netcat must be installed'
|
|
215
230
|
end
|
|
216
231
|
|
|
@@ -245,7 +260,7 @@ module Inspec::Resources
|
|
|
245
260
|
end
|
|
246
261
|
|
|
247
262
|
def resolve(hostname)
|
|
248
|
-
|
|
263
|
+
resolve_with_getent(hostname)
|
|
249
264
|
end
|
|
250
265
|
end
|
|
251
266
|
|
data/lib/resources/http.rb
CHANGED
|
@@ -22,50 +22,206 @@ module Inspec::Resources
|
|
|
22
22
|
its('Content-Length') { should cmp 258 }
|
|
23
23
|
its('Content-Type') { should cmp 'text/html; charset=UTF-8' }
|
|
24
24
|
end
|
|
25
|
+
|
|
26
|
+
# properly execute the HTTP call on the scanned machine instead of the
|
|
27
|
+
# machine executing InSpec. This will be the default behavior in InSpec 2.0.
|
|
28
|
+
describe http('http://localhost:8080', enable_remote_worker: true) do
|
|
29
|
+
its('body') { should cmp 'local web server on target machine' }
|
|
30
|
+
end
|
|
25
31
|
"
|
|
26
32
|
|
|
27
33
|
def initialize(url, opts = {})
|
|
28
34
|
@url = url
|
|
29
|
-
@
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
@opts = opts
|
|
36
|
+
|
|
37
|
+
if use_remote_worker?
|
|
38
|
+
return skip_resource 'curl is not available on the target machine' unless inspec.command('curl').exist?
|
|
39
|
+
@worker = Worker::Remote.new(inspec, http_method, url, opts)
|
|
40
|
+
else
|
|
41
|
+
@worker = Worker::Local.new(http_method, url, opts)
|
|
42
|
+
end
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
def status
|
|
40
|
-
|
|
46
|
+
@worker.status
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def headers
|
|
50
|
+
Hashie::Mash.new(@worker.response_headers)
|
|
41
51
|
end
|
|
42
52
|
|
|
43
53
|
def body
|
|
44
|
-
|
|
54
|
+
@worker.body
|
|
45
55
|
end
|
|
46
56
|
|
|
47
|
-
def
|
|
48
|
-
|
|
57
|
+
def http_method
|
|
58
|
+
@opts.fetch(:method, 'GET')
|
|
49
59
|
end
|
|
50
60
|
|
|
51
61
|
def to_s
|
|
52
|
-
"http #{
|
|
62
|
+
"http #{http_method} on #{@url}"
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
private
|
|
56
66
|
|
|
57
|
-
def
|
|
58
|
-
|
|
67
|
+
def use_remote_worker?
|
|
68
|
+
return false if inspec.local_transport?
|
|
69
|
+
return true if @opts[:enable_remote_worker]
|
|
70
|
+
|
|
71
|
+
warn "[DEPRECATION] #{self} will execute locally instead of the target machine. To execute remotely, add `enable_remote_worker: true`."
|
|
72
|
+
warn '[DEPRECATION] `enable_remote_worker: true` will be the default behavior in InSpec 2.0.'
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class Worker
|
|
77
|
+
class Base
|
|
78
|
+
attr_reader :http_method, :opts, :url
|
|
79
|
+
|
|
80
|
+
def initialize(http_method, url, opts)
|
|
81
|
+
@http_method = http_method
|
|
82
|
+
@url = url
|
|
83
|
+
@opts = opts
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def params
|
|
89
|
+
opts.fetch(:params, nil)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def username
|
|
93
|
+
opts.fetch(:auth, {})[:user]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def password
|
|
97
|
+
opts.fetch(:auth, {})[:pass]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def request_headers
|
|
101
|
+
opts.fetch(:headers, {})
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def request_body
|
|
105
|
+
opts[:data]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def open_timeout
|
|
109
|
+
opts.fetch(:open_timeout, 60)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def read_timeout
|
|
113
|
+
opts.fetch(:read_timeout, 60)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def ssl_verify?
|
|
117
|
+
opts.fetch(:ssl_verify, true)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class Local < Base
|
|
122
|
+
def status
|
|
123
|
+
response.status
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def body
|
|
127
|
+
response.body
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def response_headers
|
|
131
|
+
response.headers.to_h
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def response
|
|
137
|
+
return @response if @response
|
|
138
|
+
conn = Faraday.new url: url, headers: request_headers, params: params, ssl: { verify: ssl_verify? }
|
|
139
|
+
|
|
140
|
+
# set basic authentication
|
|
141
|
+
conn.basic_auth username, password unless username.nil? || password.nil?
|
|
142
|
+
|
|
143
|
+
# set default timeout
|
|
144
|
+
conn.options.timeout = read_timeout # open/read timeout in seconds
|
|
145
|
+
conn.options.open_timeout = open_timeout # connection open timeout in seconds
|
|
146
|
+
|
|
147
|
+
@response = conn.send(http_method.downcase) do |req|
|
|
148
|
+
req.body = request_body
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
class Remote < Base
|
|
154
|
+
attr_reader :inspec
|
|
155
|
+
|
|
156
|
+
def initialize(inspec, http_method, url, opts)
|
|
157
|
+
@inspec = inspec
|
|
158
|
+
super(http_method, url, opts)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def status
|
|
162
|
+
run_curl
|
|
163
|
+
@status
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def body
|
|
167
|
+
run_curl
|
|
168
|
+
@body&.strip
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def response_headers
|
|
172
|
+
run_curl
|
|
173
|
+
@response_headers
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
private
|
|
177
|
+
|
|
178
|
+
def run_curl
|
|
179
|
+
return if @ran_curl
|
|
180
|
+
|
|
181
|
+
response = inspec.command(curl_command).stdout
|
|
182
|
+
@ran_curl = true
|
|
183
|
+
return if response.nil?
|
|
184
|
+
|
|
185
|
+
# strip any carriage returns to normalize output
|
|
186
|
+
response.delete!("\r")
|
|
187
|
+
|
|
188
|
+
# split the prelude (status line and headers) and the body
|
|
189
|
+
prelude, @body = response.split("\n\n", 2)
|
|
190
|
+
prelude = prelude.lines
|
|
191
|
+
|
|
192
|
+
# grab the status off of the first line of the prelude
|
|
193
|
+
status_line = prelude.shift
|
|
194
|
+
@status = status_line.split(' ', 3)[1].to_i
|
|
195
|
+
|
|
196
|
+
# parse the rest of the prelude which will be all the HTTP headers
|
|
197
|
+
@response_headers = {}
|
|
198
|
+
prelude.each do |line|
|
|
199
|
+
line.strip!
|
|
200
|
+
key, value = line.split(':', 2)
|
|
201
|
+
@response_headers[key] = value.strip
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def curl_command # rubocop:disable Metrics/AbcSize
|
|
206
|
+
cmd = ["curl -i -X #{http_method}"]
|
|
207
|
+
cmd << "--connect-timeout #{open_timeout}"
|
|
208
|
+
cmd << "--max-time #{open_timeout+read_timeout}"
|
|
209
|
+
cmd << "--user \'#{username}:#{password}\'" unless username.nil? || password.nil?
|
|
210
|
+
cmd << '--insecure' unless ssl_verify?
|
|
211
|
+
cmd << "--data #{Shellwords.shellescape(request_body)}" unless request_body.nil?
|
|
59
212
|
|
|
60
|
-
|
|
61
|
-
|
|
213
|
+
request_headers.each do |k, v|
|
|
214
|
+
cmd << "-H '#{k}: #{v}'"
|
|
215
|
+
end
|
|
62
216
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
217
|
+
if params.nil?
|
|
218
|
+
cmd << "'#{url}'"
|
|
219
|
+
else
|
|
220
|
+
cmd << "'#{url}?#{params.map { |e| e.join('=') }.join('&')}'"
|
|
221
|
+
end
|
|
66
222
|
|
|
67
|
-
|
|
68
|
-
|
|
223
|
+
cmd.join(' ')
|
|
224
|
+
end
|
|
69
225
|
end
|
|
70
226
|
end
|
|
71
227
|
end
|
|
@@ -51,11 +51,11 @@ module Inspec::Resources
|
|
|
51
51
|
escaped_query = q.gsub(/\\/, '\\\\').gsub(/"/, '\\"').gsub(/\$/, '\\$')
|
|
52
52
|
# surpress 'x rows affected' in SQLCMD with 'set nocount on;'
|
|
53
53
|
cmd_string = "sqlcmd -Q \"set nocount on; #{escaped_query}\" -W -w 1024 -s ','"
|
|
54
|
-
cmd_string += " -U #{@user} -P '#{@password}'" unless @user.nil? || @password.nil?
|
|
54
|
+
cmd_string += " -U '#{@user}' -P '#{@password}'" unless @user.nil? || @password.nil?
|
|
55
55
|
if @instance.nil?
|
|
56
|
-
cmd_string += " -S #{@host}"
|
|
56
|
+
cmd_string += " -S '#{@host}'"
|
|
57
57
|
else
|
|
58
|
-
cmd_string += " -S #{@host}\\#{@instance}"
|
|
58
|
+
cmd_string += " -S '#{@host}\\#{@instance}'"
|
|
59
59
|
end
|
|
60
60
|
cmd = inspec.command(cmd_string)
|
|
61
61
|
out = cmd.stdout + "\n" + cmd.stderr
|
data/lib/resources/mysql_conf.rb
CHANGED
|
@@ -33,6 +33,16 @@ module Inspec::Resources
|
|
|
33
33
|
describe mysql_conf('path') do
|
|
34
34
|
its('setting') { should eq 'value' }
|
|
35
35
|
end
|
|
36
|
+
|
|
37
|
+
# Test a parameter set within the [mysqld] section
|
|
38
|
+
describe mysql_conf do
|
|
39
|
+
its('mysqld.port') { should cmp 3306 }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Test a parameter set within the [mariadb] section using array notation
|
|
43
|
+
describe mysql_conf do
|
|
44
|
+
its(['mariadb', 'max-connections']) { should_not be_nil }
|
|
45
|
+
end
|
|
36
46
|
"
|
|
37
47
|
|
|
38
48
|
include FindFiles
|
|
@@ -38,7 +38,7 @@ module Inspec::Resources
|
|
|
38
38
|
command += " -h #{@host}"
|
|
39
39
|
end
|
|
40
40
|
command += " --port #{@port}" unless @port.nil?
|
|
41
|
-
command += " #{db} -s -
|
|
41
|
+
command += " #{db} -s -e \"#{escaped_query}\""
|
|
42
42
|
|
|
43
43
|
cmd = inspec.command(command)
|
|
44
44
|
out = cmd.stdout + "\n" + cmd.stderr
|