# encoding: utf-8 require 'parslet' require 'parslet/convenience' require 'pp' module Util class ControlParser < Parslet::Parser root :controls rule :controls do control.repeat(1) end rule :control do header >> applicability >> description.maybe >> rationale.maybe >> audit.maybe >> remediation.maybe >> impact.maybe >> default_value.maybe >> references.maybe >> cis_controls.maybe end rule :attribute_absent do str('Description:').absent? >> str('Rationale:').absent? >> str('Audit:').absent? >> str('Remediation:').absent? >> str('Impact:').absent? >> str('Default Value:').absent? >> str('References:').absent? >> str('CIS Controls:').absent? >> str('Profile Applicability::').absent? >> header.absent? end rule(:header) do newline.maybe >> spaces.maybe >> (section_num.as(:section_num) >> title.as(:title) >> score.as(:score)).as(:header) >> newline end rule(:title) do (str('(Scored)').absent? >> str('(Not Scored)').absent? >> str('(Not').absent? >> str('(Not ').absent? >> (anyChar | lparn | rparn | newline) | space).repeat(1) end rule :applicability do str('Profile Applicability:') >> newline.maybe >> lines('Description:').as(:applicability) end rule :section_num do (integer.repeat(1) >> dot).repeat(1) >> integer.repeat(1) >> space end rule :description do str('Description:') >> newline.maybe >> lines('Rationale:').as(:description) end rule :rationale do str('Rationale:') >> newline.maybe >> lines('Audit:').as(:rationale) end rule :audit do str('Audit:') >> newline.maybe >> lines('Remediation:').as(:audit) end rule :remediation do str('Remediation:') >> newline.maybe >> lines('Impact:').as(:remediation) end rule :impact do str('Impact:') >> newline.maybe >> lines('Default Value:').as(:impact) end rule :default_value do str('Default Value:') >> newline.maybe >> lines('References:').as(:default_value) end rule :references do str('References:') >> newline.maybe >> lines('CIS Controls:').as(:references) end rule :cis_controls do str('CIS Controls:') >> newline.maybe >> lines("\n").as(:cis_controls) end rule :blank_line do spaces >> newline >> spaces end rule :newline do str("\r").maybe >> str("\n") end rule :semicolon do str(';') end rule :spaces do space.repeat(0) end rule :space do match(/\s/) end rule :space? do space.maybe end rule :hyphen do str('-') end # @FIXME doesn't the parslet `any` function alreayd take care of this? rule :anyChar do match('.') end rule :integer do match('[0-9]').repeat(1) end rule :word do match('[a-zA-Z0-9/,\.:\'$-_\"*]').repeat(1) end rule :words do (space? >> word >> (space | dot | hyphen).maybe).repeat(1) >> (newline >> (word >> space).repeat(1)).maybe end def line_body(_ending) (attribute_absent >> any).repeat(1) end def line(ending) line_body(ending) end def lines(ending) line(ending).as(:line).repeat end rule(:eol?) { str("\n").maybe } rule(:eof?) { any.absent? } rule :dot do str('.') end rule :real do integer.repeat(1) >> dot >> integer.repeat(1) >> dot.absent? end rule(:score) { lparn >> scored >> rparn } rule :lparn do str('(') end rule :rparn do str(')') end rule :scored do (str(' Scored') | str('Scored') | str('Not Scored') | (str('Not ') >> newline.maybe) | (str('Not') >> newline.maybe)).repeat end end class Trans < Parslet::Transform rule(line: simple(:text)) { text } rule(section_num: simple(:section), title: simple(:title), score: simple(:score)) { section + title + score } rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), impact: sequence(:impact), default_value: sequence(:default_value), references: sequence(:references), cis_controls: sequence(:cis_controls)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, impact: impact[0].to_s, default: default_value[0].to_s, ref: references[0].to_s, cis: cis_controls[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), impact: sequence(:impact), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, impact: impact[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), impact: sequence(:impact)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, impact: impact[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), default_value: sequence(:default_value), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, default: default_value[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), impact: sequence(:impact), default_value: sequence(:default_value), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, impact: impact[0].to_s, default: default_value[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), default_value: sequence(:default_value)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, default: default_value[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), remediation: sequence(:remediation), impact: sequence(:impact)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", fix: remediation[0].to_s, impact: impact[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), remediation: sequence(:remediation), impact: sequence(:impact), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", fix: remediation[0].to_s, impact: impact[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), audit: sequence(:audit), remediation: sequence(:remediation), default_value: sequence(:default_value), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: description[0].to_s, check: audit[0].to_s, fix: remediation[0].to_s, default: default_value[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), impact: sequence(:impact), default_value: sequence(:default_value)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, impact: impact[0].to_s, default: default_value[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: rationale[0].to_s, check: audit[0].to_s, fix: remediation[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), remediation: sequence(:remediation), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", fix: remediation[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), default_value: sequence(:default_value), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, default: default_value[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), impact: sequence(:impact), default_value: sequence(:default_value)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, impact: impact[0].to_s, default: default_value[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), impact: sequence(:impact), references: sequence(:references)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, impact: impact[0].to_s, ref: references[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), impact: sequence(:impact)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, impact: impact[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), default_value: sequence(:default_value), references: sequence(:references), cis_controls: sequence(:cis_controls)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, default: default_value[0].to_s, ref: references[0].to_s, cis: cis_controls[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), impact: sequence(:impact), references: sequence(:references), cis_controls: sequence(:cis_controls)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, impact: impact[0].to_s, ref: references[0].to_s, cis: cis_controls[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale), audit: sequence(:audit), remediation: sequence(:remediation), references: sequence(:references), cis_controls: sequence(:cis_controls)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}", check: audit[0].to_s, fix: remediation[0].to_s, ref: references[0].to_s, cis: cis_controls[0].to_s } end rule(header: simple(:header), applicability: sequence(:applicability), description: sequence(:description), rationale: sequence(:rationale)) do { title: header.to_s, level: applicability[0].to_s, descr: "#{description[0]}#{rationale[0]}" } end end class PrepareData def initialize(clean_text) @parser = ControlParser.new @attributes = [] data = parse(clean_text) @transformed_data = Trans.new.apply(data) add_cis end attr_reader :transformed_data def parse(clean_text) @parser.parse(clean_text) rescue Parslet::ParseFailed => e puts e.parse_failure_cause.ascii_tree end def convert_str(value) value.to_s end def add_cis @transformed_data&.map do |ctrl| if !ctrl[:cis] && ctrl[:ref] references = ctrl[:ref].split("\n") references.each do |ref| match = ref.scan(/(?<=#)\d+\.\d+/).map(&:inspect).join(',').delete('"').tr(',', ' ') ctrl[:cis] = match.split unless match.empty? end ctrl[:cis] = 'No CIS Control' unless ctrl[:cis] elsif !ctrl[:cis] && !ctrl[:ref] ctrl[:cis] = 'No CIS Control' elsif ctrl[:cis] && ctrl[:ref] ctrl[:cis] = ctrl[:cis].scan(/^\d+[.\d+]*/) end end end end end