octool 0.0.3 → 0.0.8

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: 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