inspec 0.19.3 → 0.20.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.
Files changed (39) hide show
  1. checksums.yaml +5 -13
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +30 -2
  4. data/inspec.gemspec +1 -1
  5. data/lib/bundles/inspec-compliance.rb +1 -0
  6. data/lib/bundles/inspec-compliance/.kitchen.yml +21 -0
  7. data/lib/bundles/inspec-compliance/README.md +24 -0
  8. data/lib/bundles/inspec-compliance/bootstrap.sh +37 -0
  9. data/lib/bundles/inspec-compliance/cli.rb +2 -2
  10. data/lib/bundles/inspec-compliance/support.rb +36 -0
  11. data/lib/bundles/inspec-compliance/target.rb +3 -5
  12. data/lib/bundles/inspec-compliance/test/integration/default/cli.rb +56 -0
  13. data/lib/fetchers/url.rb +7 -2
  14. data/lib/inspec/backend.rb +1 -1
  15. data/lib/inspec/cli.rb +13 -13
  16. data/lib/inspec/plugins/fetcher.rb +1 -0
  17. data/lib/inspec/version.rb +1 -1
  18. data/lib/resources/file.rb +11 -23
  19. data/lib/resources/os.rb +10 -1
  20. data/lib/resources/package.rb +16 -0
  21. data/lib/resources/user.rb +14 -0
  22. data/lib/resources/xinetd.rb +39 -94
  23. data/lib/utils/filter.rb +184 -0
  24. data/lib/utils/hash_map.rb +37 -0
  25. data/test/functional/inspec_test.rb +23 -0
  26. data/test/helper.rb +5 -0
  27. data/test/resource/file_test.rb +3 -1
  28. data/test/unit/{fetchers.rb → fetchers_test.rb} +1 -0
  29. data/test/unit/mock/cmd/logins-x +4 -0
  30. data/test/unit/mock/cmd/swlist-l-product +1 -0
  31. data/test/unit/mock/profiles/resource-tiny/inspec.yml +10 -0
  32. data/test/unit/mock/profiles/resource-tiny/libraries/resource.rb +3 -0
  33. data/test/unit/resources/file_test.rb +21 -0
  34. data/test/unit/resources/package_test.rb +9 -0
  35. data/test/unit/resources/user_test.rb +6 -0
  36. data/test/unit/resources/xinetd_test.rb +3 -3
  37. data/test/unit/utils/filter_table_test.rb +125 -0
  38. metadata +46 -31
  39. data/lib/utils/detect.rb +0 -15
data/lib/resources/os.rb CHANGED
@@ -13,7 +13,7 @@ module Inspec::Resources
13
13
  "
14
14
 
15
15
  # reuse helper methods from backend
16
- %w{aix? redhat? debian? suse? bsd? solaris? linux? unix? windows?}.each do |os_family|
16
+ %w{aix? redhat? debian? suse? bsd? solaris? linux? unix? windows? hpux?}.each do |os_family|
17
17
  define_method(os_family.to_sym) do
18
18
  inspec.backend.os.send(os_family)
19
19
  end
@@ -25,6 +25,15 @@ module Inspec::Resources
25
25
  inspec.backend.os[name]
26
26
  end
27
27
 
28
+ def params
29
+ {
30
+ name: inspec.backend.os[:name],
31
+ family: inspec.backend.os[:family],
32
+ release: inspec.backend.os[:release],
33
+ arch: inspec.backend.os[:arch],
34
+ }
35
+ end
36
+
28
37
  def to_s
29
38
  'Operating System Detection'
30
39
  end
@@ -41,6 +41,8 @@ module Inspec::Resources
41
41
  @pkgman = BffPkg.new(inspec)
42
42
  elsif os.solaris?
43
43
  @pkgman = SolarisPkg.new(inspec)
44
+ elsif ['hpux'].include?(os[:family])
45
+ @pkgman = HpuxPkg.new(inspec)
44
46
  else
45
47
  return skip_resource 'The `package` resource is not supported on your OS yet.'
46
48
  end
@@ -168,6 +170,20 @@ module Inspec::Resources
168
170
  end
169
171
  end
170
172
 
173
+ class HpuxPkg < PkgManagement
174
+ def info(package_name)
175
+ cmd = inspec.command("swlist -l product | grep #{package_name}")
176
+ return nil if cmd.exit_status.to_i != 0
177
+ pkg = cmd.stdout.strip.split(' ')
178
+ {
179
+ name: pkg[0],
180
+ installed: true,
181
+ version: pkg[1],
182
+ type: 'pkg',
183
+ }
184
+ end
185
+ end
186
+
171
187
  # Determines the installed packages on Windows
172
188
  # Currently we use 'Get-WmiObject -Class Win32_Product' as a detection method
173
189
  # TODO: evaluate if alternative methods as proposed by Microsoft are still valid:
@@ -67,6 +67,8 @@ module Inspec::Resources
67
67
  @user_provider = AixUser.new(inspec)
68
68
  elsif os.solaris?
69
69
  @user_provider = SolarisUser.new(inspec)
70
+ elsif ['hpux'].include?(os[:family])
71
+ @user_provider = HpuxUser.new(inspec)
70
72
  else
71
73
  return skip_resource 'The `user` resource is not supported on your OS yet.'
72
74
  end
@@ -330,6 +332,18 @@ module Inspec::Resources
330
332
  end
331
333
  end
332
334
 
335
+ class HpuxUser < UnixUser
336
+ def meta_info(username)
337
+ hpuxuser = inspec.command("logins -x -l #{username}")
338
+ return nil if hpuxuser.exit_status != 0
339
+ user = hpuxuser.stdout.chomp.split(' ')
340
+ {
341
+ home: user[4],
342
+ shell: user[5],
343
+ }
344
+ end
345
+ end
346
+
333
347
  # we do not use 'finger' for MacOS, because it is harder to parse data with it
334
348
  # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/fingerd.8.html
335
349
  # instead we use 'dscl' to request user data
@@ -3,9 +3,10 @@
3
3
  # author: Dominik Richter
4
4
 
5
5
  require 'utils/parser'
6
+ require 'utils/filter'
6
7
 
7
8
  module Inspec::Resources
8
- class XinetdConf < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
9
+ class XinetdConf < Inspec.resource(1)
9
10
  name 'xinetd_conf'
10
11
  desc 'Xinetd services configuration.'
11
12
  example "
@@ -20,112 +21,33 @@ module Inspec::Resources
20
21
 
21
22
  include XinetdParser
22
23
 
23
- def initialize(conf_path = '/etc/xinetd.conf', opts = {})
24
+ def initialize(conf_path = '/etc/xinetd.conf')
24
25
  @conf_path = conf_path
25
- @params = opts[:params] unless opts[:params].nil?
26
- @filters = opts[:filters] || ''
27
26
  @contents = {}
28
27
  end
29
28
 
30
29
  def to_s
31
- "Xinetd config #{@conf_path}"
32
- end
33
-
34
- def services(condition = nil)
35
- condition.nil? ? params['services'].keys : filter(service: condition)
36
- end
37
-
38
- def ids(condition = nil)
39
- condition.nil? ? services_field('id') : filter(id: condition)
40
- end
41
-
42
- def socket_types(condition = nil)
43
- condition.nil? ? services_field('socket_type') : filter(socket_type: condition)
44
- end
45
-
46
- def types(condition = nil)
47
- condition.nil? ? services_field('type') : filter(type: condition)
48
- end
49
-
50
- def wait(condition = nil)
51
- condition.nil? ? services_field('wait') : filter(wait: condition)
52
- end
53
-
54
- def disabled?
55
- filter(disable: 'no').services.empty?
56
- end
57
-
58
- def enabled?
59
- filter(disable: 'yes').services.empty?
30
+ "Xinetd config #{@conf_path}#{@filters}"
60
31
  end
61
32
 
62
33
  def params
63
- return @params if defined?(@params)
64
- return @params = {} if read_content.nil?
65
- flat_params = parse_xinetd(read_content)
66
- @params = { 'services' => {} }
67
- flat_params.each do |k, v|
68
- name = k[/^service (.+)$/, 1]
69
- if name.nil?
70
- @params[k] = v
71
- else
72
- @params['services'][name] = v
73
- end
74
- end
75
- @params
34
+ @params ||= read_params
76
35
  end
77
36
 
78
- def filter(conditions = {})
79
- res = params.dup
80
- filters = ''
81
- conditions.each do |k, v|
82
- v = v.to_s if v.is_a? Integer
83
- filters += " #{k} = #{v.inspect}"
84
- res['services'] = filter_by(res['services'], k.to_s, v)
85
- end
86
- XinetdConf.new(@conf_path, params: res, filters: filters)
87
- end
37
+ filter = FilterTable.create
38
+ filter.add_accessor(:where)
39
+ .add_accessor(:entries)
40
+ .add(:services, field: 'service')
41
+ .add(:ids, field: 'id')
42
+ .add(:socket_types, field: 'socket_type')
43
+ .add(:types, field: 'type')
44
+ .add(:wait, field: 'wait')
45
+ .add(:disabled?) { |x| x.where('disable' => 'no').services.empty? }
46
+ .add(:enabled?) { |x| x.where('disable' => 'yes').services.empty? }
47
+ .connect(self, :service_lines)
88
48
 
89
49
  private
90
50
 
91
- # Retrieve the provided field from all configured services.
92
- #
93
- # @param [String] field name, e.g. `socket_type`
94
- # @return [Array[String]] all values of this field across services
95
- def services_field(field)
96
- params['services'].values.compact.flatten
97
- .map { |x| x.params[field] }.flatten.compact
98
- end
99
-
100
- def match_condition(sth, condition)
101
- case sth
102
- # this does Regex-matching as well as string comparison
103
- when condition
104
- true
105
- else
106
- false
107
- end
108
- end
109
-
110
- # Filter services by a criteria. This allows for search queries for
111
- # certain values.
112
- #
113
- # @param [Hash] service collection
114
- # @param [String] search key you want to query
115
- # @param [Any] search value that the key should match
116
- # @return [Hash] filtered service collection
117
- def filter_by(services, k, v)
118
- if k == 'service'
119
- return Hash[services.find_all { |name, _| match_condition(v, name) }]
120
- end
121
- Hash[services.map { |name, service_arr|
122
- found = service_arr.find_all { |service|
123
- match_condition(service.params[k], v)
124
- }
125
- found.empty? ? nil : [name, found]
126
- }.compact]
127
- end
128
-
129
51
  def read_content(path = @conf_path)
130
52
  return @contents[path] if @contents.key?(path)
131
53
  file = inspec.file(path)
@@ -140,5 +62,28 @@ module Inspec::Resources
140
62
 
141
63
  @contents[path]
142
64
  end
65
+
66
+ def read_params
67
+ return {} if read_content.nil?
68
+ flat_params = parse_xinetd(read_content)
69
+ params = { 'services' => {} }
70
+
71
+ # parse services that were defined:
72
+ flat_params.each do |k, v|
73
+ name = k[/^service (.+)$/, 1]
74
+ if name.nil?
75
+ params[k] = v
76
+ else
77
+ params['services'][name] = v
78
+ # add the service identifier to its parameters
79
+ v.each { |service| service.params['service'] = name }
80
+ end
81
+ end
82
+ params
83
+ end
84
+
85
+ def service_lines
86
+ @services ||= params['services'].values.flatten.map(&:params)
87
+ end
143
88
  end
144
89
  end
@@ -0,0 +1,184 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Stephan Renatus
4
+ # author: Christoph Hartmann
5
+
6
+ module FilterTable
7
+ module Show; end
8
+
9
+ class Trace
10
+ def initialize
11
+ @chain = []
12
+ end
13
+
14
+ %w{== != >= > < <= =~ !~}.each do |m|
15
+ define_method m.to_sym do |*args|
16
+ res = Trace.new
17
+ @chain.push([[m.to_sym] + args, res])
18
+ res
19
+ end
20
+ end
21
+
22
+ def method_missing(*args)
23
+ res = Trace.new
24
+ @chain.push([args, res])
25
+ res
26
+ end
27
+
28
+ def self.to_ruby(trace)
29
+ chain = trace.instance_variable_get(:@chain)
30
+ return '' if chain.empty?
31
+ ' ' + chain.map do |el|
32
+ m = el[0][0]
33
+ args = el[0].drop(1)
34
+ nxt = to_ruby(el[1])
35
+ next m.to_s + nxt if args.empty?
36
+ next m.to_s + ' ' + args[0].inspect + nxt if args.length == 1
37
+ m.to_s + '(' + args.map(&:inspect).join(', ') + ')' + nxt
38
+ end.join(' ')
39
+ end
40
+ end
41
+
42
+ class Table
43
+ attr_reader :params
44
+ def initialize(resource, params, filters)
45
+ @resource = resource
46
+ @params = params
47
+ @filters = filters
48
+ end
49
+
50
+ def where(conditions = {}, &block)
51
+ return self if !conditions.is_a?(Hash)
52
+ return self if conditions.empty? && !block_given?
53
+
54
+ filters = ''
55
+ table = @params
56
+ conditions.each do |field, condition|
57
+ filters += " #{field} == #{condition.inspect}"
58
+ table = filter_lines(table, field, condition)
59
+ end
60
+
61
+ if block_given?
62
+ table = table.find_all { |e| new_entry(e, '').instance_eval(&block) }
63
+ src = Trace.new
64
+ src.instance_eval(&block)
65
+ filters += Trace.to_ruby(src)
66
+ end
67
+
68
+ self.class.new(@resource, table, @filters + filters)
69
+ end
70
+
71
+ def new_entry(*_)
72
+ fail "#{self.class} must not be used on its own. It must be inherited "\
73
+ 'and the #new_entry method must be implemented. This is an internal '\
74
+ 'error and should not happen.'
75
+ end
76
+
77
+ def entries
78
+ f = @resource.to_s + @filters.to_s + ' one entry'
79
+ @params.map do |line|
80
+ new_entry(line, f)
81
+ end
82
+ end
83
+
84
+ def get_fields(*fields)
85
+ @params.map do |line|
86
+ fields.map { |f| line[f] }
87
+ end.flatten
88
+ end
89
+
90
+ def to_s
91
+ @resource.to_s + @filters
92
+ end
93
+
94
+ alias inspect to_s
95
+
96
+ private
97
+
98
+ def filter_lines(table, field, condition)
99
+ table.find_all do |line|
100
+ next unless line.key?(field)
101
+ case line[field]
102
+ when condition
103
+ true
104
+ else
105
+ false
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ class Factory
112
+ def initialize
113
+ @accessors = []
114
+ @fields = {}
115
+ @blocks = {}
116
+ end
117
+
118
+ def connect(resource, table_accessor) # rubocop:disable Metrics/AbcSize
119
+ # create the table structure
120
+ fields = @fields
121
+ blocks = @blocks
122
+ struct_fields = fields.values
123
+
124
+ # the struct to hold single items from the #entries method
125
+ entry_struct = Struct.new(*struct_fields.map(&:to_sym)) do
126
+ attr_accessor :__filter
127
+ def to_s # rubocop:disable Lint/NestedMethodDefinition
128
+ @__filter || super
129
+ end
130
+ end unless struct_fields.empty?
131
+
132
+ # the main filter table
133
+ table = Class.new(Table) {
134
+ fields.each do |method, field_name|
135
+ block = blocks[method]
136
+ define_method method.to_sym do |condition = Show, &cond_block|
137
+ return block.call(self) unless block.nil?
138
+ return where(nil).get_fields(field_name) if condition == Show && !block_given?
139
+ where({ field_name => condition }, &cond_block)
140
+ end
141
+ end
142
+
143
+ define_method :new_entry do |hashmap, filter = ''|
144
+ return entry_struct.new if hashmap.nil?
145
+ res = entry_struct.new(*struct_fields.map { |x| hashmap[x] })
146
+ res.__filter = filter
147
+ res
148
+ end
149
+ }
150
+
151
+ # define all access methods with the parent resource
152
+ accessors = @accessors + @fields.keys
153
+ accessors.each do |method_name|
154
+ resource.send(:define_method, method_name.to_sym) do |*args, &block|
155
+ filter = table.new(self, method(table_accessor).call, ' with')
156
+ filter.method(method_name.to_sym).call(*args, &block)
157
+ end
158
+ end
159
+ end
160
+
161
+ def add_accessor(method_name)
162
+ if method_name.nil?
163
+ throw RuntimeError, "Called filter.add_delegator for resource #{@resource} with method name nil!"
164
+ end
165
+ @accessors.push(method_name)
166
+ self
167
+ end
168
+
169
+ def add(method_name, opts = {}, &block)
170
+ if method_name.nil?
171
+ throw RuntimeError, "Called filter.add for resource #{@resource} with method name nil!"
172
+ end
173
+
174
+ field_name = opts[:field] || method_name
175
+ @fields[method_name.to_sym] = field_name
176
+ @blocks[method_name.to_sym] = block
177
+ self
178
+ end
179
+ end
180
+
181
+ def self.create
182
+ Factory.new
183
+ end
184
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ class HashMap
6
+ class << self
7
+ def [](hash, *keys)
8
+ return hash if keys.empty? || hash.nil?
9
+ key = keys.shift
10
+ if hash.is_a?(Array)
11
+ map = hash.map { |i| [i, key] }
12
+ else
13
+ map = hash[key]
14
+ end
15
+ [map, *keys]
16
+ rescue NoMethodError => _
17
+ nil
18
+ end
19
+ end
20
+ end
21
+
22
+ class StringMap
23
+ class << self
24
+ def [](hash, *keys)
25
+ return hash if keys.empty? || hash.nil?
26
+ key = keys.shift
27
+ if hash.is_a?(Array)
28
+ map = hash.map { |i| [i, key] }
29
+ else
30
+ map = hash[key]
31
+ end
32
+ [map, *keys]
33
+ rescue NoMethodError => _
34
+ nil
35
+ end
36
+ end
37
+ end