diff --git a/lib/objects/system.rb b/lib/objects/system.rb index 85ce9a969..17874416f 100644 --- a/lib/objects/system.rb +++ b/lib/objects/system.rb @@ -9,27 +9,81 @@ class System attr_accessor :module_selections # (after resolution) attr_accessor :num_actioned_module_conflicts attr_accessor :system_networks - attr_accessor :network_ranges # populated when provided via command line options + attr_accessor :options #(command line options hash) # Initalizes System object # @param [Object] name of the system # @param [Object] attributes such as base box selection # @param [Object] module_selectors these are modules that define filters for selecting the actual modules to use - def initialize(name, attributes, module_selectors, network_ranges) + def initialize(name, attributes, module_selectors) self.name = name self.attributes = attributes self.module_selectors = module_selectors self.module_selections = [] self.num_actioned_module_conflicts = 0 self.system_networks = [] - self.network_ranges = network_ranges + self.options = {} end # selects from the available modules, based on the selection filters that have been specified # @param [Object] available_modules all available modules (vulnerabilities, services, bases) + # @param [Object] opts command line options hash # @return [Object] the list of selected modules - def resolve_module_selection(available_modules) + def resolve_module_selection(available_modules, opts) + @options = opts retry_count = 0 + + # Replace $IP_addresses with options ip_ranges if required + begin + if @options[:ip_ranges] and $datastore['IP_addresses'] and !$datastore['replaced_ranges'] + unused_opts_ranges = @options[:ip_ranges].clone + option_range_map = {} # k = ds_range, v = opts_range + new_ip_addresses = [] + + # Iterate over the DS IPs + $datastore['IP_addresses'].each do |ds_ip_address| + # Split the IP into ['X.X.X', 'Y'] + split_ip = ds_ip_address.split('.') + ds_ip_array = [split_ip[0..2].join('.'), split_ip[3]] + ds_range = ds_ip_array[0] + '.0' + # Check if we have encountered first 3 octets before i.e. look in option_range_map for key(ds_range) + if option_range_map.has_key? ds_range + # if we have, grab that value (opts_range) + opts_range = option_range_map[ds_range] + # replace first 3 in ds_ip with first 3 in opts_range + split_opts_range = opts_range.split('.') + split_opts_range[3] = ds_ip_array[1] + new_ds_ip = split_opts_range.join('.') + # save in $datastore['IP_addresses'] + new_ip_addresses << new_ds_ip + else #(if we haven't seen the first 3 octets before) + # grab the first range that we haven't used yet from unused_opts_ranges with .shift (also removes the range) + opts_range = unused_opts_ranges.shift + # store the range mapping in option_range_map (ds_range => opts_range) + option_range_map[ds_range] = opts_range + # split the opts_range and replace last octet with last octet of ds_ip_address + split_opts_range = opts_range.split('.') + split_opts_range[3] = ds_ip_array[1] + new_ds_ip = split_opts_range.join('.') + # save in $datastore['IP_addresses'] + new_ip_addresses << new_ds_ip + end + end + $datastore['IP_addresses'] = new_ip_addresses + $datastore['replaced_ranges'] = true + end + rescue NoMethodError + required_ranges = [] + $datastore['IP_addresses'].each { |ip_address| + split_range = ip_address.split('.') + split_range[3] = 0 + required_ranges << split_range.join('.') + } + required_ranges.uniq! + Print.err("Fatal: Not enough ranges were provided with --network-ranges. Provided: #{options[:ip_ranges].size} Required: #{required_ranges.uniq.size}") + exit + end + begin selected_modules = [] @@ -88,30 +142,25 @@ class System # filter to those that satisfy the attribute filters # select based on selected type, access, cve... - search_list.delete_if{|module_for_possible_exclusion| + search_list.delete_if { |module_for_possible_exclusion| !module_for_possible_exclusion.matches_attributes_requirement(required_attributes) } Print.verbose "Filtered to modules matching: #{required_attributes.inspect} ~= (n=#{search_list.size})" # remove non-options due to conflicts - search_list.delete_if{|module_for_possible_exclusion| + search_list.delete_if { |module_for_possible_exclusion| check_conflicts_with_list(module_for_possible_exclusion, previously_selected_modules) } # check if modules need to be unique # write_module_path_to_datastore if write_module_path_to_datastore != nil && $datastore[write_module_path_to_datastore] != nil - search_list.delete_if{|module_for_possible_exclusion| + search_list.delete_if { |module_for_possible_exclusion| ($datastore[write_module_path_to_datastore] ||=[]).include? module_for_possible_exclusion.module_path } Print.verbose "Filtering to remove non-unique #{$datastore[write_module_path_to_datastore]} ~= (n=#{search_list.size})" end - # check if we have a network range - if self.network_ranges != nil && ($datastore['network'] == nil or $datastore['network'].empty?) - $datastore['network_override'] = network_ranges - end - if search_list.length == 0 raise 'failed' Print.err 'Could not find a matching module. Please check the scenario specification' @@ -327,7 +376,7 @@ class System if /^.*defaultinput/ =~ def_unique_id def_unique_id = def_unique_id.gsub(/^.*defaultinput/, selected.unique_id) end - + default_modules_to_add.concat select_modules(module_to_add.module_type, module_to_add.attributes, available_modules, previously_selected_modules + default_modules_to_add, def_unique_id, module_to_add.write_output_variable, def_write_to, module_to_add.received_inputs, module_to_add.default_inputs_literals, module_to_add.write_to_datastore, module_to_add.received_datastores, module_to_add.write_module_path_to_datastore) end end @@ -387,7 +436,7 @@ class System end def get_networks - if (self.system_networks = []) # assign the networks + if (self.system_networks = []) # assign the networks self.module_selections.each do |mod| if mod.module_type == 'network' self.system_networks << mod diff --git a/lib/output/project_files_creator.rb b/lib/output/project_files_creator.rb index acde9cab4..95cfe5736 100644 --- a/lib/output/project_files_creator.rb +++ b/lib/output/project_files_creator.rb @@ -39,13 +39,13 @@ class ProjectFilesCreator if File.exists? "#{@out_dir}/Vagrantfile" or File.exists? "#{@out_dir}/puppet" dest_dir = "#{@out_dir}/MOVED_#{Time.new.strftime("%Y%m%d_%H%M")}" Print.warn "Project already built to this directory -- moving last build to: #{dest_dir}" - Dir.glob( "#{@out_dir}/**/*" ).select { |f| File.file?( f ) }.each do |f| + Dir.glob("#{@out_dir}/**/*").select { |f| File.file?(f) }.each do |f| dest = "#{dest_dir}/#{f}" - FileUtils.mkdir_p( File.dirname( dest ) ) + FileUtils.mkdir_p(File.dirname(dest)) if f =~ /\.vagrant/ - FileUtils.cp( f, dest ) + FileUtils.cp(f, dest) else - FileUtils.mv( f, dest ) + FileUtils.mv(f, dest) end end end @@ -80,7 +80,7 @@ class ProjectFilesCreator if File.file? packerfile_path Print.info "Would you like to use the packerfile to create the packerfile from the given url (y/n)" - (Print.info "Exiting as vagrant needs the basebox to continue"; exit) unless ['y','yes'].include?(STDIN.gets.chomp.downcase) + (Print.info "Exiting as vagrant needs the basebox to continue"; exit) unless ['y', 'yes'].include?(STDIN.gets.chomp.downcase) Print.std "Packerfile #{packerfile_path.split('/').last} found, building basebox #{url.split('/').last} via packer" template_based_file_write(packerfile_path, packerfile_path.split(/.erb$/).first) @@ -156,10 +156,16 @@ class ProjectFilesCreator end # Resolves the network based on the scenario and ip_range. - def resolve_network(scenario_ip_range) + def resolve_network(network_module) + current_network = network_module + scenario_ip_range = network_module.attributes['range'].first + + # Use datastore IP_address if we have them + if current_network.received_inputs.include? 'IP_address' + ip_address = current_network.received_inputs['IP_address'].first + elsif @options.has_key? :ip_ranges # if we have options[:ip_ranges] we want to use those instead of the ip_range argument. # Store the mappings of scenario_ip_ranges => @options[:ip_range] in @option_range_map - if @options.has_key? :ip_ranges # Have we seen this scenario_ip_range before? If so, use the value we've assigned if @option_range_map.has_key? scenario_ip_range ip_range = @option_range_map[scenario_ip_range] @@ -170,10 +176,14 @@ class ProjectFilesCreator @option_range_map[scenario_ip_range] = options_ips.first ip_range = options_ips.first end + ip_address = get_ip_from_range(ip_range) else - ip_range = scenario_ip_range + ip_address = get_ip_from_range(scenario_ip_range) end + ip_address + end + def get_ip_from_range(ip_range) # increment @scenario_networks{ip_range=>counter} @scenario_networks[ip_range] += 1 diff --git a/lib/readers/system_reader.rb b/lib/readers/system_reader.rb index b8b4d313f..dfc5c299d 100644 --- a/lib/readers/system_reader.rb +++ b/lib/readers/system_reader.rb @@ -10,7 +10,7 @@ class SystemReader # This includes module filters, which are module objects that contain filters for selecting # from the actual modules that are available # @return [Array] Array containing Systems objects - def self.read_scenario(scenario_file, network_ranges) + def self.read_scenario(scenario_file) systems = [] Print.verbose "Reading scenario file: #{scenario_file}" doc, xsd = nil @@ -38,19 +38,6 @@ class SystemReader # remove xml namespaces for ease of processing doc.remove_namespaces! - # hack for networks -- TODO: Remove me ASAP DO NOT MERGE TO MASTER - ranges = [] - if network_ranges - network_ranges.each { |range| - doc.xpath('/scenario/system').size.times { |count| - range_array = range.split('.') - range_array[-1] = count+2 - ranges << range_array.join('.') - } - } - network_ranges = ranges - end - doc.xpath('/scenario/system').each_with_index do |system_node, system_index| module_selectors = [] system_attributes = {} @@ -159,7 +146,7 @@ class SystemReader end end - systems << System.new(system_name, system_attributes, module_selectors, network_ranges) + systems << System.new(system_name, system_attributes, module_selectors) end return systems diff --git a/lib/schemas/network_metadata_schema.xsd b/lib/schemas/network_metadata_schema.xsd index b0571191e..98f8fb9b8 100644 --- a/lib/schemas/network_metadata_schema.xsd +++ b/lib/schemas/network_metadata_schema.xsd @@ -38,6 +38,8 @@ + + @@ -57,4 +59,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/schemas/scenario_schema.xsd b/lib/schemas/scenario_schema.xsd index 25291a7bd..2dcf001b3 100644 --- a/lib/schemas/scenario_schema.xsd +++ b/lib/schemas/scenario_schema.xsd @@ -126,6 +126,9 @@ + + + diff --git a/lib/templates/Vagrantfile.erb b/lib/templates/Vagrantfile.erb index c786269a9..37f1a374a 100644 --- a/lib/templates/Vagrantfile.erb +++ b/lib/templates/Vagrantfile.erb @@ -114,6 +114,11 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| <% system.module_selections.each do |selected_module| -%> <%= selected_module.to_s_comment -%> +<% if selected_module.module_type == 'network' and selected_module.received_inputs.include? 'IP_address' %> +<%= ' # This module has a datastore entry for IP_address, using that instead of the default.' -%> +<% elsif selected_module.module_type == 'network' and @options.has_key? :ip_ranges -%> +<%= ' # This module has a command line ip_range, using that instead of the default.' -%> +<% end -%> <% case selected_module.module_type when 'base' -%> <% if (@options.has_key? :ovirtuser) && (@options.has_key? :ovirtpass) %> # TODO @@ -131,7 +136,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| <%= system.name %>.vm.network :forwarded_port, guest: 5985, host: 5985, id: "winrm", auto_correct: true <% end %> <% when 'network' -%> -<% if selected_module.attributes['range'].first.nil? || selected_module.attributes['range'].first == "dhcp" -%> +<% if (selected_module.attributes['range'].first.nil? || selected_module.attributes['range'].first == "dhcp") and (!selected_module.received_inputs.include? 'IP_address' and !@options[:ip_ranges])-%> <% if (@options.has_key? :ovirtnetwork) && (@options.has_key? :ovirtuser) && (@options.has_key? :ovirtpass) %> <%= system.name %>.vm.network :<%= selected_module.attributes['type'].first %>, type: "dhcp", :ovirt__network_name => '<%= "#{@options[:ovirtnetwork]}" %>' <% else %> @@ -140,16 +145,16 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| <% else -%> <% if (@options.has_key? :ovirtuser) && (@options.has_key? :ovirtpass) %> <% if @ovirt_template and @ovirt_template.include? 'kali_linux_msf' %> - <%= system.name %>.vm.provision 'shell', inline: "echo \"auto lo\niface lo inet loopback\n\nauto eth0\niface eth0 inet static\n\taddress <%= resolve_network(selected_module.attributes['range'].first)%>\" > /etc/network/interfaces" + <%= system.name %>.vm.provision 'shell', inline: "echo \"auto lo\niface lo inet loopback\n\nauto eth0\niface eth0 inet static\n\taddress <%= resolve_network(selected_module)%>\" > /etc/network/interfaces" <%= system.name %>.vm.provision 'shell', inline: "echo '' > /etc/environment" <% elsif @ovirt_template and @ovirt_template.include? 'debian_desktop_kde' %> - <%= system.name %>.vm.provision 'shell', inline: "echo \"\nauto eth1\niface eth1 inet static\n\taddress <%= resolve_network(selected_module.attributes['range'].first)%>\" >> /etc/network/interfaces" + <%= system.name %>.vm.provision 'shell', inline: "echo \"\nauto eth1\niface eth1 inet static\n\taddress <%= resolve_network(selected_module)%>\" >> /etc/network/interfaces" <%= system.name %>.vm.provision 'shell', inline: "echo '' > /etc/environment" <% else %> - <%= system.name %>.vm.network :<%= selected_module.attributes['type'].first %>, :ovirt__ip => "<%= resolve_network(selected_module.attributes['range'].first)%>", :ovirt__network_name => '<%= "#{@options[:ovirtnetwork]}" %>' + <%= system.name %>.vm.network :<%= selected_module.attributes['type'].first %>, :ovirt__ip => "<%= resolve_network(selected_module)%>", :ovirt__network_name => '<%= "#{@options[:ovirtnetwork]}" %>' <% end %> <% else %> - <%= system.name %>.vm.network :<%= selected_module.attributes['type'].first %>, ip: "<%= resolve_network(selected_module.attributes['range'].first)%>" + <%= system.name %>.vm.network :<%= selected_module.attributes['type'].first %>, ip: "<%= resolve_network(selected_module)%>" <% end %> <% end -%> <% when 'vulnerability', 'service', 'utility', 'build' -%> diff --git a/modules/networks/host_only/private_network_1/secgen_metadata.xml b/modules/networks/host_only/private_network_1/secgen_metadata.xml index 2c3dd4e46..61d9bc16e 100644 --- a/modules/networks/host_only/private_network_1/secgen_metadata.xml +++ b/modules/networks/host_only/private_network_1/secgen_metadata.xml @@ -11,4 +11,6 @@ private_network 172.16.0.0 + IP_address + \ No newline at end of file diff --git a/modules/networks/host_only/private_network_2/secgen_metadata.xml b/modules/networks/host_only/private_network_2/secgen_metadata.xml index 67fce352c..10ef7b52a 100644 --- a/modules/networks/host_only/private_network_2/secgen_metadata.xml +++ b/modules/networks/host_only/private_network_2/secgen_metadata.xml @@ -11,4 +11,6 @@ private_network 172.17.0.0 + IP_address + \ No newline at end of file diff --git a/secgen.rb b/secgen.rb index 6eb53d762..5ced0ca26 100644 --- a/secgen.rb +++ b/secgen.rb @@ -63,7 +63,7 @@ def build_config(scenario, out_dir, options) Print.info 'Reading configuration file for virtual machines you want to create...' # read the scenario file describing the systems, which contain vulnerabilities, services, etc # this returns an array/hashes structure - systems = SystemReader.read_scenario(scenario, options[:ip_ranges]) + systems = SystemReader.read_scenario(scenario) Print.std "#{systems.size} system(s) specified" Print.info 'Reading available base modules...' @@ -104,12 +104,10 @@ def build_config(scenario, out_dir, options) all_available_services + all_available_utilities + all_available_generators + all_available_encoders + all_available_networks # update systems with module selections systems.map! {|system| - system.module_selections = system.resolve_module_selection(all_available_modules) + system.module_selections = system.resolve_module_selection(all_available_modules, options) system } - validate_network_ranges(systems, options) - Print.info "Creating project: #{out_dir}..." # create's vagrant file / report a starts the vagrant installation' creator = ProjectFilesCreator.new(systems, out_dir, scenario, options) @@ -280,26 +278,6 @@ def delete_all_projects FileUtils.rm_r(Dir.glob("#{PROJECTS_DIR}/*")) end -# Ensure enough ranges are provided with --network-ranges -def validate_network_ranges(systems, options) - if options.has_key? :ip_ranges - scenario_networks = [] - systems.each { |sys| scenario_networks << sys.get_networks } - scenario_networks.flatten! - - # Remove dhcp networks - scenario_networks.delete_if { |network| network.attributes['range'][0] == 'dhcp' } - - # Remove non-unique network ranges - scenario_networks = Hash[*scenario_networks.map { |network| [network.attributes['range'], network] }.flatten].values - - if options[:ip_ranges].size < scenario_networks.size - Print.err("Fatal: Not enough ranges were provided with --network-ranges. Provided: #{options[:ip_ranges].size} Required: #{scenario_networks.size}") - exit - end - end -end - # end of method declarations # start of program execution