opencontrol-linter 0.1.4 → 0.1.5

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: c357e597012b9d0a0d3e39d2142641b07da23a7a4b76c3f6354a7a7f71293b2a
4
- data.tar.gz: 56ce59414bc589a19ea9c6d48be26fe7179c157693180314d03ac8bf7537e756
3
+ metadata.gz: ed96388c4ae00d37b78992687c43e054b5a8d043db7f7f28fbb1702723055932
4
+ data.tar.gz: c66468f2db29a8ea4de93d228c3c1d02efa3a6409c5e1e4a3e387977c490570c
5
5
  SHA512:
6
- metadata.gz: 127982ea79db363912df7ae1c6e30dc148b3c7dc6d256d446892fbbb24262c31e2db38faf49ababcde44b51874d3aa8e9e99a1120f5f77d2ef8a453677ecd8dd
7
- data.tar.gz: 86724dbdbda2d39d63239481b064a4c41bb68bf341bbad26fc50a11ce7e540e70a0a2bd432d280ead7e33019b71be342e6959d6794609e500340d72a912f9f1e
6
+ metadata.gz: bd127b83e300f08a26191b67e960f5acc1cc1fb79c8100ec10c9373956017f69ae2833a5aa083211f6cd3b0e2406135ee87641633211553078ec9513feb78ca9
7
+ data.tar.gz: 73289f4f62be55c3e42eee035257790571e832468c2aa3acee38c224bcee3020fdeffe38d9d618f39d1d9a6f2317693ada7285edf1d79ae302b8cab43d7969e8
@@ -1,53 +1,198 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "yaml"
5
- require "json"
6
- require "pp"
4
+ require 'yaml'
5
+ require 'json'
6
+ require 'pp'
7
7
  require 'date'
8
- require 'csv'
9
8
  require 'colorize'
9
+ require 'rubyXL'
10
+ require 'rubyXL/convenience_methods/cell'
11
+ require 'rationalist'
10
12
 
11
- DEFAULT_OPENCONTROLS_FILENAME = "./opencontrols/opencontrol.yaml".freeze
12
- CFACTS_COLUMNS = ['Control Key', 'Response']
13
- opencontrols_filename = DEFAULT_OPENCONTROLS_FILENAME
14
- cfacts_csv_filename = "./opencontrols/opencontrol.cfacts.csv".freeze
13
+ CFACTS_WORKBOOK_INDEX = 0
14
+ CFACTS_CONTROL_KEY_COLUMN_INDEX = 1
15
+ CFACTS_NARRATIVE_COLUMN_INDEX = 4
15
16
 
16
- puts "OpenControl conversion to CSV CFACTS format starting".green
17
+ DEFAULT_OPENCONTROLS_FILENAME = './opencontrols/opencontrol.yaml'
18
+ DEFAULT_CFACTS_XLSX_FILENAME = './assets/opencontrol.cfacts.xlsx'
19
+ DEFAULT_CFACTS_XLSX_OUTPUT_FILENAME = './opencontrols/opencontrol.cfacts.xlsx'
17
20
 
18
- if not File.exists?(opencontrols_filename)
19
- puts "Expected an opencontrol.yaml, but could not find opencontrol.yaml " +
20
- "in this directory.".red
21
- exit(1)
21
+ USAGE_TEXT = <<USAGE_TEXT
22
+ usage: opencontrol2cfacts
23
+ Usually used with a control masonry process that outputs merged yaml results.
24
+
25
+ optional arguments:
26
+ -h, --help show this help message and exit
27
+ -i, --cfacts_in_filename --input
28
+ Specify the xlsx spreadsheet file input from cfacts
29
+ (this should contain your various id's).
30
+ defaults to #{DEFAULT_CFACTS_XLSX_FILENAME}
31
+ -o, --cfacts_out_filename --output
32
+ Specify the xlsx spreadsheet file output for cfacts
33
+ (this should contain your various id's).
34
+ defaults to #{DEFAULT_CFACTS_XLSX_OUTPUT_FILENAME}
35
+ -v, --version Show the version of this utility.
36
+
37
+ Usage examples:
38
+
39
+ # convert using default directories #{DEFAULT_OPENCONTROLS_FILENAME}
40
+ # #{DEFAULT_CFACTS_XLSX_FILENAME} to #{DEFAULT_CFACTS_XLSX_OUTPUT_FILENAME}
41
+ opencontrol2cfacts
42
+
43
+ # lint all components subdir components
44
+ opencontrol2cfacts --input './cfacts.inputfile.xslx'
45
+ USAGE_TEXT
46
+
47
+ ALIASES = {
48
+ input: %w[i cfacts_in_filename],
49
+ output: %w[o cfacts_out_filename],
50
+ controls: %w[c opencontrols_filename],
51
+ help: 'h',
52
+ version: 'v'
53
+ }.freeze
54
+
55
+ VERSION = '0.1'
56
+
57
+ def write_when_empty_merge_strategy(row, narrative)
58
+ if row[CFACTS_NARRATIVE_COLUMN_INDEX].nil? ||
59
+ row[CFACTS_NARRATIVE_COLUMN_INDEX].value.nil? ||
60
+ row[CFACTS_NARRATIVE_COLUMN_INDEX].value.strip == ''
61
+ row[CFACTS_NARRATIVE_COLUMN_INDEX].change_contents(narrative)
62
+ end
63
+ end
64
+
65
+ def merge_response(row, narrative)
66
+ write_when_empty_merge_strategy(row, narrative)
67
+ end
68
+
69
+ def read_opencontrols_yaml(opencontrols_filename)
70
+ puts ' . Reading OpenControl components:'
71
+ YAML.load_file(opencontrols_filename)
72
+ end
73
+
74
+ def build_narrative(key, control, control_responses)
75
+ control['narrative'].each do |narrative|
76
+ control_responses[key]['text'] += key + "\n"
77
+ control_responses[key]['text'] += narrative['text']
78
+ control_responses[key]['text'] += "\n"
79
+ end
80
+ end
81
+
82
+ def narrative?(key, control_responses)
83
+ control_responses[key].key?('text')
84
+ end
85
+
86
+ def build_control_response(key, control, control_responses)
87
+ control_responses[key] = {} unless control_responses.key?(key)
88
+ control_responses[key]['text'] = '' unless narrative?(key, control_responses)
89
+ control_responses[key]['control_key'] = control['control_key']
90
+ control_responses[key]['standard_key'] = control['standard_key']
91
+ build_narrative(key, control, control_responses)
92
+ end
93
+
94
+ def key_for_control(control)
95
+ "#{control['standard_key']} #{control['control_key']}"
96
+ end
97
+
98
+ def build_control_responses(opencontrols)
99
+ control_responses = {}
100
+ opencontrols['data']['components'].each do |component|
101
+ puts " . #{component['name']}"
102
+ component['satisfies'].each do |control|
103
+ key = key_for_control(control)
104
+ build_control_response(key, control, control_responses)
105
+ end
106
+ end
107
+ control_responses
108
+ end
109
+
110
+ def row_malformed?(row)
111
+ row.nil? || row.size < CFACTS_CONTROL_KEY_COLUMN_INDEX
22
112
  end
23
113
 
24
- puts " . Reading OpenControl components:"
25
- control_responses = {}
26
- opencontrols = YAML.load_file(opencontrols_filename)
27
-
28
- opencontrols['data']['components'].each do |component|
29
- puts " . #{component['name']}"
30
- component['satisfies'].each do |control|
31
- key = "#{control['standard_key']} #{control['control_key']}"
32
- control_responses[key] = {} unless control_responses.has_key?(key)
33
- control_responses[key]['text'] = "" unless control_responses[key].has_key?('text')
34
- control_responses[key]['control_key'] = control['control_key']
35
- control_responses[key]['standard_key'] = control['standard_key']
36
- control['narrative'].each do |narrative|
37
- control_responses[key]['text'] += key + "\n"
38
- control_responses[key]['text'] += narrative['text']
39
- control_responses[key]['text'] += "\n"
114
+ def control_key_matches?(control_key, control_response)
115
+ control_key == control_response['control_key'].strip
116
+ end
117
+
118
+ def maybe_merge_into_row(row, row_number, control_response)
119
+ if row_malformed?(row)
120
+ puts "Skip malformed row (line no. #{row_number} [#{row}])" unless row.nil?
121
+ else
122
+ control_key = row[CFACTS_CONTROL_KEY_COLUMN_INDEX].value.strip
123
+ # puts "Searching in row #{control_key}"
124
+ if control_key_matches?(control_key, control_response)
125
+ merge_response(row, control_response['text'])
40
126
  end
41
127
  end
42
128
  end
43
129
 
44
- puts " . Building CFACTS file:"
45
- CSV.open(cfacts_csv_filename, "w") do |csv|
46
- csv << CFACTS_COLUMNS
47
- control_responses.each do |key, control_response|
48
- puts " . #{control_response['control_key']}"
49
- csv << [control_response['control_key'], control_response['text']]
130
+ def locate_and_merge_control(worksheet, control_response)
131
+ puts " . #{control_response['control_key']}"
132
+ # we have to locate the key in the sheet - skip the titles row
133
+ 1.upto(worksheet.sheet_data.size) do |row_number|
134
+ row = worksheet[row_number]
135
+ maybe_merge_into_row(row, row_number, control_response)
50
136
  end
51
137
  end
52
138
 
53
- puts "Completed (OpenControl 2 CFACTS)".green
139
+ def update_workbook(in_filename, control_responses, out_filename)
140
+ puts ' . Building CFACTS file:'
141
+ workbook = RubyXL::Parser.parse(in_filename)
142
+ worksheet = workbook[CFACTS_WORKBOOK_INDEX]
143
+ control_responses.each do |_key, control_response|
144
+ locate_and_merge_control(worksheet, control_response)
145
+ end
146
+ workbook.write(out_filename)
147
+ end
148
+
149
+ def no_opencontrol_exit
150
+ puts 'Expected an opencontrol.yaml, but could not find opencontrol.yaml ' +
151
+ 'in this directory.'.red
152
+ exit(1)
153
+ end
154
+
155
+ def check_files_exist(opencontrols_filename)
156
+ no_opencontrol_exit unless File.exist?(opencontrols_filename)
157
+ end
158
+
159
+ def self.parse_args(arguments)
160
+ opts = Rationalist.parse(arguments, alias: ALIASES)
161
+
162
+ exit(show_version) if opts[:version]
163
+ exit(show_help) if opts[:help]
164
+
165
+ opts[:controls] = DEFAULT_OPENCONTROLS_FILENAME unless opts.key?(:controls)
166
+ opts[:input] = DEFAULT_CFACTS_XLSX_FILENAME unless opts.key?(:input)
167
+ opts[:output] = DEFAULT_CFACTS_XLSX_OUTPUT_FILENAME unless opts.key?(:output)
168
+ opts
169
+ end
170
+
171
+ def show_help
172
+ puts USAGE_TEXT
173
+ 0 # exit with no error
174
+ end
175
+
176
+ def show_version
177
+ puts 'opecontrol2cfacts: v' + VERSION
178
+ puts 'CMS 2019 Adrian Kierman '
179
+ 0 # exit with no error
180
+ end
181
+
182
+ def main
183
+ opts = parse_args(ARGV)
184
+ opencontrols_filename = opts[:controls]
185
+ in_filename = opts[:input]
186
+ out_filename = opts[:output]
187
+ puts 'OpenControl conversion to CSV CFACTS format starting'.green
188
+
189
+ check_files_exist(opencontrols_filename)
190
+ opencontrols = read_opencontrols_yaml(opencontrols_filename)
191
+ control_responses = build_control_responses(opencontrols)
192
+ update_workbook(in_filename, control_responses, out_filename)
193
+
194
+ puts 'Completed (OpenControl 2 CFACTS)'.green
195
+ end
196
+
197
+ # only run main if called directly - don't call it if running as a library
198
+ main if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,37 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'opencontrol'
4
+ require 'rationalist'
5
+ require 'colorize'
6
+ require 'pp'
7
+ require 'active_support/core_ext/hash/indifferent_access'
8
+
9
+ certification_key = ''
10
+ certification_filename = "./certifications/#{certification_key}.yaml"
11
+
12
+ data = YAML.load_file('./opencontrol.yaml').with_indifferent_access
13
+ data[:standards] = data[:standards].collect do |filename|
14
+ YAML.load_file(filename)
15
+ end
16
+ data[:components] = data[:components].collect do |filename|
17
+ YAML.load_file(filename + '/component.yaml')
18
+ end
19
+ # just load the one certification here instead
20
+ data[:certification] = YAML.load_file(certification_filename)
21
+ .merge({key: certification_key})
22
+
23
+ data = JSON.parse(data.to_json)
24
+ puts data.to_yaml
25
+
26
+ # to get the inspector output we want we'd pull in the controls from components where we have controls in the standard
27
+ # we might want to invert the data so that each control is identified in the expected way
28
+ #
29
+ # We want to count up the completion status of the whole project and see where we are easily
30
+
31
+ data[:components].each do |component|
32
+ component[:satisfies] = component[:satisfies].select do |control|
33
+ data[:certification][:standards][control[:standard_key]].keys.include? control[:control_key]
34
+ end
35
+ end
36
+
37
+ # after this we want to iterate the components and add up the status of the components in terms of implementation
@@ -3,7 +3,7 @@
3
3
  module Opencontrol
4
4
  # This module holds the Opencontrol Linter version information.
5
5
  module Version
6
- STRING = '0.1.4'.freeze
6
+ STRING = '0.1.5'.freeze
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, running on ' \
9
9
  '%<ruby_engine>s %<ruby_version>s %<ruby_platform>s)'.freeze
@@ -69,7 +69,7 @@ satisfies:
69
69
  - text: "Justification in narrative form for 2.1"
70
70
  standard_key: PCI-DSS-MAY-2015
71
71
  responsible_role: "AWS Staff"
72
- schema_version: 3.0.0
72
+ schema_version: 3.1.0
73
73
  verifications:
74
74
  - key: EC2_Verification_2
75
75
  name: EC2 Governor 2
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opencontrol-linter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Kierman
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-03-27 00:00:00.000000000 Z
12
+ date: 2019-04-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: colorize
@@ -143,6 +143,34 @@ dependencies:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
145
  version: 3.0.4
146
+ - !ruby/object:Gem::Dependency
147
+ name: activesupport
148
+ requirement: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 5.2.2.1
153
+ type: :runtime
154
+ prerelease: false
155
+ version_requirements: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 5.2.2.1
160
+ - !ruby/object:Gem::Dependency
161
+ name: rubyXL
162
+ requirement: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 3.4.3
167
+ type: :runtime
168
+ prerelease: false
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 3.4.3
146
174
  - !ruby/object:Gem::Dependency
147
175
  name: bundler
148
176
  requirement: !ruby/object:Gem::Requirement
@@ -279,6 +307,7 @@ files:
279
307
  - exe/opencontrol2cfacts
280
308
  - lib/opencontrol.rb
281
309
  - lib/opencontrol/cli.rb
310
+ - lib/opencontrol/inspector.rb
282
311
  - lib/opencontrol/linter.rb
283
312
  - lib/opencontrol/messages.rb
284
313
  - lib/opencontrol/version.rb