abide_dev_utils 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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