inspec 0.14.3 → 0.14.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -3
- data/docs/dsl_inspec.rst +16 -3
- data/lib/inspec/describe.rb +27 -0
- data/lib/inspec/expect.rb +45 -0
- data/lib/inspec/metadata.rb +1 -0
- data/lib/inspec/profile_context.rb +18 -5
- data/lib/inspec/resource.rb +1 -0
- data/lib/inspec/rule.rb +29 -42
- data/lib/inspec/runner.rb +25 -11
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/xinetd.rb +142 -0
- data/lib/utils/parser.rb +53 -0
- data/test/helper.rb +6 -0
- data/test/unit/mock/cmd/find-xinetd.d +2 -0
- data/test/unit/mock/files/xinetd.conf +9 -0
- data/test/unit/mock/files/xinetd.d/.gitkeep +0 -0
- data/test/unit/mock/files/xinetd.d_chargen-dgram +9 -0
- data/test/unit/mock/files/xinetd.d_chargen-stream +9 -0
- data/test/unit/profile_context_test.rb +95 -30
- data/test/unit/resources/xinetd_test.rb +60 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b08bf257cb8949cae2b2a72aa26ba4e992724ca9
|
4
|
+
data.tar.gz: 6e8c50d7a6025d2714d012c7fe262b4be1087860
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c58d8262678198e8c88647dbf1e8f204f1a9c9da268ecc439b76d234c346f403b717ca4d0c7e54941848fa64341ec3af85c86167b7250adcec6200dbbed9b43d
|
7
|
+
data.tar.gz: 5505c2d86085b0cb45e4d6c9562fb44b462354e45e001265681cba5d48d6c3f1950573470ce25b59c6c56c1413c90f8a5c5bfd6f3b3603298d754b1af0136334
|
data/CHANGELOG.md
CHANGED
@@ -1,16 +1,32 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
## [0.14.
|
4
|
-
[Full Changelog](https://github.com/chef/inspec/compare/v0.14.
|
3
|
+
## [0.14.4](https://github.com/chef/inspec/tree/0.14.4) (2016-02-26)
|
4
|
+
[Full Changelog](https://github.com/chef/inspec/compare/v0.14.3...0.14.4)
|
5
5
|
|
6
6
|
**Implemented enhancements:**
|
7
7
|
|
8
|
-
-
|
8
|
+
- add `describe.one`: collection of tests with at least one passing [\#497](https://github.com/chef/inspec/pull/497) ([arlimus](https://github.com/arlimus))
|
9
9
|
|
10
10
|
**Merged pull requests:**
|
11
11
|
|
12
|
+
- don't crash on empty metadata during finalize [\#500](https://github.com/chef/inspec/pull/500) ([arlimus](https://github.com/arlimus))
|
13
|
+
- add xinetd\_conf resource [\#499](https://github.com/chef/inspec/pull/499) ([arlimus](https://github.com/arlimus))
|
14
|
+
|
15
|
+
## [v0.14.3](https://github.com/chef/inspec/tree/v0.14.3) (2016-02-24)
|
16
|
+
[Full Changelog](https://github.com/chef/inspec/compare/v0.14.2...v0.14.3)
|
17
|
+
|
18
|
+
**Implemented enhancements:**
|
19
|
+
|
20
|
+
- cmp matcher should compare expected string == number [\#487](https://github.com/chef/inspec/pull/487) ([chris-rock](https://github.com/chris-rock))
|
21
|
+
|
22
|
+
**Fixed bugs:**
|
23
|
+
|
12
24
|
- expose inspec errors during profile read [\#492](https://github.com/chef/inspec/pull/492) ([arlimus](https://github.com/arlimus))
|
13
25
|
|
26
|
+
**Merged pull requests:**
|
27
|
+
|
28
|
+
- 0.14.3 [\#493](https://github.com/chef/inspec/pull/493) ([arlimus](https://github.com/arlimus))
|
29
|
+
|
14
30
|
## [v0.14.2](https://github.com/chef/inspec/tree/v0.14.2) (2016-02-22)
|
15
31
|
[Full Changelog](https://github.com/chef/inspec/compare/v0.14.1...v0.14.2)
|
16
32
|
|
data/docs/dsl_inspec.rst
CHANGED
@@ -45,9 +45,22 @@ where
|
|
45
45
|
* ``its('Port')`` is the matcher; ``{ should eq('22') }`` is the test. A ``describe`` block must contain at least one matcher, but may contain as many as required
|
46
46
|
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
Advanced concepts
|
49
|
+
=====================================================
|
50
|
+
|
51
|
+
With inspec it is possible to check if at least one of a collection of checks is true. For example: If a setting is configured in two different locations, you may want to test if either configuration A or configuration B have been set. This is accomplished via ``describe.one``. It defines a block of tests with at least one valid check.
|
52
|
+
|
53
|
+
.. code-block:: ruby
|
54
|
+
|
55
|
+
describe.one do
|
56
|
+
describe ConfigurationA do
|
57
|
+
its('setting_1') { should eq true }
|
58
|
+
end
|
59
|
+
|
60
|
+
describe ConfigurationB do
|
61
|
+
its('setting_2') { should eq true }
|
62
|
+
end
|
63
|
+
end
|
51
64
|
|
52
65
|
Examples
|
53
66
|
=====================================================
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Dominik Richter
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
|
5
|
+
module Inspec
|
6
|
+
class DescribeBase
|
7
|
+
def initialize(action)
|
8
|
+
@action = action
|
9
|
+
@checks = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Evaluate the given block and collect all checks. These will be registered
|
13
|
+
# with the callback function under the 'describe.one' name.
|
14
|
+
#
|
15
|
+
# @param [Proc] ruby block containing checks (e.g. via describe)
|
16
|
+
# @return [nil]
|
17
|
+
def one(&block)
|
18
|
+
return unless block_given?
|
19
|
+
instance_eval(&block)
|
20
|
+
@action.call('describe.one', @checks, nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
def describe(*args, &block)
|
24
|
+
@checks.push(['describe', args, block])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# copyright: 2016, Chef Software Inc.
|
3
|
+
# author: Dominik Richter
|
4
|
+
# author: Christoph Hartmann
|
5
|
+
|
6
|
+
require 'rspec/expectations'
|
7
|
+
|
8
|
+
module Inspec
|
9
|
+
class Expect
|
10
|
+
attr_reader :calls, :value, :block
|
11
|
+
def initialize(value, &block)
|
12
|
+
@value = value
|
13
|
+
@block = block
|
14
|
+
@calls = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def to(*args, &block)
|
18
|
+
@calls.push([:to, args, block, caller])
|
19
|
+
end
|
20
|
+
|
21
|
+
def not_to(*args, &block)
|
22
|
+
@calls.push([:not_to, args, block, caller])
|
23
|
+
end
|
24
|
+
|
25
|
+
def example_group
|
26
|
+
that = self
|
27
|
+
|
28
|
+
opts = { 'caller' => calls[0][3] }
|
29
|
+
if !calls[0][3].nil? && !calls[0][3].empty? &&
|
30
|
+
(m = calls[0][3][0].match(/^([^:]*):(\d+):/))
|
31
|
+
opts['file_path'] = m[0]
|
32
|
+
opts['line_number'] = m[1]
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::ExampleGroup.describe(that.value, opts) do
|
36
|
+
that.calls.each do |method, args, block, clr|
|
37
|
+
it(nil, caller: clr) do
|
38
|
+
x = expect(that.value, &that.block).method(method)
|
39
|
+
x.call(*args, &block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/inspec/metadata.rb
CHANGED
@@ -140,6 +140,7 @@ module Inspec
|
|
140
140
|
end
|
141
141
|
|
142
142
|
def self.finalize(metadata, profile_id)
|
143
|
+
return nil if metadata.nil?
|
143
144
|
param = metadata.params || {}
|
144
145
|
param['name'] = profile_id.to_s unless profile_id.to_s.empty?
|
145
146
|
param['version'] = param['version'].to_s unless param['version'].nil?
|
@@ -85,7 +85,7 @@ module Inspec
|
|
85
85
|
#
|
86
86
|
# @param outer_dsl [OuterDSLClass]
|
87
87
|
# @return [ProfileContextClass]
|
88
|
-
def create_context(resources_dsl, rule_class)
|
88
|
+
def create_context(resources_dsl, rule_class) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
89
89
|
profile_context_owner = self
|
90
90
|
|
91
91
|
# rubocop:disable Lint/NestedMethodDefinition
|
@@ -117,13 +117,15 @@ module Inspec
|
|
117
117
|
alias_method :rule, :control
|
118
118
|
|
119
119
|
define_method :describe do |*args, &block|
|
120
|
-
|
121
|
-
|
122
|
-
|
120
|
+
loc = block_location(block, caller[0])
|
121
|
+
id = "(generated from #{loc} #{SecureRandom.hex})"
|
122
|
+
|
123
|
+
res = nil
|
123
124
|
rule = rule_class.new(id, {}) do
|
124
|
-
describe(*args, &block)
|
125
|
+
res = describe(*args, &block)
|
125
126
|
end
|
126
127
|
profile_context_owner.register_rule(rule, &block)
|
128
|
+
res
|
127
129
|
end
|
128
130
|
|
129
131
|
# TODO: mock method for attributes; import attribute handling
|
@@ -141,6 +143,17 @@ module Inspec
|
|
141
143
|
return unless block_given?
|
142
144
|
@skip_profile = !yield
|
143
145
|
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def block_location(block, alternate_caller)
|
150
|
+
if block.nil?
|
151
|
+
alternate_caller[/^(.+:\d+):in .+$/, 1] || 'unknown'
|
152
|
+
else
|
153
|
+
path, line = block.source_location
|
154
|
+
"#{File.basename(path)}:#{line}"
|
155
|
+
end
|
156
|
+
end
|
144
157
|
end
|
145
158
|
# rubocop:enable all
|
146
159
|
end
|
data/lib/inspec/resource.rb
CHANGED
data/lib/inspec/rule.rb
CHANGED
@@ -4,47 +4,11 @@
|
|
4
4
|
# author: Dominik Richter
|
5
5
|
# author: Christoph Hartmann
|
6
6
|
|
7
|
-
require 'rspec/expectations'
|
8
7
|
require 'method_source'
|
8
|
+
require 'inspec/describe'
|
9
|
+
require 'inspec/expect'
|
9
10
|
|
10
11
|
module Inspec
|
11
|
-
class ExpectationTarget
|
12
|
-
attr_reader :calls, :value, :block
|
13
|
-
def initialize(value, &block)
|
14
|
-
@value = value
|
15
|
-
@block = block
|
16
|
-
@calls = []
|
17
|
-
end
|
18
|
-
|
19
|
-
def to(*args, &block)
|
20
|
-
@calls.push([:to, args, block, caller])
|
21
|
-
end
|
22
|
-
|
23
|
-
def not_to(*args, &block)
|
24
|
-
@calls.push([:not_to, args, block, caller])
|
25
|
-
end
|
26
|
-
|
27
|
-
def example_group
|
28
|
-
that = self
|
29
|
-
|
30
|
-
opts = { 'caller' => calls[0][3] }
|
31
|
-
if !calls[0][3].nil? && !calls[0][3].empty? &&
|
32
|
-
(m = calls[0][3][0].match(/^([^:]*):(\d+):/))
|
33
|
-
opts['file_path'] = m[0]
|
34
|
-
opts['line_number'] = m[1]
|
35
|
-
end
|
36
|
-
|
37
|
-
RSpec::Core::ExampleGroup.describe(that.value, opts) do
|
38
|
-
that.calls.each do |method, args, block, clr|
|
39
|
-
it(nil, caller: clr) do
|
40
|
-
x = expect(that.value, &that.block).method(method)
|
41
|
-
x.call(*args, &block)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
12
|
class Rule
|
49
13
|
include ::RSpec::Matchers
|
50
14
|
|
@@ -83,13 +47,32 @@ module Inspec
|
|
83
47
|
@desc
|
84
48
|
end
|
85
49
|
|
86
|
-
|
87
|
-
|
50
|
+
# Describe will add one or more tests to this control. There is 2 ways
|
51
|
+
# of calling it:
|
52
|
+
#
|
53
|
+
# describe resource do ... end
|
54
|
+
#
|
55
|
+
# or
|
56
|
+
#
|
57
|
+
# describe.one do ... end
|
58
|
+
#
|
59
|
+
# @param [any] Resource to be describe, string, or nil
|
60
|
+
# @param [Proc] An optional block containing tests for the described resource
|
61
|
+
# @return [nil|DescribeBase] if called without arguments, returns DescribeBase
|
62
|
+
def describe(*values, &block)
|
63
|
+
if values.empty? && !block_given?
|
64
|
+
dsl = self.class.ancestors[1]
|
65
|
+
Class.new(DescribeBase) do
|
66
|
+
include dsl
|
67
|
+
end.new(method(:add_check))
|
68
|
+
else
|
69
|
+
add_check('describe', values, block)
|
70
|
+
end
|
88
71
|
end
|
89
72
|
|
90
73
|
def expect(value, &block)
|
91
|
-
target =
|
92
|
-
|
74
|
+
target = Inspec::Expect.new(value, &block)
|
75
|
+
add_check('expect', [value], target)
|
93
76
|
target
|
94
77
|
end
|
95
78
|
|
@@ -148,6 +131,10 @@ module Inspec
|
|
148
131
|
|
149
132
|
private
|
150
133
|
|
134
|
+
def add_check(describe_or_expect, values, block)
|
135
|
+
@checks.push([describe_or_expect, values, block])
|
136
|
+
end
|
137
|
+
|
151
138
|
# Idio(ma)tic unindent
|
152
139
|
# TODO: replace this
|
153
140
|
#
|
data/lib/inspec/runner.rb
CHANGED
@@ -13,7 +13,7 @@ require 'inspec/metadata'
|
|
13
13
|
# spec requirements
|
14
14
|
|
15
15
|
module Inspec
|
16
|
-
class Runner
|
16
|
+
class Runner # rubocop:disable Metrics/ClassLength
|
17
17
|
extend Forwardable
|
18
18
|
attr_reader :backend, :rules
|
19
19
|
def initialize(conf = {})
|
@@ -96,13 +96,17 @@ module Inspec
|
|
96
96
|
|
97
97
|
private
|
98
98
|
|
99
|
-
def
|
99
|
+
def block_source_info(block)
|
100
|
+
return {} if block.nil? || !block.respond_to?(:source_location)
|
100
101
|
opts = {}
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
102
|
+
file_path, line = block.source_location
|
103
|
+
opts['file_path'] = file_path
|
104
|
+
opts['line_number'] = line
|
105
|
+
opts
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_check_example(method_name, arg, block)
|
109
|
+
opts = block_source_info(block)
|
106
110
|
|
107
111
|
if !arg.empty? &&
|
108
112
|
arg[0].respond_to?(:resource_skipped) &&
|
@@ -117,6 +121,16 @@ module Inspec
|
|
117
121
|
return @test_collector.example_group(*arg, opts, &block)
|
118
122
|
when 'expect'
|
119
123
|
return block.example_group
|
124
|
+
when 'describe.one'
|
125
|
+
tests = arg.map do |x|
|
126
|
+
@test_collector.example_group(x[1][0], block_source_info(x[2]), &x[2])
|
127
|
+
end
|
128
|
+
return nil if tests.empty?
|
129
|
+
ok_tests = tests.find_all(&:run)
|
130
|
+
# return all tests if none succeeds; we will just report full failure
|
131
|
+
return tests if ok_tests.empty?
|
132
|
+
# otherwise return all working tests
|
133
|
+
return ok_tests
|
120
134
|
else
|
121
135
|
fail "A rule was registered with #{method_name.inspect}, "\
|
122
136
|
"which isn't understood and cannot be processed."
|
@@ -128,10 +142,11 @@ module Inspec
|
|
128
142
|
def register_rule(rule_id, rule)
|
129
143
|
@rules[rule_id] = rule
|
130
144
|
checks = rule.instance_variable_get(:@checks)
|
131
|
-
checks.
|
132
|
-
|
133
|
-
|
145
|
+
examples = checks.map do |m, a, b|
|
146
|
+
get_check_example(m, a, b)
|
147
|
+
end.flatten.compact
|
134
148
|
|
149
|
+
examples.each do |example|
|
135
150
|
# TODO: Remove this!! It is very dangerous to do this here.
|
136
151
|
# The goal of this is to make the audit DSL available to all
|
137
152
|
# describe blocks. Right now, these blocks are executed outside
|
@@ -140,7 +155,6 @@ module Inspec
|
|
140
155
|
# scope.
|
141
156
|
dsl = Inspec::Resource.create_dsl(backend)
|
142
157
|
example.send(:include, dsl)
|
143
|
-
|
144
158
|
@test_collector.add_test(example, rule_id)
|
145
159
|
end
|
146
160
|
end
|
data/lib/inspec/version.rb
CHANGED
@@ -0,0 +1,142 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Christoph Hartmann
|
3
|
+
# author: Dominik Richter
|
4
|
+
|
5
|
+
require 'utils/parser'
|
6
|
+
|
7
|
+
class XinetdConf < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
|
8
|
+
name 'xinetd_conf'
|
9
|
+
desc 'Xinetd services configuration.'
|
10
|
+
example "
|
11
|
+
describe xinetd_conf.services('chargen') do
|
12
|
+
its('socket_types') { should include 'dgram' }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe xinetd_conf.services('chargen').socket_types('dgram') do
|
16
|
+
it { should be_disabled }
|
17
|
+
end
|
18
|
+
"
|
19
|
+
|
20
|
+
include XinetdParser
|
21
|
+
|
22
|
+
def initialize(conf_path = '/etc/xinetd.conf', opts = {})
|
23
|
+
@conf_path = conf_path
|
24
|
+
@params = opts[:params] unless opts[:params].nil?
|
25
|
+
@filters = opts[:filters] || ''
|
26
|
+
@contents = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"Xinetd config #{@conf_path}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def services(condition = nil)
|
34
|
+
condition.nil? ? params['services'].keys : filter(service: condition)
|
35
|
+
end
|
36
|
+
|
37
|
+
def ids(condition = nil)
|
38
|
+
condition.nil? ? services_field('id') : filter(id: condition)
|
39
|
+
end
|
40
|
+
|
41
|
+
def socket_types(condition = nil)
|
42
|
+
condition.nil? ? services_field('socket_type') : filter(socket_type: condition)
|
43
|
+
end
|
44
|
+
|
45
|
+
def types(condition = nil)
|
46
|
+
condition.nil? ? services_field('type') : filter(type: condition)
|
47
|
+
end
|
48
|
+
|
49
|
+
def wait(condition = nil)
|
50
|
+
condition.nil? ? services_field('wait') : filter(wait: condition)
|
51
|
+
end
|
52
|
+
|
53
|
+
def disabled?
|
54
|
+
filter(disable: 'no').services.empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
def enabled?
|
58
|
+
filter(disable: 'yes').services.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
def params
|
62
|
+
return @params if defined?(@params)
|
63
|
+
return @params = {} if read_content.nil?
|
64
|
+
flat_params = parse_xinetd(read_content)
|
65
|
+
@params = { 'services' => {} }
|
66
|
+
flat_params.each do |k, v|
|
67
|
+
name = k[/^service (.+)$/, 1]
|
68
|
+
if name.nil?
|
69
|
+
@params[k] = v
|
70
|
+
else
|
71
|
+
@params['services'][name] = v
|
72
|
+
end
|
73
|
+
end
|
74
|
+
@params
|
75
|
+
end
|
76
|
+
|
77
|
+
def filter(conditions = {})
|
78
|
+
res = params.dup
|
79
|
+
filters = ''
|
80
|
+
conditions.each do |k, v|
|
81
|
+
v = v.to_s if v.is_a? Integer
|
82
|
+
filters += " #{k} = #{v.inspect}"
|
83
|
+
res['services'] = filter_by(res['services'], k.to_s, v)
|
84
|
+
end
|
85
|
+
XinetdConf.new(@conf_path, params: res, filters: filters)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# Retrieve the provided field from all configured services.
|
91
|
+
#
|
92
|
+
# @param [String] field name, e.g. `socket_type`
|
93
|
+
# @return [Array[String]] all values of this field across services
|
94
|
+
def services_field(field)
|
95
|
+
params['services'].values.compact.flatten
|
96
|
+
.map { |x| x.params[field] }.flatten.compact
|
97
|
+
end
|
98
|
+
|
99
|
+
def match_condition(sth, condition)
|
100
|
+
case sth
|
101
|
+
# this does Regex-matching as well as string comparison
|
102
|
+
when condition
|
103
|
+
true
|
104
|
+
else
|
105
|
+
false
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Filter services by a criteria. This allows for search queries for
|
110
|
+
# certain values.
|
111
|
+
#
|
112
|
+
# @param [Hash] service collection
|
113
|
+
# @param [String] search key you want to query
|
114
|
+
# @param [Any] search value that the key should match
|
115
|
+
# @return [Hash] filtered service collection
|
116
|
+
def filter_by(services, k, v)
|
117
|
+
if k == 'service'
|
118
|
+
return Hash[services.find_all { |name, _| match_condition(v, name) }]
|
119
|
+
end
|
120
|
+
Hash[services.map { |name, service_arr|
|
121
|
+
found = service_arr.find_all { |service|
|
122
|
+
match_condition(service.params[k], v)
|
123
|
+
}
|
124
|
+
found.empty? ? nil : [name, found]
|
125
|
+
}.compact]
|
126
|
+
end
|
127
|
+
|
128
|
+
def read_content(path = @conf_path)
|
129
|
+
return @contents[path] if @contents.key?(path)
|
130
|
+
file = inspec.file(path)
|
131
|
+
if !file.file?
|
132
|
+
return skip_resource "Can't find file \"#{path}\""
|
133
|
+
end
|
134
|
+
|
135
|
+
@contents[path] = file.content
|
136
|
+
if @contents[path].empty? && file.size > 0
|
137
|
+
return skip_resource "Can't read file \"#{path}\""
|
138
|
+
end
|
139
|
+
|
140
|
+
@contents[path]
|
141
|
+
end
|
142
|
+
end
|
data/lib/utils/parser.rb
CHANGED
@@ -177,3 +177,56 @@ module SolarisNetstatParser
|
|
177
177
|
line.match(Regexp.new(arr.join))
|
178
178
|
end
|
179
179
|
end
|
180
|
+
|
181
|
+
module XinetdParser
|
182
|
+
def xinetd_include_dir(dir)
|
183
|
+
return [] if dir.nil?
|
184
|
+
|
185
|
+
unless inspec.file(dir).directory?
|
186
|
+
return skip_resource "Cannot read folder in #{dir}"
|
187
|
+
end
|
188
|
+
|
189
|
+
files = inspec.command("find #{dir} -type f").stdout.split("\n")
|
190
|
+
files.map { |file| parse_xinetd(read_content(file)) }
|
191
|
+
end
|
192
|
+
|
193
|
+
def parse_xinetd(raw) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
194
|
+
return {} if raw.nil?
|
195
|
+
res = {}
|
196
|
+
cur_group = nil
|
197
|
+
simple_conf = []
|
198
|
+
rest = raw
|
199
|
+
until rest.empty?
|
200
|
+
nl = rest.index("\n") || (rest.length-1)
|
201
|
+
comment = rest.index('#') || (rest.length-1)
|
202
|
+
dst_idx = (comment < nl) ? comment : nl
|
203
|
+
inner_line = (dst_idx == 0) ? '' : rest[0..dst_idx-1].strip
|
204
|
+
rest = rest[nl+1..-1]
|
205
|
+
next if inner_line.empty?
|
206
|
+
|
207
|
+
if inner_line == '}'
|
208
|
+
res[cur_group] = SimpleConfig.new(simple_conf.join("\n"))
|
209
|
+
cur_group = nil
|
210
|
+
elsif rest.lstrip[0] == '{'
|
211
|
+
cur_group = inner_line
|
212
|
+
simple_conf = []
|
213
|
+
rest = rest[rest.index("\n")+1..-1]
|
214
|
+
elsif cur_group.nil?
|
215
|
+
others = xinetd_include_dir(inner_line[/includedir (.+)/, 1])
|
216
|
+
|
217
|
+
# complex merging of included configurations, as multiple services
|
218
|
+
# may be defined with the same name but different configuration
|
219
|
+
others.each { |ores|
|
220
|
+
ores.each { |k, v|
|
221
|
+
res[k] ||= []
|
222
|
+
res[k].push(v)
|
223
|
+
}
|
224
|
+
}
|
225
|
+
else
|
226
|
+
simple_conf.push(inner_line)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
res
|
231
|
+
end
|
232
|
+
end
|
data/test/helper.rb
CHANGED
@@ -111,6 +111,10 @@ class MockLoader
|
|
111
111
|
'/etc/apache2/apache2.conf' => mockfile.call('apache2.conf'),
|
112
112
|
'/etc/apache2/ports.conf' => mockfile.call('ports.conf'),
|
113
113
|
'/etc/apache2/conf-enabled/serve-cgi-bin.conf' => mockfile.call('serve-cgi-bin.conf'),
|
114
|
+
'/etc/xinetd.conf' => mockfile.call('xinetd.conf'),
|
115
|
+
'/etc/xinetd.d' => mockfile.call('xinetd.d'),
|
116
|
+
'/etc/xinetd.d/chargen-stream' => mockfile.call('xinetd.d_chargen-stream'),
|
117
|
+
'/etc/xinetd.d/chargen-dgram' => mockfile.call('xinetd.d_chargen-dgram'),
|
114
118
|
}
|
115
119
|
|
116
120
|
# create all mock commands
|
@@ -225,6 +229,8 @@ class MockLoader
|
|
225
229
|
'pkg info system/file-system/zfs' => cmd.call('pkg-info-system-file-system-zfs'),
|
226
230
|
# port netstat on solaris 10 & 11
|
227
231
|
'netstat -an -f inet -f inet6' => cmd.call('s11-netstat-an-finet-finet6'),
|
232
|
+
# xinetd configuration
|
233
|
+
'find /etc/xinetd.d -type f' => cmd.call('find-xinetd.d'),
|
228
234
|
}
|
229
235
|
|
230
236
|
@backend
|
File without changes
|
@@ -5,10 +5,64 @@
|
|
5
5
|
require 'helper'
|
6
6
|
require 'inspec/profile_context'
|
7
7
|
|
8
|
+
class Module
|
9
|
+
include Minitest::Spec::DSL
|
10
|
+
end
|
11
|
+
|
12
|
+
module DescribeOneTest
|
13
|
+
it 'loads an empty describe.one' do
|
14
|
+
profile.load(format(context_format, 'describe.one'))
|
15
|
+
get_checks.must_equal([])
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'loads an empty describe.one block' do
|
19
|
+
profile.load(format(context_format, 'describe.one do; end'))
|
20
|
+
get_checks.must_equal([['describe.one', [], nil]])
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'loads a simple describe.one block' do
|
24
|
+
profile.load(format(context_format, '
|
25
|
+
describe.one do
|
26
|
+
describe true do; it { should eq true }; end
|
27
|
+
end'))
|
28
|
+
c = get_checks[0]
|
29
|
+
c[0].must_equal 'describe.one'
|
30
|
+
childs = c[1]
|
31
|
+
childs.length.must_equal 1
|
32
|
+
childs[0][0].must_equal 'describe'
|
33
|
+
childs[0][1].must_equal [true]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'loads a complex describe.one block' do
|
37
|
+
profile.load(format(context_format, '
|
38
|
+
describe.one do
|
39
|
+
describe 0 do; it { should eq true }; end
|
40
|
+
describe 1 do; it { should eq true }; end
|
41
|
+
describe 2 do; it { should eq true }; end
|
42
|
+
end'))
|
43
|
+
c = get_checks[0]
|
44
|
+
c[0].must_equal 'describe.one'
|
45
|
+
childs = c[1]
|
46
|
+
childs.length.must_equal 3
|
47
|
+
childs.each_with_index do |ci, idx|
|
48
|
+
ci[0].must_equal 'describe'
|
49
|
+
ci[1].must_equal [idx]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
8
54
|
describe Inspec::ProfileContext do
|
9
55
|
let(:backend) { MockLoader.new.backend }
|
10
56
|
let(:profile) { Inspec::ProfileContext.new(nil, backend) }
|
11
57
|
|
58
|
+
def get_rule
|
59
|
+
profile.rules.values[0]
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_checks
|
63
|
+
get_rule.instance_variable_get(:@checks)
|
64
|
+
end
|
65
|
+
|
12
66
|
it 'must be able to load empty content' do
|
13
67
|
profile.load('', 'dummy', 1).must_be_nil
|
14
68
|
end
|
@@ -18,6 +72,10 @@ describe Inspec::ProfileContext do
|
|
18
72
|
proc { profile.load(call) }
|
19
73
|
end
|
20
74
|
|
75
|
+
let(:context_format) { '%s' }
|
76
|
+
|
77
|
+
include DescribeOneTest
|
78
|
+
|
21
79
|
it 'must provide os resource' do
|
22
80
|
load('print os[:family]').must_output 'ubuntu'
|
23
81
|
end
|
@@ -30,6 +88,13 @@ describe Inspec::ProfileContext do
|
|
30
88
|
load('print command("").stdout').must_output ''
|
31
89
|
end
|
32
90
|
|
91
|
+
it 'supports empty describe calls' do
|
92
|
+
load('describe').must_output ''
|
93
|
+
profile.rules.keys.length.must_equal 1
|
94
|
+
profile.rules.keys[0].must_match /^\(generated from unknown:1 [0-9a-f]+\)$/
|
95
|
+
profile.rules.values[0].must_be_kind_of Inspec::Rule
|
96
|
+
end
|
97
|
+
|
33
98
|
it 'provides the describe keyword in the global DSL' do
|
34
99
|
load('describe true do; it { should_eq true }; end')
|
35
100
|
.must_output ''
|
@@ -61,6 +126,13 @@ describe Inspec::ProfileContext do
|
|
61
126
|
|
62
127
|
describe 'rule DSL' do
|
63
128
|
let(:rule_id) { rand.to_s }
|
129
|
+
let(:context_format) { "rule #{rule_id.inspect} do\n%s\nend" }
|
130
|
+
|
131
|
+
def get_rule
|
132
|
+
profile.rules[rule_id]
|
133
|
+
end
|
134
|
+
|
135
|
+
include DescribeOneTest
|
64
136
|
|
65
137
|
it 'doesnt add any checks if none are provided' do
|
66
138
|
profile.load("rule #{rule_id.inspect}")
|
@@ -68,17 +140,20 @@ describe Inspec::ProfileContext do
|
|
68
140
|
rule.instance_variable_get(:@checks).must_equal([])
|
69
141
|
end
|
70
142
|
|
143
|
+
describe 'supports empty describe blocks' do
|
144
|
+
it 'doesnt crash, but doesnt add anything either' do
|
145
|
+
profile.load(format(context_format, 'describe'))
|
146
|
+
profile.rules.keys.must_include(rule_id)
|
147
|
+
get_checks.must_equal([])
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
71
151
|
describe 'adds a check via describe' do
|
72
|
-
let(:cmd) {<<-EOF
|
73
|
-
rule #{rule_id.inspect} do
|
74
|
-
describe os[:family] { it { must_equal 'ubuntu' } }
|
75
|
-
end
|
76
|
-
EOF
|
77
|
-
}
|
78
152
|
let(:check) {
|
79
|
-
profile.load(
|
80
|
-
|
81
|
-
|
153
|
+
profile.load(format(context_format,
|
154
|
+
"describe os[:family] { it { must_equal 'ubuntu' } }"
|
155
|
+
))
|
156
|
+
get_checks[0]
|
82
157
|
}
|
83
158
|
|
84
159
|
it 'registers the check with describe' do
|
@@ -95,16 +170,11 @@ describe Inspec::ProfileContext do
|
|
95
170
|
end
|
96
171
|
|
97
172
|
describe 'adds a check via expect' do
|
98
|
-
let(:cmd) {<<-EOF
|
99
|
-
rule #{rule_id.inspect} do
|
100
|
-
expect(os[:family]).to eq('ubuntu')
|
101
|
-
end
|
102
|
-
EOF
|
103
|
-
}
|
104
173
|
let(:check) {
|
105
|
-
profile.load(
|
106
|
-
|
107
|
-
|
174
|
+
profile.load(format(context_format,
|
175
|
+
"expect(os[:family]).to eq('ubuntu')"
|
176
|
+
))
|
177
|
+
get_checks[0]
|
108
178
|
}
|
109
179
|
|
110
180
|
it 'registers the check with describe' do
|
@@ -116,23 +186,18 @@ describe Inspec::ProfileContext do
|
|
116
186
|
end
|
117
187
|
|
118
188
|
it 'registers the check with the provided proc' do
|
119
|
-
check[2].must_be_kind_of Inspec::
|
189
|
+
check[2].must_be_kind_of Inspec::Expect
|
120
190
|
end
|
121
191
|
end
|
122
192
|
|
123
193
|
describe 'adds a check via describe + expect' do
|
124
|
-
let(:cmd) {<<-EOF
|
125
|
-
rule #{rule_id.inspect} do
|
126
|
-
describe 'the actual test' do
|
127
|
-
expect(os[:family]).to eq('ubuntu')
|
128
|
-
end
|
129
|
-
end
|
130
|
-
EOF
|
131
|
-
}
|
132
194
|
let(:check) {
|
133
|
-
profile.load(
|
134
|
-
|
135
|
-
|
195
|
+
profile.load(format(context_format,
|
196
|
+
"describe 'the actual test' do
|
197
|
+
expect(os[:family]).to eq('ubuntu')
|
198
|
+
end"
|
199
|
+
))
|
200
|
+
get_checks[0]
|
136
201
|
}
|
137
202
|
|
138
203
|
it 'registers the check with describe' do
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Christoph Hartmann
|
3
|
+
# author: Dominik Richter
|
4
|
+
|
5
|
+
require 'helper'
|
6
|
+
require 'inspec/resource'
|
7
|
+
|
8
|
+
describe 'Inspec::Resources::XinetdConf' do
|
9
|
+
let(:resource) { load_resource('xinetd_conf') }
|
10
|
+
it 'reads default params' do
|
11
|
+
d = resource.params['defaults']
|
12
|
+
_(d).must_be_kind_of SimpleConfig
|
13
|
+
_(d.params['instances']).must_equal '50'
|
14
|
+
_(d.params['log_type']).must_equal 'SYSLOG daemon info'
|
15
|
+
_(d.params.length).must_equal 2
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'with services from child configs' do
|
19
|
+
it 'has one service name' do
|
20
|
+
_(resource.services).must_equal %w{chargen}
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'has multiple service definitions' do
|
24
|
+
_(resource.ids).must_equal %w{chargen-stream chargen-dgram}
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'can filter by name' do
|
28
|
+
_(resource.services('not here').params['services']).must_be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'can chain filters' do
|
32
|
+
one = resource.services('chargen').socket_types('dgram')
|
33
|
+
_(one.params['services'].length).must_equal 1
|
34
|
+
_(one.ids).must_equal %w{chargen-dgram}
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'checks if all are disabled on one disabled service' do
|
38
|
+
one = resource.ids('chargen-stream')
|
39
|
+
_(one.disabled?).must_equal true
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'checks if all are disabled on multiple mixed' do
|
43
|
+
_(resource.disabled?).must_equal false
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'checks if all are enabled on one enabled service' do
|
47
|
+
one = resource.ids(/dgram$/)
|
48
|
+
_(one.enabled?).must_equal true
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'checks if all are enabled on one enabled service' do
|
52
|
+
one = resource.ids(/stream$/)
|
53
|
+
_(one.enabled?).must_equal false
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'checks if all are enabled on multiple mixed' do
|
57
|
+
_(resource.enabled?).must_equal false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.14.
|
4
|
+
version: 0.14.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dominik Richter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: r-train
|
@@ -237,7 +237,9 @@ files:
|
|
237
237
|
- lib/inspec/archive/tar.rb
|
238
238
|
- lib/inspec/archive/zip.rb
|
239
239
|
- lib/inspec/backend.rb
|
240
|
+
- lib/inspec/describe.rb
|
240
241
|
- lib/inspec/dsl.rb
|
242
|
+
- lib/inspec/expect.rb
|
241
243
|
- lib/inspec/fetcher.rb
|
242
244
|
- lib/inspec/log.rb
|
243
245
|
- lib/inspec/metadata.rb
|
@@ -309,6 +311,7 @@ files:
|
|
309
311
|
- lib/resources/ssh_conf.rb
|
310
312
|
- lib/resources/user.rb
|
311
313
|
- lib/resources/windows_feature.rb
|
314
|
+
- lib/resources/xinetd.rb
|
312
315
|
- lib/resources/yaml.rb
|
313
316
|
- lib/resources/yum.rb
|
314
317
|
- lib/source_readers/flat.rb
|
@@ -409,6 +412,7 @@ files:
|
|
409
412
|
- test/unit/mock/cmd/find-apache2-ports-conf
|
410
413
|
- test/unit/mock/cmd/find-etc-rc-d-name-S
|
411
414
|
- test/unit/mock/cmd/find-net-interface
|
415
|
+
- test/unit/mock/cmd/find-xinetd.d
|
412
416
|
- test/unit/mock/cmd/gem-list-local-a-q-rubocop
|
413
417
|
- test/unit/mock/cmd/get-net-tcpconnection
|
414
418
|
- test/unit/mock/cmd/get-netadapter-binding-bridge
|
@@ -475,6 +479,10 @@ files:
|
|
475
479
|
- test/unit/mock/files/shadow
|
476
480
|
- test/unit/mock/files/ssh_config
|
477
481
|
- test/unit/mock/files/sshd_config
|
482
|
+
- test/unit/mock/files/xinetd.conf
|
483
|
+
- test/unit/mock/files/xinetd.d/.gitkeep
|
484
|
+
- test/unit/mock/files/xinetd.d_chargen-dgram
|
485
|
+
- test/unit/mock/files/xinetd.d_chargen-stream
|
478
486
|
- test/unit/mock/profiles/complete-metadata/inspec.yml
|
479
487
|
- test/unit/mock/profiles/complete-profile/controls/filesystem_spec.rb
|
480
488
|
- test/unit/mock/profiles/complete-profile/inspec.yml
|
@@ -533,6 +541,7 @@ files:
|
|
533
541
|
- test/unit/resources/ssh_conf_test.rb
|
534
542
|
- test/unit/resources/user_test.rb
|
535
543
|
- test/unit/resources/windows_feature.rb
|
544
|
+
- test/unit/resources/xinetd_test.rb
|
536
545
|
- test/unit/resources/yaml_test.rb
|
537
546
|
- test/unit/resources/yum_test.rb
|
538
547
|
- test/unit/source_reader_test.rb
|
@@ -652,6 +661,7 @@ test_files:
|
|
652
661
|
- test/unit/mock/cmd/find-apache2-ports-conf
|
653
662
|
- test/unit/mock/cmd/find-etc-rc-d-name-S
|
654
663
|
- test/unit/mock/cmd/find-net-interface
|
664
|
+
- test/unit/mock/cmd/find-xinetd.d
|
655
665
|
- test/unit/mock/cmd/gem-list-local-a-q-rubocop
|
656
666
|
- test/unit/mock/cmd/get-net-tcpconnection
|
657
667
|
- test/unit/mock/cmd/get-netadapter-binding-bridge
|
@@ -718,6 +728,10 @@ test_files:
|
|
718
728
|
- test/unit/mock/files/shadow
|
719
729
|
- test/unit/mock/files/ssh_config
|
720
730
|
- test/unit/mock/files/sshd_config
|
731
|
+
- test/unit/mock/files/xinetd.conf
|
732
|
+
- test/unit/mock/files/xinetd.d/.gitkeep
|
733
|
+
- test/unit/mock/files/xinetd.d_chargen-dgram
|
734
|
+
- test/unit/mock/files/xinetd.d_chargen-stream
|
721
735
|
- test/unit/mock/profiles/complete-metadata/inspec.yml
|
722
736
|
- test/unit/mock/profiles/complete-profile/controls/filesystem_spec.rb
|
723
737
|
- test/unit/mock/profiles/complete-profile/inspec.yml
|
@@ -776,6 +790,7 @@ test_files:
|
|
776
790
|
- test/unit/resources/ssh_conf_test.rb
|
777
791
|
- test/unit/resources/user_test.rb
|
778
792
|
- test/unit/resources/windows_feature.rb
|
793
|
+
- test/unit/resources/xinetd_test.rb
|
779
794
|
- test/unit/resources/yaml_test.rb
|
780
795
|
- test/unit/resources/yum_test.rb
|
781
796
|
- test/unit/source_reader_test.rb
|