inspec 0.14.3 → 0.14.4
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/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
|