octool 0.0.3 → 0.0.8

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: 2b4309625dcbe93d28161179e763a38c4d4c25bd46f75b8a717e314167984356
4
- data.tar.gz: eb021712e5165c88df02c1ecb40b6017c4c1abbe9c05c7ef77e074255b04298a
3
+ metadata.gz: ed9a9d64af06f7c36bf6559eab5919e7b5488ec5a265830d757967dc60784b28
4
+ data.tar.gz: 0b71a16cd55225d995f0f1ff31c24517900726fe6e9a70a9ae54fa6b93b770fb
5
5
  SHA512:
6
- metadata.gz: 4534786fd6fbb5a0e1be118872e9a0fba4cb50b87d05b59ba4262b6cf6fc884d3f81b9dd48690cb382c5f5cc347228e37497dc2a4c02ecc9bdb2c1290b4268b1
7
- data.tar.gz: a715d521b431f1f20770bba0df3ac1eb2091f837a1902c017bc15d881f09a9301c3eef794f54c18d7eccc9b8836b8d347a0d71819a1a7b688590f5fb14f4a9be
6
+ metadata.gz: 338981986492cd44db6b36844f833865b5ed8966c332a74ed313d12650d00fe0f8d14dccd8db04bf0421adf305619a97bc3a5f17a0ff2ee400537c087f2d7895
7
+ data.tar.gz: 634d4943387eae05d72ab887c7e555fcb0c119ce5a0cec02ae855dc42cdba95f46a1a553c541b388e7c6e5f0edf141c3ade05870e6e510938426535f9e0ec2f5
data/bin/octool CHANGED
@@ -8,6 +8,22 @@ require 'octool'
8
8
  class App
9
9
  extend GLI::App
10
10
 
11
+ def self.data_dir
12
+ [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], '/tmp'].each do |dir|
13
+ next if dir.nil?
14
+
15
+ stat = File.stat(dir)
16
+ return dir if stat.directory? && stat.writable?
17
+ end
18
+ OCTool::DEFAULT_OUTPUT_DIR
19
+ end
20
+
21
+ def self.find_config(args)
22
+ path = args.first || OCTool::DEFAULT_CONFIG_FILENAME
23
+ path = File.join(path, OCTool::DEFAULT_CONFIG_FILENAME) if File.directory?(path)
24
+ File.expand_path(path)
25
+ end
26
+
11
27
  program_desc 'Open Compliance Tool'
12
28
  version OCTool::VERSION
13
29
 
@@ -35,11 +51,28 @@ class App
35
51
  v.default_command :data
36
52
  end
37
53
 
38
- 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'
39
72
  arg_name 'path/to/system/config.yaml'
40
73
  command :ssp do |s|
41
74
  s.desc 'where to store outputs'
42
- s.default_value OCTool::DEFAULT_OUTPUT_DIR
75
+ s.default_value data_dir
43
76
  s.long_desc 'Default output directory respects env vars TMPDIR, TMP, TEMP'
44
77
  s.arg_name 'path/to/output/dir'
45
78
  s.flag [:d, :dir]
@@ -76,20 +109,14 @@ class App
76
109
  # Error logic here
77
110
  # Return false to skip default error handling.
78
111
  if ENV['DEBUG']
79
- STDERR.puts exception.message
80
- STDERR.puts exception.backtrace
112
+ warn exception.message
113
+ warn exception.backtrace
81
114
  pp exception
82
115
  false
83
116
  end
84
- STDERR.puts '[FAIL]'
117
+ warn '[FAIL]'
85
118
  true
86
119
  end
87
120
  end
88
121
 
89
- def find_config(args)
90
- path = args.first || OCTool::DEFAULT_CONFIG_FILENAME
91
- path = File.join(path, OCTool::DEFAULT_CONFIG_FILENAME) if File.directory?(path)
92
- path
93
- end
94
-
95
122
  exit App.run(ARGV)
@@ -3,8 +3,8 @@
3
3
  require 'octool/version.rb'
4
4
 
5
5
  # Built-ins.
6
+ require 'csv'
6
7
  require 'pp'
7
- require 'tmpdir'
8
8
 
9
9
  # 3rd-party libs.
10
10
  require 'kwalify'
@@ -16,12 +16,6 @@ require 'octool/parser'
16
16
  require 'octool/ssp'
17
17
  require 'octool/system'
18
18
 
19
- # Generated libs.
20
- require 'octool/generated/certification'
21
- require 'octool/generated/component'
22
- require 'octool/generated/config'
23
- require 'octool/generated/standard'
24
-
25
19
  # Mixins.
26
20
  module OCTool
27
21
  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.1'
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
@@ -6,21 +8,28 @@ module OCTool
6
8
  def initialize(system, output_dir)
7
9
  @system = system
8
10
  @output_dir = output_dir
9
- # Load paru/pandoc late enough that help functions work
10
- # and early enough to be confident that we catch the correct error.
11
- require 'paru/pandoc'
12
- rescue UncaughtThrowError => e
13
- STDERR.puts '[FAIL] octool requires pandoc to generate the SSP. Is pandoc installed?'
11
+ end
12
+
13
+ def pandoc
14
+ @pandoc ||= begin
15
+ # Load paru/pandoc late enough that help functions work
16
+ # and early enough to be confident that we catch the correct error.
17
+ require 'paru/pandoc'
18
+ Paru::Pandoc.new
19
+ end
20
+ rescue UncaughtThrowError
21
+ warn '[FAIL] octool requires pandoc to generate the SSP. Is pandoc installed?'
14
22
  exit(1)
15
23
  end
16
24
 
17
25
  def generate
18
- if not File.writable?(@output_dir)
19
- STDERR.puts "[FAIL] #{@output_dir} is not writable"
26
+ unless File.writable?(@output_dir)
27
+ warn "[FAIL] #{@output_dir} is not writable"
20
28
  exit(1)
21
29
  end
22
30
  render_template
23
- write
31
+ write 'pdf'
32
+ write 'docx'
24
33
  end
25
34
 
26
35
  def render_template
@@ -32,29 +41,30 @@ module OCTool
32
41
  puts 'done'
33
42
  end
34
43
 
35
- def write
36
- print "Building #{pdf_path} ... "
37
- pandoc = Paru::Pandoc.new
44
+ # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
45
+ def write(type = 'pdf')
46
+ out_path = File.join(@output_dir, "ssp.#{type}")
47
+ print "Building #{out_path} ... "
38
48
  converter = pandoc.configure do
39
49
  from 'markdown'
40
- to 'pdf'
50
+ to type
41
51
  pdf_engine 'lualatex'
42
52
  toc
43
53
  toc_depth 3
44
54
  number_sections
45
55
  highlight_style 'pygments'
56
+ # https://en.wikibooks.org/wiki/LaTeX/Source_Code_Listings#Encoding_issue
57
+ # Uncomment the following line after the "listings" package is compatible with utf8
58
+ # listings
46
59
  end
47
60
  output = converter << File.read(md_path)
48
- File.new(pdf_path, 'wb').write(output)
61
+ File.new(out_path, 'wb').write(output)
49
62
  puts 'done'
50
63
  end
64
+ # rubocop:enable Metrics/AbcSize,Metrics/MethodLength
51
65
 
52
66
  def md_path
53
67
  @md_path ||= File.join(@output_dir, 'ssp.md')
54
68
  end
55
-
56
- def pdf_path
57
- @pdf_path ||= File.join(@output_dir, 'ssp.pdf')
58
- end
59
69
  end
60
70
  end
@@ -6,73 +6,75 @@ 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
 
14
25
  def certifications
15
- @certifications ||= @data.select { |e| e.is_a?(OCTool::Certification) }
26
+ @certifications ||= data.select { |e| e['type'] == 'certification' }
16
27
  end
17
28
 
18
29
  def components
19
- @components ||= @data.select { |e| e.is_a?(OCTool::Component) }
30
+ @components ||= data.select { |e| e['type'] == 'component' }
20
31
  end
21
32
 
22
33
  def standards
23
- @standards ||= @data.select { |e| e.is_a?(OCTool::Standard) }
34
+ @standards ||= data.select { |e| e['type'] == 'standard' }
24
35
  end
25
36
 
26
37
  # List of all attestations claimed by components in the system.
27
38
  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
39
+ @attestations ||= components.map { |c| c['attestations'] }.flatten
37
40
  end
38
41
 
39
42
  # List of all coverages.
40
43
  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
44
+ @satisfies ||= attestations.map { |a| a['satisfies'] }.flatten
51
45
  end
52
46
 
53
47
  # List of all controls defined by standards in the system.
54
48
  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
49
+ @controls ||= standards.map { |s| s['controls'] }.flatten
64
50
  end
65
51
 
66
52
  # List of all families defined by standards in the system.
67
53
  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!
54
+ @families ||= standards.map { |s| s['families'] }.flatten
55
+ end
56
+
57
+ # List of required controls for all certifications.
58
+ def requires
59
+ @requires ||= certifications.map { |c| c['requires'] }.flatten
60
+ end
61
+
62
+ def dump(writable_dir)
63
+ TABLE_NAMES.each do |table|
64
+ write_csv method(table.to_sym).call, File.join(writable_dir, "#{table}.csv")
65
+ end
66
+ end
67
+
68
+ # Convert array of hashes into a CSV.
69
+ def write_csv(ary, filename)
70
+ # Throw away nested hashes. The parser already created separate tables for them.
71
+ ary = ary.map { |e| e.reject { |_, val| val.is_a?(Enumerable) } }
72
+
73
+ warn "[INFO] write #{filename}"
74
+ CSV.open(filename, 'wb') do |csv|
75
+ column_names = ary.first.keys
76
+ csv << column_names
77
+ ary.each { |hash| csv << hash.values_at(*column_names) }
76
78
  end
77
79
  end
78
80
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OCTool
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.8'
5
5
  end
@@ -1,6 +1,6 @@
1
1
  == octool - Open Compliance Tool
2
2
 
3
- v0.0.3
3
+ v0.0.8
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
@@ -33,7 +45,7 @@ generate System Security Plan
33
45
 
34
46
  where to store outputs
35
47
 
36
- [Default Value] /data
48
+ [Default Value] /tmp
37
49
  Default output directory respects env vars TMPDIR, TMP, TEMP
38
50
 
39
51
  ==== Command: <tt>validate </tt>
@@ -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
@@ -0,0 +1,60 @@
1
+ ---
2
+ type: map
3
+ class: Component
4
+ mapping:
5
+ name:
6
+ desc: Human-friendly name to appear in the SSP.
7
+ type: str
8
+ required: true
9
+ component_key:
10
+ desc: Unique identifier for referential integrity.
11
+ type: str
12
+ required: true
13
+ description:
14
+ desc: A paragraph or two that describes the component.
15
+ type: str
16
+ required: true
17
+ attestations:
18
+ desc: List of attestations.
19
+ type: seq
20
+ sequence:
21
+ - type: map
22
+ class: Attestation
23
+ mapping:
24
+ summary:
25
+ desc: Arbitrary verbiage to appear in SSP as a TLDR.
26
+ type: str
27
+ required: true
28
+ status:
29
+ desc: To what extent is this attestation "done"?
30
+ type: str
31
+ required: true
32
+ enum:
33
+ - partial
34
+ - complete
35
+ - planned
36
+ - none
37
+ date_verified:
38
+ desc: When was this last verified?
39
+ type: date
40
+ required: false
41
+ satisfies:
42
+ desc: List of control IDs covered by this attestation.
43
+ type: seq
44
+ required: false
45
+ sequence:
46
+ - type: map
47
+ class: ControlID
48
+ mapping:
49
+ standard_key:
50
+ type: text
51
+ required: true
52
+ control_key:
53
+ type: text
54
+ required: true
55
+ narrative:
56
+ desc: |
57
+ Explain how attestation satisfies the indicated controls.
58
+ The content should be in markdown format.
59
+ type: str
60
+ required: true
@@ -0,0 +1,79 @@
1
+ ---
2
+ type: map
3
+ class: Config
4
+ mapping:
5
+ schema_version:
6
+ desc: |
7
+ Must match one of the schema directories in the octool source.
8
+ required: true
9
+ type: str
10
+
11
+ logo:
12
+ desc: Image for title page.
13
+ required: false
14
+ type: map
15
+ class: Logo
16
+ mapping:
17
+ path:
18
+ desc: Path to image.
19
+ type: str
20
+ required: true
21
+ width:
22
+ desc: Width of image, such as "1in" or "254mm"
23
+ type: str
24
+ required: true
25
+
26
+ name:
27
+ desc: Human-friendly to appear in the SSP.
28
+ required: true
29
+ type: str
30
+
31
+ overview:
32
+ desc: Human-friendly description to appear in the SSP.
33
+ required: true
34
+ type: str
35
+
36
+ maintainers:
37
+ desc: Who should somebody contact for questions about this SSP?
38
+ required: true
39
+ type: seq
40
+ sequence:
41
+ - type: str
42
+
43
+ metadata:
44
+ desc: Optional metadata.
45
+ required: false
46
+ type: map
47
+ class: Metadata
48
+ mapping:
49
+ abstract:
50
+ desc: Abstract appears in document metadata.
51
+ required: false
52
+ type: str
53
+ description:
54
+ desc: Description appears in document metadata.
55
+ required: false
56
+ type: str
57
+ '=':
58
+ desc: Arbitrary key:value pair of strings.
59
+ type: str
60
+
61
+ includes:
62
+ desc: Additional files to include from the system repo.
63
+ required: true
64
+ type: seq
65
+ sequence:
66
+ - type: map
67
+ class: Include
68
+ mapping:
69
+ type:
70
+ required: true
71
+ type: str
72
+ enum:
73
+ - certification
74
+ - component
75
+ - standard
76
+ path:
77
+ desc: Path must be relative within the repo.
78
+ required: true
79
+ type: str
@@ -0,0 +1,50 @@
1
+ ---
2
+ type: map
3
+ class: Standard
4
+ mapping:
5
+ name:
6
+ desc: Human-friendly name to appear in SSP.
7
+ type: str
8
+ required: true
9
+
10
+ standard_key:
11
+ desc: Unique ID to use within YAML files.
12
+ type: str
13
+ required: true
14
+
15
+ families:
16
+ desc: Optional list of control families.
17
+ type: seq
18
+ required: false
19
+ sequence:
20
+ - type: map
21
+ class: ControlFamily
22
+ mapping:
23
+ family_key:
24
+ desc: Unique ID of the family
25
+ type: str
26
+ unique: true
27
+ name:
28
+ desc: Human-friendly name of the family
29
+ type: str
30
+ controls:
31
+ desc: Mandatory list of controls defined by the standard.
32
+ required: true
33
+ type: seq
34
+ sequence:
35
+ - type: map
36
+ class: Control
37
+ mapping:
38
+ control_key:
39
+ type: str
40
+ unique: true
41
+ required: true
42
+ family_key:
43
+ type: str
44
+ required: false
45
+ name:
46
+ type: str
47
+ required: true
48
+ description:
49
+ type: str
50
+ required: true
@@ -1,17 +1,25 @@
1
1
  ---
2
- title: "<%= @system.config.name -%>"
2
+ <% if @system.config['logo'] -%>
3
+ title: |
4
+ ![](<%= @system.config['logo']['path'] -%>){width=<%= @system.config['logo']['width'] %>}
5
+
6
+ <%= @system.config['name'] %>
7
+ <% else %>
8
+ title: "<%= @system.config['name'] -%>"
9
+ <% end %>
10
+
3
11
  subtitle: "System Security Plan"
4
12
 
5
13
  author:
6
- <% @system.config.maintainers.each do |maintainer| %>
14
+ <% @system.config['maintainers'].each do |maintainer| %>
7
15
  - <%= maintainer -%>
8
16
  <% end %>
9
17
 
10
18
  absract: |
11
- <%= @system.config.metadata.abstract rescue 'None' %>
19
+ <%= @system.config['metadata']['abstract'] rescue 'None' %>
12
20
 
13
21
  description: |
14
- <%= @system.config.metadata.description rescue 'None' %>
22
+ <%= @system.config['metadata']['description'] rescue 'None' %>
15
23
 
16
24
  fontsize: 11pt
17
25
  mainfont: NotoSans
@@ -25,10 +33,10 @@ mainfontoptions:
25
33
  - BoldFont=*-Bold
26
34
  - BoldItalicFont=*-BoldItalic
27
35
 
28
- lof: false
29
- lot: false
36
+ lof: true
37
+ lot: true
30
38
  colorlinks: true
31
- linkcolor: blue
39
+ linkcolor: black # internal links (e.g., lof and lot)
32
40
  urlcolor: blue
33
41
 
34
42
  documentclass: report
@@ -44,52 +52,128 @@ geometry:
44
52
  - left=2cm
45
53
  - right=2cm
46
54
  - bottom=2cm
55
+
56
+ header-includes:
57
+ - |
58
+ ```{=latex}
59
+ % https://github.com/jgm/pandoc/wiki/Pandoc-Tricks#left-aligning-tables-in-latex
60
+ \usepackage[margins=raggedright]{floatrow}
61
+ ```
62
+ - |
63
+ ```{=latex}
64
+ % https://github.com/jgm/pandoc/wiki/Pandoc-Tricks#definition-list-terms-on-their-own-line-in-latex
65
+ % "Clone" the original \item command
66
+ \let\originalitem\item
67
+
68
+ % Redefine the \item command using the "clone"
69
+ \makeatletter
70
+ \renewcommand{\item}[1][\@nil]{%
71
+ \def\tmp{#1}%
72
+ \ifx\tmp\@nnil\originalitem\else\originalitem[#1]\hfill\par\fi}
73
+ \makeatother
74
+ ```
75
+ - |
76
+ ```{=latex}
77
+ % The are at least two ways to configure how LaTeX floats figures.
78
+ %
79
+ % 1. One approach is described in section 17.2 of
80
+ % http://tug.ctan.org/tex-archive/info/epslatex/english/epslatex.pdf
81
+ % However, the approach described there requires to teach people
82
+ % how to write LaTeX cross-references in markdown.
83
+ %
84
+ % 2. Force figures, listings, etc., to float "[H]ere".
85
+ % This is a LaTeX anti-pattern because it causes large gaps of whitespace on some pages.
86
+ % This approach avoids having to teach people to create LaTeX cross-references.
87
+ % https://tex.stackexchange.com/a/101726
88
+ %
89
+ % Use option 2.
90
+ \usepackage{float}
91
+ \floatplacement{figure}{H}
92
+ ```
47
93
  ---
48
94
 
49
- # <%= @system.config.name %>
95
+ # Introduction
50
96
 
51
- ## Overview
97
+ ## About this document
98
+
99
+ A System Security Plan (SSP) is a document to describe security controls in use
100
+ on an information system and their implementation. An SSP provides:
101
+
102
+ - Narrative of security control implementation
103
+ - Description of components and services
104
+ - System data flows and authorization boundaries
52
105
 
53
- <%= @system.config.overview %>
54
106
 
55
107
  ## Standards
56
108
 
57
- This System Security Plan (SSP) addresses these standards:
109
+ This SSP draws from these standards:
58
110
 
59
111
  <% @system.standards.each do |s| -%>
60
- - <%= s.name %>
112
+ - <%= s['name'] %>
61
113
  <% end %>
62
114
 
63
115
  The full copy of each standard is included in the appendix.
64
116
 
65
117
 
66
- ## Components
118
+ ## Certifications
67
119
 
68
- <% @system.components.each do |c| %>
69
- ### <%= c.name %>
120
+ A certification is a logical grouping of controls that are of interest to
121
+ a given subject. A particular certification does not necessarily target all
122
+ controls from a standard, nor does a particular certification need to draw
123
+ from a single standard.
70
124
 
71
- <%= c.description %>
125
+ This SSP addresses these certifications:
126
+
127
+ <% @system.certifications.each do |c| -%>
128
+ - <%=c['name']%>
129
+
130
+ <% c['requires'].each do |r| -%>
131
+ - <%=r['standard_key']-%> control <%=r['control_key']%>
132
+ <% end -%>
72
133
 
73
- <% if c.attestations.empty? %>
74
- _The organization has not yet documented attestations for this component_.
75
- <% else %>
76
- The organization offers the following attestations for this component.
77
134
  <% end %>
78
135
 
79
- <% c.attestations.each do |a| %>
80
- #### <%= a.summary %>
81
136
 
82
- Status: <%= a.status %>
137
+ # <%= @system.config['name'] %>
83
138
 
84
- Date verified: <%= a.date_verified if a.date_verified %>
139
+ ## Overview
85
140
 
86
- Satisfies:
141
+ <%= @system.config['overview'] %>
87
142
 
88
- <% a.satisfies.each do |cid| -%>
89
- - <%= cid.standard_key %> control <%= cid.control_key %>
90
- <% end -%>
91
143
 
92
- <%= a.narrative %>
144
+ ## Components
145
+
146
+ <% @system.components.each do |c| %>
147
+ ### <%= c['name'] %>
148
+
149
+ <%= c['description'] %>
150
+
151
+ <% if c['attestations'].empty? %>
152
+ _The organization has not yet documented attestations for this component_.
153
+ <% else %>
154
+ The organization offers the following attestations for this component.
155
+ <% end %>
156
+
157
+ <% c['attestations'].compact.each do |a| %>
158
+ #### <%= a['summary'] %>
159
+
160
+ +----------+---------------+--------------------------------------------------------------+
161
+ | Status | Date verified | Satisfies |
162
+ +==========+===============+==============================================================+
163
+ <%
164
+ s = a['satisfies'][0]
165
+ verbiage = sprintf('%-58s', [s['standard_key'], 'control', s['control_key']].join(' '))
166
+ -%>
167
+ | <%=sprintf('%-8s', a['status'])-%> | <%=sprintf('%-13s', a['date_verified'])-%> | - <%=verbiage-%> |
168
+ <%
169
+ a['satisfies'][1..].each do |s|
170
+ verbiage = sprintf('%-58s', [s['standard_key'], 'control', s['control_key']].join(' '))
171
+ -%>
172
+ | | | - <%=verbiage-%> |
173
+ <% end -%>
174
+ +----------+---------------+--------------------------------------------------------------+
175
+
176
+ <%= a['narrative'] %>
93
177
 
94
178
  <% end %>
95
179
  <% end %>
@@ -98,23 +182,29 @@ Satisfies:
98
182
  # Appendix: Standards
99
183
 
100
184
  <% @system.standards.each do |s| %>
101
- ## <%=s.name %>
185
+ ## <%=s['name'] %>
102
186
 
103
- <% if s.families and !s.families.empty? %>
187
+ <% if s['families'] and !s['families'].empty? %>
104
188
  ### Families
105
189
 
106
- <% s.families.each do |family| %>
107
- - <%= family.family_key -%>: <%= family.name %>
108
- <% end %>
190
+ <%=s['name']-%> categorizes controls into logical groups called families.
191
+
192
+ | Family abbreviation | Family name |
193
+ | -------------------------- | -------------------- |
194
+ <% s['families'].each do |family| -%>
195
+ | <%=family['family_key']-%> | <%=family['name']-%> |
196
+ <% end -%>
197
+
198
+ : Control families for <%=s['name']%>
109
199
 
110
200
  <% end %>
111
201
 
112
202
  ### Controls
113
203
 
114
- <% s.controls.each do |c| %>
115
- #### Control <%= c.control_key -%>: <%= c.name %>
204
+ <% s['controls'].each do |c| %>
205
+ #### Control <%= c['control_key'] -%>: <%= c['name'] %>
116
206
 
117
- <%= c.description %>
207
+ <%= c['description'] %>
118
208
 
119
209
  <% end %>
120
210
  <% end %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: octool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Morgan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-15 00:00:00.000000000 Z
11
+ date: 2020-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -147,10 +147,6 @@ files:
147
147
  - bin/octool
148
148
  - lib/octool.rb
149
149
  - lib/octool/constants.rb
150
- - lib/octool/generated/certification.rb
151
- - lib/octool/generated/component.rb
152
- - lib/octool/generated/config.rb
153
- - lib/octool/generated/standard.rb
154
150
  - lib/octool/parser.rb
155
151
  - lib/octool/ssp.rb
156
152
  - lib/octool/system.rb
@@ -160,6 +156,10 @@ files:
160
156
  - schemas/v1.0.0/component.yaml
161
157
  - schemas/v1.0.0/config.yaml
162
158
  - schemas/v1.0.0/standard.yaml
159
+ - schemas/v1.0.1/certification.yaml
160
+ - schemas/v1.0.1/component.yaml
161
+ - schemas/v1.0.1/config.yaml
162
+ - schemas/v1.0.1/standard.yaml
163
163
  - templates/ssp.erb
164
164
  homepage: https://github.com/jumanjiman/octool
165
165
  licenses:
@@ -1,35 +0,0 @@
1
- require 'kwalify/util/hashlike'
2
-
3
- module OCTool
4
-
5
-
6
- class Certification
7
- include Kwalify::Util::HashLike
8
- def initialize(hash=nil)
9
- if hash.nil?
10
- return
11
- end
12
- @certification_key = hash['certification_key']
13
- @name = hash['name']
14
- @requires = (v=hash['requires']) ? v.map!{|e| e.is_a?(ControlID) ? e : ControlID.new(e)} : v
15
- end
16
- attr_accessor :certification_key # str
17
- attr_accessor :name # str
18
- attr_accessor :requires # seq
19
- end
20
-
21
-
22
- class ControlID
23
- include Kwalify::Util::HashLike
24
- def initialize(hash=nil)
25
- if hash.nil?
26
- return
27
- end
28
- @standard_key = hash['standard_key']
29
- @control_key = hash['control_key']
30
- end
31
- attr_accessor :standard_key # str
32
- attr_accessor :control_key # str
33
- end
34
-
35
- end
@@ -1,57 +0,0 @@
1
- require 'kwalify/util/hashlike'
2
-
3
- module OCTool
4
-
5
-
6
- class Component
7
- include Kwalify::Util::HashLike
8
- def initialize(hash=nil)
9
- if hash.nil?
10
- return
11
- end
12
- @name = hash['name']
13
- @component_key = hash['component_key']
14
- @description = hash['description']
15
- @attestations = (v=hash['attestations']) ? v.map!{|e| e.is_a?(Attestation) ? e : Attestation.new(e)} : v
16
- end
17
- attr_accessor :name # str
18
- attr_accessor :component_key # str
19
- attr_accessor :description # str
20
- attr_accessor :attestations # seq
21
- end
22
-
23
-
24
- class Attestation
25
- include Kwalify::Util::HashLike
26
- def initialize(hash=nil)
27
- if hash.nil?
28
- return
29
- end
30
- @summary = hash['summary']
31
- @status = hash['status']
32
- @date_verified = hash['date_verified']
33
- @satisfies = (v=hash['satisfies']) ? v.map!{|e| e.is_a?(ControlID) ? e : ControlID.new(e)} : v
34
- @narrative = hash['narrative']
35
- end
36
- attr_accessor :summary # str
37
- attr_accessor :status # str
38
- attr_accessor :date_verified # date
39
- attr_accessor :satisfies # seq
40
- attr_accessor :narrative # str
41
- end
42
-
43
-
44
- class ControlID
45
- include Kwalify::Util::HashLike
46
- def initialize(hash=nil)
47
- if hash.nil?
48
- return
49
- end
50
- @standard_key = hash['standard_key']
51
- @control_key = hash['control_key']
52
- end
53
- attr_accessor :standard_key # text
54
- attr_accessor :control_key # text
55
- end
56
-
57
- end
@@ -1,55 +0,0 @@
1
- require 'kwalify/util/hashlike'
2
-
3
- module OCTool
4
-
5
-
6
- class Config
7
- include Kwalify::Util::HashLike
8
- def initialize(hash=nil)
9
- if hash.nil?
10
- return
11
- end
12
- @schema_version = hash['schema_version']
13
- @name = hash['name']
14
- @overview = hash['overview']
15
- @maintainers = hash['maintainers']
16
- @metadata = (v=hash['metadata']) && v.is_a?(Hash) ? Metadata.new(v) : v
17
- @includes = (v=hash['includes']) ? v.map!{|e| e.is_a?(Include) ? e : Include.new(e)} : v
18
- end
19
- attr_accessor :schema_version # str
20
- attr_accessor :name # str
21
- attr_accessor :overview # str
22
- attr_accessor :maintainers # seq
23
- attr_accessor :metadata # map
24
- attr_accessor :includes # seq
25
- end
26
-
27
- ## Optional metadata.
28
- class Metadata
29
- include Kwalify::Util::HashLike
30
- def initialize(hash=nil)
31
- if hash.nil?
32
- return
33
- end
34
- @abstract = hash['abstract']
35
- @description = hash['description']
36
- end
37
- attr_accessor :abstract # str
38
- attr_accessor :description # str
39
- end
40
-
41
-
42
- class Include
43
- include Kwalify::Util::HashLike
44
- def initialize(hash=nil)
45
- if hash.nil?
46
- return
47
- end
48
- @type = hash['type']
49
- @path = hash['path']
50
- end
51
- attr_accessor :type # str
52
- attr_accessor :path # str
53
- end
54
-
55
- end
@@ -1,55 +0,0 @@
1
- require 'kwalify/util/hashlike'
2
-
3
- module OCTool
4
-
5
-
6
- class Standard
7
- include Kwalify::Util::HashLike
8
- def initialize(hash=nil)
9
- if hash.nil?
10
- return
11
- end
12
- @name = hash['name']
13
- @standard_key = hash['standard_key']
14
- @families = (v=hash['families']) ? v.map!{|e| e.is_a?(ControlFamily) ? e : ControlFamily.new(e)} : v
15
- @controls = (v=hash['controls']) ? v.map!{|e| e.is_a?(Control) ? e : Control.new(e)} : v
16
- end
17
- attr_accessor :name # str
18
- attr_accessor :standard_key # str
19
- attr_accessor :families # seq
20
- attr_accessor :controls # seq
21
- end
22
-
23
-
24
- class ControlFamily
25
- include Kwalify::Util::HashLike
26
- def initialize(hash=nil)
27
- if hash.nil?
28
- return
29
- end
30
- @family_key = hash['family_key']
31
- @name = hash['name']
32
- end
33
- attr_accessor :family_key # str
34
- attr_accessor :name # str
35
- end
36
-
37
-
38
- class Control
39
- include Kwalify::Util::HashLike
40
- def initialize(hash=nil)
41
- if hash.nil?
42
- return
43
- end
44
- @control_key = hash['control_key']
45
- @family_key = hash['family_key']
46
- @name = hash['name']
47
- @description = hash['description']
48
- end
49
- attr_accessor :control_key # str
50
- attr_accessor :family_key # str
51
- attr_accessor :name # str
52
- attr_accessor :description # str
53
- end
54
-
55
- end