octool 0.0.4 → 0.0.9

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: 6a99d4ae79b106f19c3a49be0fd5fa84de52aaf790494a0f78e55c16fd00bbc5
4
- data.tar.gz: d50899b6e334df9a7f40071f053824af296c842e7d11c8add1ae8e0b17c8313b
3
+ metadata.gz: 5f2733d9e7a9f5a736a283139540cdae45cf887c2bd54e0de2461a30bc4602da
4
+ data.tar.gz: 87854e857038bafc19bdcbb34e23fe8a58f9c0d9e7836d3297d50b3cbe032367
5
5
  SHA512:
6
- metadata.gz: d447539ed45f3c6d55eaabdc04763b21a15bcfb1c9a25bcc8db050c885dba0d030634693353614243e59cec9c9622f09289c35d71aec52fdc56a1fc2922e896e
7
- data.tar.gz: 728c1d363e0f30c4c3f52149088d867cefdb0b63e3b3594fbe5aa1e70b04e66749fe2466ed90643fd84e94b416367f59f26e9d4f049741f8b0bb2d61b15f6306
6
+ metadata.gz: adce4065a12f2de271ce3c5c3faf2ffb28b6bf5c652b95e6c62826fa327b8a08ef2b8b7ee85bf7674a116ff2ea85cc604e124f83107adff5bac53374992c18fa
7
+ data.tar.gz: ed4f5e6de7c5463ae7a287bd3cf3189a393e6d458bd13813dfce82eab3c120031bbab94608b185bf2f5731664f9c32aeca767da8e0bcf01048b1f5beb75904a3
data/bin/octool CHANGED
@@ -21,7 +21,7 @@ class App
21
21
  def self.find_config(args)
22
22
  path = args.first || OCTool::DEFAULT_CONFIG_FILENAME
23
23
  path = File.join(path, OCTool::DEFAULT_CONFIG_FILENAME) if File.directory?(path)
24
- path
24
+ File.expand_path(path)
25
25
  end
26
26
 
27
27
  program_desc 'Open Compliance Tool'
@@ -51,7 +51,24 @@ class App
51
51
  v.default_command :data
52
52
  end
53
53
 
54
- desc 'generate System Security Plan'
54
+ desc 'export data to CSV'
55
+ arg_name 'path/to/system/config.yaml'
56
+ command :csv do |csv|
57
+ csv.desc 'where to store outputs'
58
+ csv.default_value data_dir
59
+ csv.long_desc 'Default output directory respects env vars TMPDIR, TMP, TEMP'
60
+ csv.arg_name 'path/to/output/dir'
61
+ csv.flag [:d, :dir]
62
+
63
+ csv.action do |global_options, options, args|
64
+ export_dir = options[:dir]
65
+ config_file = find_config(args)
66
+ system = OCTool::Parser.new(config_file).load_system
67
+ system.dump export_dir
68
+ end
69
+ end
70
+
71
+ desc 'validate data and generate System Security Plan'
55
72
  arg_name 'path/to/system/config.yaml'
56
73
  command :ssp do |s|
57
74
  s.desc 'where to store outputs'
@@ -92,12 +109,12 @@ class App
92
109
  # Error logic here
93
110
  # Return false to skip default error handling.
94
111
  if ENV['DEBUG']
95
- STDERR.puts exception.message
96
- STDERR.puts exception.backtrace
112
+ warn exception.message
113
+ warn exception.backtrace
97
114
  pp exception
98
115
  false
99
116
  end
100
- STDERR.puts '[FAIL]'
117
+ warn '[FAIL]'
101
118
  true
102
119
  end
103
120
  end
@@ -3,6 +3,8 @@
3
3
  require 'octool/version.rb'
4
4
 
5
5
  # Built-ins.
6
+ require 'csv'
7
+ require 'json'
6
8
  require 'pp'
7
9
 
8
10
  # 3rd-party libs.
@@ -15,12 +17,6 @@ require 'octool/parser'
15
17
  require 'octool/ssp'
16
18
  require 'octool/system'
17
19
 
18
- # Generated libs.
19
- require 'octool/generated/certification'
20
- require 'octool/generated/component'
21
- require 'octool/generated/config'
22
- require 'octool/generated/standard'
23
-
24
20
  # Mixins.
25
21
  module OCTool
26
22
  include Kwalify::Util::HashLike # defines [], []=, and keys?
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OCTool
4
- LATEST_SCHEMA_VERSION = 'v1.0.0'
4
+ LATEST_SCHEMA_VERSION = 'v1.0.2'
5
5
  BASE_SCHEMA_DIR = File.join(File.dirname(__FILE__), '..', '..', 'schemas').freeze
6
6
  ERB_DIR = File.join(File.dirname(__FILE__), '..', '..', 'templates').freeze
7
7
  DEFAULT_CONFIG_FILENAME = 'config.yaml'
@@ -4,6 +4,7 @@ module OCTool
4
4
  # Custom error to show validation errors.
5
5
  class ValidationError < StandardError
6
6
  attr_reader :errors
7
+
7
8
  def initialize(path, errors)
8
9
  @path = path
9
10
  @errors = errors
@@ -40,9 +41,9 @@ module OCTool
40
41
  end
41
42
 
42
43
  def validate_file(path, type)
43
- parser = kwalify_parser(type)
44
- data = parser.parse_file(path)
45
- errors = parser.errors
44
+ kwalify = kwalifyer(type)
45
+ data = kwalify.parse_file(path)
46
+ errors = kwalify.errors
46
47
  raise ValidationError.new(path, errors) unless errors.empty?
47
48
 
48
49
  data
@@ -50,7 +51,7 @@ module OCTool
50
51
  die e.message
51
52
  end
52
53
 
53
- def kwalify_parser(type)
54
+ def kwalifyer(type)
54
55
  schema_file = File.join(schema_dir, "#{type}.yaml")
55
56
  schema = Kwalify::Yaml.load_file(schema_file)
56
57
  validator = Kwalify::Validator.new(schema)
@@ -64,40 +65,57 @@ module OCTool
64
65
  def schema_version
65
66
  @schema_version ||= Kwalify::Yaml.load_file(@config_file)['schema_version']
66
67
  rescue StandarError
67
- STDERR.puts '[FAIL] Unable to read schema_version'
68
+ warn '[FAIL] Unable to read schema_version'
68
69
  exit(1)
69
70
  end
70
71
 
71
- # Check that all data files are valid.
72
+ # Validate and load data in one pass.
72
73
  def validate_data
73
74
  base_dir = File.dirname(@config_file)
74
75
  config = validate_file(@config_file, 'config')
76
+ sys = System.new(config)
75
77
  config['includes'].each do |inc|
76
78
  path = File.join(base_dir, inc['path'])
77
- type = inc['type']
78
- validate_file(path, type)
79
+ sys.data << include_data(path, inc['type'])
79
80
  end
80
- config
81
+ sys
81
82
  end
82
83
 
83
- def load_system
84
- base_dir = File.dirname(@config_file)
85
- config = load_file(@config_file, 'config')
86
- system = System.new(config)
87
- config.includes.each do |inc|
88
- path = File.join(base_dir, inc.path)
89
- system.data << load_file(path, inc.type)
84
+ def include_data(path, type)
85
+ data = validate_file(path, type)
86
+ data['type'] = type
87
+ method("parsed_#{type}".to_sym).call(data)
88
+ end
89
+
90
+ def parsed_component(component)
91
+ component['attestations'].map! do |a|
92
+ # Add a "component_key" field to each attestation.
93
+ a['component_key'] = component['component_key']
94
+ a['satisfies'].map! do |s|
95
+ # Add "attestation_key" to each control satisfied by this attestation.
96
+ s['attestation_key'] = a['summary']
97
+ # Add "component_key" to each control satisfied by this attestation.
98
+ s['component_key'] = component['component_key']
99
+ s
100
+ end
101
+ a
90
102
  end
91
- system
103
+ component
92
104
  end
93
105
 
94
- def load_file(path, type)
95
- die "#{File.expand_path(path)} not readable" unless File.readable?(path)
96
- klass = Object.const_get("OCTool::#{type.capitalize}")
97
- ydoc = Kwalify::Yaml.load_file(path)
98
- klass.new(ydoc)
99
- rescue SystemCallError, Kwalify::SyntaxError => e
100
- die e.message
106
+ def parsed_standard(standard)
107
+ # Add 'standard_key' to each control family and to each control.
108
+ standard['families'].map! { |f| f['standard_key'] = standard['standard_key']; f }
109
+ standard['controls'].map! { |c| c['standard_key'] = standard['standard_key']; c }
110
+ standard
111
+ end
112
+
113
+ def parsed_certification(cert)
114
+ cert['requires'].map! { |r| r['certification_key'] = cert['certification_key']; r }
115
+ cert
101
116
  end
117
+
118
+ alias load_system validate_data
119
+ alias load_file validate_file
102
120
  end
103
121
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
 
3
5
  module OCTool
@@ -15,17 +17,18 @@ module OCTool
15
17
  require 'paru/pandoc'
16
18
  Paru::Pandoc.new
17
19
  end
18
- rescue UncaughtThrowError => e
19
- STDERR.puts '[FAIL] octool requires pandoc to generate the SSP. Is pandoc installed?'
20
+ rescue UncaughtThrowError
21
+ warn '[FAIL] octool requires pandoc to generate the SSP. Is pandoc installed?'
20
22
  exit(1)
21
23
  end
22
24
 
23
25
  def generate
24
- if not File.writable?(@output_dir)
25
- STDERR.puts "[FAIL] #{@output_dir} is not writable"
26
+ unless File.writable?(@output_dir)
27
+ warn "[FAIL] #{@output_dir} is not writable"
26
28
  exit(1)
27
29
  end
28
30
  render_template
31
+ write_acronyms
29
32
  write 'pdf'
30
33
  write 'docx'
31
34
  end
@@ -39,6 +42,15 @@ module OCTool
39
42
  puts 'done'
40
43
  end
41
44
 
45
+ def write_acronyms
46
+ return unless @system.acronyms
47
+
48
+ out_path = File.join(@output_dir, 'acronyms.json')
49
+ File.open(out_path, 'w') { |f| f.write JSON.pretty_generate(@system.acronyms) }
50
+ ENV['PANDOC_ACRONYMS_ACRONYMS'] = out_path
51
+ end
52
+
53
+ # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
42
54
  def write(type = 'pdf')
43
55
  out_path = File.join(@output_dir, "ssp.#{type}")
44
56
  print "Building #{out_path} ... "
@@ -50,11 +62,16 @@ module OCTool
50
62
  toc_depth 3
51
63
  number_sections
52
64
  highlight_style 'pygments'
65
+ filter 'pandoc-acronyms' if ENV['PANDOC_ACRONYMS_ACRONYMS']
66
+ # https://en.wikibooks.org/wiki/LaTeX/Source_Code_Listings#Encoding_issue
67
+ # Uncomment the following line after the "listings" package is compatible with utf8
68
+ # listings
53
69
  end
54
70
  output = converter << File.read(md_path)
55
71
  File.new(out_path, 'wb').write(output)
56
72
  puts 'done'
57
73
  end
74
+ # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
58
75
 
59
76
  def md_path
60
77
  @md_path ||= File.join(@output_dir, 'ssp.md')
@@ -6,73 +6,79 @@ module OCTool
6
6
  attr_accessor :config
7
7
  attr_accessor :data
8
8
 
9
+ TABLE_NAMES = [
10
+ 'components',
11
+ 'satisfies',
12
+ 'attestations',
13
+ 'standards',
14
+ 'controls',
15
+ 'families',
16
+ 'certifications',
17
+ 'requires',
18
+ ].freeze
19
+
9
20
  def initialize(config)
10
21
  @config = config
11
22
  @data = []
12
23
  end
13
24
 
25
+ def acronyms
26
+ @acronyms ||= config['acronyms']
27
+ end
28
+
14
29
  def certifications
15
- @certifications ||= @data.select { |e| e.is_a?(OCTool::Certification) }
30
+ @certifications ||= data.select { |e| e['type'] == 'certification' }
16
31
  end
17
32
 
18
33
  def components
19
- @components ||= @data.select { |e| e.is_a?(OCTool::Component) }
34
+ @components ||= data.select { |e| e['type'] == 'component' }
20
35
  end
21
36
 
22
37
  def standards
23
- @standards ||= @data.select { |e| e.is_a?(OCTool::Standard) }
38
+ @standards ||= data.select { |e| e['type'] == 'standard' }
24
39
  end
25
40
 
26
41
  # List of all attestations claimed by components in the system.
27
42
  def attestations
28
- @attestations ||= begin
29
- @attestations = []
30
- components.each do |c|
31
- # Add a "component_key" field to each attestation.
32
- c.attestations.map! { |e| e['component_key'] = c.component_key; e }
33
- @attestations << c.attestations
34
- end
35
- @attestations.flatten!
36
- end
43
+ @attestations ||= components.map { |c| c['attestations'] }.flatten
37
44
  end
38
45
 
39
46
  # List of all coverages.
40
47
  def satisfies
41
- @satisfies ||= begin
42
- @satisfies = []
43
- attestations.each do |a|
44
- # Add an "attestation_key" field to each cover.
45
- a.satisfies.map! { |e| e['component_key'] = a.commponent_key; e }
46
- a.satisfies.map! { |e| e['attestation_key'] = a.attestation_summary; e }
47
- @satisfies << a.satisfies
48
- end
49
- @satisfies.flatten!
50
- end
48
+ @satisfies ||= attestations.map { |a| a['satisfies'] }.flatten
51
49
  end
52
50
 
53
51
  # List of all controls defined by standards in the system.
54
52
  def controls
55
- @controls || begin
56
- @controls = []
57
- standards.each do |s|
58
- # Add a "standard_key" field to each control.
59
- s.controls.map! { |e| e['standard_key'] = s.standard_key; e }
60
- @controls << s.controls
61
- end
62
- @controls.flatten!
63
- end
53
+ @controls ||= standards.map { |s| s['controls'] }.flatten
64
54
  end
65
55
 
66
56
  # List of all families defined by standards in the system.
67
57
  def families
68
- @families || begin
69
- @families = []
70
- standards.each do |s|
71
- # Add a "standard_key" field to each family.
72
- s.families.map! { |e| e['standard_key'] = s.standard_key; e }
73
- @families << s.families
74
- end
75
- @families.flatten!
58
+ @families ||= standards.map { |s| s['families'] }.flatten
59
+ end
60
+
61
+ # List of required controls for all certifications.
62
+ def requires
63
+ @requires ||= certifications.map { |c| c['requires'] }.flatten
64
+ end
65
+
66
+ def dump(writable_dir)
67
+ TABLE_NAMES.each do |table|
68
+ write_csv method(table.to_sym).call, File.join(writable_dir, "#{table}.csv")
69
+ end
70
+ end
71
+
72
+ # Convert array of hashes into a CSV.
73
+ def write_csv(ary, filename)
74
+ # Throw away nested hashes. The parser already created separate tables for them.
75
+ ary = ary.map { |e| e.reject { |_, val| val.is_a?(Enumerable) } }
76
+
77
+ warn "[INFO] write #{filename}"
78
+ CSV.open(filename, 'wb') do |csv|
79
+ column_names = ary.first.keys
80
+ csv << column_names
81
+ ary.each { |hash| csv << hash.values_at(*column_names) }
76
82
  end
77
83
  end
78
84
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OCTool
4
- VERSION = '0.0.4'
4
+ VERSION = '0.0.9'
5
5
  end
@@ -1,6 +1,6 @@
1
1
  == octool - Open Compliance Tool
2
2
 
3
- v0.0.4
3
+ v0.0.9
4
4
 
5
5
  === Global Options
6
6
  === --help
@@ -14,6 +14,18 @@ Display the program version
14
14
 
15
15
 
16
16
  === Commands
17
+ ==== Command: <tt>csv path/to/system/config.yaml</tt>
18
+ export data to CSV
19
+
20
+
21
+ ===== Options
22
+ ===== -d|--dir path/to/output/dir
23
+
24
+ where to store outputs
25
+
26
+ [Default Value] /tmp
27
+ Default output directory respects env vars TMPDIR, TMP, TEMP
28
+
17
29
  ==== Command: <tt>help command</tt>
18
30
  Shows a list of commands or help for one command
19
31
 
@@ -25,7 +37,7 @@ List commands one per line, to assist with shell completion
25
37
 
26
38
 
27
39
  ==== Command: <tt>ssp path/to/system/config.yaml</tt>
28
- generate System Security Plan
40
+ validate data and generate System Security Plan
29
41
 
30
42
 
31
43
  ===== Options
@@ -0,0 +1,27 @@
1
+ ---
2
+ type: map
3
+ class: Certification
4
+ mapping:
5
+ certification_key:
6
+ desc: A short, unique identifier for this certification.
7
+ required: true
8
+ type: str
9
+ unique: true
10
+ name:
11
+ desc: A human-friendly name for the certification.
12
+ required: true
13
+ type: str
14
+ requires:
15
+ desc: List of control IDs required by the certification.
16
+ required: true
17
+ type: seq
18
+ sequence:
19
+ - type: map
20
+ class: ControlID
21
+ mapping:
22
+ standard_key:
23
+ required: true
24
+ type: str
25
+ control_key:
26
+ required: true
27
+ type: str