network-ranges => scenario passthrough / rework

This commit is contained in:
thomashaw
2017-10-18 14:57:53 +01:00
parent 444d9ea9eb
commit 0033a7c642
9 changed files with 133 additions and 66 deletions

View File

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

View File

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

View File

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

View File

@@ -38,6 +38,8 @@
</xs:simpleType>
</xs:element>
<xs:element name="read_fact" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="default_input" type="InputElements" minOccurs="0" maxOccurs="unbounded" />
<!-- cannot co-exist with a network matching ALL of the optionally specified values (can be repeated for OR)-->
<xs:element name="conflict" minOccurs="0" maxOccurs="unbounded">
@@ -57,4 +59,33 @@
</xs:complexType>
</xs:element>
<xs:complexType name="InputElements">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name='generator' type='ServiceUtilityEncoderGeneratorType' minOccurs='0' maxOccurs='unbounded' />
<xs:element name='encoder' type='ServiceUtilityEncoderGeneratorType' minOccurs='0' maxOccurs='unbounded' />
<xs:element name='value' type='xs:string' minOccurs='0' maxOccurs='unbounded' />
</xs:choice>
</xs:sequence>
<xs:attribute name='into' type='xs:string'/>
</xs:complexType>
<xs:complexType name="ServiceUtilityEncoderGeneratorType">
<xs:sequence>
<xs:element name="input" type="InputElements" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="module_path" type="xs:string"/>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="author" type="xs:string"/>
<xs:attribute name="module_license" type="xs:string"/>
<xs:attribute name="description" type="xs:string"/>
<xs:attribute name="type" type="xs:string"/>
<xs:attribute name="platform" type="xs:string"/>
<xs:attribute name="reference" type="xs:string"/>
<xs:attribute name="software_name" type="xs:string"/>
<xs:attribute name="software_license" type="xs:string"/>
</xs:complexType>
</xs:schema>

View File

@@ -126,6 +126,9 @@
</xs:complexType>
<xs:complexType name="NetworkType">
<xs:sequence>
<xs:element name="input" type="InputElements" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="module_path" type="xs:string"/>
<xs:attribute name="name" type="xs:string"/>

View File

@@ -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' -%>

View File

@@ -11,4 +11,6 @@
<type>private_network</type>
<range>172.16.0.0</range>
<read_fact>IP_address</read_fact>
</network>

View File

@@ -11,4 +11,6 @@
<type>private_network</type>
<range>172.17.0.0</range>
<read_fact>IP_address</read_fact>
</network>

View File

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