inspec-core 4.18.51 → 4.18.85
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/Gemfile +61 -0
- data/README.md +3 -3
- data/inspec-core.gemspec +51 -0
- data/lib/bundles/inspec-supermarket/cli.rb +1 -0
- data/lib/inspec/backend.rb +49 -47
- data/lib/inspec/base_cli.rb +2 -2
- data/lib/inspec/cached_fetcher.rb +4 -0
- data/lib/inspec/cli.rb +5 -0
- data/lib/inspec/config.rb +1 -1
- data/lib/inspec/control_eval_context.rb +131 -199
- data/lib/inspec/dependencies/requirement.rb +1 -1
- data/lib/inspec/dependencies/resolver.rb +46 -0
- data/lib/inspec/dsl_shared.rb +25 -3
- data/lib/inspec/fetcher.rb +0 -3
- data/lib/inspec/fetcher/git.rb +4 -0
- data/lib/inspec/fetcher/url.rb +1 -2
- data/lib/inspec/file_provider.rb +4 -2
- data/lib/inspec/library_eval_context.rb +37 -37
- data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +27 -0
- data/lib/inspec/plugin/v1/plugins.rb +0 -1
- data/lib/inspec/profile.rb +8 -6
- data/lib/inspec/profile_context.rb +74 -9
- data/lib/inspec/profile_vendor.rb +48 -3
- data/lib/inspec/resource.rb +192 -41
- data/lib/inspec/resources/aide_conf.rb +1 -1
- data/lib/inspec/resources/apache_conf.rb +15 -31
- data/lib/inspec/resources/command.rb +1 -1
- data/lib/inspec/resources/crontab.rb +56 -56
- data/lib/inspec/resources/etc_fstab.rb +1 -1
- data/lib/inspec/resources/etc_group.rb +1 -1
- data/lib/inspec/resources/etc_hosts.rb +2 -3
- data/lib/inspec/resources/etc_hosts_allow_deny.rb +1 -1
- data/lib/inspec/resources/file.rb +2 -2
- data/lib/inspec/resources/filesystem.rb +4 -4
- data/lib/inspec/resources/groups.rb +16 -2
- data/lib/inspec/resources/iis_app.rb +1 -1
- data/lib/inspec/resources/ini.rb +1 -2
- data/lib/inspec/resources/mount.rb +2 -2
- data/lib/inspec/resources/oracledb_session.rb +1 -1
- data/lib/inspec/resources/package.rb +22 -0
- data/lib/inspec/resources/passwd.rb +1 -1
- data/lib/inspec/resources/platform.rb +36 -36
- data/lib/inspec/resources/port.rb +1 -1
- data/lib/inspec/resources/postfix_conf.rb +1 -1
- data/lib/inspec/resources/service.rb +23 -15
- data/lib/inspec/resources/users.rb +3 -3
- data/lib/inspec/resources/virtualization.rb +15 -11
- data/lib/inspec/resources/x509_certificate.rb +18 -4
- data/lib/inspec/resources/xinetd_conf.rb +1 -1
- data/lib/inspec/resources/xml.rb +1 -2
- data/lib/inspec/rspec_extensions.rb +12 -0
- data/lib/inspec/rule.rb +63 -22
- data/lib/inspec/utils/filter.rb +2 -0
- data/lib/inspec/utils/parser.rb +244 -240
- data/lib/inspec/utils/simpleconfig.rb +1 -1
- data/lib/inspec/version.rb +1 -1
- data/lib/matchers/matchers.rb +11 -10
- data/lib/plugins/inspec-compliance/lib/inspec-compliance.rb +3 -0
- data/lib/plugins/inspec-habitat/lib/inspec-habitat/profile.rb +2 -2
- data/lib/plugins/inspec-init/templates/profiles/aws/README.md +192 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/attributes.yml +2 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/controls/example.rb +39 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +22 -0
- data/lib/plugins/inspec-init/templates/profiles/azure/README.md +56 -0
- data/lib/plugins/inspec-init/templates/profiles/azure/controls/example.rb +14 -0
- data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +14 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/README.md +66 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/attributes.yml +2 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/controls/example.rb +27 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +19 -0
- data/lib/source_readers/inspec.rb +1 -1
- metadata +87 -74
- data/lib/inspec/plugin/v1/plugin_types/resource.rb +0 -176
- data/lib/plugins/inspec-init/templates/profiles/os/libraries/.gitkeep +0 -0
|
@@ -4,7 +4,7 @@ require "inspec/utils/simpleconfig"
|
|
|
4
4
|
module Inspec::Resources
|
|
5
5
|
class PostfixConf < IniConfig
|
|
6
6
|
name "postfix_conf"
|
|
7
|
-
supports platform: "
|
|
7
|
+
supports platform: "unix"
|
|
8
8
|
desc "Use the postfix_conf Inspec audit resource to test the configuration of the Postfix Mail Transfer Agent"
|
|
9
9
|
|
|
10
10
|
# Allow user to specify a custom configuration path, use default Postfix configuration path if no custom path is provided
|
|
@@ -112,21 +112,23 @@ module Inspec::Resources
|
|
|
112
112
|
# Ubuntu < 15.04 : Upstart
|
|
113
113
|
# Upstart runs with PID 1 as /sbin/init.
|
|
114
114
|
# Systemd runs with PID 1 as /lib/systemd/systemd.
|
|
115
|
-
|
|
115
|
+
|
|
116
|
+
case platform
|
|
117
|
+
when "ubuntu"
|
|
116
118
|
version = os[:release].to_f
|
|
117
119
|
if version < 15.04
|
|
118
120
|
Upstart.new(inspec, service_ctl)
|
|
119
121
|
else
|
|
120
122
|
Systemd.new(inspec, service_ctl)
|
|
121
123
|
end
|
|
122
|
-
|
|
124
|
+
when "linuxmint"
|
|
123
125
|
version = os[:release].to_f
|
|
124
126
|
if version < 18
|
|
125
127
|
Upstart.new(inspec, service_ctl)
|
|
126
128
|
else
|
|
127
129
|
Systemd.new(inspec, service_ctl)
|
|
128
130
|
end
|
|
129
|
-
|
|
131
|
+
when "debian"
|
|
130
132
|
if os[:release] == "buster/sid"
|
|
131
133
|
version = 10
|
|
132
134
|
else
|
|
@@ -137,41 +139,47 @@ module Inspec::Resources
|
|
|
137
139
|
elsif version > 0
|
|
138
140
|
SysV.new(inspec, service_ctl || "/usr/sbin/service")
|
|
139
141
|
end
|
|
140
|
-
|
|
142
|
+
when "redhat", "fedora", "centos", "oracle", "cloudlinux"
|
|
141
143
|
version = os[:release].to_i
|
|
142
|
-
|
|
144
|
+
|
|
145
|
+
systemd = ((platform != "fedora" && version >= 7) ||
|
|
146
|
+
(platform == "fedora" && version >= 15))
|
|
147
|
+
|
|
148
|
+
if systemd
|
|
143
149
|
Systemd.new(inspec, service_ctl)
|
|
144
150
|
else
|
|
145
151
|
SysV.new(inspec, service_ctl || "/sbin/service")
|
|
146
152
|
end
|
|
147
|
-
|
|
153
|
+
when "wrlinux"
|
|
148
154
|
SysV.new(inspec, service_ctl)
|
|
149
|
-
|
|
155
|
+
when "mac_os_x"
|
|
150
156
|
LaunchCtl.new(inspec, service_ctl)
|
|
151
|
-
|
|
157
|
+
when "windows"
|
|
152
158
|
WindowsSrv.new(inspec)
|
|
153
|
-
|
|
159
|
+
when "freebsd"
|
|
154
160
|
BSDInit.new(inspec, service_ctl)
|
|
155
|
-
|
|
161
|
+
when "arch"
|
|
156
162
|
Systemd.new(inspec, service_ctl)
|
|
157
|
-
|
|
163
|
+
when "coreos"
|
|
158
164
|
Systemd.new(inspec, service_ctl)
|
|
159
|
-
|
|
165
|
+
when "suse", "opensuse"
|
|
160
166
|
if os[:release].to_i >= 12
|
|
161
167
|
Systemd.new(inspec, service_ctl)
|
|
162
168
|
else
|
|
163
169
|
SysV.new(inspec, service_ctl || "/sbin/service")
|
|
164
170
|
end
|
|
165
|
-
|
|
171
|
+
when "aix"
|
|
166
172
|
SrcMstr.new(inspec)
|
|
167
|
-
|
|
173
|
+
when "amazon"
|
|
168
174
|
if os[:release] =~ /^20\d\d/
|
|
169
175
|
Upstart.new(inspec, service_ctl)
|
|
170
176
|
else
|
|
171
177
|
Systemd.new(inspec, service_ctl)
|
|
172
178
|
end
|
|
173
|
-
|
|
179
|
+
when "solaris", "smartos", "omnios", "openindiana", "opensolaris", "nexentacore"
|
|
174
180
|
Svcs.new(inspec)
|
|
181
|
+
when "yocto"
|
|
182
|
+
Systemd.new(inspec, service_ctl)
|
|
175
183
|
end
|
|
176
184
|
end
|
|
177
185
|
|
|
@@ -442,8 +442,8 @@ module Inspec::Resources
|
|
|
442
442
|
end
|
|
443
443
|
|
|
444
444
|
class LinuxUser < UnixUser
|
|
445
|
-
include PasswdParser
|
|
446
|
-
include CommentParser
|
|
445
|
+
include Inspec::Utils::PasswdParser
|
|
446
|
+
include Inspec::Utils::CommentParser
|
|
447
447
|
|
|
448
448
|
def meta_info(username)
|
|
449
449
|
cmd = inspec.command("getent passwd #{username}")
|
|
@@ -589,7 +589,7 @@ module Inspec::Resources
|
|
|
589
589
|
# - chpass(1) A flexible tool for changing user database information.
|
|
590
590
|
# - passwd(1) The command-line tool to change user passwords.
|
|
591
591
|
class FreeBSDUser < UnixUser
|
|
592
|
-
include PasswdParser
|
|
592
|
+
include Inspec::Utils::PasswdParser
|
|
593
593
|
|
|
594
594
|
def meta_info(username)
|
|
595
595
|
cmd = inspec.command("pw usershow #{username} -7")
|
|
@@ -3,8 +3,8 @@ require "hashie/mash"
|
|
|
3
3
|
module Inspec::Resources
|
|
4
4
|
class Virtualization < Inspec.resource(1)
|
|
5
5
|
name "virtualization"
|
|
6
|
-
supports platform: "
|
|
7
|
-
desc "Use the virtualization InSpec audit resource to test
|
|
6
|
+
supports platform: "unix"
|
|
7
|
+
desc "Use the virtualization InSpec audit resource to test various virtualization platforms."
|
|
8
8
|
example <<~EXAMPLE
|
|
9
9
|
describe virtualization do
|
|
10
10
|
its('system') { should eq 'docker' }
|
|
@@ -23,19 +23,21 @@ module Inspec::Resources
|
|
|
23
23
|
EXAMPLE
|
|
24
24
|
|
|
25
25
|
def initialize
|
|
26
|
+
# TODO: no need for hashie here... in fact, no reason for a hash at all
|
|
26
27
|
@virtualization_data = Hashie::Mash.new
|
|
27
28
|
collect_data_linux
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
# add helper methods for easy access of properties
|
|
31
32
|
# allows users to use virtualization.role, virtualization.system
|
|
32
|
-
%
|
|
33
|
-
define_method(property
|
|
34
|
-
@virtualization_data[property
|
|
33
|
+
%i{role system}.each do |property|
|
|
34
|
+
define_method(property) do
|
|
35
|
+
@virtualization_data[property]
|
|
35
36
|
end
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
def params
|
|
40
|
+
# TODO: this is broken. cannot return anything but nil
|
|
39
41
|
collect_data_linux
|
|
40
42
|
end
|
|
41
43
|
|
|
@@ -215,6 +217,7 @@ module Inspec::Resources
|
|
|
215
217
|
|
|
216
218
|
@virtualization_data[:system] = "docker"
|
|
217
219
|
@virtualization_data[:role] = "guest"
|
|
220
|
+
# TODO: needs to support host
|
|
218
221
|
true
|
|
219
222
|
end
|
|
220
223
|
|
|
@@ -233,23 +236,24 @@ module Inspec::Resources
|
|
|
233
236
|
true
|
|
234
237
|
end
|
|
235
238
|
|
|
236
|
-
def collect_data_linux
|
|
239
|
+
def collect_data_linux
|
|
237
240
|
# This avoids doing multiple detections in a single test
|
|
238
241
|
return unless @virtualization_data.empty?
|
|
239
242
|
|
|
240
243
|
# each detect method will return true if it matched and was successfully
|
|
241
244
|
# able to populate @virtualization_data with stuff.
|
|
242
245
|
return if detect_xen
|
|
246
|
+
return if detect_docker
|
|
243
247
|
return if detect_virtualbox
|
|
244
|
-
return if
|
|
248
|
+
return if detect_lxd
|
|
249
|
+
return if detect_lxc_docker
|
|
250
|
+
return if detect_linux_vserver
|
|
245
251
|
return if detect_kvm_from_cpuinfo
|
|
246
252
|
return if detect_kvm_from_sys
|
|
247
253
|
return if detect_openvz
|
|
254
|
+
return if detect_openstack
|
|
248
255
|
return if detect_parallels
|
|
249
|
-
|
|
250
|
-
return if detect_lxc_docker
|
|
251
|
-
return if detect_docker
|
|
252
|
-
return if detect_lxd
|
|
256
|
+
# TODO: where is vmware?
|
|
253
257
|
end
|
|
254
258
|
end
|
|
255
259
|
end
|
|
@@ -34,13 +34,15 @@ module Inspec::Resources
|
|
|
34
34
|
include FileReader
|
|
35
35
|
|
|
36
36
|
# @see https://tools.ietf.org/html/rfc5280#page-23
|
|
37
|
-
def initialize(
|
|
38
|
-
@
|
|
37
|
+
def initialize(opts)
|
|
38
|
+
@opts = options(opts)
|
|
39
39
|
@issuer = nil
|
|
40
40
|
@parsed_subject = nil
|
|
41
41
|
@parsed_issuer = nil
|
|
42
42
|
@extensions = nil
|
|
43
|
-
@
|
|
43
|
+
@content = @opts[:content]
|
|
44
|
+
@content ||= read_file_content(@opts[:filepath])
|
|
45
|
+
@cert = OpenSSL::X509::Certificate.new @content
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
# Forward these methods directly to OpenSSL::X509::Certificate instance
|
|
@@ -137,7 +139,19 @@ module Inspec::Resources
|
|
|
137
139
|
end
|
|
138
140
|
|
|
139
141
|
def to_s
|
|
140
|
-
|
|
142
|
+
cert = @opts[:filepath]
|
|
143
|
+
cert ||= subject.CN
|
|
144
|
+
"x509_certificate #{cert}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def options(opts)
|
|
150
|
+
if opts.is_a?(String)
|
|
151
|
+
{ filepath: opts }
|
|
152
|
+
else
|
|
153
|
+
opts
|
|
154
|
+
end
|
|
141
155
|
end
|
|
142
156
|
end
|
|
143
157
|
end
|
data/lib/inspec/resources/xml.rb
CHANGED
|
@@ -3,8 +3,7 @@ require "inspec/resources/json"
|
|
|
3
3
|
module Inspec::Resources
|
|
4
4
|
class XmlConfig < JsonConfig
|
|
5
5
|
name "xml"
|
|
6
|
-
supports platform: "
|
|
7
|
-
supports platform: "windows"
|
|
6
|
+
supports platform: "os"
|
|
8
7
|
desc "Use the xml InSpec resource to test configuration data in an XML file"
|
|
9
8
|
example <<~EXAMPLE
|
|
10
9
|
describe xml('default.xml') do
|
|
@@ -114,3 +114,15 @@ class RSpec::Core::ExampleGroup
|
|
|
114
114
|
# So, we use prepend.
|
|
115
115
|
prepend Inspec::TestDslLazyLoader
|
|
116
116
|
end
|
|
117
|
+
|
|
118
|
+
class ResourceInspector < RSpec::Support::ObjectFormatter::BaseInspector
|
|
119
|
+
def self.can_inspect?(object)
|
|
120
|
+
Inspec::Resource === object
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def inspect # do NOT use default inspect in rspec w/ resources
|
|
124
|
+
object.to_s
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
RSpec::Support::ObjectFormatter::INSPECTOR_CLASSES.unshift ResourceInspector
|
data/lib/inspec/rule.rb
CHANGED
|
@@ -13,32 +13,20 @@ module Inspec
|
|
|
13
13
|
class Rule
|
|
14
14
|
include ::RSpec::Matchers
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
# Include any resources from the given resource DSL. The passed
|
|
18
|
-
# resource_dsl will also be included in any Inspec::Expect objects
|
|
19
|
-
# we make.
|
|
20
|
-
#
|
|
21
|
-
# @params resource_dsl [Module]
|
|
22
|
-
# @returns [TrueClass]
|
|
23
|
-
#
|
|
24
|
-
def self.with_resource_dsl(resource_dsl)
|
|
25
|
-
include resource_dsl
|
|
26
|
-
@resource_dsl = resource_dsl
|
|
27
|
-
true
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def self.resource_dsl # rubocop:disable Style/TrivialAccessors
|
|
31
|
-
@resource_dsl
|
|
32
|
-
end
|
|
33
|
-
|
|
34
16
|
attr_reader :__waiver_data
|
|
35
|
-
|
|
17
|
+
attr_accessor :resource_dsl
|
|
18
|
+
attr_reader :__profile_id
|
|
19
|
+
|
|
20
|
+
def initialize(id, profile_id, resource_dsl, opts, &block)
|
|
36
21
|
@impact = nil
|
|
37
22
|
@title = nil
|
|
38
23
|
@descriptions = {}
|
|
39
24
|
@refs = []
|
|
40
25
|
@tags = {}
|
|
41
26
|
|
|
27
|
+
@resource_dsl = resource_dsl
|
|
28
|
+
extend resource_dsl # TODO: remove! do it via method_missing
|
|
29
|
+
|
|
42
30
|
# not changeable by the user:
|
|
43
31
|
@__code = nil
|
|
44
32
|
@__block = block
|
|
@@ -168,7 +156,7 @@ module Inspec
|
|
|
168
156
|
# @return [nil|DescribeBase] if called without arguments, returns DescribeBase
|
|
169
157
|
def describe(*values, &block)
|
|
170
158
|
if values.empty? && !block_given?
|
|
171
|
-
dsl =
|
|
159
|
+
dsl = resource_dsl
|
|
172
160
|
Class.new(DescribeBase) do
|
|
173
161
|
include dsl
|
|
174
162
|
end.new(method(:__add_check))
|
|
@@ -183,6 +171,59 @@ module Inspec
|
|
|
183
171
|
target
|
|
184
172
|
end
|
|
185
173
|
|
|
174
|
+
# allow attributes to be accessed within control blocks
|
|
175
|
+
def input(input_name, options = {})
|
|
176
|
+
if options.empty?
|
|
177
|
+
# Simply an access, no event here
|
|
178
|
+
Inspec::InputRegistry.find_or_register_input(input_name, __profile_id).value
|
|
179
|
+
else
|
|
180
|
+
options[:priority] ||= 20
|
|
181
|
+
options[:provider] = :inline_control_code
|
|
182
|
+
evt = Inspec::Input.infer_event(options)
|
|
183
|
+
Inspec::InputRegistry.find_or_register_input(input_name, __profile_id, event: evt).value
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Find the Input object, but don't collapse to a value.
|
|
188
|
+
# Will return nil on a miss.
|
|
189
|
+
def input_object(input_name)
|
|
190
|
+
Inspec::InputRegistry.find_or_register_input(input_name, __profile_id)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def attribute(name, options = {})
|
|
194
|
+
Inspec.deprecate(:attrs_dsl, "Input name: #{name}, Profile: #{__profile_id}")
|
|
195
|
+
input(name, options)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Support for Control DSL plugins.
|
|
199
|
+
# This is called when an unknown method is encountered
|
|
200
|
+
# within a control block.
|
|
201
|
+
def method_missing(method_name, *arguments, &block)
|
|
202
|
+
# Check to see if there is a control_dsl plugin activator hook with the method name
|
|
203
|
+
registry = Inspec::Plugin::V2::Registry.instance
|
|
204
|
+
hook = registry.find_activators(plugin_type: :control_dsl, activator_name: method_name).first
|
|
205
|
+
if hook
|
|
206
|
+
# OK, load the hook if it hasn't been already. We'll then know a module,
|
|
207
|
+
# which we can then inject into the context
|
|
208
|
+
hook.activate
|
|
209
|
+
|
|
210
|
+
# Inject the module's methods into the context.
|
|
211
|
+
# implementation_class is the field name, but this is actually a module.
|
|
212
|
+
self.class.include(hook.implementation_class)
|
|
213
|
+
# Now that the module is loaded, it defined one or more methods
|
|
214
|
+
# (presumably the one we were looking for.)
|
|
215
|
+
# We still haven't called it, so do so now.
|
|
216
|
+
send(method_name, *arguments, &block)
|
|
217
|
+
else
|
|
218
|
+
begin
|
|
219
|
+
Inspec::DSL.method_missing_resource(inspec, method_name, *arguments)
|
|
220
|
+
rescue LoadError
|
|
221
|
+
super
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# TODO: figure out why these violations exist and nuke them.
|
|
186
227
|
def self.rule_id(rule)
|
|
187
228
|
rule.instance_variable_get(:@__rule_id)
|
|
188
229
|
end
|
|
@@ -290,7 +331,7 @@ module Inspec
|
|
|
290
331
|
def __apply_waivers
|
|
291
332
|
input_name = @__rule_id # TODO: control ID slugging
|
|
292
333
|
registry = Inspec::InputRegistry.instance
|
|
293
|
-
input = registry.inputs_by_profile.dig(
|
|
334
|
+
input = registry.inputs_by_profile.dig(__profile_id, input_name)
|
|
294
335
|
return unless input
|
|
295
336
|
|
|
296
337
|
# An InSpec Input is a datastructure that tracks a profile parameter
|
|
@@ -347,7 +388,7 @@ module Inspec
|
|
|
347
388
|
def with_dsl(block)
|
|
348
389
|
return nil if block.nil?
|
|
349
390
|
|
|
350
|
-
dsl =
|
|
391
|
+
dsl = resource_dsl
|
|
351
392
|
|
|
352
393
|
return block unless dsl
|
|
353
394
|
|
data/lib/inspec/utils/filter.rb
CHANGED
data/lib/inspec/utils/parser.rb
CHANGED
|
@@ -1,277 +1,281 @@
|
|
|
1
1
|
require "inspec/resources/command"
|
|
2
2
|
|
|
3
|
-
module
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
#
|
|
18
|
-
# @param [String] line a line of /etc/passwd
|
|
19
|
-
# @return [Hash] Map of entries in this line
|
|
20
|
-
def parse_passwd_line(line)
|
|
21
|
-
x = line.split(":")
|
|
22
|
-
{
|
|
23
|
-
# rubocop:disable Layout/AlignHash
|
|
24
|
-
"user" => x[0],
|
|
25
|
-
"password" => x[1],
|
|
26
|
-
"uid" => x[2],
|
|
27
|
-
"gid" => x[3],
|
|
28
|
-
"desc" => x[4],
|
|
29
|
-
"home" => x[5],
|
|
30
|
-
"shell" => x[6],
|
|
31
|
-
}
|
|
32
|
-
end
|
|
33
|
-
end
|
|
3
|
+
module Inspec
|
|
4
|
+
module Utils
|
|
5
|
+
module PasswdParser
|
|
6
|
+
# Parse /etc/passwd files.
|
|
7
|
+
#
|
|
8
|
+
# @param [String] content the raw content of /etc/passwd
|
|
9
|
+
# @return [Array] Collection of passwd entries
|
|
10
|
+
def parse_passwd(content)
|
|
11
|
+
content.to_s.split("\n").map do |line|
|
|
12
|
+
next if line[0] == "#"
|
|
13
|
+
|
|
14
|
+
parse_passwd_line(line)
|
|
15
|
+
end.compact
|
|
16
|
+
end
|
|
34
17
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
line = raw[0..(idx_comment - 1)]
|
|
52
|
-
# in case we don't allow comments at the end
|
|
53
|
-
# of an assignment/statement, ignore it and fall
|
|
54
|
-
# back to treating this as a regular line
|
|
55
|
-
if opts[:standalone_comments] && !is_empty_line(line)
|
|
56
|
-
line = raw[0..(idx_nl - 1)]
|
|
18
|
+
# Parse a line of /etc/passwd
|
|
19
|
+
#
|
|
20
|
+
# @param [String] line a line of /etc/passwd
|
|
21
|
+
# @return [Hash] Map of entries in this line
|
|
22
|
+
def parse_passwd_line(line)
|
|
23
|
+
x = line.split(":")
|
|
24
|
+
{
|
|
25
|
+
# rubocop:disable Layout/AlignHash
|
|
26
|
+
"user" => x[0],
|
|
27
|
+
"password" => x[1],
|
|
28
|
+
"uid" => x[2],
|
|
29
|
+
"gid" => x[3],
|
|
30
|
+
"desc" => x[4],
|
|
31
|
+
"home" => x[5],
|
|
32
|
+
"shell" => x[6],
|
|
33
|
+
}
|
|
57
34
|
end
|
|
58
|
-
# if there is no comment in this line
|
|
59
|
-
elsif idx_comment > idx_nl && idx_nl != 0
|
|
60
|
-
line = raw[0..(idx_nl - 1)]
|
|
61
35
|
end
|
|
62
|
-
[line, idx_nl]
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
36
|
|
|
66
|
-
module
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
37
|
+
module CommentParser
|
|
38
|
+
# Parse a line with a command. For example: `a = b # comment`.
|
|
39
|
+
# Retrieves the actual content.
|
|
40
|
+
#
|
|
41
|
+
# @param [String] raw the content lines you want to be parsed
|
|
42
|
+
# @param [Hash] opts optional configuration
|
|
43
|
+
# @return [Array] contains the actual line and the position of the line end
|
|
44
|
+
def parse_comment_line(raw, opts)
|
|
45
|
+
idx_nl = raw.index("\n")
|
|
46
|
+
idx_comment = raw.index(opts[:comment_char])
|
|
47
|
+
idx_nl = raw.length if idx_nl.nil?
|
|
48
|
+
idx_comment = idx_nl + 1 if idx_comment.nil?
|
|
49
|
+
line = ""
|
|
50
|
+
|
|
51
|
+
# is a comment inside this line
|
|
52
|
+
if idx_comment < idx_nl && idx_comment != 0
|
|
53
|
+
line = raw[0..(idx_comment - 1)]
|
|
54
|
+
# in case we don't allow comments at the end
|
|
55
|
+
# of an assignment/statement, ignore it and fall
|
|
56
|
+
# back to treating this as a regular line
|
|
57
|
+
if opts[:standalone_comments] && !is_empty_line(line)
|
|
58
|
+
line = raw[0..(idx_nl - 1)]
|
|
59
|
+
end
|
|
60
|
+
# if there is no comment in this line
|
|
61
|
+
elsif idx_comment > idx_nl && idx_nl != 0
|
|
62
|
+
line = raw[0..(idx_nl - 1)]
|
|
63
|
+
end
|
|
64
|
+
[line, idx_nl]
|
|
65
|
+
end
|
|
82
66
|
end
|
|
83
67
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
|
|
68
|
+
module LinuxMountParser
|
|
69
|
+
# this parses the output of mount command (only tested on linux)
|
|
70
|
+
# this method expects only one line of the mount output
|
|
71
|
+
def parse_mount_options(mount_line, compatibility = false)
|
|
72
|
+
if includes_whitespaces?(mount_line)
|
|
73
|
+
# Device-/Sharenames and Mountpoints including whitespaces require special treatment:
|
|
74
|
+
# We use the keyword ' type ' to split up and rebuild the desired array of fields
|
|
75
|
+
type_split = mount_line.split(" type ")
|
|
76
|
+
fs_path = type_split[0]
|
|
77
|
+
other_opts = type_split[1]
|
|
78
|
+
fs, path = fs_path.match(%r{^(.+?)\son\s(/.+?)$}).captures
|
|
79
|
+
mount = [fs, "on", path, "type"]
|
|
80
|
+
mount.concat(other_opts.scan(/\S+/))
|
|
81
|
+
else
|
|
82
|
+
# ... otherwise we just split the fields by whitespaces
|
|
83
|
+
mount = mount_line.scan(/\S+/)
|
|
100
84
|
end
|
|
101
|
-
|
|
85
|
+
|
|
86
|
+
# parse device and type
|
|
87
|
+
mount_options = { device: mount[0], type: mount[4] }
|
|
88
|
+
|
|
89
|
+
if compatibility == false
|
|
90
|
+
# parse options as array
|
|
91
|
+
mount_options[:options] = mount[5].gsub(/\(|\)/, "").split(",")
|
|
92
|
+
else
|
|
93
|
+
Inspec.deprecate(:mount_parser_serverspec_compat, "Parsing mount options in this fashion is deprecated")
|
|
94
|
+
mount_options[:options] = {}
|
|
95
|
+
mount[5].gsub(/\(|\)/, "").split(",").each do |option|
|
|
96
|
+
name, val = option.split("=")
|
|
97
|
+
if val.nil?
|
|
98
|
+
val = true
|
|
99
|
+
elsif val =~ /^\d+$/
|
|
100
|
+
# parse numbers
|
|
101
|
+
val = val.to_i
|
|
102
|
+
end
|
|
103
|
+
mount_options[:options][name.to_sym] = val
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
mount_options
|
|
102
108
|
end
|
|
103
|
-
end
|
|
104
109
|
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
# Device-/Sharename or Mountpoint includes whitespaces?
|
|
111
|
+
def includes_whitespaces?(mount_line)
|
|
112
|
+
ws = mount_line.match(/^(.+)\son\s(.+)\stype\s.*$/)
|
|
113
|
+
ws.captures[0].include?(" ") || ws.captures[1].include?(" ")
|
|
114
|
+
end
|
|
115
|
+
end
|
|
107
116
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
end
|
|
117
|
+
module BsdMountParser
|
|
118
|
+
# this parses the output of mount command (only tested on freebsd)
|
|
119
|
+
# this method expects only one line of the mount output
|
|
120
|
+
def parse_mount_options(mount_line, _compatibility = false)
|
|
121
|
+
return {} if mount_line.nil? || mount_line.empty?
|
|
114
122
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
# this method expects only one line of the mount output
|
|
118
|
-
def parse_mount_options(mount_line, _compatibility = false)
|
|
119
|
-
return {} if mount_line.nil? || mount_line.empty?
|
|
123
|
+
mount = mount_line.chomp.split(" ", 4)
|
|
124
|
+
options = mount[3].tr("()", "").split(", ")
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
# parse device and type
|
|
127
|
+
{ device: mount[0], type: options.shift, options: options }
|
|
128
|
+
end
|
|
129
|
+
end
|
|
123
130
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
module SolarisNetstatParser
|
|
132
|
+
# takes this as a input and parses the values
|
|
133
|
+
# UDP: IPv4
|
|
134
|
+
# Local Address Remote Address State
|
|
135
|
+
# -------------------- -------------------- ----------
|
|
136
|
+
# *.* Unbound
|
|
137
|
+
def parse_netstat(content) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
138
|
+
return [] if content.nil? || content.empty?
|
|
128
139
|
|
|
129
|
-
|
|
130
|
-
# takes this as a input and parses the values
|
|
131
|
-
# UDP: IPv4
|
|
132
|
-
# Local Address Remote Address State
|
|
133
|
-
# -------------------- -------------------- ----------
|
|
134
|
-
# *.* Unbound
|
|
135
|
-
def parse_netstat(content) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
136
|
-
return [] if content.nil? || content.empty?
|
|
137
|
-
|
|
138
|
-
protocol = nil
|
|
139
|
-
column_widths = nil
|
|
140
|
-
ports = []
|
|
141
|
-
cache_name_line = nil
|
|
142
|
-
|
|
143
|
-
content.each_line do |line|
|
|
144
|
-
# find header, its delimiter
|
|
145
|
-
if line =~ /TCP:|UDP:|SCTP:/
|
|
146
|
-
# get protocol
|
|
147
|
-
protocol = line.split(":")[0].chomp.strip.downcase
|
|
148
|
-
|
|
149
|
-
# determine version tcp, tcp6, udp, udp6
|
|
150
|
-
proto_version = line.split(":")[1].chomp.strip
|
|
151
|
-
protocol += "6" if proto_version == "IPv6"
|
|
152
|
-
|
|
153
|
-
# reset names cache
|
|
140
|
+
protocol = nil
|
|
154
141
|
column_widths = nil
|
|
142
|
+
ports = []
|
|
155
143
|
cache_name_line = nil
|
|
156
|
-
names = nil
|
|
157
|
-
# calulate width of a column based on the horizontal line
|
|
158
|
-
elsif line =~ /^[- ]+$/
|
|
159
|
-
column_widths = columns(line)
|
|
160
|
-
# parse header values from line
|
|
161
|
-
elsif column_widths.nil? && !line.nil?
|
|
162
|
-
# we do not know the width at this point of time, therefore we need to cache
|
|
163
|
-
cache_name_line = line
|
|
164
|
-
# content line
|
|
165
|
-
elsif !column_widths.nil? && !line.nil? && !line.chomp.empty?
|
|
166
|
-
# default row
|
|
167
|
-
port = split_columns(column_widths, line).to_a.map { |v| v.chomp.strip }
|
|
168
|
-
|
|
169
|
-
# parse the header names
|
|
170
|
-
# TODO: names should be optional
|
|
171
|
-
names = split_columns(column_widths, cache_name_line).to_a.map { |v| v.chomp.strip.downcase.tr(" ", "-").gsub(/[^\w-]/, "_") }
|
|
172
|
-
info = {
|
|
173
|
-
"protocol" => protocol.downcase,
|
|
174
|
-
}
|
|
175
144
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
145
|
+
content.each_line do |line|
|
|
146
|
+
# find header, its delimiter
|
|
147
|
+
if line =~ /TCP:|UDP:|SCTP:/
|
|
148
|
+
# get protocol
|
|
149
|
+
protocol = line.split(":")[0].chomp.strip.downcase
|
|
150
|
+
|
|
151
|
+
# determine version tcp, tcp6, udp, udp6
|
|
152
|
+
proto_version = line.split(":")[1].chomp.strip
|
|
153
|
+
protocol += "6" if proto_version == "IPv6"
|
|
154
|
+
|
|
155
|
+
# reset names cache
|
|
156
|
+
column_widths = nil
|
|
157
|
+
cache_name_line = nil
|
|
158
|
+
names = nil
|
|
159
|
+
# calulate width of a column based on the horizontal line
|
|
160
|
+
elsif line =~ /^[- ]+$/
|
|
161
|
+
column_widths = columns(line)
|
|
162
|
+
# parse header values from line
|
|
163
|
+
elsif column_widths.nil? && !line.nil?
|
|
164
|
+
# we do not know the width at this point of time, therefore we need to cache
|
|
165
|
+
cache_name_line = line
|
|
166
|
+
# content line
|
|
167
|
+
elsif !column_widths.nil? && !line.nil? && !line.chomp.empty?
|
|
168
|
+
# default row
|
|
169
|
+
port = split_columns(column_widths, line).to_a.map { |v| v.chomp.strip }
|
|
170
|
+
|
|
171
|
+
# parse the header names
|
|
172
|
+
# TODO: names should be optional
|
|
173
|
+
names = split_columns(column_widths, cache_name_line).to_a.map { |v| v.chomp.strip.downcase.tr(" ", "-").gsub(/[^\w-]/, "_") }
|
|
174
|
+
info = {
|
|
175
|
+
"protocol" => protocol.downcase,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# generate hash for each line and use the names as keys
|
|
179
|
+
names.each_index do |i|
|
|
180
|
+
info[names[i]] = port[i] if i != 0
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
ports.push(info)
|
|
184
|
+
end
|
|
179
185
|
end
|
|
180
|
-
|
|
181
|
-
ports.push(info)
|
|
186
|
+
ports
|
|
182
187
|
end
|
|
183
|
-
end
|
|
184
|
-
ports
|
|
185
|
-
end
|
|
186
188
|
|
|
187
|
-
|
|
189
|
+
private
|
|
188
190
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
191
|
+
# takes a line like "-------------------- -------------------- ----------"
|
|
192
|
+
# as input and calculates the length of each column
|
|
193
|
+
def columns(line)
|
|
194
|
+
# find all columns
|
|
195
|
+
m = line.scan(/-+/)
|
|
196
|
+
# calculate the length each column
|
|
197
|
+
m.map { |x| x.length } # rubocop:disable Style/SymbolProc
|
|
198
|
+
end
|
|
197
199
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
200
|
+
# takes a line and the width of the columns to extract the values
|
|
201
|
+
def split_columns(columns, line)
|
|
202
|
+
# generate regex based on columns
|
|
203
|
+
sep = '\\s'
|
|
204
|
+
length = columns.length
|
|
205
|
+
arr = columns.map.with_index do |x, i|
|
|
206
|
+
reg = "(.{#{x}})#{sep}" # add seperator between columns
|
|
207
|
+
reg = "(.{,#{x}})#{sep}" if i == length - 2 # make the pre-last one optional
|
|
208
|
+
reg = "(.{,#{x}})" if i == length - 1 # use , to say max value
|
|
209
|
+
reg
|
|
210
|
+
end
|
|
211
|
+
# extracts the columns
|
|
212
|
+
line.match(Regexp.new(arr.join))
|
|
213
|
+
end
|
|
208
214
|
end
|
|
209
|
-
# extracts the columns
|
|
210
|
-
line.match(Regexp.new(arr.join))
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
215
|
|
|
214
|
-
# This parser for xinetd (extended Internet daemon) configuration files
|
|
215
|
-
module XinetdParser
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
# This parser for xinetd (extended Internet daemon) configuration files
|
|
217
|
+
module XinetdParser
|
|
218
|
+
def xinetd_include_dir(dir)
|
|
219
|
+
return [] if dir.nil?
|
|
218
220
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
unless inspec.file(dir).directory?
|
|
222
|
+
raise Inspec::Exceptions::ResourceSkipped, "Can't find folder: #{dir}"
|
|
223
|
+
end
|
|
222
224
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
files = inspec.command("find #{dir} -type f").stdout.split("\n")
|
|
226
|
+
files.map { |file| parse_xinetd(read_content(file)) }
|
|
227
|
+
end
|
|
226
228
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
cur_group = nil
|
|
234
|
-
simple_conf = []
|
|
235
|
-
rest = raw + "\n"
|
|
236
|
-
until rest.empty?
|
|
237
|
-
# extract content line
|
|
238
|
-
nl = rest.index("\n") || (rest.length - 1)
|
|
239
|
-
comment = rest.index("#") || (rest.length - 1)
|
|
240
|
-
dst_idx = comment < nl ? comment : nl
|
|
241
|
-
inner_line = dst_idx == 0 ? "" : rest[0..dst_idx - 1].strip
|
|
242
|
-
# update unparsed content
|
|
243
|
-
rest = rest[nl + 1..-1]
|
|
244
|
-
next if inner_line.empty?
|
|
245
|
-
|
|
246
|
-
if inner_line == "}"
|
|
247
|
-
if cur_group == "defaults"
|
|
248
|
-
res[cur_group] = SimpleConfig.new(simple_conf.join("\n"))
|
|
249
|
-
else
|
|
250
|
-
res[cur_group] ||= []
|
|
251
|
-
res[cur_group].push(SimpleConfig.new(simple_conf.join("\n")))
|
|
252
|
-
end
|
|
229
|
+
def parse_xinetd(raw) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
230
|
+
return {} if raw.nil?
|
|
231
|
+
|
|
232
|
+
require "inspec/utils/simpleconfig"
|
|
233
|
+
|
|
234
|
+
res = {}
|
|
253
235
|
cur_group = nil
|
|
254
|
-
elsif rest.lstrip[0] == "{"
|
|
255
|
-
cur_group = inner_line
|
|
256
236
|
simple_conf = []
|
|
257
|
-
rest =
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
237
|
+
rest = raw + "\n"
|
|
238
|
+
until rest.empty?
|
|
239
|
+
# extract content line
|
|
240
|
+
nl = rest.index("\n") || (rest.length - 1)
|
|
241
|
+
comment = rest.index("#") || (rest.length - 1)
|
|
242
|
+
dst_idx = comment < nl ? comment : nl
|
|
243
|
+
inner_line = dst_idx == 0 ? "" : rest[0..dst_idx - 1].strip
|
|
244
|
+
# update unparsed content
|
|
245
|
+
rest = rest[nl + 1..-1]
|
|
246
|
+
next if inner_line.empty?
|
|
247
|
+
|
|
248
|
+
if inner_line == "}"
|
|
249
|
+
if cur_group == "defaults"
|
|
250
|
+
res[cur_group] = SimpleConfig.new(simple_conf.join("\n"))
|
|
251
|
+
else
|
|
252
|
+
res[cur_group] ||= []
|
|
253
|
+
res[cur_group].push(SimpleConfig.new(simple_conf.join("\n")))
|
|
254
|
+
end
|
|
255
|
+
cur_group = nil
|
|
256
|
+
elsif rest.lstrip[0] == "{"
|
|
257
|
+
cur_group = inner_line
|
|
258
|
+
simple_conf = []
|
|
259
|
+
rest = rest[rest.index("\n") + 1..-1]
|
|
260
|
+
elsif cur_group.nil?
|
|
261
|
+
# parse all included files
|
|
262
|
+
others = xinetd_include_dir(inner_line[/includedir (.+)/, 1])
|
|
263
|
+
|
|
264
|
+
# complex merging of included configurations, as multiple services
|
|
265
|
+
# may be defined with the same name but different configuration
|
|
266
|
+
others.each do |ores|
|
|
267
|
+
ores.each do |k, v|
|
|
268
|
+
res[k] ||= []
|
|
269
|
+
res[k].concat(v)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
else
|
|
273
|
+
simple_conf.push(inner_line)
|
|
268
274
|
end
|
|
269
275
|
end
|
|
270
|
-
|
|
271
|
-
|
|
276
|
+
|
|
277
|
+
res
|
|
272
278
|
end
|
|
273
279
|
end
|
|
274
|
-
|
|
275
|
-
res
|
|
276
280
|
end
|
|
277
281
|
end
|