oscal 0.1.0 → 0.1.1
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 +4 -4
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +24 -0
- data/.gitignore +11 -0
- data/.gitmodules +3 -0
- data/.hound.yml +5 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +2 -0
- data/README.adoc +63 -0
- data/bin/console +30 -0
- data/bin/setup +8 -0
- data/exe/convert2oscalyaml.rb +560 -0
- data/lib/oscal/add.rb +25 -0
- data/lib/oscal/address.rb +21 -0
- data/lib/oscal/address_line.rb +10 -0
- data/lib/oscal/alter.rb +21 -0
- data/lib/oscal/back_matter.rb +19 -0
- data/lib/oscal/base64_object.rb +10 -0
- data/lib/oscal/base_class.rb +49 -0
- data/lib/oscal/catalog.rb +50 -10
- data/lib/oscal/choice.rb +10 -0
- data/lib/oscal/citation.rb +21 -0
- data/lib/oscal/combine.rb +10 -0
- data/lib/oscal/common_utils.rb +35 -0
- data/lib/oscal/constraint.rb +19 -0
- data/lib/oscal/control.rb +20 -31
- data/lib/oscal/custom.rb +21 -0
- data/lib/oscal/document_id.rb +10 -0
- data/lib/oscal/email_address.rb +10 -0
- data/lib/oscal/exclude_control.rb +21 -0
- data/lib/oscal/external_id.rb +10 -0
- data/lib/oscal/group.rb +26 -35
- data/lib/oscal/guideline.rb +10 -0
- data/lib/oscal/hash_object.rb +10 -0
- data/lib/oscal/import_object.rb +21 -0
- data/lib/oscal/include_control.rb +21 -0
- data/lib/oscal/insert_control.rb +21 -0
- data/lib/oscal/link.rb +10 -0
- data/lib/oscal/location.rb +30 -0
- data/lib/oscal/location_uuid.rb +10 -0
- data/lib/oscal/matching.rb +10 -0
- data/lib/oscal/member_of_organization.rb +10 -0
- data/lib/oscal/merge.rb +19 -0
- data/lib/oscal/metadata_block.rb +28 -14
- data/lib/oscal/modify.rb +21 -0
- data/lib/oscal/parameter.rb +22 -20
- data/lib/oscal/part.rb +13 -22
- data/lib/oscal/party.rb +35 -0
- data/lib/oscal/party_uuid.rb +10 -0
- data/lib/oscal/profile.rb +32 -7
- data/lib/oscal/property.rb +3 -25
- data/lib/oscal/remove.rb +10 -0
- data/lib/oscal/resource.rb +28 -0
- data/lib/oscal/responsible_party.rb +23 -0
- data/lib/oscal/revision.rb +22 -0
- data/lib/oscal/rlink.rb +19 -0
- data/lib/oscal/role.rb +21 -0
- data/lib/oscal/select.rb +19 -0
- data/lib/oscal/serializer.rb +17 -4
- data/lib/oscal/set_parameter.rb +30 -0
- data/lib/oscal/telephone_number.rb +10 -0
- data/lib/oscal/test.rb +10 -0
- data/lib/oscal/url.rb +10 -0
- data/lib/oscal/value.rb +36 -0
- data/lib/oscal/version.rb +1 -1
- data/lib/oscal/with_id.rb +39 -0
- data/lib/oscal.rb +1 -13
- data/oscal.gemspec +9 -11
- data/spec/oscal/catalog_spec.rb +39 -0
- data/spec/oscal_spec.rb +7 -0
- data/spec/spec_helper.rb +15 -0
- metadata +65 -9
- data/lib/oscal/component.rb +0 -14
- data/lib/oscal/prose.rb +0 -13
- data/lib/oscal/statement.rb +0 -12
@@ -0,0 +1,560 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Confidential and proprietary trade secret material of Ribose, Inc.
|
4
|
+
# (c) 2023 Ribose, Inc. as unpublished work.
|
5
|
+
#
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# This script attempts to read the ISO 27002:2022 controls and convert to
|
9
|
+
# OSCAL yaml file.
|
10
|
+
|
11
|
+
require "fileutils"
|
12
|
+
require "optparse"
|
13
|
+
require "ostruct"
|
14
|
+
require "yaml"
|
15
|
+
require "securerandom"
|
16
|
+
require "asciidoctor"
|
17
|
+
require "nokogiri"
|
18
|
+
require "json"
|
19
|
+
require "time"
|
20
|
+
|
21
|
+
def convert_control_to_oscal(control)
|
22
|
+
clause_no = control["identifier"].split(":")[-1]
|
23
|
+
|
24
|
+
# set control id
|
25
|
+
catalog_control = Catalog::Control.new("clause_#{clause_no}")
|
26
|
+
catalog_control.props = []
|
27
|
+
catalog_control.parts = []
|
28
|
+
|
29
|
+
control.each do |key, value|
|
30
|
+
case key
|
31
|
+
when "identifier"
|
32
|
+
# add control props
|
33
|
+
catalog_control.props << {
|
34
|
+
"name" => "clause",
|
35
|
+
"value" => clause_no,
|
36
|
+
}
|
37
|
+
when "maps_27002_2013"
|
38
|
+
catalog_control.props << {
|
39
|
+
"name" => "maps_27002_2013",
|
40
|
+
"value" => value,
|
41
|
+
}
|
42
|
+
when "title"
|
43
|
+
catalog_control.title = value
|
44
|
+
when "tags"
|
45
|
+
# add tags as props
|
46
|
+
value.each do |k, v|
|
47
|
+
catalog_control.props << {
|
48
|
+
"name" => k,
|
49
|
+
"value" => v,
|
50
|
+
}
|
51
|
+
end
|
52
|
+
when "control"
|
53
|
+
part = Catalog::Part.new
|
54
|
+
part.id = "control_#{clause_no}"
|
55
|
+
part.name = "clause_#{clause_no}_control"
|
56
|
+
part.prose = value.chomp
|
57
|
+
catalog_control.parts << part.to_hash
|
58
|
+
when "purpose"
|
59
|
+
part = Catalog::Part.new
|
60
|
+
part.id = "purpose_#{clause_no}"
|
61
|
+
part.name = "clause_#{clause_no}_purpose"
|
62
|
+
part.prose = value.chomp
|
63
|
+
catalog_control.parts << part.to_hash
|
64
|
+
when "other_info"
|
65
|
+
part = Catalog::Part.new
|
66
|
+
part.id = "other_info_#{clause_no}"
|
67
|
+
part.name = "clause_#{clause_no}_other_info"
|
68
|
+
unless value.empty?
|
69
|
+
part.parts = parse_content(
|
70
|
+
value,
|
71
|
+
clause_no,
|
72
|
+
"other_info",
|
73
|
+
)
|
74
|
+
end
|
75
|
+
catalog_control.parts << part.to_hash
|
76
|
+
when "guidance"
|
77
|
+
# guidance contains multiple parts with title and content
|
78
|
+
# each part may contains empty title
|
79
|
+
# each part contains content
|
80
|
+
# content may contains list or table
|
81
|
+
value.each do |v|
|
82
|
+
part = Catalog::Part.new
|
83
|
+
part.id = "scls_#{clause_no.gsub('.', '-')}"
|
84
|
+
part.name = if v["title"].nil?
|
85
|
+
"clause_#{clause_no}_guidance"
|
86
|
+
else
|
87
|
+
v["title"]
|
88
|
+
end
|
89
|
+
unless v["content"].empty?
|
90
|
+
part.parts = parse_content(
|
91
|
+
v["content"],
|
92
|
+
clause_no,
|
93
|
+
)
|
94
|
+
end
|
95
|
+
catalog_control.parts << part.to_hash
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
catalog_control
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_note(nokogiri_children, _content, part)
|
104
|
+
note = nokogiri_children.css(".note").first.content
|
105
|
+
note = note.gsub("Note", "NOTE:")
|
106
|
+
note_part = Catalog::Part.new
|
107
|
+
note_part.id = "#{part.id}_note"
|
108
|
+
note_part.name = "#{part.name}_note"
|
109
|
+
note_part.prose = note
|
110
|
+
|
111
|
+
note_part
|
112
|
+
end
|
113
|
+
|
114
|
+
def replace_link(nokogiri_children, content)
|
115
|
+
nokogiri_children.xpath("//a/@href").each do |href|
|
116
|
+
href_value = href.value[1..-1]
|
117
|
+
|
118
|
+
clause_no = if href_value.start_with?("scls_")
|
119
|
+
href_value.split("_").last.gsub("-", ".")
|
120
|
+
else
|
121
|
+
href_value
|
122
|
+
end
|
123
|
+
|
124
|
+
content = content.gsub(
|
125
|
+
"[#{href_value}]",
|
126
|
+
"[#{clause_no}](##{href_value})",
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
content
|
131
|
+
end
|
132
|
+
|
133
|
+
def parse_content(content, clause_no, content_type = "guidance")
|
134
|
+
parts = []
|
135
|
+
|
136
|
+
# convert content into html
|
137
|
+
html = Asciidoctor.convert content, safe: :safe
|
138
|
+
# replace \n with space
|
139
|
+
html = html.gsub("\n", " ")
|
140
|
+
html = html.gsub("> <", "><")
|
141
|
+
# parse html by nokogiri
|
142
|
+
htmldoc = Nokogiri::HTML(html)
|
143
|
+
# get the body
|
144
|
+
body = htmldoc.children[1].children[0]
|
145
|
+
|
146
|
+
prev_part = nil
|
147
|
+
prev_second_part = nil
|
148
|
+
first_level_index = 0
|
149
|
+
|
150
|
+
body.children.each do |c|
|
151
|
+
next if c.content == " "
|
152
|
+
|
153
|
+
tag_name = c.name
|
154
|
+
css_classes = c.attr("class").split
|
155
|
+
is_olist = css_classes.include?("olist")
|
156
|
+
is_table = tag_name.match?("table")
|
157
|
+
contains_alink = c.search("a").count.positive?
|
158
|
+
contains_colon_at_end = c.content.chars.last(1).join == ":"
|
159
|
+
is_link_to_table = contains_alink &&
|
160
|
+
c.xpath("//a/@href")[0].value.match?("table")
|
161
|
+
|
162
|
+
if is_olist
|
163
|
+
# get all second level list items
|
164
|
+
list_items = c.css("ol[class=arabic] > li")
|
165
|
+
second_level_index = 0
|
166
|
+
|
167
|
+
list_items.each do |item|
|
168
|
+
part = Catalog::Part.new
|
169
|
+
part.id = "#{content_type}_#{clause_no}_part_#{first_level_index}_#{second_level_index + 1}"
|
170
|
+
part.name = "#{content_type}_part_list_item"
|
171
|
+
content = item.css("p").first.content
|
172
|
+
|
173
|
+
if item.search("a").count.positive?
|
174
|
+
content = replace_link(item, content)
|
175
|
+
end
|
176
|
+
|
177
|
+
if item.search(".note").count.positive?
|
178
|
+
note_part = add_note(item, content, part)
|
179
|
+
part.parts ||= []
|
180
|
+
part.parts << note_part
|
181
|
+
end
|
182
|
+
|
183
|
+
part.prose = content
|
184
|
+
prev_part.parts ||= []
|
185
|
+
|
186
|
+
# list item contains sublist items
|
187
|
+
if item.css("ol").count.positive?
|
188
|
+
prev_second_part = part
|
189
|
+
sublist_items = item.css("li")
|
190
|
+
third_level_index = 0
|
191
|
+
|
192
|
+
sublist_items.each do |sub_item|
|
193
|
+
sub_part = Catalog::Part.new
|
194
|
+
sub_part.id = "#{content_type}_#{clause_no}_part_#{first_level_index}_#{second_level_index + 1}_#{third_level_index + 1}"
|
195
|
+
sub_part.name = "#{content_type}_part_list_item"
|
196
|
+
content = sub_item.content
|
197
|
+
|
198
|
+
if sub_item.search("a").count.positive?
|
199
|
+
content = replace_link(sub_item, content)
|
200
|
+
end
|
201
|
+
|
202
|
+
sub_part.prose = content
|
203
|
+
|
204
|
+
prev_second_part.parts ||= []
|
205
|
+
prev_second_part.parts << sub_part
|
206
|
+
|
207
|
+
third_level_index = third_level_index + 1
|
208
|
+
end
|
209
|
+
|
210
|
+
part = prev_second_part
|
211
|
+
end
|
212
|
+
|
213
|
+
prev_part.parts << part
|
214
|
+
|
215
|
+
second_level_index = second_level_index + 1
|
216
|
+
end
|
217
|
+
|
218
|
+
first_level_index = first_level_index - 1
|
219
|
+
parts << prev_part
|
220
|
+
elsif is_table
|
221
|
+
column_num = 0
|
222
|
+
table_header = []
|
223
|
+
table_body = []
|
224
|
+
table_prose = ""
|
225
|
+
|
226
|
+
c.children.each do |item|
|
227
|
+
case item.name
|
228
|
+
when "caption"
|
229
|
+
part = Catalog::Part.new
|
230
|
+
part.id = "#{content_type}_#{clause_no}_part_#{first_level_index + 1}"
|
231
|
+
part.name = "#{content_type}_table_title"
|
232
|
+
part.prose = item.content
|
233
|
+
part.props = []
|
234
|
+
if item.content.match?(".")
|
235
|
+
part.props << {
|
236
|
+
"name" => "table",
|
237
|
+
"value" => item.content.split(".")[0].split[1],
|
238
|
+
}
|
239
|
+
end
|
240
|
+
|
241
|
+
parts << part.to_hash
|
242
|
+
when "colgroup"
|
243
|
+
column_num = item.children.count
|
244
|
+
when "thead"
|
245
|
+
item.children.children.each do |th|
|
246
|
+
# bold header
|
247
|
+
head = th.content.empty? ? " " : "*#{th.content}*"
|
248
|
+
table_header << head
|
249
|
+
end
|
250
|
+
when "tbody"
|
251
|
+
# loop through tr
|
252
|
+
item.children.each do |tr|
|
253
|
+
tr_data = []
|
254
|
+
|
255
|
+
# loop through td
|
256
|
+
tr.css("td").each do |td|
|
257
|
+
tr_data << td.content
|
258
|
+
end
|
259
|
+
|
260
|
+
table_body << tr_data
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# create prose for table
|
266
|
+
table_prose = "|===\n"
|
267
|
+
table_prose << ("|#{table_header.join(' | ')}\n")
|
268
|
+
table_body.each do |tr|
|
269
|
+
# bold first column
|
270
|
+
tr[0] = "*#{tr[0]}*"
|
271
|
+
table_prose << ("|#{tr.join(' | ')}\n")
|
272
|
+
end
|
273
|
+
table_prose << "|==="
|
274
|
+
|
275
|
+
# create part for table
|
276
|
+
part = Catalog::Part.new
|
277
|
+
part.id = "#{content_type}_#{clause_no}_part_#{first_level_index + 1}"
|
278
|
+
part.name = "#{content_type}_table"
|
279
|
+
part.prose = table_prose
|
280
|
+
|
281
|
+
parts << part.to_hash
|
282
|
+
elsif contains_colon_at_end
|
283
|
+
part = Catalog::Part.new
|
284
|
+
part.id = "#{content_type}_#{clause_no}_part_#{first_level_index + 1}"
|
285
|
+
part.name = "#{content_type}_part"
|
286
|
+
content = c.content
|
287
|
+
|
288
|
+
if contains_alink
|
289
|
+
content = replace_link(c, content)
|
290
|
+
end
|
291
|
+
|
292
|
+
part.prose = content
|
293
|
+
|
294
|
+
prev_part = part
|
295
|
+
else
|
296
|
+
part = Catalog::Part.new
|
297
|
+
part.id = "#{content_type}_#{clause_no}_part_#{first_level_index + 1}"
|
298
|
+
part.name = "#{content_type}_part"
|
299
|
+
content = c.content
|
300
|
+
|
301
|
+
if contains_alink
|
302
|
+
content = replace_link(c, content)
|
303
|
+
end
|
304
|
+
|
305
|
+
if content.start_with?("Note ")
|
306
|
+
content = content.gsub("Note ", "NOTE: ")
|
307
|
+
end
|
308
|
+
|
309
|
+
part.prose = content
|
310
|
+
|
311
|
+
parts << part.to_hash
|
312
|
+
end
|
313
|
+
|
314
|
+
first_level_index = first_level_index + 1
|
315
|
+
end
|
316
|
+
|
317
|
+
parts
|
318
|
+
end
|
319
|
+
|
320
|
+
#
|
321
|
+
# https://dev.to/ayushn21/how-to-generate-yaml-from-ruby-objects-without-type-annotations-4fli
|
322
|
+
module Hashify
|
323
|
+
# Classes that include this module can exclude certain
|
324
|
+
# instance variable from its hash representation by overriding
|
325
|
+
# this method
|
326
|
+
def ivars_excluded_from_hash
|
327
|
+
[]
|
328
|
+
end
|
329
|
+
|
330
|
+
def to_hash
|
331
|
+
hash = {}
|
332
|
+
excluded_ivars = ivars_excluded_from_hash
|
333
|
+
|
334
|
+
# Iterate over all the instance variables and store their
|
335
|
+
# names and values in a hash
|
336
|
+
instance_variables.each do |var|
|
337
|
+
next if excluded_ivars.include? var.to_s
|
338
|
+
|
339
|
+
value = instance_variable_get(var)
|
340
|
+
value = value.map(&:to_hash) if value.is_a? Array
|
341
|
+
|
342
|
+
hash[var.to_s.delete("@")] = value
|
343
|
+
end
|
344
|
+
|
345
|
+
hash
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
#########################################
|
350
|
+
#
|
351
|
+
# Catalog
|
352
|
+
#
|
353
|
+
# Catalog contains uuid, metadata and groups.
|
354
|
+
#
|
355
|
+
class Catalog
|
356
|
+
include Hashify
|
357
|
+
|
358
|
+
attr_accessor :catalog
|
359
|
+
|
360
|
+
def initialize(title = nil, remark = nil)
|
361
|
+
@catalog = Hash.new
|
362
|
+
@catalog["uuid"] = SecureRandom.uuid
|
363
|
+
@catalog["metadata"] = Metadata.build(title, remark)
|
364
|
+
@catalog["groups"] = []
|
365
|
+
@catalog["back-matter"] = BackMatter.build
|
366
|
+
end
|
367
|
+
|
368
|
+
def catalog_groups=(arr)
|
369
|
+
@catalog["groups"] = arr
|
370
|
+
end
|
371
|
+
|
372
|
+
def add_catalog_groups(arr)
|
373
|
+
@catalog["groups"] << arr
|
374
|
+
end
|
375
|
+
|
376
|
+
def get_catalog_groups(num)
|
377
|
+
@catalog["groups"].select { |g| g.id == num }.first
|
378
|
+
end
|
379
|
+
|
380
|
+
def hashify_catalog_groups
|
381
|
+
@catalog["groups"].map!(&:to_hash)
|
382
|
+
end
|
383
|
+
|
384
|
+
def catalog_groups
|
385
|
+
@catalog["groups"]
|
386
|
+
end
|
387
|
+
|
388
|
+
#
|
389
|
+
# BackMatter
|
390
|
+
#
|
391
|
+
# BackMatter contains title, published, last-modified, version, oscal-version
|
392
|
+
# and remarks
|
393
|
+
#
|
394
|
+
class BackMatter
|
395
|
+
include Hashify
|
396
|
+
|
397
|
+
def self.build
|
398
|
+
back_matter_path = File.expand_path(
|
399
|
+
"../sources/sections/back-matter.yml",
|
400
|
+
__dir__,
|
401
|
+
)
|
402
|
+
back_matter = YAML.safe_load(File.read(back_matter_path))
|
403
|
+
|
404
|
+
back_matter["resources"].each do |res|
|
405
|
+
res["uuid"] = SecureRandom.uuid.to_s
|
406
|
+
end
|
407
|
+
|
408
|
+
back_matter
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
#
|
413
|
+
# Metadata
|
414
|
+
#
|
415
|
+
# Metadata contains title, published, last-modified, version, oscal-version
|
416
|
+
# and remarks
|
417
|
+
#
|
418
|
+
class Metadata
|
419
|
+
include Hashify
|
420
|
+
|
421
|
+
def self.build(title = nil, remark = nil)
|
422
|
+
default_title = "Catalog for ISO27002:2022"
|
423
|
+
default_remarks = "OSCAL yaml generated from ISO27002:2022"
|
424
|
+
|
425
|
+
{
|
426
|
+
"title" => title.nil? ? default_title : "",
|
427
|
+
"published" => Time.now.iso8601,
|
428
|
+
"last-modified" => Time.now.iso8601,
|
429
|
+
"version" => "1.0",
|
430
|
+
"oscal-version" => "1.0.0",
|
431
|
+
"remarks" => remark.nil? ? default_remarks : "",
|
432
|
+
}
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
#
|
437
|
+
# Group
|
438
|
+
#
|
439
|
+
# Group has id and title.
|
440
|
+
# Group can contains mulitple controls or groups.
|
441
|
+
# Group can contains props in name-value pairs
|
442
|
+
#
|
443
|
+
class Group
|
444
|
+
include Hashify
|
445
|
+
|
446
|
+
attr_accessor :id, :title, :groups, :controls, :props
|
447
|
+
|
448
|
+
def initialize(id, title)
|
449
|
+
@id = id
|
450
|
+
@title = title
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
#
|
455
|
+
# Control
|
456
|
+
#
|
457
|
+
# Control has id and title.
|
458
|
+
# Control can contains mulitple parts.
|
459
|
+
# Control can contains props in name-value pairs
|
460
|
+
#
|
461
|
+
class Control
|
462
|
+
include Hashify
|
463
|
+
|
464
|
+
attr_accessor :id, :title, :parts, :props
|
465
|
+
|
466
|
+
def initialize(id)
|
467
|
+
@id = id
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
#
|
472
|
+
# Part
|
473
|
+
#
|
474
|
+
# Part has id and name
|
475
|
+
# Part may contains prose.
|
476
|
+
# Part can contains mulitple parts.
|
477
|
+
# Part can contains props in name-value pairs
|
478
|
+
#
|
479
|
+
class Part
|
480
|
+
include Hashify
|
481
|
+
|
482
|
+
attr_accessor :id, :name, :prose, :parts, :props
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def get_group_num_and_name(control_path)
|
487
|
+
group_shortname = control_path.split("/")[-2]
|
488
|
+
group_num = control_path.split("/")[-1].split("-")[0]
|
489
|
+
groupname = ""
|
490
|
+
|
491
|
+
case group_shortname
|
492
|
+
when "controls-org"
|
493
|
+
groupname = "Organizational controls"
|
494
|
+
when "controls-people"
|
495
|
+
groupname = "People controls"
|
496
|
+
when "controls-physical"
|
497
|
+
groupname = "Physical controls"
|
498
|
+
when "controls-tech"
|
499
|
+
groupname = "Technological controls"
|
500
|
+
end
|
501
|
+
|
502
|
+
[group_num, groupname]
|
503
|
+
end
|
504
|
+
|
505
|
+
########
|
506
|
+
# Main #
|
507
|
+
########
|
508
|
+
oscal_catalog = Catalog.new
|
509
|
+
controls_sections_dir = File.expand_path("../sources/sections", __dir__)
|
510
|
+
controls_paths_files = Dir["#{controls_sections_dir}/**/paths.yml"]
|
511
|
+
|
512
|
+
# read controls based on paths.yml
|
513
|
+
controls_paths_files.sort.each do |controls_paths_file|
|
514
|
+
controls_paths = YAML.safe_load(File.read(controls_paths_file))
|
515
|
+
prev_groupnum = nil
|
516
|
+
|
517
|
+
controls_paths.each do |control_path|
|
518
|
+
groupnum = get_group_num_and_name(control_path)[0]
|
519
|
+
groupname = get_group_num_and_name(control_path)[1]
|
520
|
+
|
521
|
+
if prev_groupnum.nil? || groupnum != prev_groupnum
|
522
|
+
# create groups for sections, e.g. 5, 6, 7, 8
|
523
|
+
catalog_group = Catalog::Group.new("cls_#{groupnum}", groupname)
|
524
|
+
catalog_group.props = [
|
525
|
+
{
|
526
|
+
"name" => "clause",
|
527
|
+
"value" => groupnum,
|
528
|
+
},
|
529
|
+
]
|
530
|
+
|
531
|
+
oscal_catalog.add_catalog_groups(catalog_group)
|
532
|
+
prev_groupnum = groupnum
|
533
|
+
else
|
534
|
+
# get existing group
|
535
|
+
catalog_group = oscal_catalog.get_catalog_groups("cls_#{groupnum}")
|
536
|
+
end
|
537
|
+
|
538
|
+
# DEBUG
|
539
|
+
# set control path to a specific yml file path
|
540
|
+
# if control_path == "sections/controls-<TYPE>/X-YY.yml"
|
541
|
+
# if control_path == "sections/controls-org/5-02.yml"
|
542
|
+
control = YAML.safe_load(File.read("./sources/#{control_path}"))
|
543
|
+
converted_control = convert_control_to_oscal(control)
|
544
|
+
|
545
|
+
catalog_group.controls ||= []
|
546
|
+
catalog_group.controls << converted_control.to_hash
|
547
|
+
# end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
##########
|
552
|
+
# Output #
|
553
|
+
##########
|
554
|
+
|
555
|
+
# Remove type annotation from ruby objects
|
556
|
+
oscal_catalog.hashify_catalog_groups
|
557
|
+
|
558
|
+
File.write("scripts/output/iso27002-oscal.yml", oscal_catalog.to_hash.to_yaml)
|
559
|
+
|
560
|
+
exit 0
|
data/lib/oscal/add.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "base_class"
|
2
|
+
|
3
|
+
module Oscal
|
4
|
+
class Add < Oscal::BaseClass
|
5
|
+
KEY = %i(position by_id title params props links parts)
|
6
|
+
|
7
|
+
attr_accessor *KEY
|
8
|
+
attr_serializable *KEY
|
9
|
+
|
10
|
+
def set_value(key_name, val)
|
11
|
+
case key_name
|
12
|
+
when 'params'
|
13
|
+
Parameter.wrap(val)
|
14
|
+
when 'props'
|
15
|
+
Property.wrap(val)
|
16
|
+
when 'links'
|
17
|
+
Link.wrap(val)
|
18
|
+
when 'part'
|
19
|
+
Part.wrap(val)
|
20
|
+
else
|
21
|
+
val
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative "base_class"
|
2
|
+
|
3
|
+
module Oscal
|
4
|
+
class Address < Oscal::BaseClass
|
5
|
+
KEY = %i(type addr_lines city state postal_code country)
|
6
|
+
|
7
|
+
attr_accessor *KEY
|
8
|
+
attr_serializable *KEY
|
9
|
+
|
10
|
+
def set_value(key_name, val)
|
11
|
+
case key_name
|
12
|
+
when 'addr_lines'
|
13
|
+
AddressLine.wrap(val)
|
14
|
+
when 'links'
|
15
|
+
Link.wrap(val)
|
16
|
+
else
|
17
|
+
val
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/oscal/alter.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative "base_class"
|
2
|
+
|
3
|
+
module Oscal
|
4
|
+
class Alter < Oscal::BaseClass
|
5
|
+
KEY = %i(control_id klass removes adds)
|
6
|
+
|
7
|
+
attr_accessor *KEY
|
8
|
+
attr_serializable *KEY
|
9
|
+
|
10
|
+
def set_value(key_name, val)
|
11
|
+
case key_name
|
12
|
+
when 'removes'
|
13
|
+
Remove.wrap(val)
|
14
|
+
when 'adds'
|
15
|
+
Add.wrap(val)
|
16
|
+
else
|
17
|
+
val
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "base_class"
|
2
|
+
|
3
|
+
module Oscal
|
4
|
+
class BackMatter < Oscal::BaseClass
|
5
|
+
KEY = %i(resources)
|
6
|
+
|
7
|
+
attr_accessor *KEY
|
8
|
+
attr_serializable *KEY
|
9
|
+
|
10
|
+
def set_value(key_name, val)
|
11
|
+
case key_name
|
12
|
+
when 'resources'
|
13
|
+
Resource.wrap(val)
|
14
|
+
else
|
15
|
+
val
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|