inspec-core 4.10.4 → 4.12.0
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/README.md +7 -7
- data/lib/inspec/resources/command.rb +2 -0
- data/lib/inspec/resources/dh_params.rb +63 -61
- data/lib/inspec/resources/etc_hosts.rb +46 -44
- data/lib/inspec/resources/groups.rb +26 -8
- data/lib/inspec/resources/grub_conf.rb +180 -178
- data/lib/inspec/resources/iis_app_pool.rb +106 -104
- data/lib/inspec/resources/service.rb +12 -3
- data/lib/inspec/resources/ssl.rb +74 -72
- data/lib/inspec/runner.rb +1 -0
- data/lib/inspec/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 873c4c83c46030d3a3adb7093f085650f8400c6bbfc7c87984ebc77305aa660b
|
4
|
+
data.tar.gz: '091ccf8b261dfeafac84a771ec549e46f015dcc9e5da9836982788df40c456c1'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed9c35b0c96d17f96eea08658b251812ac3f03c4912a1663b9d5de600a5d6637460bf223ca90e9ca84625298b5dce64f999ac8d019c2b615b621247c48e1693a
|
7
|
+
data.tar.gz: fca132a2f7221c41c78bf7379ccf74e4c07cc412d36da10817d99adc15a9c2e5a049f6311a8c6bf323a8c7deffed18462d8fb660b0c9669d5182b477cf3ca585
|
data/README.md
CHANGED
@@ -327,13 +327,13 @@ Remote Targets
|
|
327
327
|
|
328
328
|
In addition, runtime support is provided for:
|
329
329
|
|
330
|
-
| Platform | Versions |
|
331
|
-
| -------- | -------- |
|
332
|
-
| Debian | 8, 9 |
|
333
|
-
| RHEL | 6, 7 |
|
334
|
-
| Ubuntu | 12.04+ |
|
335
|
-
| Windows | 7+ |
|
336
|
-
| Windows | 2012+ |
|
330
|
+
| Platform | Versions | Arch |
|
331
|
+
| -------- | -------- | ------ |
|
332
|
+
| Debian | 8, 9 | x86_64 |
|
333
|
+
| RHEL | 6, 7 | x86_64 |
|
334
|
+
| Ubuntu | 12.04+ | x86_64 |
|
335
|
+
| Windows | 7+ | x86_64 |
|
336
|
+
| Windows | 2012+ | x86_64 |
|
337
337
|
|
338
338
|
## Documentation
|
339
339
|
|
@@ -1,81 +1,83 @@
|
|
1
1
|
require "openssl"
|
2
2
|
require "inspec/utils/file_reader"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
4
|
+
module Inspec::Resources
|
5
|
+
class DhParams < Inspec.resource(1)
|
6
|
+
name "dh_params"
|
7
|
+
supports platform: "unix"
|
8
|
+
desc '
|
9
|
+
Use the `dh_params` InSpec audit resource to test Diffie-Hellman (DH)
|
10
|
+
parameters.
|
11
|
+
'
|
12
|
+
|
13
|
+
example <<~EXAMPLE
|
14
|
+
describe dh_params('/path/to/file.dh_pem') do
|
15
|
+
it { should be_dh_params }
|
16
|
+
it { should be_valid }
|
17
|
+
its('generator') { should eq 2 }
|
18
|
+
its('modulus') { should eq '00:91:a0:15:89:e5:bc:38:93:12:02:fc:...' }
|
19
|
+
its('prime_length') { should eq 2048 }
|
20
|
+
its('pem') { should eq '-----BEGIN DH PARAMETERS...' }
|
21
|
+
its('text') { should eq 'PKCS#3 DH Parameters: (2048 bit)...' }
|
22
|
+
end
|
23
|
+
EXAMPLE
|
24
|
+
|
25
|
+
include FileReader
|
26
|
+
|
27
|
+
def initialize(filename)
|
28
|
+
@dh_params_path = filename
|
29
|
+
@dh_params = OpenSSL::PKey::DH.new read_file_content(@dh_params_path)
|
21
30
|
end
|
22
|
-
EXAMPLE
|
23
31
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@dh_params = OpenSSL::PKey::DH.new read_file_content(@dh_params_path)
|
29
|
-
end
|
30
|
-
|
31
|
-
# it { should be_dh_params }
|
32
|
-
def dh_params?
|
33
|
-
!@dh_params.nil?
|
34
|
-
end
|
32
|
+
# it { should be_dh_params }
|
33
|
+
def dh_params?
|
34
|
+
!@dh_params.nil?
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
# its('generator') { should eq 2 }
|
38
|
+
def generator
|
39
|
+
return if @dh_params.nil?
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
@dh_params.g.to_i
|
42
|
+
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
# its('modulus') { should eq '00:91:a0:15:89:e5:bc:38:93:12:02:fc:...' }
|
45
|
+
def modulus
|
46
|
+
return if @dh_params.nil?
|
46
47
|
|
47
|
-
|
48
|
-
|
48
|
+
"00:" + @dh_params.p.to_s(16).downcase.scan(/.{2}/).join(":")
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
51
|
+
# its('pem') { should eq '-----BEGIN DH PARAMETERS...' }
|
52
|
+
def pem
|
53
|
+
return if @dh_params.nil?
|
53
54
|
|
54
|
-
|
55
|
-
|
55
|
+
@dh_params.to_pem
|
56
|
+
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
58
|
+
# its('prime_length') { should be 2048 }
|
59
|
+
def prime_length
|
60
|
+
return if @dh_params.nil?
|
60
61
|
|
61
|
-
|
62
|
-
|
62
|
+
@dh_params.p.num_bits
|
63
|
+
end
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
65
|
+
# its('text') { should eq 'human-readable-text' }
|
66
|
+
def text
|
67
|
+
return if @dh_params.nil?
|
67
68
|
|
68
|
-
|
69
|
-
|
69
|
+
@dh_params.to_text
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
# it { should be_valid }
|
73
|
+
def valid?
|
74
|
+
return if @dh_params.nil?
|
74
75
|
|
75
|
-
|
76
|
-
|
76
|
+
@dh_params.params_ok?
|
77
|
+
end
|
77
78
|
|
78
|
-
|
79
|
-
|
79
|
+
def to_s
|
80
|
+
"dh_params #{@dh_params_path}"
|
81
|
+
end
|
80
82
|
end
|
81
83
|
end
|
@@ -1,62 +1,64 @@
|
|
1
1
|
require "inspec/utils/parser"
|
2
2
|
require "inspec/utils/file_reader"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
4
|
+
module Inspec::Resources
|
5
|
+
class EtcHosts < Inspec.resource(1)
|
6
|
+
name "etc_hosts"
|
7
|
+
supports platform: "linux"
|
8
|
+
supports platform: "bsd"
|
9
|
+
supports platform: "windows"
|
10
|
+
desc 'Use the etc_hosts InSpec audit resource to find an
|
11
|
+
ip_address and its associated hosts'
|
12
|
+
example <<~EXAMPLE
|
13
|
+
describe etc_hosts.where { ip_address == '127.0.0.1' } do
|
14
|
+
its('ip_address') { should cmp '127.0.0.1' }
|
15
|
+
its('primary_name') { should cmp 'localhost' }
|
16
|
+
its('all_host_names') { should eq [['localhost', 'localhost.localdomain', 'localhost4', 'localhost4.localdomain4']] }
|
17
|
+
end
|
18
|
+
EXAMPLE
|
18
19
|
|
19
|
-
|
20
|
+
attr_reader :params
|
20
21
|
|
21
|
-
|
22
|
-
|
22
|
+
include CommentParser
|
23
|
+
include FileReader
|
23
24
|
|
24
|
-
|
25
|
-
|
25
|
+
DEFAULT_UNIX_PATH = "/etc/hosts".freeze
|
26
|
+
DEFAULT_WINDOWS_PATH = 'C:\windows\system32\drivers\etc\hosts'.freeze
|
26
27
|
|
27
|
-
|
28
|
-
|
28
|
+
def initialize(hosts_path = nil)
|
29
|
+
content = read_file_content(hosts_path || default_hosts_file_path)
|
29
30
|
|
30
|
-
|
31
|
-
|
31
|
+
@params = parse_conf(content.lines)
|
32
|
+
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
FilterTable.create
|
35
|
+
.register_column(:ip_address, field: "ip_address")
|
36
|
+
.register_column(:primary_name, field: "primary_name")
|
37
|
+
.register_column(:all_host_names, field: "all_host_names")
|
38
|
+
.install_filter_methods_on_resource(self, :params)
|
38
39
|
|
39
|
-
|
40
|
+
private
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
def default_hosts_file_path
|
43
|
+
inspec.os.windows? ? DEFAULT_WINDOWS_PATH : DEFAULT_UNIX_PATH
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
def parse_conf(lines)
|
47
|
+
lines.reject(&:empty?).reject(&comment?).map(&parse_data).map(&format_data)
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
-
|
50
|
+
def comment?
|
51
|
+
parse_options = { comment_char: "#", standalone_comments: false }
|
51
52
|
|
52
|
-
|
53
|
-
|
53
|
+
->(data) { parse_comment_line(data, parse_options).first.empty? }
|
54
|
+
end
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
56
|
+
def parse_data
|
57
|
+
->(data) { [data.split[0], data.split[1], data.split[1..-1]] }
|
58
|
+
end
|
58
59
|
|
59
|
-
|
60
|
-
|
60
|
+
def format_data
|
61
|
+
->(data) { %w{ip_address primary_name all_host_names}.zip(data).to_h }
|
62
|
+
end
|
61
63
|
end
|
62
64
|
end
|
@@ -164,22 +164,40 @@ module Inspec::Resources
|
|
164
164
|
# OSX uses opendirectory for groups, so `/etc/group` may not be fully accurate
|
165
165
|
# This uses `dscacheutil` to get the group info instead of `etc_group`
|
166
166
|
class DarwinGroup < GroupInfo
|
167
|
+
def runmap(cmd, &blk)
|
168
|
+
hashmap(inspec.command(cmd).stdout.lines, &blk)
|
169
|
+
end
|
170
|
+
|
171
|
+
def hashmap(enum, &blk)
|
172
|
+
enum.map(&blk).to_h
|
173
|
+
end
|
174
|
+
|
167
175
|
def groups
|
168
|
-
|
176
|
+
group_by_id = runmap("dscl . -list /Groups PrimaryGroupID") { |l| name, id = l.split; [id.to_i, name] }
|
177
|
+
userss = runmap("dscl . -list /Users PrimaryGroupID") { |l| name, id = l.split; [name, id.to_i] }
|
178
|
+
membership = runmap("dscl . -list /Groups GroupMembership") { |l| key, *vs = l.split; [key, vs] }
|
179
|
+
membership.default_proc = ->(h, k) { h[k] = [] }
|
180
|
+
|
181
|
+
users_by_group = hashmap(userss.keys.group_by { |k| userss[k] }) { |k, vs| [group_by_id[k], vs] }
|
182
|
+
users_by_group.each do |name, users|
|
183
|
+
membership[name].concat users
|
184
|
+
end
|
185
|
+
|
186
|
+
group_info = inspec.command("dscacheutil -q group").stdout.split("\n\n").uniq
|
169
187
|
|
170
|
-
groups = []
|
171
188
|
regex = /^([^:]*?)\s*:\s(.*?)\s*$/
|
172
|
-
group_info.
|
173
|
-
|
189
|
+
groups = group_info.map do |data|
|
190
|
+
inspec.parse_config(data, assignment_regex: regex).params
|
174
191
|
end
|
175
192
|
|
176
193
|
# Convert the `dscacheutil` groups to match `inspec.etc_group.entries`
|
177
194
|
groups.each { |g| g["gid"] = g["gid"].to_i }
|
178
195
|
groups.each do |g|
|
179
|
-
|
180
|
-
|
181
|
-
g["
|
182
|
-
g["members"]
|
196
|
+
users = g.delete("users") || ""
|
197
|
+
users = users.split
|
198
|
+
users += Array(users_by_group[g["name"]])
|
199
|
+
g["members"] = users
|
200
|
+
g["members"].sort.join ","
|
183
201
|
end
|
184
202
|
end
|
185
203
|
end
|
@@ -1,228 +1,230 @@
|
|
1
1
|
require "inspec/utils/simpleconfig"
|
2
2
|
require "inspec/utils/file_reader"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
4
|
+
module Inspec::Resources
|
5
|
+
class GrubConfig < Inspec.resource(1)
|
6
|
+
name "grub_conf"
|
7
|
+
supports platform: "unix"
|
8
|
+
desc "Use the grub_conf InSpec audit resource to test the boot config of Linux systems that use Grub."
|
9
|
+
example <<~EXAMPLE
|
10
|
+
describe grub_conf('/etc/grub.conf', 'default') do
|
11
|
+
its('kernel') { should include '/vmlinuz-2.6.32-573.7.1.el6.x86_64' }
|
12
|
+
its('initrd') { should include '/initramfs-2.6.32-573.el6.x86_64.img=1' }
|
13
|
+
its('default') { should_not eq '1' }
|
14
|
+
its('timeout') { should eq '5' }
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
also check specific kernels
|
18
|
+
describe grub_conf('/etc/grub.conf', 'CentOS (2.6.32-573.12.1.el6.x86_64)') do
|
19
|
+
its('kernel') { should include 'audit=1' }
|
20
|
+
end
|
21
|
+
EXAMPLE
|
21
22
|
|
22
|
-
|
23
|
+
include FileReader
|
23
24
|
|
24
|
-
|
25
|
+
class UnknownGrubConfig < StandardError; end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
def initialize(path = nil, kernel = nil)
|
28
|
+
config_for_platform(path)
|
29
|
+
@content = read_file(@conf_path)
|
30
|
+
@kernel = kernel || "default"
|
31
|
+
rescue UnknownGrubConfig
|
32
|
+
skip_resource "The `grub_config` resource is not supported on your OS yet."
|
33
|
+
end
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
35
|
+
def config_for_platform(path)
|
36
|
+
os = inspec.os
|
37
|
+
if os.redhat? || os[:name] == "fedora"
|
38
|
+
config_for_redhatish(path)
|
39
|
+
elsif os.debian?
|
40
|
+
@conf_path = path || "/boot/grub/grub.cfg"
|
41
|
+
@defaults_path = "/etc/default/grub"
|
42
|
+
@grubenv_path = "/boot/grub2/grubenv"
|
43
|
+
@version = "grub2"
|
44
|
+
elsif os[:name] == "amazon"
|
45
|
+
@conf_path = path || "/etc/grub.conf"
|
46
|
+
@version = "legacy"
|
47
|
+
else
|
48
|
+
raise UnknownGrubConfig
|
49
|
+
end
|
48
50
|
end
|
49
|
-
end
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
52
|
+
def config_for_redhatish(path)
|
53
|
+
if inspec.os[:release].to_f < 7
|
54
|
+
@conf_path = path || "/etc/grub.conf"
|
55
|
+
@version = "legacy"
|
56
|
+
else
|
57
|
+
@conf_path = path || "/boot/grub2/grub.cfg"
|
58
|
+
@defaults_path = "/etc/default/grub"
|
59
|
+
@grubenv_path = "/boot/grub2/grubenv"
|
60
|
+
@version = "grub2"
|
61
|
+
end
|
60
62
|
end
|
61
|
-
end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
64
|
+
def method_missing(name)
|
65
|
+
read_params[name.to_s]
|
66
|
+
end
|
66
67
|
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
def to_s
|
69
|
+
"Grub Config"
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
+
private
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
74
|
+
######################################################################
|
75
|
+
# Grub2 This is used by all supported versions of Ubuntu and Rhel 7+ #
|
76
|
+
######################################################################
|
76
77
|
|
77
|
-
|
78
|
-
|
78
|
+
def grub2_parse_kernel_lines(content, conf)
|
79
|
+
menu_entries = extract_menu_entries(content)
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
if @kernel == "default"
|
82
|
+
default_menu_entry(menu_entries, conf["GRUB_DEFAULT"])
|
83
|
+
else
|
84
|
+
menu_entries.find { |entry| entry["name"] == @kernel }
|
85
|
+
end
|
84
86
|
end
|
85
|
-
end
|
86
87
|
|
87
|
-
|
88
|
-
|
88
|
+
def extract_menu_entries(content)
|
89
|
+
menu_entries = []
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
lines = content.split("\n")
|
92
|
+
lines.each_with_index do |line, index|
|
93
|
+
next unless line =~ /^menuentry\s+.*/
|
93
94
|
|
94
|
-
|
95
|
-
|
95
|
+
entry = {}
|
96
|
+
entry["insmod"] = []
|
96
97
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
98
|
+
# Extract name from menuentry line
|
99
|
+
capture_data = line.match(/(?:^|\s+).*menuentry\s*['|"](.*)['|"]\s*--/)
|
100
|
+
if capture_data.nil? || capture_data.captures[0].nil?
|
101
|
+
raise Inspec::Exceptions::ResourceFailed "Failed to extract menuentry name from #{line}"
|
102
|
+
end
|
102
103
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
104
|
+
entry["name"] = capture_data.captures[0]
|
105
|
+
|
106
|
+
# Begin processing from index forward until a `}` line is met
|
107
|
+
lines.drop(index + 1).each do |mline|
|
108
|
+
break if mline =~ /^\s*}\s*$/
|
109
|
+
|
110
|
+
case mline
|
111
|
+
when /(?:^|\s*)initrd.*/
|
112
|
+
entry["initrd"] = mline.split(" ")[1]
|
113
|
+
when /(?:^|\s*)linux.*/
|
114
|
+
entry["kernel"] = mline.split
|
115
|
+
when /(?:^|\s*)set root=.*/
|
116
|
+
entry["root"] = mline.split("=")[1].tr("'", "")
|
117
|
+
when /(?:^|\s*)insmod.*/
|
118
|
+
entry["insmod"] << mline.split(" ")[1]
|
119
|
+
end
|
118
120
|
end
|
121
|
+
|
122
|
+
menu_entries << entry
|
119
123
|
end
|
120
124
|
|
121
|
-
menu_entries
|
125
|
+
menu_entries
|
122
126
|
end
|
123
127
|
|
124
|
-
menu_entries
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
# If the default entry isn't `saved` then a number is used as an index.
|
129
|
-
# By default this is `0`, which would be the first item in the list.
|
130
|
-
return menu_entries[default.to_i] unless default == "saved"
|
128
|
+
def default_menu_entry(menu_entries, default)
|
129
|
+
# If the default entry isn't `saved` then a number is used as an index.
|
130
|
+
# By default this is `0`, which would be the first item in the list.
|
131
|
+
return menu_entries[default.to_i] unless default == "saved"
|
131
132
|
|
132
|
-
|
133
|
+
grubenv_contents = inspec.file(@grubenv_path).content
|
133
134
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
135
|
+
# The location of the grubenv file is not guaranteed. In the case that
|
136
|
+
# the file does not exist this will return the 0th entry. This will also
|
137
|
+
# return the 0th entry if InSpec lacks permission to read the file. Both
|
138
|
+
# of these reflect the default Grub2 behavior.
|
139
|
+
return menu_entries[0] if grubenv_contents.nil?
|
139
140
|
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
default_name = SimpleConfig.new(grubenv_contents).params["saved_entry"]
|
142
|
+
default_entry = menu_entries.select { |k| k["name"] == default_name }[0]
|
143
|
+
return default_entry unless default_entry.nil?
|
143
144
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
145
|
+
# It is possible for the saved entry to not be valid . For example, grubenv
|
146
|
+
# not being up to date. If so, the 0th entry is the default.
|
147
|
+
menu_entries[0]
|
148
|
+
end
|
148
149
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
150
|
+
###################################################################
|
151
|
+
# Grub1 aka legacy-grub config. Primarily used by Centos/Rhel 6.x #
|
152
|
+
###################################################################
|
153
|
+
|
154
|
+
def parse_kernel_lines(content, conf)
|
155
|
+
# Find all "title" lines and then parse them into arrays
|
156
|
+
menu_entry = 0
|
157
|
+
lines = content.split("\n")
|
158
|
+
kernel_opts = {}
|
159
|
+
lines.each_with_index do |file_line, index|
|
160
|
+
next unless file_line =~ /^title.*/
|
161
|
+
|
162
|
+
current_kernel = file_line.split(" ", 2)[1]
|
163
|
+
lines.drop(index + 1).each do |kernel_line|
|
164
|
+
if kernel_line =~ /^\s.*/
|
165
|
+
option_type = kernel_line.split(" ")[0]
|
166
|
+
line_options = kernel_line.split(" ").drop(1)
|
167
|
+
if (menu_entry == conf["default"].to_i && @kernel == "default") || current_kernel == @kernel
|
168
|
+
if option_type == "kernel"
|
169
|
+
kernel_opts["kernel"] = line_options
|
170
|
+
else
|
171
|
+
kernel_opts[option_type] = line_options[0]
|
172
|
+
end
|
171
173
|
end
|
174
|
+
else
|
175
|
+
menu_entry += 1
|
176
|
+
break
|
172
177
|
end
|
173
|
-
else
|
174
|
-
menu_entry += 1
|
175
|
-
break
|
176
178
|
end
|
177
179
|
end
|
180
|
+
kernel_opts
|
178
181
|
end
|
179
|
-
kernel_opts
|
180
|
-
end
|
181
182
|
|
182
|
-
|
183
|
-
|
184
|
-
|
183
|
+
def read_file(config_file)
|
184
|
+
read_file_content(config_file)
|
185
|
+
end
|
185
186
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
187
|
+
def read_params
|
188
|
+
return @params if defined?(@params)
|
189
|
+
|
190
|
+
content = read_file(@conf_path)
|
191
|
+
|
192
|
+
if @version == "legacy"
|
193
|
+
# parse the file
|
194
|
+
conf = SimpleConfig.new(
|
195
|
+
content,
|
196
|
+
multiple_values: true
|
197
|
+
).params
|
198
|
+
# convert single entry arrays into strings
|
199
|
+
conf.each do |key, value|
|
200
|
+
if value.size == 1
|
201
|
+
conf[key] = conf[key][0].to_s
|
202
|
+
end
|
201
203
|
end
|
204
|
+
kernel_opts = parse_kernel_lines(content, conf)
|
205
|
+
@params = conf.merge(kernel_opts)
|
202
206
|
end
|
203
|
-
kernel_opts = parse_kernel_lines(content, conf)
|
204
|
-
@params = conf.merge(kernel_opts)
|
205
|
-
end
|
206
207
|
|
207
|
-
|
208
|
-
|
209
|
-
|
208
|
+
if @version == "grub2"
|
209
|
+
# read defaults
|
210
|
+
defaults = read_file(@defaults_path)
|
210
211
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
212
|
+
conf = SimpleConfig.new(
|
213
|
+
defaults,
|
214
|
+
multiple_values: true
|
215
|
+
).params
|
215
216
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
217
|
+
# convert single entry arrays into strings
|
218
|
+
conf.each do |key, value|
|
219
|
+
if value.size == 1
|
220
|
+
conf[key] = conf[key][0].to_s
|
221
|
+
end
|
220
222
|
end
|
221
|
-
end
|
222
223
|
|
223
|
-
|
224
|
-
|
224
|
+
kernel_opts = grub2_parse_kernel_lines(content, conf)
|
225
|
+
@params = conf.merge(kernel_opts)
|
226
|
+
end
|
227
|
+
@params
|
225
228
|
end
|
226
|
-
@params
|
227
229
|
end
|
228
230
|
end
|
@@ -5,125 +5,127 @@ require "inspec/resources/powershell"
|
|
5
5
|
# check for web applications in IIS
|
6
6
|
# Note: this is only supported in windows 2012 and later
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def pool_name
|
31
|
-
iis_app_pool[:pool_name]
|
32
|
-
end
|
33
|
-
|
34
|
-
def runtime_version
|
35
|
-
iis_app_pool[:version]
|
36
|
-
end
|
8
|
+
module Inspec::Resources
|
9
|
+
class IisAppPool < Inspec.resource(1)
|
10
|
+
name "iis_app_pool"
|
11
|
+
desc "Tests IIS application pool configuration on windows."
|
12
|
+
supports platform: "windows"
|
13
|
+
example <<~EXAMPLE
|
14
|
+
describe iis_app_pool('DefaultAppPool') do
|
15
|
+
it { should exist }
|
16
|
+
its('enable32bit') { should cmp 'True' }
|
17
|
+
its('runtime_version') { should eq 'v4.0' }
|
18
|
+
its('pipeline_mode') { should eq 'Integrated' }
|
19
|
+
end
|
20
|
+
EXAMPLE
|
21
|
+
|
22
|
+
def initialize(pool_name)
|
23
|
+
@pool_name = pool_name
|
24
|
+
@pool_path = "IIS:\\AppPools\\#{@pool_name}"
|
25
|
+
@cache = nil
|
26
|
+
|
27
|
+
# verify that this resource is only supported on Windows
|
28
|
+
return skip_resource "The `iis_app_pool` resource is not supported on your OS." unless inspec.os.windows?
|
29
|
+
end
|
37
30
|
|
38
|
-
|
39
|
-
|
40
|
-
|
31
|
+
def pool_name
|
32
|
+
iis_app_pool[:pool_name]
|
33
|
+
end
|
41
34
|
|
42
|
-
|
43
|
-
|
44
|
-
|
35
|
+
def runtime_version
|
36
|
+
iis_app_pool[:version]
|
37
|
+
end
|
45
38
|
|
46
|
-
|
47
|
-
|
48
|
-
|
39
|
+
def enable32bit
|
40
|
+
iis_app_pool[:e32b]
|
41
|
+
end
|
49
42
|
|
50
|
-
|
51
|
-
|
52
|
-
|
43
|
+
def pipeline_mode
|
44
|
+
iis_app_pool[:mode]
|
45
|
+
end
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
|
47
|
+
def max_processes
|
48
|
+
iis_app_pool[:processes]
|
49
|
+
end
|
57
50
|
|
58
|
-
|
59
|
-
|
60
|
-
|
51
|
+
def timeout
|
52
|
+
iis_app_pool[:timeout]
|
53
|
+
end
|
61
54
|
|
62
|
-
|
63
|
-
|
64
|
-
|
55
|
+
def timeout_days
|
56
|
+
iis_app_pool[:timeout_days]
|
57
|
+
end
|
65
58
|
|
66
|
-
|
67
|
-
|
68
|
-
|
59
|
+
def timeout_hours
|
60
|
+
iis_app_pool[:timeout_hours]
|
61
|
+
end
|
69
62
|
|
70
|
-
|
71
|
-
|
72
|
-
|
63
|
+
def timeout_minutes
|
64
|
+
iis_app_pool[:timeout_minutes]
|
65
|
+
end
|
73
66
|
|
74
|
-
|
75
|
-
|
76
|
-
|
67
|
+
def timeout_seconds
|
68
|
+
iis_app_pool[:timeout_seconds]
|
69
|
+
end
|
77
70
|
|
78
|
-
|
79
|
-
|
80
|
-
|
71
|
+
def user_identity_type
|
72
|
+
iis_app_pool[:user_identity_type]
|
73
|
+
end
|
81
74
|
|
82
|
-
|
83
|
-
|
84
|
-
|
75
|
+
def username
|
76
|
+
iis_app_pool[:username]
|
77
|
+
end
|
85
78
|
|
86
|
-
|
79
|
+
def exists?
|
80
|
+
!iis_app_pool[:pool_name].empty?
|
81
|
+
end
|
87
82
|
|
88
|
-
|
89
|
-
|
83
|
+
def to_s
|
84
|
+
"IIS App Pool '#{@pool_name}'"
|
85
|
+
end
|
90
86
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
87
|
+
private
|
88
|
+
|
89
|
+
def iis_app_pool
|
90
|
+
return @cache unless @cache.nil?
|
91
|
+
|
92
|
+
# We use `-Compress` here to avoid a bug in PowerShell
|
93
|
+
# It does not affect validity of the output, only the representation
|
94
|
+
# See: https://github.com/inspec/inspec/pull/3842
|
95
|
+
script = <<~EOH
|
96
|
+
Import-Module WebAdministration
|
97
|
+
If (Test-Path '#{@pool_path}') {
|
98
|
+
Get-Item '#{@pool_path}' | Select-Object * | ConvertTo-Json -Compress
|
99
|
+
} Else {
|
100
|
+
Write-Host '{}'
|
101
|
+
}
|
102
|
+
EOH
|
103
|
+
cmd = inspec.powershell(script)
|
104
|
+
|
105
|
+
begin
|
106
|
+
pool = JSON.parse(cmd.stdout)
|
107
|
+
rescue JSON::ParserError => _e
|
108
|
+
raise Inspec::Exceptions::ResourceFailed, "Unable to parse app pool JSON"
|
109
|
+
end
|
110
|
+
|
111
|
+
process_model = pool.fetch("processModel", {})
|
112
|
+
idle_timeout = process_model.fetch("idleTimeout", {})
|
113
|
+
|
114
|
+
# map our values to a hash table
|
115
|
+
@cache = {
|
116
|
+
pool_name: pool["name"],
|
117
|
+
version: pool["managedRuntimeVersion"],
|
118
|
+
e32b: pool["enable32BitAppOnWin64"],
|
119
|
+
mode: pool["managedPipelineMode"],
|
120
|
+
processes: process_model["maxProcesses"],
|
121
|
+
timeout: "#{idle_timeout["Hours"]}:#{idle_timeout["Minutes"]}:#{idle_timeout["Seconds"]}",
|
122
|
+
timeout_days: idle_timeout["Days"],
|
123
|
+
timeout_hours: idle_timeout["Hours"],
|
124
|
+
timeout_minutes: idle_timeout["Minutes"],
|
125
|
+
timeout_seconds: idle_timeout["Seconds"],
|
126
|
+
user_identity_type: process_model["identityType"],
|
127
|
+
username: process_model["userName"],
|
100
128
|
}
|
101
|
-
|
102
|
-
cmd = inspec.powershell(script)
|
103
|
-
|
104
|
-
begin
|
105
|
-
pool = JSON.parse(cmd.stdout)
|
106
|
-
rescue JSON::ParserError => _e
|
107
|
-
raise Inspec::Exceptions::ResourceFailed, "Unable to parse app pool JSON"
|
108
|
-
end
|
109
|
-
|
110
|
-
process_model = pool.fetch("processModel", {})
|
111
|
-
idle_timeout = process_model.fetch("idleTimeout", {})
|
112
|
-
|
113
|
-
# map our values to a hash table
|
114
|
-
@cache = {
|
115
|
-
pool_name: pool["name"],
|
116
|
-
version: pool["managedRuntimeVersion"],
|
117
|
-
e32b: pool["enable32BitAppOnWin64"],
|
118
|
-
mode: pool["managedPipelineMode"],
|
119
|
-
processes: process_model["maxProcesses"],
|
120
|
-
timeout: "#{idle_timeout["Hours"]}:#{idle_timeout["Minutes"]}:#{idle_timeout["Seconds"]}",
|
121
|
-
timeout_days: idle_timeout["Days"],
|
122
|
-
timeout_hours: idle_timeout["Hours"],
|
123
|
-
timeout_minutes: idle_timeout["Minutes"],
|
124
|
-
timeout_seconds: idle_timeout["Seconds"],
|
125
|
-
user_identity_type: process_model["identityType"],
|
126
|
-
username: process_model["userName"],
|
127
|
-
}
|
129
|
+
end
|
128
130
|
end
|
129
131
|
end
|
@@ -243,6 +243,13 @@ module Inspec::Resources
|
|
243
243
|
info[:startmode]
|
244
244
|
end
|
245
245
|
|
246
|
+
# returns the service's user from info
|
247
|
+
def startname
|
248
|
+
return nil if info.nil?
|
249
|
+
|
250
|
+
info[:startname]
|
251
|
+
end
|
252
|
+
|
246
253
|
def to_s
|
247
254
|
"Service #{@service_name}"
|
248
255
|
end
|
@@ -575,12 +582,13 @@ module Inspec::Resources
|
|
575
582
|
# Also see: https://msdn.microsoft.com/en-us/library/aa384896(v=vs.85).aspx
|
576
583
|
# Use the following powershell to determine the start mode
|
577
584
|
# PS: Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $name -or $_.DisplayName -eq $name} | Select-Object -Prop
|
578
|
-
# erty Name, StartMode, State, Status | ConvertTo-Json
|
585
|
+
# erty Name, StartMode, State, Status, StartName | ConvertTo-Json
|
579
586
|
# {
|
580
587
|
# "Name": "Dhcp",
|
581
588
|
# "StartMode": "Auto",
|
582
589
|
# "State": "Running",
|
583
|
-
# "Status": "OK"
|
590
|
+
# "Status": "OK",
|
591
|
+
# "StartName": "LocalSystem"
|
584
592
|
# }
|
585
593
|
#
|
586
594
|
# Windows Services have the following status code:
|
@@ -593,7 +601,7 @@ module Inspec::Resources
|
|
593
601
|
# - 6: Pause Pending
|
594
602
|
# - 7: Paused
|
595
603
|
def info(service_name)
|
596
|
-
cmd = inspec.command("New-Object -Type PSObject | Add-Member -MemberType NoteProperty -Name Service -Value (Get-Service -Name '#{service_name}'| Select-Object -Property Name, DisplayName, Status) -PassThru | Add-Member -MemberType NoteProperty -Name WMI -Value (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq '#{service_name}' -or $_.DisplayName -eq '#{service_name}'} | Select-Object -Property StartMode) -PassThru | ConvertTo-Json")
|
604
|
+
cmd = inspec.command("New-Object -Type PSObject | Add-Member -MemberType NoteProperty -Name Service -Value (Get-Service -Name '#{service_name}'| Select-Object -Property Name, DisplayName, Status) -PassThru | Add-Member -MemberType NoteProperty -Name WMI -Value (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq '#{service_name}' -or $_.DisplayName -eq '#{service_name}'} | Select-Object -Property StartMode, StartName) -PassThru | ConvertTo-Json")
|
597
605
|
|
598
606
|
# cannot rely on exit code for now, successful command returns exit code 1
|
599
607
|
# return nil if cmd.exit_status != 0
|
@@ -614,6 +622,7 @@ module Inspec::Resources
|
|
614
622
|
running: service_running?(service),
|
615
623
|
enabled: service_enabled?(service),
|
616
624
|
startmode: service["WMI"]["StartMode"],
|
625
|
+
startname: service["WMI"]["StartName"],
|
617
626
|
type: "windows",
|
618
627
|
}
|
619
628
|
end
|
data/lib/inspec/resources/ssl.rb
CHANGED
@@ -6,92 +6,94 @@ require "uri"
|
|
6
6
|
require "parallel"
|
7
7
|
|
8
8
|
# Custom resource based on the InSpec resource DSL
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
module Inspec::Resources
|
10
|
+
class SSL < Inspec.resource(1)
|
11
|
+
name "ssl"
|
12
|
+
supports platform: "unix"
|
13
|
+
supports platform: "windows"
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
desc "
|
16
|
+
SSL test resource
|
17
|
+
"
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
example <<~EXAMPLE
|
20
|
+
describe ssl(port: 443) do
|
21
|
+
it { should be_enabled }
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
# protocols: ssl2, ssl3, tls1.0, tls1.1, tls1.2
|
25
|
+
describe ssl(port: 443).protocols('ssl2') do
|
26
|
+
it { should_not be_enabled }
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
# any ciphers, filter by name or regex
|
30
|
+
describe ssl(port: 443).ciphers(/rc4/i) do
|
31
|
+
it { should_not be_enabled }
|
32
|
+
end
|
33
|
+
EXAMPLE
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
VERSIONS = [
|
36
|
+
"ssl2",
|
37
|
+
"ssl3",
|
38
|
+
"tls1.0",
|
39
|
+
"tls1.1",
|
40
|
+
"tls1.2",
|
41
|
+
].freeze
|
41
42
|
|
42
|
-
|
43
|
+
attr_reader :host, :port, :timeout, :retries
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
45
|
+
def initialize(opts = {})
|
46
|
+
@host = opts[:host]
|
47
|
+
if @host.nil?
|
48
|
+
# Transports like SSH and WinRM will provide a hostname
|
49
|
+
if inspec.backend.respond_to?("hostname")
|
50
|
+
@host = inspec.backend.hostname
|
51
|
+
elsif inspec.backend.class.to_s == "Train::Transports::Local::Connection"
|
52
|
+
@host = "localhost"
|
53
|
+
end
|
52
54
|
end
|
55
|
+
@port = opts[:port] || 443
|
56
|
+
@timeout = opts[:timeout]
|
57
|
+
@retries = opts[:retries]
|
53
58
|
end
|
54
|
-
@port = opts[:port] || 443
|
55
|
-
@timeout = opts[:timeout]
|
56
|
-
@retries = opts[:retries]
|
57
|
-
end
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
filter = FilterTable.create
|
61
|
+
filter.register_custom_matcher(:enabled?) do |x|
|
62
|
+
raise "Cannot determine host for SSL test. Please specify it or use a different target." if x.resource.host.nil?
|
62
63
|
|
63
|
-
|
64
|
-
end
|
65
|
-
filter.register_column(:ciphers, field: "cipher")
|
66
|
-
.register_column(:protocols, field: "protocol")
|
67
|
-
.register_custom_property(:handshake) do |x|
|
68
|
-
groups = x.entries.group_by(&:protocol)
|
69
|
-
res = Parallel.map(groups, in_threads: 8) do |proto, e|
|
70
|
-
[proto, SSLShake.hello(x.resource.host, port: x.resource.port,
|
71
|
-
protocol: proto, ciphers: e.map(&:cipher),
|
72
|
-
timeout: x.resource.timeout, retries: x.resource.retries, servername: x.resource.host)]
|
73
|
-
end
|
74
|
-
Hash[res]
|
64
|
+
x.handshake.values.any? { |i| i["success"] }
|
75
65
|
end
|
76
|
-
.
|
66
|
+
filter.register_column(:ciphers, field: "cipher")
|
67
|
+
.register_column(:protocols, field: "protocol")
|
68
|
+
.register_custom_property(:handshake) do |x|
|
69
|
+
groups = x.entries.group_by(&:protocol)
|
70
|
+
res = Parallel.map(groups, in_threads: 8) do |proto, e|
|
71
|
+
[proto, SSLShake.hello(x.resource.host, port: x.resource.port,
|
72
|
+
protocol: proto, ciphers: e.map(&:cipher),
|
73
|
+
timeout: x.resource.timeout, retries: x.resource.retries, servername: x.resource.host)]
|
74
|
+
end
|
75
|
+
Hash[res]
|
76
|
+
end
|
77
|
+
.install_filter_methods_on_resource(self, :scan_config)
|
77
78
|
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
def to_s
|
80
|
+
"SSL/TLS on #{@host}:#{@port}"
|
81
|
+
end
|
81
82
|
|
82
|
-
|
83
|
+
private
|
83
84
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
85
|
+
def scan_config
|
86
|
+
[
|
87
|
+
{ "protocol" => "ssl2", "ciphers" => SSLShake::SSLv2::CIPHERS.keys },
|
88
|
+
{ "protocol" => "ssl3", "ciphers" => SSLShake::TLS::SSL3_CIPHERS.keys },
|
89
|
+
{ "protocol" => "tls1.0", "ciphers" => SSLShake::TLS::TLS10_CIPHERS.keys },
|
90
|
+
{ "protocol" => "tls1.1", "ciphers" => SSLShake::TLS::TLS10_CIPHERS.keys },
|
91
|
+
{ "protocol" => "tls1.2", "ciphers" => SSLShake::TLS::TLS_CIPHERS.keys },
|
92
|
+
].map do |line|
|
93
|
+
line["ciphers"].map do |cipher|
|
94
|
+
{ "protocol" => line["protocol"], "cipher" => cipher }
|
95
|
+
end
|
96
|
+
end.flatten
|
97
|
+
end
|
96
98
|
end
|
97
99
|
end
|
data/lib/inspec/runner.rb
CHANGED
data/lib/inspec/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inspec-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dominik Richter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-08-
|
11
|
+
date: 2019-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: train-core
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '3.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '3.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: license-acceptance
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|