diff --git a/Gemfile b/Gemfile index 13318bc79..85e7c97b0 100644 --- a/Gemfile +++ b/Gemfile @@ -7,4 +7,6 @@ gem 'xml-simple' group :test, :development do gem 'minitest' gem 'rake' + gem 'rdoc' + gem 'yard' end \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 0d1b3cc12..6dff02ee3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,12 +1,16 @@ GEM remote: https://rubygems.org/ specs: + json (1.8.1) mini_portile2 (2.0.0) minitest (5.8.4) nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) rake (10.5.0) + rdoc (4.2.2) + json (~> 1.4) xml-simple (1.1.5) + yard (0.8.7.6) PLATFORMS ruby @@ -15,4 +19,9 @@ DEPENDENCIES minitest nokogiri rake + rdoc xml-simple + yard + +BUNDLED WITH + 1.11.2 diff --git a/documentation/yard/rakefile.rb b/documentation/yard/rakefile.rb new file mode 100644 index 000000000..a119b7571 --- /dev/null +++ b/documentation/yard/rakefile.rb @@ -0,0 +1,32 @@ +task :default => ["yard"] + +desc "Generate_yard_documentation" +task :yard do + require 'yard' + require_relative '../../lib/constants.rb' + + YARD::Rake::YardocTask.new do |t| + # Files to include in yard documentation. Ruby files before the -, Other files after the dash + t.files = ["#{ROOT_DIR}/lib/**/*.rb", + "#{ROOT_DIR}/tests/**/*.rb" + # '-', + # "#{ROOT_DIR} + ] # optional + + t.options = [ + "--title=SecGen #{VERSION_NUMBER} Documentation", + "--readme=#{ROOT_DIR}/README.md", + "--output-dir #{DOCUMENTATION_PATH}" + ] # optional + t.stats_options = ['--list-undoc'] # optional + end +end + +task :yard_clean do + require_relative '../../lib/constants.rb' + + # NEED TO FIND A BETTER WAY TO CLEAN FILES AS VULNERABILITIES IN 'rm_rf' + + # Remove the documentation directory and all files in it + rm_rf(DOCUMENTATION_PATH) +end \ No newline at end of file diff --git a/lib/configuration.rb b/lib/configuration.rb index c69566925..a8128a3cc 100644 --- a/lib/configuration.rb +++ b/lib/configuration.rb @@ -2,12 +2,14 @@ require_relative 'systemreader.rb' class Configuration - # populates the system class with an array of System objects. + # Populates the system class with an array of System objects. def initialize @systemreader = SystemReader.new @systems = init_systems() end + # Return all systems + # @return [Array] Array of systems objects def get_systems if @systems.empty? init_systems() @@ -15,10 +17,13 @@ class Configuration return @systems end + # Initialise configuration of all systems def init_systems() @systems = @systemreader.parse_systems end + # Returns the existing networks if defined, else returns network from the file networks.xml + # @return [Array] Array of network objects def self.networks if defined? @@networks return @@networks @@ -26,6 +31,8 @@ class Configuration return @@networks = _get_list(NETWORKS_XML, "//networks/network", Network) end + # Returns the existing bases if defined, else returns bases the from the file base.xml + # @return [Array] Array of base_box objects def self.bases if defined? @@bases return @@bases @@ -33,6 +40,8 @@ class Configuration return @@bases = _get_list(BASE_XML, "//bases/base", Basebox) end + # Returns the existing vulnerabilities if defined, else returns vulnerabilities the from the file vuln.xml + # @return [Array] Array of vulnerability objects def self.vulnerabilities if defined? @@vulnerabilities return @@vulnerabilities @@ -40,6 +49,8 @@ class Configuration return @@vulnerabilities = _get_list(VULN_XML, "//vulnerabilities/vulnerability", Vulnerability) end + # Returns the existing services if defined, else returns services the from the file services.xml + # @return [Array] Array of service objects def self.services if defined? @@services return @@services @@ -47,6 +58,11 @@ class Configuration return @@services = _get_list(SCENARIO_XML, "/systems/system/services/service", Service) end + # Reads xml file and returns relevent items + # @param xmlfile [File] Name of XML file to read + # @param xpath [String] Path to puppet files + # @param cls [Class] Class to be imported in + # @return [Array] List containing all item from given xml file def self._get_list(xmlfile, xpath, cls) itemlist = [] diff --git a/lib/constants.rb b/lib/constants.rb index 46252a1bc..839f63433 100644 --- a/lib/constants.rb +++ b/lib/constants.rb @@ -1,25 +1,74 @@ -#FILE CONSTANTS +## FILE_CONSTANTS ## + +# Root directory of SecGen file structure ROOT_DIR = File.expand_path('../../../SecGen',__FILE__) + +# Path to Scenario.xml file SCENARIO_XML = "#{ROOT_DIR}/config/scenario.xml" + +# Path to Networks.xml file NETWORKS_XML = "#{ROOT_DIR}/xml/networks.xml" + +# Path to services.xml file SERVICES_XML = "#{ROOT_DIR}/xml/services.xml" + +# Path to bases.xml file BASE_XML = "#{ROOT_DIR}/xml/bases.xml" + +# Path to mount directory MOUNT_DIR = "#{ROOT_DIR}/mount/" + +# Path to build directory BUILD_DIR = "#{ROOT_DIR}/modules/build/" + +# Path to mount/puppet directory MOUNT_PUPPET_DIR = "#{ROOT_DIR}/mount/puppet" + +# Path to projects directory PROJECTS_DIR = "#{ROOT_DIR}/projects" + +# Path to environments directory ENVIRONMENTS_PATH = "#{ROOT_DIR}/modules/environments" -#PATH CONSTANTS + + +## PATH_CONSTANTS ## + +# Path to modules directory MODULES_PATH = "#{ROOT_DIR}/modules/" + +# Path to vulnerabilities directory VULNERABILITIES_PATH = "#{ROOT_DIR}/modules/vulnerabilities/" -#ERROR CONSTANTS +# Path to documentation (Make sure documentation directory is already deleted with rake yard_clean before changing this) +DOCUMENTATION_PATH = "#{ROOT_DIR}/documentation/yard/doc" + + +## ERROR_CONSTANTS ## + +# Vulnerability not found in scenario.xml file error VULN_NOT_FOUND = "Matching vulnerability was not found please check the xml scenario.xml" -#RUNTIME_CONSTANTS + +## RUNTIME_CONSTANTS ## + +# CVE numbers available AVAILABLE_CVE_NUMBERS = [] -#VAGRANT_FILE_CONSTANTS + +## VAGRANT_FILE_CONSTANTS ## + +# Path to cleanup directory PATH_TO_CLEANUP = "#{ROOT_DIR}/modules/build/puppet/" + +# Path to vagrantbase.erb file VAGRANT_TEMPLATE_FILE = "#{ROOT_DIR}/lib/templates/vagrantbase.erb" + +# Path to report.erb file REPORT_TEMPLATE_FILE = "#{ROOT_DIR}/lib/templates/report.erb" + + +## VERSION_CONSTANTS ## + +# Version number of SecGen +# e.g. [release state (0 = alpha, 3 = final release)].[Major bug fix].[Minor bug fix].[Cosmetic or other features] +VERSION_NUMBER = '0.0.0.1' \ No newline at end of file diff --git a/lib/erb_controller.rb b/lib/erb_controller.rb index f44808fa2..cc2de2eb2 100644 --- a/lib/erb_controller.rb +++ b/lib/erb_controller.rb @@ -2,9 +2,15 @@ class ERBController # ERB Controller initializes the system and returns the binding when mapping .erb files attr_accessor :systems + + # Initialise systems array + # @return [Array] Empty array for systems def initialize @systems = [] end + + # Returns binding of mapped .erb files + # @return binding ????? def get_binding return binding end diff --git a/lib/filecreator.rb b/lib/filecreator.rb index f2bd58b4f..6bab86d7c 100644 --- a/lib/filecreator.rb +++ b/lib/filecreator.rb @@ -2,14 +2,21 @@ require 'erb' require_relative 'erb_controller' require_relative 'constants' require_relative 'configuration' +require_relative 'xml_report_generator' require 'fileutils' + class FileCreator # Creates project directory, uses .erb files to create a report and the vagrant file that will be used # to create the virtual machines + + # Initialises configuration variable + # @param config [Object] def initialize(config) @configuration = config end - + + # Generate all relevant files for the project + # @return [Int] Build number of the newly generated project def generate() systems = @configuration.get_systems Dir::mkdir("#{PROJECTS_DIR}") unless File.exists?("#{PROJECTS_DIR}") @@ -20,6 +27,8 @@ class FileCreator puts "The system is now creating the Project#{build_number}" Dir::mkdir("#{PROJECTS_DIR}/Project#{build_number}") unless File.exists?("#{PROJECTS_DIR}/#{build_number}") + puts 'Creating the projects mount directory' + Dir::mkdir("#{PROJECTS_DIR}/Project#{build_number}/mount") unless File.exists?("#{PROJECTS_DIR}/Project#{build_number}/mount") # initialises box before creation command = "cd #{PROJECTS_DIR}/Project#{build_number}/; vagrant init" @@ -29,16 +38,21 @@ class FileCreator controller.systems = systems vagrant_template = ERB.new(File.read(VAGRANT_TEMPLATE_FILE), 0, '<>') if File.exists?("#{PROJECTS_DIR}/Project#{build_number}/Vagrantfile") - File.delete("#{PROJECTS_DIR}/Project#{build_number}/Vagrantfile") - end + File.delete("#{PROJECTS_DIR}/Project#{build_number}/Vagrantfile") + end puts "#{PROJECTS_DIR}/Project#{build_number}/Vagrantfile file has been created" File.open("#{PROJECTS_DIR}/Project#{build_number}/Vagrantfile", 'w') { |file| file.write(vagrant_template.result(controller.get_binding)) } - - #report_template = ERB.new(File.read(REPORT_TEMPLATE_FILE), 0, '<>') - #puts "#{PROJECTS_DIR}/Project#{build_number}/Report file has been created" - #File.open("#{PROJECTS_DIR}/Project#{build_number}/Report", 'w'){ |file| file.write(report_template.result(controller.get_binding)) } + # Create the Report file + report_template = ERB.new(File.read(REPORT_TEMPLATE_FILE), 0, '<>') + puts "#{PROJECTS_DIR}/Project#{build_number}/Report file has been created" + File.open("#{PROJECTS_DIR}/Project#{build_number}/Report", 'w'){ |file| file.write(report_template.result(controller.get_binding)) } - return build_number + # Create the Report.xml file + xml_report_generator = XMLReportGenerator.new(systems, build_number) + xml_report_generator.write_xml_report + puts "#{PROJECTS_DIR}/Project#{build_number}/Report.xml file has been created" + + return build_number end end \ No newline at end of file diff --git a/lib/helpers/bootstrap.rb b/lib/helpers/bootstrap.rb index d82a1b549..719bd859f 100644 --- a/lib/helpers/bootstrap.rb +++ b/lib/helpers/bootstrap.rb @@ -1,9 +1,10 @@ require 'fileutils' class Bootstrap + # Bootstrap the application by creating or moving all relevant puppet files def bootstrap puts 'Bootstrapping application..' - #if mount doesnt exist create the directory structure + #if mount does not exist create the directory structure if !Dir.exists?("#{ROOT_DIR}/mount") create_directory_structure move_vulnerability_puppet_files @@ -21,6 +22,8 @@ class Bootstrap private + # Create directory structure for puppet files + # Structure /mount/puppet/module and /mount/puppet/manifest def create_directory_structure print 'Mount directory not present, creating..' Dir.mkdir("#{ROOT_DIR}/mount") @@ -33,6 +36,7 @@ class Bootstrap puts ' Complete' end + # Copy all puppet files from /modules/vulnerabilities/ to /mount/puppet/module and /mount/puppet/module def move_vulnerability_puppet_files puts 'Moving vulnerability manifests' Dir.glob("#{ROOT_DIR}/modules/vulnerabilities/*/*/*/*.pp").each do |puppet_file| @@ -48,6 +52,7 @@ class Bootstrap end end + # Copy all puppet files from /modules/services to /mount/puppet/manifest and /mount/puppet/module def move_secure_service_puppet_files puts 'Moving Service manifests' Dir.glob("#{ROOT_DIR}/modules/services/*/*/*/*.pp").each do |puppet_file| @@ -67,6 +72,7 @@ class Bootstrap end end + # Move dependency modules, build manifests and build modules def move_build_puppet_files puts 'Moving Dependency modules' @@ -96,6 +102,7 @@ class Bootstrap end + # Purge all puppet files from mount directory def purge_puppet_files FileUtils.rm_rf("#{ROOT_DIR}/mount") end diff --git a/lib/helpers/vulnerability_helper.rb b/lib/helpers/vulnerability_helper.rb index 98fb38010..f270c5f41 100644 --- a/lib/helpers/vulnerability_helper.rb +++ b/lib/helpers/vulnerability_helper.rb @@ -2,6 +2,9 @@ require_relative '../objects/vulnerability.rb' require_relative '../constants.rb' class VulnerabilityHelper + # Assign all values to a new vulnerability object + # @param vulnerability_hash [Hash] + # @return [Object] Vulnerability object containing all available values def getVulnerabilityObject(vulnerability_hash) vulnerability = Vulnerability.new vulnerability.type = vulnerability_hash['type'] if vulnerability_hash['type'] diff --git a/lib/helpers/vulnerability_processor.rb b/lib/helpers/vulnerability_processor.rb index 7ef1bc6d2..b19737625 100644 --- a/lib/helpers/vulnerability_processor.rb +++ b/lib/helpers/vulnerability_processor.rb @@ -4,114 +4,124 @@ require_relative 'vulnerability_helper' require 'xmlsimple' class VulnerabilityProcessor - def initialize() - @vulnerability_helper = VulnerabilityHelper.new - end - # returns a hash of compatible vulnerabilities based on what is provided in scenario.xml (scenario_vulns) - # based on the attributes optionally specified in scenario.xml (scenario_vulns) - def process(scenario_vulns) + #Initialise the Vulnerability processor object by creating a new VulnerabilityHelper object + def initialize() + @vulnerability_helper = VulnerabilityHelper.new + end + # returns a hash of compatible vulnerabilities based on what is provided in scenario.xml (scenario_vulns) + # based on the attributes optionally specified in scenario.xml (scenario_vulns) + # @param scenario_vulns [String] Attributes specified in scenario.xml + # @return [Hash] Vulnerability values + def process(scenario_vulns) - return_vulns = {} + return_vulns = {} - all_vulnerabilities = get_vulnerabilities_array + all_vulnerabilities = get_vulnerabilities_array - scenario_vulns.each do |vulnerability_query| - # select based on selected type, access, cve... - search_list = all_vulnerabilities.clone - # shuffle order of available vulnerabilities - search_list.shuffle! - # remove all the vulns that don't match the current selection (type, etc) - if vulnerability_query.type.length > 0 - puts "Searching for vulnerability matching type: " + vulnerability_query.type - search_list.delete_if{|x| x.type != vulnerability_query.type} - end - if vulnerability_query.access.length > 0 - puts "Searching for vulnerability matching access: " + vulnerability_query.access - search_list.delete_if{|x| x.access != vulnerability_query.access} - end - if vulnerability_query.cve.length > 0 - puts "Searching for vulnerability matching CVE: " + vulnerability_query.cve - search_list.delete_if{|x| x.cve != vulnerability_query.cve} - end - if vulnerability_query.difficulty.length > 0 - puts "Searching for vulnerability matching difficulty: " + vulnerability_query.difficulty - search_list.delete_if{|x| x.difficulty != vulnerability_query.difficulty} - end - - if vulnerability_query.cvss_rating.length > 0 - puts "Searching for vulnerability matching cvss rating: " + vulnerability_query.cvss_rating - remove_by_cvss(vulnerability_query, search_list) - end - - if vulnerability_query.vector_string.length > 0 - puts "Searching for vulnerability based on vector string: " + vulnerability_query.vector_string - remove_by_vector(vulnerability_query, search_list) - end - - if search_list.length == 0 - puts VULN_NOT_FOUND - puts "(note: you can only have one of each type of vulnerability per system)" - exit - else - # use from the top of the top of the randomised list - return_vulns[vulnerability_query.id] = search_list[0] - if search_list[0].type.length > 0 - puts "Selected vulnerability : " + search_list[0].type - end - - # enforce only one of any vulnerability type (remove from available) - search_list.delete_if{|x| x.type == vulnerability_query.type} - end + scenario_vulns.each do |vulnerability_query| + # select based on selected type, access, cve... + search_list = all_vulnerabilities.clone + # shuffle order of available vulnerabilities + search_list.shuffle! + # remove all the vulns that don't match the current selection (type, etc) + if vulnerability_query.type.length > 0 + puts "Searching for vulnerability matching type: " + vulnerability_query.type + search_list.delete_if{|x| x.type != vulnerability_query.type} + end + if vulnerability_query.access.length > 0 + puts "Searching for vulnerability matching access: " + vulnerability_query.access + search_list.delete_if{|x| x.access != vulnerability_query.access} + end + if vulnerability_query.cve.length > 0 + puts "Searching for vulnerability matching CVE: " + vulnerability_query.cve + search_list.delete_if{|x| x.cve != vulnerability_query.cve} + end + if vulnerability_query.difficulty.length > 0 + puts "Searching for vulnerability matching difficulty: " + vulnerability_query.difficulty + search_list.delete_if{|x| x.difficulty != vulnerability_query.difficulty} end - return return_vulns.values - - - end - - def get_vulnerabilities_array - vulnerabilities = [] - Dir.glob("#{ROOT_DIR}/modules/vulnerabilities/**/**/secgen_metadata.xml").each do |file| - vulnerability_hash = XmlSimple.xml_in(file, {}) - vulnerability = @vulnerability_helper.getVulnerabilityObject(vulnerability_hash) - vulnerabilities.push(vulnerability) + if vulnerability_query.cvss_rating.length > 0 + puts "Searching for vulnerability matching cvss rating: " + vulnerability_query.cvss_rating + remove_by_cvss(vulnerability_query, search_list) end - return vulnerabilities + if vulnerability_query.vector_string.length > 0 + puts "Searching for vulnerability based on vector string: " + vulnerability_query.vector_string + remove_by_vector(vulnerability_query, search_list) + end + + if search_list.length == 0 + puts VULN_NOT_FOUND + puts "(note: you can only have one of each type of vulnerability per system)" + exit + else + # use from the top of the top of the randomised list + return_vulns[vulnerability_query.id] = search_list[0] + if search_list[0].type.length > 0 + puts "Selected vulnerability : " + search_list[0].type + end + + # enforce only one of any vulnerability type (remove from available) + search_list.delete_if{|x| x.type == vulnerability_query.type} + end end - def remove_by_cvss (vulnerability_query, search_list) - puts case vulnerability_query.cvss_rating - when 'none' # 0.0 - search_list.delete_if{|x| x.cvss_score.to_f > 0 } - when 'low' # 0.1 - 3.9 - search_list.delete_if{|x| x.cvss_score.to_f == 0 or x.cvss_score.to_f > 4 } - when 'medium' # 4.0 - 6.9 - search_list.delete_if{|x| x.cvss_score.to_f < 4 or x.cvss_score.to_f > 7 } - when 'high' # 7.0 - 8.9 - search_list.delete_if{|x| x.cvss_score.to_f < 7 and x.cvss_score.to_f <= 9 } - when 'critical' # 9.0 - 10 - search_list.delete_if{|x| x.cvss_score.to_f < 9 } - end + return return_vulns.values + + + end + + # Get array of vulnerability objects + # @return [Array] Array containing vulnerability objects + def get_vulnerabilities_array + vulnerabilities = [] + Dir.glob("#{ROOT_DIR}/modules/vulnerabilities/**/**/secgen_metadata.xml").each do |file| + vulnerability_hash = XmlSimple.xml_in(file, {}) + vulnerability = @vulnerability_helper.getVulnerabilityObject(vulnerability_hash) + vulnerabilities.push(vulnerability) end - # method which removes vulnerabilities from the search_list based on vector string provided - # in the vulnerability_query (i.e. a user specified in scenario.xml) - def remove_by_vector (query_vulnerability, search_list) + return vulnerabilities + end + + # Remove vulnerability queries from search list + # @param vulnerability_query [String] Vulnerability query + # @param search_list [Array] List containing all remaining vulnerabilities + def remove_by_cvss (vulnerability_query, search_list) + puts case vulnerability_query.cvss_rating + when 'none' # 0.0 + search_list.delete_if{|x| x.cvss_score.to_f > 0 } + when 'low' # 0.1 - 3.9 + search_list.delete_if{|x| x.cvss_score.to_f == 0 or x.cvss_score.to_f > 4 } + when 'medium' # 4.0 - 6.9 + search_list.delete_if{|x| x.cvss_score.to_f < 4 or x.cvss_score.to_f > 7 } + when 'high' # 7.0 - 8.9 + search_list.delete_if{|x| x.cvss_score.to_f < 7 and x.cvss_score.to_f <= 9 } + when 'critical' # 9.0 - 10 + search_list.delete_if{|x| x.cvss_score.to_f < 9 } + end + end + + # method which removes vulnerabilities from the search_list based on vector string provided + # in the vulnerability_query (i.e. a user specified in scenario.xml) + # @param query_vulnerability [String] Vector string for desired vulnerabilities + # @param search_list [Array] List containing all remaining vulnerabilities + def remove_by_vector(query_vulnerability, search_list) query_vector_hash = query_vulnerability.get_vector_hash for query_vector_pair in query_vector_hash - search_list.delete_if{ |vulnerability| - search_vector_hash = vulnerability.get_vector_hash - search_vector_pair = search_vector_hash.assoc(query_vector_pair[0]) - if search_vector_pair != nil - query_vector_pair[1] != search_vector_pair[1] - else - true - end - } - end + search_list.delete_if{ |vulnerability| + search_vector_hash = vulnerability.get_vector_hash + search_vector_pair = search_vector_hash.assoc(query_vector_pair[0]) + if search_vector_pair != nil + query_vector_pair[1] != search_vector_pair[1] + else + true + end + } + end end end \ No newline at end of file diff --git a/lib/managers/base_manager.rb b/lib/managers/base_manager.rb index 5df054be1..a081d0508 100644 --- a/lib/managers/base_manager.rb +++ b/lib/managers/base_manager.rb @@ -1,4 +1,8 @@ class BaseManager + # Generates a basebox system from a sample of the bases.xml file + # @param system [Object] System object + # @param bases [Array] Bases array + # @return [Object] Basebox system def self.generate_base(system,bases) # takes a sample from bases.xml and then assigns it to system box = bases.sample diff --git a/lib/managers/network_manager.rb b/lib/managers/network_manager.rb index bbfa3fb74..fa68b14ef 100644 --- a/lib/managers/network_manager.rb +++ b/lib/managers/network_manager.rb @@ -1,5 +1,10 @@ class NetworkManager # the user will either specify a blank misc type or a knownnetwork type + + # Check if given networks are valid if networks valid return the values, else display error message + # @param networks [Array] Networks to check for validity + # @param valid_network [String] Valid network value + # @return [Array] Returns all the values for new_networks as an array def self.process(networks,valid_network) new_networks = {} # intersection of valid networks / user defined networks diff --git a/lib/managers/service_manager.rb b/lib/managers/service_manager.rb index f9a5cda7d..477d901b1 100644 --- a/lib/managers/service_manager.rb +++ b/lib/managers/service_manager.rb @@ -2,6 +2,10 @@ class ServiceManager # secure services are randomly selected from the definitions in services.xml (secure_services) # based on the attributes optionally specified in scenario.xml (want_services) # However, if the service type has already had a vulnerability assigned (selected_vulns), it is ignored here + # @param want_services [String] Services specified in scenario.xml + # @param secure_services [String] Random services selected from definitions in services.xml + # @param selected_vulns [Array] Vulnerabilities that have already been assigned + # @return [Object] Service object def self.process(want_services, secure_services, selected_vulns=[]) return_services = {} legal_services = secure_services.clone diff --git a/lib/objects/base_box.rb b/lib/objects/base_box.rb index da9f51d03..cdb3d00ae 100644 --- a/lib/objects/base_box.rb +++ b/lib/objects/base_box.rb @@ -1,3 +1,17 @@ class Basebox - attr_accessor :name, :os, :distro, :vagrantbase, :url + + # Name of the basebox + attr_accessor :name + + # Operating system on the basebox + attr_accessor :os + + # Distro running on the basebox + attr_accessor :distro + + # Selected vagrantbase of the system + attr_accessor :vagrantbase + + # Url link to the puppet basebox + attr_accessor :url end \ No newline at end of file diff --git a/lib/objects/base_module.rb b/lib/objects/base_module.rb index 0ec68af00..1aceb62d5 100644 --- a/lib/objects/base_module.rb +++ b/lib/objects/base_module.rb @@ -1,7 +1,7 @@ #Contains common components that modules will inherit from. class BaseModule - #Name of the module + # Name of the module attr_accessor :name #Type of the module diff --git a/lib/objects/network.rb b/lib/objects/network.rb index dbbcb6170..289d6f41d 100644 --- a/lib/objects/network.rb +++ b/lib/objects/network.rb @@ -1,22 +1,36 @@ class Network - attr_accessor :name, :range + # Network name + attr_accessor :name + # Network range + attr_accessor :range + + # Initialise Network object + # @param name [String] Network name + # @param range [String] Network range def initialize(name="", range="") @name = name @range = range end + # Returns a string containing all object variables concatenated together + # @return [String] Hash containing @name and @range object variables as a concatenated string def id hash = @name + @range return hash # return string that connects everything to 1 massive string end + # Check if name matches networks.xml from scenario.xml + # @param other [String] + # @return [Boolean] Returns true if @name matches networks.xml from scenario.xml def eql? other # checks if name matches networks.xml from scenario.xml other.kind_of?(self.class) && @name == other.name end + # Returns a hash of the type + # @return [Hash] Hash of the object variable @type def hash @type.hash end diff --git a/lib/objects/service.rb b/lib/objects/service.rb index 33c998841..94536d950 100644 --- a/lib/objects/service.rb +++ b/lib/objects/service.rb @@ -1,6 +1,21 @@ class Service - attr_accessor :name, :type, :details, :puppets + # Service name + attr_accessor :name + # Type of service + attr_accessor :type + + # Service details + attr_accessor :details + + # Puppet files used to create service + attr_accessor :puppets + + # Initialise Service object + # @param name [String] service name + # @param type [String] service range + # @param details [String] service details + # @param puppets [Array] puppet files used to create service def initialize(name="", type="", details="", puppets=[]) @name = name @type = type @@ -8,14 +23,21 @@ class Service @puppets = puppets end + # Check if name matches services.xml from scenario.xml + # @param other [String] + # @return [Boolean] Returns true if @type matches services.xml from scenario.xml def eql? other other.kind_of?(self.class) && @type == other.type end + # Returns a hash of the type + # @return [Hash] hash of the object variable @type def hash @type.hash end + # Returns string containing the object type variable + # @return [String] Services id string def id return @type end diff --git a/lib/objects/site.rb b/lib/objects/site.rb index 02d0b2949..949747ee9 100644 --- a/lib/objects/site.rb +++ b/lib/objects/site.rb @@ -1,6 +1,13 @@ class Site - attr_accessor :name, :type + # Site name + attr_accessor :name + # Type of site + attr_accessor :type + + # Initialize site object + # @param name [String] + # @param type [String] def initialize(name='', type='') @name = name @type = type diff --git a/lib/objects/system.rb b/lib/objects/system.rb index 26805a766..1d60bf891 100644 --- a/lib/objects/system.rb +++ b/lib/objects/system.rb @@ -1,8 +1,39 @@ class System # can access from outside of class - attr_accessor :id, :os, :url,:basebox, :networks, :vulns, :services, :sites - #initalizes system variables + # System's id number + attr_accessor :id + + # Operating system running on the system + attr_accessor :os + + # URL to the puppet basebox + attr_accessor :url + + # Puppet basebox name + attr_accessor :basebox + + # Networks used by the system + attr_accessor :networks + + # Vulnerabilite's installed on the system + attr_accessor :vulns + + # Services installed on the system + attr_accessor :services + + # Sites to be served from the system + attr_accessor :sites + + # Initalizes System object + # @param id [String] Identifier string for system object + # @param os [String] Operating system installed on the system + # @param basebox [String] Puppet basebox used to create the system + # @param url [String] url to the selected puppet basebox + # @param vulns [Array] Array containing selected vulnerability objects + # @param networks [Array] Array containing selected network objects + # @param services [Array] Array containing selected services objects + # @param sites [Array] Array containing selected sites objects def initialize(id, os, basebox, url, vulns=[], networks=[], services=[], sites=[]) @id = id @os = os @@ -14,6 +45,8 @@ class System @sites = sites end + # Checks to see if the selected base is a valid basebox and is in the vagrant file + # @return [Boolean] Is the basebox valid def is_valid_base valid_base = Configuration.bases diff --git a/lib/objects/vulnerability.rb b/lib/objects/vulnerability.rb index a17def346..8856eea59 100644 --- a/lib/objects/vulnerability.rb +++ b/lib/objects/vulnerability.rb @@ -1,65 +1,131 @@ require_relative('../constants.rb') class Vulnerability - attr_accessor :type, :privilege, :access ,:puppets, :details, :ports, :name, :cve, :files, :scripts, :platform, :difficulty, :cvss_rating, :cvss_score, :vector_string + # The type of vulnerability + attr_accessor :type - def initialize(type='', privilege='', access='', puppets=[], details='', ports=[], platform ='', name='', cve='', files=[], scripts=[], difficulty ='', cvss_rating='', cvss_score='',vector_string='') - @type = type - @privilege = privilege - @access = access - @puppets = puppets - @details = details - @ports = ports - @platform = platform - @name = name - @cve = cve - @files = files - @scripts = scripts - @difficulty = difficulty - @cvss_rating = cvss_rating - @cvss_score = cvss_score - @vector_string = vector_string + # The privilege level the vulnerability gives + attr_accessor :privilege - # Base Vector String: - # Example 1: 'AV:L/AC:H/Au:N/C:N/I:P/A:C' - # Access Vector: L = Local access, A = adjacent access, N = network access - # Access Complexity: H = High, M = Medium, L = Low - # Authentication: N = None required, S = Single instance, M = Multi instance - # Confidentiality Impact: N = None, P = Partial, C = Complete - # Integrity Impact: N = None, P = Partial, C = Complete - # Availabiliy Impact: N = None, P = Partial, C = Complete + # The access level the vulnerability gives + attr_accessor :access - end + # The puppet files used for the vulnerability + attr_accessor :puppets - def id - return @type + @privilege + @access - end + # Details describing the vulnerability + attr_accessor :details - def vulnerability_path - return "#{ROOT_DIR}/modules/vulnerabilities/#{@platform}/#{@type}/#{@name}" - end + # Ports used by the vulnerability + attr_accessor :ports - def puppet_path - return vulnerability_path + '/puppet' - end + # Name given to the vulnerability + attr_accessor :name - def is_vector_populated - return vector_string.length > 0 - end + # Vulnerability's CVE number + attr_accessor :cve - # - def get_vector_hash - base_vector_string = vector_string # for example: "AV:L/AC:H/Au:N/C:N/I:P/A:C" - base_vector_array = base_vector_string.split('/') # split to get: ['AV:L', 'AC:H', 'Au:N','C:N', 'I:P', 'A:C'] + # + attr_accessor :files + + # + attr_accessor :scripts + + # Platform the vulnerability will work on + attr_accessor :platform + + # Difficulty of the vulnerability + attr_accessor :difficulty + + # Vulnerability's cvss_rating + attr_accessor :cvss_rating + + # Vulnerability's cvss_score + attr_accessor :cvss_score + + # Vulnerability's vector_string, e.g. AV:L/AC:H/Au:N/C:N/I:P/A:C + attr_accessor :vector_string + + # Initialises Vulnerability object + # @param type [String] Type of vulnerability + # @param privilege [String] Privilege obtained after successful exploitation + # @param access [String] Access obtained after successful exploitation + # @param puppets [Array] Array of puppet files needed for the vulnerability + # @param details [String] Details of the vulnerability + # @param ports [Array] Ports used by the vulnerability + # @param platform [String] Platform the vulnerability will work on + # @param name [String] Name of the vulnerability + # @param cve [String] CVE number of the vulnerability + # @param files [Array] + # @param scripts [Array] + # @param difficulty [String] Difficulty level of exploiting the vulnerability + # @param cvss_rating [String] Vulnerability's cvss_rating + # @param cvss_score [String] Vulnerability's cvss_score + # @param vector_string [String] Vulnerability's vector_string, e.g. AV:L/AC:H/Au:N/C:N/I:P/A:C + def initialize(type='', privilege='', access='', puppets=[], details='', ports=[], platform ='', name='', cve='', files=[], scripts=[], difficulty ='', cvss_rating='', cvss_score='',vector_string='') + @type = type + @privilege = privilege + @access = access + @puppets = puppets + @details = details + @ports = ports + @platform = platform + @name = name + @cve = cve + @files = files + @scripts = scripts + @difficulty = difficulty + @cvss_rating = cvss_rating + @cvss_score = cvss_score + @vector_string = vector_string + + # Base Vector String: + # Example 1: 'AV:L/AC:H/Au:N/C:N/I:P/A:C' + # Access Vector: L = Local access, A = adjacent access, N = network access + # Access Complexity: H = High, M = Medium, L = Low + # Authentication: N = None required, S = Single instance, M = Multi instance + # Confidentiality Impact: N = None, P = Partial, C = Complete + # Integrity Impact: N = None, P = Partial, C = Complete + # Availability Impact: N = None, P = Partial, C = Complete - # convert this into a hash map - base_vector_hash = {} - for vector_element_string in base_vector_array - vector_element_array = vector_element_string.split(':') - if vector_element_array[1] != nil - base_vector_hash.store(vector_element_array[0], vector_element_array[1]) - end - end - return base_vector_hash - end end + + # Returns identifier string made of the @type, @privilege and @access object variables + # @return [String] Identifier string made of the @type, @privilege and @access object variables + def id + return @type + @privilege + @access + end + + # Returns path to the selected vulnerabilities files + # @return [String] Path to the vulnerability files + def vulnerability_path + return "#{ROOT_DIR}/modules/vulnerabilities/#{@platform}/#{@type}/#{@name}" + end + + # Returns path to the puppet files for the selected vulnerability + # @return [String] Path to the puppet files for the selected vulnerability + def puppet_path + return vulnerability_path + '/puppet' + end + + def is_vector_populated + return vector_string.length > 0 + end + + # Returns hash made of all the components in the vector string + # @return [Hash] Hash of vector string components + def get_vector_hash + base_vector_string = vector_string # for example: "AV:L/AC:H/Au:N/C:N/I:P/A:C" + base_vector_array = base_vector_string.split('/') # split to get: ['AV:L', 'AC:H', 'Au:N','C:N', 'I:P', 'A:C'] + + # convert this into a hash map + base_vector_hash = {} + for vector_element_string in base_vector_array + vector_element_array = vector_element_string.split(':') + if vector_element_array[1] != nil + base_vector_hash.store(vector_element_array[0], vector_element_array[1]) + end + end + return base_vector_hash + end +end diff --git a/lib/systemreader.rb b/lib/systemreader.rb index 0068612b7..ba2fb7de5 100644 --- a/lib/systemreader.rb +++ b/lib/systemreader.rb @@ -12,6 +12,7 @@ require_relative 'objects/vulnerability' require 'nokogiri' class SystemReader + # initializes systems xml from BOXES_XML const def initialize() @vulnerability_processor = VulnerabilityProcessor.new @@ -19,6 +20,7 @@ class SystemReader # uses nokogiri to extract all system information from scenario.xml will add it to the system class after # checking if the vulnerabilities / networks exist from system.rb + # @return [Array] Array containing Systems objects def parse_systems systems = [] doc = Nokogiri::XML(File.read(SCENARIO_XML)) diff --git a/lib/templates/report.erb b/lib/templates/report.erb index f610d5f01..d5ed48cfb 100644 --- a/lib/templates/report.erb +++ b/lib/templates/report.erb @@ -1,43 +1,44 @@ <%if systems.count == 1%> -There was only 1 system generated for this project. +There was only 1 system generated for this project. <%else %> -There were <%systems.count%> systems generated for this project. <%end%> +There were <%systems.count%> systems generated for this project. +<%end%> The module files for puppet can be found here: "<%=ROOT_DIR%>/mount/puppet/modules" The manifest files for puppet can be found here: "<%=ROOT_DIR%>/mount/puppet/manifests" <% systems.each do |s| %> ====System: <%=s.id%>==== - <%=s.id%> uses <%=s.basebox%> a distro of <%=s.os%> which can be downloaded from <%=s.url%> -<% s.networks.each do |n| %> <%grab_system_number = s.id.gsub(/[^0-9]/i, "") %> <% n.range[9..9] = grab_system_number %> - ip address for <%=s.id%> = <%=n.range%>0 <% end %> - ==Secure services== +<%=s.id%> uses <%=s.basebox%> a distro of <%=s.os%> which can be downloaded from <%=s.url%> +<% s.networks.each do |n| %> <%grab_system_number = s.id.gsub(/[^0-9]/i, "") %> <% n.range[9..9] = grab_system_number %> +ip address for <%=s.id%> = <%=n.range%>0 +<% end %> +==Secure services== <% s.services.each do |v| %> - Here is a summary of the service <%=v.name%>: - Type: <%=v.type%>. - Name: <%= v.name %>. - Details: <%= v.details %>. +Here is a summary of the service <%=v.name%>: +Type: <%=v.type%>. +Name: <%= v.name %>. +Details: <%= v.details %>. <% v.puppets.each do |p| %> Puppet "<%=p%>.pp" has been used to create this service. <% end %> <% end %> - ==Vulnerabilities== +==Vulnerabilities== <% s.vulns.each do |v| %> - Here is a summary of the vulnerability <%=v.type%>: - Type: <%=v.type%>. - Details: <%= v.details %>. - privilege: <%= v.privilege %>. - access: <%= v.access %>. -<%if not v.cve == ""%> - cve: <%= v.cve %>. +Here is a summary of the vulnerability <%=v.type%>: +Type: <%=v.type%>. +Details: <%= v.details %>. +privilege: <%= v.privilege %>. +access: <%= v.access %>. +<%if not v.cve == ""%>cve: <%= v.cve %>. <% end %> -<% v.puppets.each do |p| %> - Puppet "<%=p%>.pp" has been used to create this vulnerability. +<% v.puppets.each do |p| %>Puppet "<%=p%>.pp" has been used to create this vulnerability. <% end %> -<% v.ports.each do |port| %> - Runs on port <%=port%> +<%#=<% v.ports.each do |port| %> +<%#=Runs on port <%=port%> +<%#=<% end %> <% end %> <% end %> -<% end %> + diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 5a3b5fbd1..d959a6cfb 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -2,10 +2,12 @@ require_relative 'filecreator.rb' class VagrantController + # Executes vagrant up for the specified build + # @param build_number [Int] Selected build number to execute vagrant up on def vagrant_up(build_number) #executes vagrant up from the current build. puts 'Building now.....' command = "cd #{PROJECTS_DIR}/Project#{build_number}/; vagrant up" - exec command + exec command end end diff --git a/lib/xml_report_generator.rb b/lib/xml_report_generator.rb new file mode 100644 index 000000000..537eab14f --- /dev/null +++ b/lib/xml_report_generator.rb @@ -0,0 +1,136 @@ +require 'xmlsimple' + +# Convert systems objects into xml +class XMLReportGenerator + + # Initialize the class with the systems array and the current build number + # @param systems [Array] Array of all systems objects + # @param build_number [Int] Current build number of system + def initialize(systems, build_number) + @systems = systems + @build_number = build_number + end + + ### Start of private methods ### + private + + ## + # Generates hashes as an array for all network interfaces showing the system's ip + # @param s [Array] Current system being generated + # @return [Array] Array of all network hashes + def get_networks_hash(s) + networks_array = Array.new + networks_hash = Hash.new + + s.networks.each do |n| + # grab_system_number = s.id.gsub(/[^0-9]/i, "") + # n.range[9..9] = grab_system_number + + networks_hash['network'] = [n.range << '0'] + + networks_array << networks_hash + end + return networks_array + end + + ## + # Generates hashes as an array for all services to be installed on the specific system + # @param s [Array] Current system being generated + # @return [Array] Array of all service hashes + def get_services_hash(s) + service_array = Array.new + service_hash = Hash.new + s.services.each do |v| + + service_hash['type'] = [v.type] unless v.type.empty? + service_hash['name'] = [v.name] unless v.name.empty? + service_hash['details'] = [v.details] unless v.details.empty? + + v.puppets.each do |p| + service_hash['puppet_file'] = ["#{p}.pp"] + end + service_array << service_hash + end + + return service_array + end + + # Generates hashes as an array for all vulnerabilities to be placed on the specific system + # @param s [Array] Current system being generated + # @return [Array] Array of all vulnerability hashes + def get_vulnerabilities_hash(s) + vulns_array = Array.new + vulns_hash = Hash.new + + s.vulns.each do |v| + + vulns_hash['type'] = [v.type] unless v.type.empty? + vulns_hash['details'] = [v.details] unless v.details.empty? + vulns_hash['privilege'] = [v.privilege] unless v.privilege.empty? + vulns_hash['access'] = [v.access] unless v.access.empty? + vulns_hash['cve'] = [v.cve] unless v.cve.empty? + vulns_hash['difficulty'] = [v.difficulty] unless v.difficulty.empty? + vulns_hash['cvss_rating'] = [v.cvss_rating] unless v.cvss_rating.empty? + vulns_hash['cvss_score'] = [v.cvss_score] unless v.cvss_score.empty? + vulns_hash['vector_string'] = [v.vector_string] unless v.vector_string.empty? + + v.puppets.each do |p| + vulns_hash['puppet_file'] = ["#{p['puppet'][0]}.pp"] + end + vulns_array << vulns_hash + end + return vulns_array + end + + # Generates hashes as an array for all sites to be placed on the specific system + # @param s [Array] Current system being generated + # @return [Array] Array of all vulnerability hashes + def get_sites_hash(s) + sites_array = Array.new + sites_hash = Hash.new + + s.sites.each do |v| + + sites_hash['name'] = [v.name] unless (v.name.nil? || v.name.empty?) + sites_hash['type'] = [v.type] unless v.type.empty? + + sites_array << sites_hash + end + return sites_array + end + + # Creates a hash in the specific format for the XmlSimple library + # @return [Hash] Hash compatible with XmlSimple + def create_xml_hash + hash = Hash.new + @systems.each do |system| + hash = { + 'id' => system.id, 'basebox' => system.basebox, 'os' => system.os, 'url' => system.url, + 'networks' => get_networks_hash(system), + 'services' => get_services_hash(system), + 'vulnerabilities' => get_vulnerabilities_hash(system), + 'sites' => get_sites_hash(system) + } + end + return hash + end + + ### Start of public methods ### + public + + # Write the xml to an xml file + def write_xml_report + XmlSimple.xml_out(create_xml_hash,{:rootname => 'system',:OutputFile => "#{PROJECTS_DIR}/Project#{@build_number}/Report.xml"}) + end + + # Return the xml as a string + # @return [String] + def return_xml + return XmlSimple.xml_out(create_xml_hash,{:rootname => 'system'}) + end + + # Print the xml to the console + def print_xml + puts XmlSimple.xml_out(create_xml_hash,{:rootname => 'system'}) + end +end \ No newline at end of file diff --git a/secgen.rb b/secgen.rb index be18ba531..fc5b11ad5 100644 --- a/secgen.rb +++ b/secgen.rb @@ -6,7 +6,7 @@ require_relative 'lib/systemreader.rb' require_relative 'lib/vagrant.rb' require_relative 'lib/helpers/bootstrap' - +# Displays secgen usage data def usage puts 'Usage: ' + $0 + ' [options] @@ -20,6 +20,8 @@ def usage exit end +# Builds the vagrant configuration file +# @return build_number [Integer] Current system's build number def build_config puts 'Reading configuration file for virtual machines you want to create' @@ -33,11 +35,14 @@ def build_config return build_number end +# Builds the vm via the vagrant file corresponding to build number +# @param build_number [Integer] Desired system's build number def build_vms(build_number) vagrant = VagrantController.new vagrant.vagrant_up(build_number) end +# Runs methods to run and configure a new vm from the configuration file def run build_number = build_config() build_vms(build_number) @@ -55,13 +60,15 @@ if ARGV.length < 1 usage end +# Get command line arguments opts = GetoptLong.new( [ '--help', '-h', GetoptLong::NO_ARGUMENT ], [ '--run', '-r', GetoptLong::NO_ARGUMENT ], [ '--build-config', '-c', GetoptLong::NO_ARGUMENT ], - [ '--build-vms', '-v', GetoptLong::NO_ARGUMENT ] + [ '--build-vms', '-v', GetoptLong::REQUIRED_ARGUMENT ] ) +# Direct via command line arguments opts.each do |opt, arg| case opt when '--help' @@ -73,7 +80,7 @@ opts.each do |opt, arg| when '--build-config' build_config() when '--build-vms' - build_vms() + build_vms(arg) end end diff --git a/xml/bases.xml b/xml/bases.xml index b2ad87686..ad37077c4 100644 --- a/xml/bases.xml +++ b/xml/bases.xml @@ -1,3 +1,4 @@ +