abide_dev_utils 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c47baf812f2bb3b64951dc87b3aa1886eca947f0bf3a57bbed12b7ed7fda97ab
4
- data.tar.gz: 4488f609550b251474337f1574c5c216601035fc5bba1fc9acb76e278252b5b5
3
+ metadata.gz: 7cf13209260261bcf509e7953d4af4a47aa7301cc7c2e8227c3d736d163cd0b1
4
+ data.tar.gz: a6d6cb52aadf58e33ce6a57d77c7921226ebb130aa2d6e4350c42c197df0f06c
5
5
  SHA512:
6
- metadata.gz: f4e517c44f1c728689d1de11d0e306f65352ecf1b0507671d588ac5eb21dc294ac7bca68e7c790f21b9a47fa565ed775c2a28ce45257395203a0fa296c6f4389
7
- data.tar.gz: 2b5ff4f1ad40d4000bb93862902c1a89e4620632825d19a34cd118fa43cc15823132cb4e9d246a1655ad1f6bd9636581398651e9082e8b16129f0c79eba9a5df
6
+ metadata.gz: be0ac34b2ee3ea116793580ef1ffff41ca4f11b41ceb6587866183734f1aee15a566b5a0063b106a18962739a1d4d7091d6e8d276dffdf6101cfcfb48afb3832
7
+ data.tar.gz: 97f9b753cb94f1a1d326eb370a59ae35d00a3c2797b6e030941e51f51e9f7713eca0af9148f9858a0f4c375273bb468ab1df928cd589903d48b9bbe7c86a92fd
data/.gitignore CHANGED
@@ -4,6 +4,7 @@
4
4
  /coverage/
5
5
  /doc/
6
6
  /pkg/
7
+ /spec/fixtures/
7
8
  /spec/reports/
8
9
  /tmp/
9
10
  w10_20h2.xml
data/.rubocop.yml CHANGED
@@ -39,6 +39,9 @@ Style/IpAddresses:
39
39
  Exclude:
40
40
  - spec/rubocop/cop/style/ip_addresses_spec.rb
41
41
 
42
+ Style/RegexpLiteral:
43
+ Enabled: false
44
+
42
45
  Layout/EndOfLine:
43
46
  EnforcedStyle: lf
44
47
 
@@ -133,4 +136,7 @@ Performance/CollectionLiteralInLoop:
133
136
  - 'spec/**/*.rb'
134
137
 
135
138
  RSpec/StubbedMock:
136
- Enabled: false
139
+ Enabled: false
140
+
141
+ Convention/ExplicitBlockArgument:
142
+ Enabled: false
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- abide_dev_utils (0.10.1)
4
+ abide_dev_utils (0.11.0)
5
5
  amatch (~> 0.4)
6
6
  cmdparse (~> 3.0)
7
+ facterdb (>= 1.18)
7
8
  google-cloud-storage (~> 1.34)
8
9
  hashdiff (~> 1.0)
9
10
  jira-ruby (~> 2.2)
@@ -62,6 +63,9 @@ GEM
62
63
  facter (4.2.10)
63
64
  hocon (~> 1.3)
64
65
  thor (>= 1.0.1, < 2.0)
66
+ facterdb (1.18.0)
67
+ facter (< 5.0.0)
68
+ jgrep
65
69
  faraday (2.3.0)
66
70
  faraday-net_http (~> 2.0)
67
71
  ruby2_keywords (>= 0.0.4)
@@ -120,6 +124,7 @@ GEM
120
124
  httpclient (2.8.3)
121
125
  i18n (1.10.0)
122
126
  concurrent-ruby (~> 1.0)
127
+ jgrep (1.5.4)
123
128
  jira-ruby (2.2.0)
124
129
  activesupport
125
130
  atlassian-jwt
@@ -138,8 +143,10 @@ GEM
138
143
  nio4r (2.5.8)
139
144
  nokogiri (1.13.6-x86_64-darwin)
140
145
  racc (~> 1.4)
146
+ nokogiri (1.13.6-x86_64-linux)
147
+ racc (~> 1.4)
141
148
  oauth (0.5.10)
142
- octokit (4.23.0)
149
+ octokit (4.25.0)
143
150
  faraday (>= 1, < 3)
144
151
  sawyer (~> 0.9)
145
152
  os (1.1.4)
@@ -159,6 +166,17 @@ GEM
159
166
  coderay (~> 1.1)
160
167
  method_source (~> 1.0)
161
168
  public_suffix (4.0.7)
169
+ puppet (7.17.0)
170
+ concurrent-ruby (~> 1.0)
171
+ deep_merge (~> 1.0)
172
+ facter (> 2.0.1, < 5)
173
+ fast_gettext (>= 1.1, < 3)
174
+ hiera (>= 3.2.1, < 4)
175
+ locale (~> 2.1)
176
+ multi_json (~> 1.10)
177
+ puppet-resource_api (~> 1.5)
178
+ scanf (~> 1.0)
179
+ semantic_puppet (~> 1.0)
162
180
  puppet (7.17.0-universal-darwin)
163
181
  CFPropertyList (~> 2.2)
164
182
  concurrent-ruby (~> 1.0)
@@ -219,7 +237,7 @@ GEM
219
237
  ruby_parser (3.19.1)
220
238
  sexp_processor (~> 4.16)
221
239
  rubyzip (2.3.2)
222
- sawyer (0.9.1)
240
+ sawyer (0.9.2)
223
241
  addressable (>= 2.3.5)
224
242
  faraday (>= 0.17.3, < 3)
225
243
  scanf (1.0.0)
@@ -249,6 +267,7 @@ GEM
249
267
 
250
268
  PLATFORMS
251
269
  x86_64-darwin-19
270
+ x86_64-linux
252
271
 
253
272
  DEPENDENCIES
254
273
  abide_dev_utils!
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rake'
3
4
  require "bundler/gem_tasks"
4
5
  require "rspec/core/rake_task"
5
6
 
@@ -10,3 +11,30 @@ require "rubocop/rake_task"
10
11
  RuboCop::RakeTask.new
11
12
 
12
13
  task default: %i[spec rubocop]
14
+
15
+ namespace 'cem' do
16
+ directory 'spec/fixtures'
17
+
18
+ directory 'spec/fixtures/puppetlabs-cem_linux' do
19
+ sh 'git clone git@github.com:puppetlabs/puppetlabs-cem_linux.git spec/fixtures/puppetlabs-cem_linux'
20
+ end
21
+ file 'spec/fixtures/puppetlabs-cem_linux' => ['spec/fixtures']
22
+
23
+ directory 'spec/fixtures/puppetlabs-cem_windows' do
24
+ sh 'git clone git@github.com:puppetlabs/puppetlabs-cem_windows.git spec/fixtures/puppetlabs-cem_windows'
25
+ end
26
+ file 'spec/fixtures/puppetlabs-cem_windows' => ['spec/fixtures']
27
+
28
+ task :fixture, [:cem_mod] do |_, args|
29
+ case args.cem_mod
30
+ when /linux/
31
+ Rake::Task['spec/fixtures/puppetlabs-cem_linux'].invoke
32
+ when /windows/
33
+ Rake::Task['spec/fixtures/puppetlabs-cem_windows'].invoke
34
+ else
35
+ raise "Unknown CEM module #{args.cem_mod}"
36
+ end
37
+ end
38
+
39
+ multitask fixtures: %w[spec/fixtures/puppetlabs-cem_linux spec/fixtures/puppetlabs-cem_windows]
40
+ end
@@ -41,6 +41,7 @@ Gem::Specification.new do |spec|
41
41
  spec.add_dependency 'google-cloud-storage', '~> 1.34'
42
42
  spec.add_dependency 'hashdiff', '~> 1.0'
43
43
  spec.add_dependency 'amatch', '~> 0.4'
44
+ spec.add_dependency 'facterdb', '>= 1.18'
44
45
 
45
46
  # Dev dependencies
46
47
  spec.add_development_dependency 'bundler'
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/errors'
4
+ require 'abide_dev_utils/ppt/facter_utils'
5
+ require 'abide_dev_utils/cem/mapping/mapper'
6
+
7
+ module AbideDevUtils
8
+ module CEM
9
+ # Repesents a benchmark for purposes of organizing data for markdown representation
10
+ class Benchmark
11
+ attr_reader :osname, :major_version, :os_facts, :osfamily, :hiera_conf, :module_name, :framework, :rules
12
+
13
+ def initialize(osname, major_version, hiera_conf, module_name, framework: 'cis')
14
+ @osname = osname
15
+ @major_version = major_version
16
+ @os_facts = AbideDevUtils::Ppt::FacterUtils.recursive_facts_for_os(@osname, @major_version)
17
+ @osfamily = @os_facts['os']['family']
18
+ @hiera_conf = hiera_conf
19
+ @module_name = module_name
20
+ @framework = framework
21
+ @rules = {}
22
+ @map_cache = {}
23
+ @rules_in_map = {}
24
+ load_rules
25
+ end
26
+
27
+ # Creates Benchmark objects from a Puppet module
28
+ # @param pupmod [AbideDevUtils::Ppt::PuppetModule] A PuppetModule instance
29
+ # @param skip_errors [Boolean] True skips errors and loads non-erroring benchmarks, false raises the error.
30
+ # @return [Array<AbideDevUtils::CEM::Benchmark>] Array of Benchmark instances
31
+ def self.benchmarks_from_puppet_module(pupmod, ignore_all_errors: false, ignore_framework_mismatch: true)
32
+ frameworks = pupmod.hiera_conf.local_hiera_files(hierarchy_name: 'Mapping Data').each_with_object([]) do |hf, ary|
33
+ parts = hf.path.split(pupmod.hiera_conf.default_datadir)[-1].split('/')
34
+ ary << parts[2] unless ary.include?(parts[2])
35
+ end
36
+ pupmod.supported_os.each_with_object([]) do |supp_os, ary|
37
+ osname, majver = supp_os.split('::')
38
+ if majver.is_a?(Array)
39
+ majver.sort.each do |v|
40
+ frameworks.each do |fw|
41
+ benchmark = Benchmark.new(osname, v, pupmod.hiera_conf, pupmod.name(strip_namespace: true), framework: fw)
42
+ ary << benchmark
43
+ rescue StandardError => e
44
+ raise e unless ignore_all_errors || (e.instance_of?(AbideDevUtils::Errors::MappingDataFrameworkMismatchError) && ignore_framework_mismatch)
45
+ end
46
+ end
47
+ else
48
+ frameworks.each do |fw|
49
+ benchmark = Benchmark.new(osname, majver, pupmod.hiera_conf, pupmod.name(strip_namespace: true), framework: fw)
50
+ ary << benchmark
51
+ rescue StandardError => e
52
+ raise e unless ignore_all_errors || (e.instance_of?(AbideDevUtils::Errors::MappingDataFrameworkMismatchError) && ignore_framework_mismatch)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ def mapper
59
+ @mapper ||= AbideDevUtils::CEM::Mapping::Mapper.new(module_name, framework, load_mapping_data)
60
+ end
61
+
62
+ def map_data
63
+ mapper.map_data
64
+ end
65
+
66
+ def resource_data
67
+ @resource_data ||= load_resource_data
68
+ end
69
+
70
+ def title
71
+ mapper.title
72
+ end
73
+
74
+ def version
75
+ mapper.version
76
+ end
77
+
78
+ def title_key
79
+ @title_key ||= "#{title} #{version}"
80
+ end
81
+
82
+ def add_rule(rule_hash)
83
+ @rules << rule_hash
84
+ end
85
+
86
+ def rules_in_map(mtype, level: nil, profile: nil)
87
+ real_mtype = map_type(mtype)
88
+ cache_key = [real_mtype, level, profile].compact.join('-')
89
+ return @rules_in_map[cache_key] if @rules_in_map.key?(cache_key)
90
+
91
+ all_rim = mapper.each_with_array_like(real_mtype) do |(lvl, profs), arr|
92
+ next if lvl == 'benchmark' || (!level.nil? && lvl != level)
93
+
94
+ profs.each do |prof, maps|
95
+ next if !profile.nil? && prof != profile
96
+
97
+ # CIS and STIG differ in that STIG does not have profiles
98
+ control_ids = maps.respond_to?(:keys) ? maps.keys : prof
99
+ arr << control_ids
100
+ end
101
+ end
102
+ @rules_in_map[cache_key] = all_rim.flatten.uniq
103
+ @rules_in_map[cache_key]
104
+ end
105
+
106
+ def map(control_id, level: nil, profile: nil)
107
+ mapper.get(control_id, level: level, profile: profile)
108
+ end
109
+
110
+ def map_type(control_id)
111
+ mapper.map_type(control_id)
112
+ end
113
+
114
+ private
115
+
116
+ def load_rules
117
+ resource_data["#{module_name}::resources"].each do |_, rdata|
118
+ unless rdata.key?('controls')
119
+ puts "Controls key not found in #{rdata}"
120
+ next
121
+ end
122
+ rdata['controls'].each do |control, control_data|
123
+ rule_title = map(control)
124
+ next if rule_title.nil? || rule_title.empty?
125
+
126
+ rule_title.find { |id| map_type(id) == 'title' }
127
+ alternate_ids = map(rule_title)
128
+
129
+ next unless rule_title.is_a?(String)
130
+
131
+ @rules[rule_title] = {} unless @rules&.key?(rule_title)
132
+ @rules[rule_title]['number'] = alternate_ids.find { |id| map_type(id) == 'number' }
133
+ @rules[rule_title]['alternate_ids'] = alternate_ids
134
+ @rules[rule_title]['params'] = [] unless @rules[rule_title].key?('params')
135
+ @rules[rule_title]['level'] = [] unless @rules[rule_title].key?('level')
136
+ @rules[rule_title]['profile'] = [] unless @rules[rule_title].key?('profile')
137
+ param_hashes(control_data).each do |param_hash|
138
+ next if @rules[rule_title]['params'].include?(param_hash[:name])
139
+
140
+ @rules[rule_title]['params'] << param_hash
141
+ end
142
+ levels, profiles = find_levels_and_profiles(control)
143
+ unless @rules[rule_title]['level'] == levels
144
+ @rules[rule_title]['level'] = @rules[rule_title]['level'] | levels
145
+ end
146
+ unless @rules[rule_title]['profile'] == profiles
147
+ @rules[rule_title]['profile'] = @rules[rule_title]['profile'] | profiles
148
+ end
149
+ @rules[rule_title]['resource'] = rdata['type']
150
+ end
151
+ end
152
+ @rules = sort_rules
153
+ end
154
+
155
+ def param_hashes(control_data)
156
+ return [] if control_data.nil? || control_data.empty?
157
+
158
+ p_hashes = []
159
+ if !control_data.respond_to?(:each) && control_data == 'no_params'
160
+ p_hashes << no_params
161
+ else
162
+ control_data.each do |param, param_val|
163
+ p_hashes << {
164
+ name: param,
165
+ type: ruby_class_to_puppet_type(param_val.class.to_s),
166
+ default: param_val,
167
+ }
168
+ end
169
+ end
170
+ p_hashes
171
+ end
172
+
173
+ def no_params
174
+ { name: 'No parameters', type: nil, default: nil }
175
+ end
176
+
177
+ # We sort the rules by their control number so they
178
+ # appear in the REFERENCE in benchmark order
179
+ def sort_rules
180
+ sorted = @rules.dup.sort_by do |_, v|
181
+ control_num_to_int(v['number'])
182
+ end
183
+ sorted.to_h
184
+ end
185
+
186
+ # In order to sort the rules by their control number,
187
+ # we need to convert the control number to an integer.
188
+ # This is a rough conversion, but should be sufficient
189
+ # for the purposes of sorting. The multipliers are
190
+ # the 20th, 15th, 10th, and 5th numbers in the Fibonacci
191
+ # sequence, then 1 after that. The reason for this is to
192
+ # ensure a "spiraled" wieghting of the sections in the control
193
+ # number, with 1st section having the most sorting weight, 2nd
194
+ # having second most, etc. However, the differences in the multipliers
195
+ # are such that it would be difficult for the product of a lesser-weighted
196
+ # section to be greater than a greater-weighted section.
197
+ def control_num_to_int(control_num)
198
+ multipliers = [6765, 610, 55, 5, 1]
199
+ nsum = 0
200
+ midx = 0
201
+ control_num.split('.').each do |num|
202
+ multiplier = midx >= multipliers.length ? 1 : multipliers[midx]
203
+ nsum += num.to_i * multiplier
204
+ midx += 1
205
+ end
206
+ nsum
207
+ end
208
+
209
+ def find_levels_and_profiles(control_id)
210
+ levels = []
211
+ profiles = []
212
+ mapper.each_like(control_id) do |lvl, profile_hash|
213
+ next if lvl == 'benchmark'
214
+
215
+ profile_hash.each do |prof, _|
216
+ unless map(control_id, level: lvl, profile: prof).nil?
217
+ levels << lvl
218
+ profiles << prof
219
+ end
220
+ end
221
+ end
222
+ [levels, profiles]
223
+ end
224
+
225
+ def ruby_class_to_puppet_type(class_name)
226
+ pup_type = class_name.split('::').last.capitalize
227
+ case pup_type
228
+ when %r{(Trueclass|Falseclass)}
229
+ 'Boolean'
230
+ when %r{(String|Pathname)}
231
+ 'String'
232
+ when %r{(Integer|Fixnum)}
233
+ 'Integer'
234
+ when %r{(Float|Double)}
235
+ 'Float'
236
+ else
237
+ pup_type
238
+ end
239
+ end
240
+
241
+ def load_mapping_data
242
+ files = case module_name
243
+ when /_windows$/
244
+ cem_windows_mapping_files
245
+ when /_linux$/
246
+ cem_linux_mapping_files
247
+ else
248
+ raise "Module name '#{module_name}' is not a CEM module"
249
+ end
250
+ validate_mapping_files_framework(files).each_with_object({}) do |f, h|
251
+ next unless f.path.include?(framework)
252
+
253
+ h[File.basename(f.path, '.yaml')] = YAML.load_file(f.path)
254
+ end
255
+ end
256
+
257
+ def cem_linux_mapping_files
258
+ facts = [['os.name', osname], ['os.release.major', major_version]]
259
+ mapping_files = hiera_conf.local_hiera_files_with_facts(*facts, hierarchy_name: 'Mapping Data')
260
+ raise AbideDevUtils::Errors::MappingFilesNotFoundError, facts if mapping_files.nil? || mapping_files.empty?
261
+
262
+ mapping_files
263
+ end
264
+
265
+ def cem_windows_mapping_files
266
+ facts = ['os.release.major', major_version]
267
+ mapping_files = hiera_conf.local_hiera_files_with_fact(facts[0], facts[1], hierarchy_name: 'Mapping Data')
268
+ raise AbideDevUtils::Errors::MappingFilesNotFoundError, facts if mapping_files.nil? || mapping_files.empty?
269
+
270
+ mapping_files
271
+ end
272
+
273
+ def validate_mapping_files_framework(files)
274
+ validated_files = files.select { |f| f.path_parts.include?(framework) }
275
+ if validated_files.nil? || validated_files.empty?
276
+ raise AbideDevUtils::Errors::MappingDataFrameworkMismatchError, framework
277
+ end
278
+
279
+ validated_files
280
+ end
281
+
282
+ def load_resource_data
283
+ facts = [['os.family', osfamily], ['os.name', osname], ['os.release.major', major_version]]
284
+ rdata_files = hiera_conf.local_hiera_files_with_facts(*facts, hierarchy_name: 'Resource Data')
285
+ raise AbideDevUtils::Errors::ResourceDataNotFoundError, facts if rdata_files.nil? || rdata_files.empty?
286
+
287
+ YAML.load_file(rdata_files[0].path)
288
+ end
289
+ end
290
+ end
291
+ end