mirror of
https://github.com/cliffe/SecGen.git
synced 2026-02-21 11:18:06 +00:00
network-ranges => scenario passthrough / rework
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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' -%>
|
||||
|
||||
@@ -11,4 +11,6 @@
|
||||
<type>private_network</type>
|
||||
<range>172.16.0.0</range>
|
||||
|
||||
<read_fact>IP_address</read_fact>
|
||||
|
||||
</network>
|
||||
@@ -11,4 +11,6 @@
|
||||
<type>private_network</type>
|
||||
<range>172.17.0.0</range>
|
||||
|
||||
<read_fact>IP_address</read_fact>
|
||||
|
||||
</network>
|
||||
26
secgen.rb
26
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user