inspec 0.33.2 → 0.34.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -2
- data/README.md +12 -13
- data/examples/meta-profile/README.md +11 -0
- data/examples/meta-profile/controls/example.rb +8 -0
- data/examples/meta-profile/inspec.yml +19 -0
- data/lib/bundles/inspec-compliance/target.rb +25 -8
- data/lib/bundles/inspec-supermarket/api.rb +12 -16
- data/lib/bundles/inspec-supermarket/target.rb +11 -6
- data/lib/fetchers/git.rb +162 -0
- data/lib/fetchers/local.rb +33 -16
- data/lib/fetchers/mock.rb +8 -4
- data/lib/fetchers/url.rb +56 -42
- data/lib/inspec/dependencies/lockfile.rb +28 -2
- data/lib/inspec/dependencies/requirement.rb +20 -51
- data/lib/inspec/dependencies/resolver.rb +30 -13
- data/lib/inspec/dependencies/vendor_index.rb +9 -42
- data/lib/inspec/errors.rb +1 -0
- data/lib/inspec/fetcher.rb +1 -2
- data/lib/inspec/file_provider.rb +219 -0
- data/lib/inspec/plugins/fetcher.rb +59 -80
- data/lib/inspec/profile.rb +31 -18
- data/lib/inspec/resource.rb +2 -1
- data/lib/inspec/source_reader.rb +0 -3
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/security_policy.rb +67 -38
- data/lib/resources/sys_info.rb +26 -0
- data/lib/resources/{user.rb → users.rb} +237 -76
- metadata +9 -5
- data/lib/fetchers/tar.rb +0 -53
- data/lib/fetchers/zip.rb +0 -49
@@ -0,0 +1,219 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems/package'
|
3
|
+
require 'zlib'
|
4
|
+
require 'zip'
|
5
|
+
|
6
|
+
module Inspec
|
7
|
+
class FileProvider
|
8
|
+
def self.for_path(path)
|
9
|
+
if path.is_a?(Hash)
|
10
|
+
MockProvider.new(path)
|
11
|
+
elsif File.directory?(path)
|
12
|
+
DirProvider.new(path)
|
13
|
+
elsif File.exist?(path) && path.end_with?('.tar.gz', 'tgz')
|
14
|
+
TarProvider.new(path)
|
15
|
+
elsif File.exist?(path) && path.end_with?('.zip')
|
16
|
+
ZipProvider.new(path)
|
17
|
+
elsif File.exist?(path)
|
18
|
+
DirProvider.new(path)
|
19
|
+
else
|
20
|
+
fail "No file provider for the provided path: #{path}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(_path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def read(_file)
|
28
|
+
fail "#{self} does not implement `read(...)`. This is required."
|
29
|
+
end
|
30
|
+
|
31
|
+
def files
|
32
|
+
fail "Fetcher #{self} does not implement `files()`. This is required."
|
33
|
+
end
|
34
|
+
|
35
|
+
def relative_provider
|
36
|
+
RelativeFileProvider.new(self)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class MockProvider < FileProvider
|
41
|
+
attr_reader :files
|
42
|
+
def initialize(path)
|
43
|
+
@data = path[:mock]
|
44
|
+
@files = @data.keys
|
45
|
+
end
|
46
|
+
|
47
|
+
def read(file)
|
48
|
+
@data[file]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class DirProvider < FileProvider
|
53
|
+
attr_reader :files
|
54
|
+
def initialize(path)
|
55
|
+
@files = if File.file?(path)
|
56
|
+
[path]
|
57
|
+
else
|
58
|
+
Dir[File.join(path, '**', '*')]
|
59
|
+
end
|
60
|
+
@path = path
|
61
|
+
end
|
62
|
+
|
63
|
+
def read(file)
|
64
|
+
return nil unless files.include?(file)
|
65
|
+
return nil unless File.file?(file)
|
66
|
+
File.read(file)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class ZipProvider < FileProvider
|
71
|
+
attr_reader :files
|
72
|
+
|
73
|
+
def initialize(path)
|
74
|
+
@path = path
|
75
|
+
@contents = {}
|
76
|
+
@files = []
|
77
|
+
::Zip::InputStream.open(@path) do |io|
|
78
|
+
while (entry = io.get_next_entry)
|
79
|
+
@files.push(entry.name.sub(%r{/+$}, ''))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def read(file)
|
85
|
+
@contents[file] ||= read_from_zip(file)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def read_from_zip(file)
|
91
|
+
return nil unless @files.include?(file)
|
92
|
+
res = nil
|
93
|
+
::Zip::InputStream.open(@path) do |io|
|
94
|
+
while (entry = io.get_next_entry)
|
95
|
+
next unless file == entry.name
|
96
|
+
res = io.read
|
97
|
+
break
|
98
|
+
end
|
99
|
+
end
|
100
|
+
res
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class TarProvider < FileProvider
|
105
|
+
attr_reader :files
|
106
|
+
|
107
|
+
def initialize(path)
|
108
|
+
@path = path
|
109
|
+
@contents = {}
|
110
|
+
@files = []
|
111
|
+
Gem::Package::TarReader.new(Zlib::GzipReader.open(@path)) do |tar|
|
112
|
+
@files = tar.map(&:full_name)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def read(file)
|
117
|
+
@contents[file] ||= read_from_tar(file)
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def read_from_tar(file)
|
123
|
+
return nil unless @files.include?(file)
|
124
|
+
res = nil
|
125
|
+
# NB `TarReader` includes `Enumerable` beginning with Ruby 2.x
|
126
|
+
Gem::Package::TarReader.new(Zlib::GzipReader.open(@path)) do |tar|
|
127
|
+
tar.each do |entry|
|
128
|
+
next unless entry.file? && file == entry.full_name
|
129
|
+
res = entry.read
|
130
|
+
break
|
131
|
+
end
|
132
|
+
end
|
133
|
+
res
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class RelativeFileProvider
|
138
|
+
BLACKLIST_FILES = [
|
139
|
+
'/pax_global_header',
|
140
|
+
'pax_global_header',
|
141
|
+
].freeze
|
142
|
+
|
143
|
+
attr_reader :files
|
144
|
+
attr_reader :prefix
|
145
|
+
attr_reader :parent
|
146
|
+
|
147
|
+
def initialize(parent_provider)
|
148
|
+
@parent = parent_provider
|
149
|
+
@prefix = get_prefix(parent.files)
|
150
|
+
if @prefix.nil?
|
151
|
+
fail "Could not determine path prefix for #{parent}"
|
152
|
+
end
|
153
|
+
@files = parent.files.find_all { |x| x.start_with?(prefix) && x != prefix }
|
154
|
+
.map { |x| x[prefix.length..-1] }
|
155
|
+
end
|
156
|
+
|
157
|
+
def abs_path(file)
|
158
|
+
return nil if file.nil?
|
159
|
+
prefix + file
|
160
|
+
end
|
161
|
+
|
162
|
+
def read(file)
|
163
|
+
parent.read(abs_path(file))
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def get_prefix(fs)
|
169
|
+
return '' if fs.empty?
|
170
|
+
|
171
|
+
# filter backlisted files
|
172
|
+
fs -= BLACKLIST_FILES
|
173
|
+
|
174
|
+
sorted = fs.sort_by(&:length)
|
175
|
+
get_folder_prefix(sorted)
|
176
|
+
end
|
177
|
+
|
178
|
+
def prefix_candidate_for(file)
|
179
|
+
if file.end_with?(File::SEPARATOR)
|
180
|
+
file
|
181
|
+
else
|
182
|
+
file + File::SEPARATOR
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_folder_prefix(fs)
|
187
|
+
return get_files_prefix(fs) if fs.length == 1
|
188
|
+
first, *rest = fs
|
189
|
+
pre = prefix_candidate_for(first)
|
190
|
+
|
191
|
+
if rest.all? { |i| i.start_with? pre }
|
192
|
+
return get_folder_prefix(rest)
|
193
|
+
end
|
194
|
+
get_files_prefix(fs)
|
195
|
+
end
|
196
|
+
|
197
|
+
def get_files_prefix(fs)
|
198
|
+
return '' if fs.empty?
|
199
|
+
|
200
|
+
file = fs[0]
|
201
|
+
bn = File.basename(file)
|
202
|
+
# no more prefixes
|
203
|
+
return '' if bn == file
|
204
|
+
|
205
|
+
i = file.rindex(bn)
|
206
|
+
pre = file[0..i-1]
|
207
|
+
|
208
|
+
rest = fs.find_all { |f| !f.start_with?(pre) }
|
209
|
+
return pre if rest.empty?
|
210
|
+
|
211
|
+
new_pre = get_prefix(rest)
|
212
|
+
return new_pre if pre.start_with? new_pre
|
213
|
+
# edge case: completely different prefixes; retry prefix detection
|
214
|
+
a = File.dirname(pre + 'a')
|
215
|
+
b = File.dirname(new_pre + 'b')
|
216
|
+
get_prefix([a, b])
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -1,106 +1,85 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
# author: Dominik Richter
|
3
3
|
# author: Christoph Hartmann
|
4
|
-
|
5
4
|
require 'utils/plugin_registry'
|
5
|
+
require 'inspec/file_provider'
|
6
|
+
require 'digest'
|
6
7
|
|
7
8
|
module Inspec
|
8
9
|
module Plugins
|
10
|
+
#
|
11
|
+
# An Inspec::Plugins::Fetcher is responsible for fetching a remote
|
12
|
+
# source to a local directory or file provided by the user.
|
13
|
+
#
|
14
|
+
# In general, there are two kinds of fetchers. (1) Fetchers that
|
15
|
+
# implement this entire API (see the Git or Url fetchers for
|
16
|
+
# examples), and (2) fetchers that only implement self.resolve and
|
17
|
+
# then call the resolve_next method with a modified target hash.
|
18
|
+
# Fetchers in (2) do not need to implement the functions in this
|
19
|
+
# class because the caller will never actually get an instance of
|
20
|
+
# those fetchers.
|
21
|
+
#
|
9
22
|
class Fetcher < PluginRegistry::Plugin
|
10
23
|
def self.plugin_registry
|
11
24
|
Inspec::Fetcher
|
12
25
|
end
|
13
26
|
|
14
|
-
# Provide a list of files that are available to this fetcher.
|
15
27
|
#
|
16
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
# Read a file using this fetcher. The name must correspond to a file
|
22
|
-
# available to this fetcher. Use #files to retrieve the list of
|
23
|
-
# files.
|
28
|
+
# The path to the archive on disk. This can be passed to a
|
29
|
+
# FileProvider to get access to the files in the fetched
|
30
|
+
# profile.
|
24
31
|
#
|
25
|
-
|
26
|
-
|
27
|
-
def read(_file)
|
28
|
-
fail "Fetcher #{self} does not implement `read(...)`. This is required."
|
29
|
-
end
|
30
|
-
|
31
|
-
def relative_target
|
32
|
-
RelFetcher.new(self)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
BLACKLIST_FILES = [
|
37
|
-
'/pax_global_header',
|
38
|
-
'pax_global_header',
|
39
|
-
].freeze
|
40
|
-
|
41
|
-
class RelFetcher < Fetcher
|
42
|
-
attr_reader :files
|
43
|
-
attr_reader :prefix
|
44
|
-
|
45
|
-
def initialize(fetcher)
|
46
|
-
@parent = fetcher
|
47
|
-
@prefix = get_prefix(fetcher.files)
|
48
|
-
@files = fetcher.files.find_all { |x| x.start_with? prefix }
|
49
|
-
.map { |x| x[prefix.length..-1] }
|
32
|
+
def archive_path
|
33
|
+
fail "Fetcher #{self} does not implement `archive_path()`. This is required."
|
50
34
|
end
|
51
35
|
|
52
|
-
|
53
|
-
|
54
|
-
|
36
|
+
#
|
37
|
+
# Fetches the remote source to a local source, using the
|
38
|
+
# provided path as a partial filename. That is, if you pass
|
39
|
+
# /foo/bar/baz, the fetcher can create:
|
40
|
+
#
|
41
|
+
# /foo/bar/baz/: A profile directory, or
|
42
|
+
# /foo/bar/baz.tar.gz: A profile tarball, or
|
43
|
+
# /foo/bar/baz.zip
|
44
|
+
#
|
45
|
+
def fetch(_path)
|
46
|
+
fail "Fetcher #{self} does not implement `fetch()`. This is required."
|
55
47
|
end
|
56
48
|
|
57
|
-
|
58
|
-
|
49
|
+
#
|
50
|
+
# The full specification of the remote source, with any
|
51
|
+
# ambigious references provided by the user resolved to an exact
|
52
|
+
# reference where possible. For example, in the Git provide, a
|
53
|
+
# tag will be resolved to an exact revision.
|
54
|
+
#
|
55
|
+
def resolved_source
|
56
|
+
fail "Fetcher #{self} does not implement `resolved_source()`. This is required for terminal fetchers."
|
59
57
|
end
|
60
58
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
59
|
+
#
|
60
|
+
# relative_target is provided to keep compatibility with 3rd
|
61
|
+
# party plugins.
|
62
|
+
#
|
63
|
+
# Deprecated: This function may be removed in future versions of
|
64
|
+
# Inspec, don't depend on it in new plugins.
|
65
|
+
#
|
66
|
+
# @returns [Inspec::RelativeFileProvider]
|
67
|
+
#
|
68
|
+
def relative_target
|
69
|
+
file_provider = Inspec::FileProvider.for_path(archive_path)
|
70
|
+
file_provider.relative_provider
|
71
71
|
end
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
73
|
+
#
|
74
|
+
# A string based on the components of the resolved source,
|
75
|
+
# suitable for constructing per-source file names.
|
76
|
+
#
|
77
|
+
def cache_key
|
78
|
+
key = ''
|
79
|
+
resolved_source.each do |k, v|
|
80
|
+
key << "#{k}:#{v}"
|
79
81
|
end
|
80
|
-
|
81
|
-
fs
|
82
|
-
end
|
83
|
-
|
84
|
-
def get_files_prefix(fs)
|
85
|
-
return '' if fs.empty?
|
86
|
-
|
87
|
-
file = fs[0]
|
88
|
-
bn = File.basename(file)
|
89
|
-
# no more prefixes
|
90
|
-
return '' if bn == file
|
91
|
-
|
92
|
-
i = file.rindex(bn)
|
93
|
-
pre = file[0..i-1]
|
94
|
-
|
95
|
-
rest = fs.find_all { |f| !f.start_with?(pre) }
|
96
|
-
return pre if rest.empty?
|
97
|
-
|
98
|
-
new_pre = get_prefix(rest)
|
99
|
-
return new_pre if pre.start_with? new_pre
|
100
|
-
# edge case: completely different prefixes; retry prefix detection
|
101
|
-
a = File.dirname(pre + 'a')
|
102
|
-
b = File.dirname(new_pre + 'b')
|
103
|
-
get_prefix([a, b])
|
82
|
+
Digest::SHA256.hexdigest key
|
104
83
|
end
|
105
84
|
end
|
106
85
|
end
|
data/lib/inspec/profile.rb
CHANGED
@@ -6,11 +6,14 @@
|
|
6
6
|
require 'forwardable'
|
7
7
|
require 'inspec/polyfill'
|
8
8
|
require 'inspec/fetcher'
|
9
|
+
require 'inspec/file_provider'
|
9
10
|
require 'inspec/source_reader'
|
10
11
|
require 'inspec/metadata'
|
11
12
|
require 'inspec/backend'
|
12
13
|
require 'inspec/rule'
|
14
|
+
require 'inspec/log'
|
13
15
|
require 'inspec/profile_context'
|
16
|
+
require 'inspec/dependencies/vendor_index'
|
14
17
|
require 'inspec/dependencies/lockfile'
|
15
18
|
require 'inspec/dependencies/dependency_set'
|
16
19
|
|
@@ -18,24 +21,34 @@ module Inspec
|
|
18
21
|
class Profile # rubocop:disable Metrics/ClassLength
|
19
22
|
extend Forwardable
|
20
23
|
|
21
|
-
def self.resolve_target(target)
|
22
|
-
|
24
|
+
def self.resolve_target(target, cache = nil)
|
25
|
+
cache ||= VendorIndex.new
|
23
26
|
fetcher = Inspec::Fetcher.resolve(target)
|
24
27
|
if fetcher.nil?
|
25
28
|
fail("Could not fetch inspec profile in #{target.inspect}.")
|
26
29
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
|
31
|
+
if cache.exists?(fetcher.cache_key)
|
32
|
+
Inspec::Log.debug "Using cached dependency for #{target}"
|
33
|
+
cache.prefered_entry_for(fetcher.cache_key)
|
34
|
+
else
|
35
|
+
fetcher.fetch(cache.base_path_for(fetcher.cache_key))
|
36
|
+
fetcher.archive_path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.for_path(path, opts)
|
41
|
+
file_provider = FileProvider.for_path(path)
|
42
|
+
reader = Inspec::SourceReader.resolve(file_provider.relative_provider)
|
30
43
|
if reader.nil?
|
31
|
-
fail("Don't understand inspec profile in #{
|
44
|
+
fail("Don't understand inspec profile in #{path}, it " \
|
32
45
|
"doesn't look like a supported profile structure.")
|
33
46
|
end
|
34
|
-
reader
|
47
|
+
new(reader, opts)
|
35
48
|
end
|
36
49
|
|
37
50
|
def self.for_target(target, opts = {})
|
38
|
-
|
51
|
+
for_path(resolve_target(target, opts[:cache]), opts.merge(target: target))
|
39
52
|
end
|
40
53
|
|
41
54
|
attr_reader :source_reader
|
@@ -46,18 +59,18 @@ module Inspec
|
|
46
59
|
|
47
60
|
# rubocop:disable Metrics/AbcSize
|
48
61
|
def initialize(source_reader, options = {})
|
49
|
-
@
|
50
|
-
@
|
51
|
-
@
|
52
|
-
@source_reader = source_reader
|
53
|
-
if options[:dependencies]
|
54
|
-
@locked_dependencies = options[:dependencies]
|
55
|
-
end
|
62
|
+
@target = options.delete(:target)
|
63
|
+
@logger = options[:logger] || Logger.new(nil)
|
64
|
+
@locked_dependencies = options[:dependencies]
|
56
65
|
@controls = options[:controls] || []
|
57
|
-
@profile_id =
|
58
|
-
@backend =
|
66
|
+
@profile_id = options[:id]
|
67
|
+
@backend = options[:backend] || Inspec::Backend.create(options)
|
68
|
+
@source_reader = source_reader
|
69
|
+
@tests_collected = false
|
59
70
|
Metadata.finalize(@source_reader.metadata, @profile_id)
|
60
|
-
@runner_context =
|
71
|
+
@runner_context = options[:profile_context] || Inspec::ProfileContext.for_profile(self,
|
72
|
+
@backend,
|
73
|
+
options[:attributes])
|
61
74
|
end
|
62
75
|
|
63
76
|
def name
|
data/lib/inspec/resource.rb
CHANGED
@@ -106,7 +106,8 @@ require 'resources/service'
|
|
106
106
|
require 'resources/shadow'
|
107
107
|
require 'resources/ssl'
|
108
108
|
require 'resources/ssh_conf'
|
109
|
-
require 'resources/
|
109
|
+
require 'resources/sys_info'
|
110
|
+
require 'resources/users'
|
110
111
|
require 'resources/vbscript'
|
111
112
|
require 'resources/windows_feature'
|
112
113
|
require 'resources/xinetd'
|
data/lib/inspec/source_reader.rb
CHANGED
@@ -11,9 +11,6 @@ module Inspec
|
|
11
11
|
class SourceReaderRegistry < PluginRegistry
|
12
12
|
def resolve(target)
|
13
13
|
return nil if target.nil?
|
14
|
-
unless target.is_a? Inspec::Plugins::Fetcher
|
15
|
-
fail "SourceReader cannot resolve targets that aren't Fetchers: #{target.class}"
|
16
|
-
end
|
17
14
|
super(target)
|
18
15
|
end
|
19
16
|
end
|
data/lib/inspec/version.rb
CHANGED
@@ -13,72 +13,101 @@
|
|
13
13
|
# All local GPO parameters can be examined via Registry, but not all security
|
14
14
|
# parameters. Therefore we need a combination of Registry and secedit output
|
15
15
|
|
16
|
+
require 'hashie'
|
17
|
+
|
16
18
|
module Inspec::Resources
|
17
19
|
class SecurityPolicy < Inspec.resource(1)
|
18
20
|
name 'security_policy'
|
19
21
|
desc 'Use the security_policy InSpec audit resource to test security policies on the Microsoft Windows platform.'
|
20
22
|
example "
|
21
23
|
describe security_policy do
|
22
|
-
its('SeNetworkLogonRight') { should
|
24
|
+
its('SeNetworkLogonRight') { should include 'S-1-5-11' }
|
23
25
|
end
|
24
26
|
"
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
|
28
|
+
def content
|
29
|
+
read_content
|
30
|
+
end
|
31
|
+
|
32
|
+
def params(*opts)
|
33
|
+
opts.inject(read_params) do |res, nxt|
|
34
|
+
res.respond_to?(:key) ? res[nxt] : nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(name)
|
39
|
+
params = read_params
|
40
|
+
return nil if params.nil?
|
41
|
+
|
42
|
+
# deep search for hash key
|
43
|
+
params.extend Hashie::Extensions::DeepFind
|
44
|
+
res = params.deep_find(name.to_s)
|
45
|
+
res
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
'Security Policy'
|
29
50
|
end
|
30
51
|
|
31
|
-
|
32
|
-
|
52
|
+
private
|
53
|
+
|
54
|
+
def read_content
|
55
|
+
return @content if defined?(@content)
|
56
|
+
|
33
57
|
# export the security policy
|
34
58
|
cmd = inspec.command('secedit /export /cfg win_secpol.cfg')
|
35
59
|
return nil if cmd.exit_status.to_i != 0
|
36
60
|
|
37
61
|
# store file content
|
38
62
|
cmd = inspec.command('Get-Content win_secpol.cfg')
|
39
|
-
|
40
|
-
|
41
|
-
@policy = cmd.stdout
|
42
|
-
@loaded = true
|
43
|
-
|
44
|
-
# returns self
|
45
|
-
self
|
63
|
+
return skip_resource "Can't read security policy" if cmd.exit_status.to_i != 0
|
64
|
+
@content = cmd.stdout
|
46
65
|
|
66
|
+
if @content.empty? && file.size > 0
|
67
|
+
return skip_resource "Can't read security policy"
|
68
|
+
end
|
69
|
+
@content
|
47
70
|
ensure
|
48
71
|
# delete temp file
|
49
72
|
inspec.command('Remove-Item win_secpol.cfg').exit_status.to_i
|
50
73
|
end
|
51
74
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
load
|
56
|
-
end
|
57
|
-
|
58
|
-
# find line with key
|
59
|
-
key = Regexp.escape(method.to_s)
|
60
|
-
target = ''
|
61
|
-
@policy.each_line {|s|
|
62
|
-
target = s.strip if s =~ /^\s*#{key}\s*=\s*(.*)\b/
|
63
|
-
}
|
75
|
+
def read_params
|
76
|
+
return @params if defined?(@params)
|
77
|
+
return @params = {} if read_content.nil?
|
64
78
|
|
65
|
-
|
66
|
-
|
79
|
+
conf = SimpleConfig.new(
|
80
|
+
@content,
|
81
|
+
assignment_re: /^\s*(.*)=\s*(\S*)\s*$/,
|
82
|
+
)
|
83
|
+
@params = convert_hash(conf.params)
|
84
|
+
end
|
67
85
|
|
68
|
-
|
69
|
-
|
70
|
-
|
86
|
+
# extracts the values, this methods detects:
|
87
|
+
# numbers and SIDs and optimizes them for further usage
|
88
|
+
def extract_value(val)
|
89
|
+
if val =~ /^\d+$/
|
90
|
+
val.to_i
|
91
|
+
# special handling for SID array
|
92
|
+
elsif val =~ /^\*\S/
|
93
|
+
val.split(',').map { |v|
|
94
|
+
v.sub('*S', 'S')
|
95
|
+
}
|
96
|
+
# special handling for string values with "
|
97
|
+
elsif !(m = /^\"(.*)\"$/.match(val)).nil?
|
98
|
+
m[1]
|
71
99
|
else
|
72
|
-
|
73
|
-
# requested value is not available
|
74
|
-
val = nil
|
100
|
+
val
|
75
101
|
end
|
76
|
-
|
77
|
-
val
|
78
102
|
end
|
79
103
|
|
80
|
-
def
|
81
|
-
|
104
|
+
def convert_hash(hash)
|
105
|
+
new_hash = {}
|
106
|
+
hash.each do |k, v|
|
107
|
+
v.is_a?(Hash) ? value = convert_hash(v) : value = extract_value(v)
|
108
|
+
new_hash[k.strip] = value
|
109
|
+
end
|
110
|
+
new_hash
|
82
111
|
end
|
83
112
|
end
|
84
113
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Inspec::Resources
|
3
|
+
# this resource returns additional system informatio
|
4
|
+
class System < Inspec.resource(1)
|
5
|
+
name 'sys_info'
|
6
|
+
|
7
|
+
desc 'Use the user InSpec system resource to test for operating system properties.'
|
8
|
+
example "
|
9
|
+
describe sysinfo do
|
10
|
+
its('hostname') { should eq 'example.com' }
|
11
|
+
end
|
12
|
+
"
|
13
|
+
|
14
|
+
# returns the hostname of the local system
|
15
|
+
def hostname
|
16
|
+
os = inspec.os
|
17
|
+
if os.linux?
|
18
|
+
inspec.command('hostname').stdout.chomp
|
19
|
+
elsif os.windows?
|
20
|
+
inspec.powershell('$env:computername').stdout.chomp
|
21
|
+
else
|
22
|
+
skip_resource 'The `sys_info.hostname` resource is not supported on your OS yet.'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|