spec/arachni/rpc/server/distributor_spec.rb in arachni-0.4.1.3 vs spec/arachni/rpc/server/distributor_spec.rb in arachni-0.4.2

- old
+ new

@@ -1,23 +1,31 @@ require_relative '../../../spec_helper' require 'timeout' -require Arachni::Options.instance.dir['lib'] + 'rpc/client/instance' -require Arachni::Options.instance.dir['lib'] + 'rpc/server/instance' +require Arachni::Options.dir['lib'] + 'rpc/client/instance' +require Arachni::Options.dir['lib'] + 'rpc/server/instance' -require Arachni::Options.instance.dir['lib'] + 'rpc/server/distributor' +require Arachni::Options.dir['lib'] + 'rpc/server/distributor' class Distributor include Arachni::RPC::Server::Framework::Distributor - attr_reader :instances + attr_reader :instances attr_accessor :master_url + [ :map_slaves, :each_slave, :slave_iterator, :iterator_for, + :split_urls, :build_elem_list, :distribute_elements, :preferred_dispatchers, + :pick_dispatchers, :distribute_and_run ].each do |sym| + private sym + public sym + end + def initialize( token ) @opts = Arachni::Options.instance @local_token = token @instances = [] + @running_slaves = Set.new end def self_url @master_url end @@ -32,24 +40,47 @@ end class FakeMaster attr_reader :issues + attr_reader :issue_summaries def initialize( opts, token ) @opts = opts @token = token @server = Arachni::RPC::Server::Base.new( @opts, token ) @pages = [] @issues = [] - @element_ids = [] + @issue_summaries = [] + @element_ids = [] @server.add_handler( "framework", self ) @server.start end + def enslave( instance_hash ) + instance = Arachni::RPC::Client::Instance.new( @opts, + instance_hash['url'], + instance_hash['token']) + + instance.framework.set_master( "#{@server.opts[:host]}:#{@server.opts[:port]}", + @token ) + end + + def update_element_ids_per_page( *args ) + end + + def register_issue_summaries( issues, token = nil ) + return false if !valid_token?( token ) + @issue_summaries |= issues + true + end + + def slave_done( *args ) + end + def register_issues( issues, token = nil ) return false if !valid_token?( token ) @issues |= issues true end @@ -112,11 +143,11 @@ @urls << url_gen.call( url2, i ) end end describe '#map_slaves' do - it 'should asynchronously iterate over all slaves' do + it 'asynchronously maps all slaves' do q = Queue.new foreach = proc { |instance, iter| instance.service.alive? { |res| iter.return( res ) } } after = proc { |res| q << res } @@ -131,11 +162,11 @@ raised.should be_false end end describe '#each_slave' do - it 'should asynchronously iterate over all slaves' do + it 'asynchronously iterates over all slaves' do q = Queue.new foreach = proc do |instance, iter| instance.service.alive? do |res| q << res @@ -150,14 +181,39 @@ rescue Timeout::Error raised = true end raised.should be_false end + + context 'when passed an "after" block' do + it 'calls it after the iteration has completed' do + q = Queue.new + + foreach = proc do |instance, iter| + instance.service.alive? do |res| + q << res + iter.next + end + end + after = proc { q << :after } + + @distributor.each_slave( foreach, after ) + + raised = false + begin + Timeout::timeout( 5 ) { [q.pop, q.pop, q.pop].should == [true, true, :after] } + rescue Timeout::Error + raised = true + end + raised.should be_false + end + + end end describe '#slave_iterator' do - it 'should return an async iterator for the slave instances' do + it 'returns an async iterator for the slave instances' do q = Queue.new foreach = proc do |instance, iter| q << instance['url'] iter.next @@ -175,11 +231,11 @@ raised.should be_false end end describe '#iterator_for' do - it 'should return an async iterator for the provided array' do + it 'returns an async iterator for the provided array' do q = Queue.new foreach = proc do |instance, iter| q << instance['url'] iter.next @@ -197,11 +253,11 @@ raised.should be_false end end describe '#split_urls' do - it 'should evenly split urls into chunks for each instance' do + it 'evenly splits urls into chunks for each instance' do @opts.min_pages_per_instance = 10 splits = @distributor.split_urls( @urls, 4 ) splits.size.should == 4 splits.flatten.uniq.size.should == @urls.uniq.size @@ -221,11 +277,11 @@ splits.first.size.should == splits.flatten.size end end describe '#build_elem_list' do - it 'should evenly distribute elements across instances' do + it 'evenly distributes elements across instances' do @opts.url = server_url_for( :parser ) @opts.audit_links = true @opts.audit_forms = true @opts.audit_cookies = true @opts.audit_headers = true @@ -240,11 +296,11 @@ @distributor.build_elem_list( page ).size.should == 7 end end describe '#distribute_elements' do - it 'should evenly distribute elements across instances' do + it 'evenly distributes elements across instances' do chunks = [[@url], [@url2]] elem_ids_per_page = { @url => %w( elem elem_1 @@ -370,12 +426,12 @@ r = @distributor.distribute_elements( chunks, elem_ids_per_page ) r.should == [ %w(elem elem_4), %w(elem_2), %w(elem_1 elem_3 elem_5)] end end - describe '#prefered_dispatchers' do - it 'should return a sorted list of dispatchers for HPG use taking into account their pipe IDs and load balancing metrics' do + describe '#preferred_dispatchers' do + it 'returns a sorted list of dispatchers for HPG use taking into account their pipe IDs and load balancing metrics' do @opts.pool_size = 1 opts = @opts port = random_port dispatchers = [] @@ -421,11 +477,11 @@ end @distributor.dispatcher_url = "#{opts.rpc_address}:#{port}" q = Queue.new - @distributor.prefered_dispatchers { |d| q << d } + @distributor.preferred_dispatchers { |d| q << d } pref_dispatchers = [] raised = false begin @@ -440,11 +496,11 @@ pref_dispatchers.should == dispatchers end end describe '#pick_dispatchers' do - it 'should return a sorted list of dispatchers based on their load balancing metrics' do + it 'returns a sorted list of dispatchers based on their load balancing metrics' do dispatchers = [] dispatchers << { 'node' => { 'score' => 0 } } dispatchers << { 'node' => { 'score' => 3 } } dispatchers << { 'node' => { 'score' => 2 } } dispatchers << { 'node' => { 'score' => 1 } } @@ -457,11 +513,11 @@ @distributor.pick_dispatchers( dispatchers ). map { |d| d['node']['score'] }.should == [0, 1] end end - describe '#spawn' do + describe '#distribute_and_run' do before( :all ) do @opts.rpc_port = random_port @opts.dir['modules'] = spec_path + 'fixtures/taint_module/' @master = FakeMaster.new( @opts, @token ) @distributor.master_url = "#{@opts.rpc_address}:#{@opts.rpc_port}" @@ -476,129 +532,145 @@ @opts.url = server_url_for( :framework_hpg ) @url = @opts.url.to_s @opts.mods = %w(taint) @dispatcher_url = "#{@opts.rpc_address}:#{port}" + + @get_instance_info = proc do + instance = @get_instance.call + info = { 'url' => instance.url, 'token' => @token } + @master.enslave( info ) + info + end end - after { @master.issues.clear } + after do + @master.issues.clear + @master.issue_summaries.clear + end context 'when called without auditable restrictions' do - it 'should let the slave run loose, like a simple instance' do + it 'lets the slave run loose, like a simple instance' do q = Queue.new - @distributor.spawn( @dispatcher_url ){ |i| q << i } + + @distributor.distribute_and_run( @get_instance_info.call ){ |i| q << i } slave_info = q.pop slave_info.should be_true slave = @distributor.connect_to_instance( slave_info ) sleep 0.1 while slave.framework.busy? sleep 1 - @master.issues.size.should == 51 + @master.issues.size.should == 500 + @master.issue_summaries.size.should == 500 end end context 'when called with auditable URL restrictions' do - it 'should restrict the audit to these URLs' do + it 'restricts the audit to these URLs' do urls = %w(/vulnerable?vulnerable_5=stuff5 /vulnerable?vulnerable_10=stuff10) absolute_urls = urls.map { |u| Arachni::Module::Utilities.normalize_url( @url + u ) } q = Queue.new - @distributor.spawn( @dispatcher_url, urls: urls ){ |i| q << i } + @distributor.distribute_and_run( @get_instance_info.call, urls: urls ){ |i| q << i } slave_info = q.pop slave_info.should be_true slave = @distributor.connect_to_instance( slave_info ) slave.opts.restrict_paths.should == absolute_urls sleep 0.1 while slave.framework.busy? sleep 1 @master.issues.size.should == 2 + @master.issue_summaries.size.should == 2 vuln_urls = @master.issues.map { |i| i.url }.sort.uniq vuln_urls.should == absolute_urls.sort.uniq end end context 'when called with auditable element restrictions' do - it 'should restrict the audit to these elements' do + it 'restricts the audit to these elements' do ids = [] ids << Arachni::Element::Link.new( @url + '/vulnerable', - inputs: { 'vulnerable_20' => 'stuff20' } + inputs: { '0_vulnerable_20' => 'stuff20' } ).scope_audit_id ids << Arachni::Element::Link.new( @url + '/vulnerable', - inputs: { 'vulnerable_30' => 'stuff30' } + inputs: { '9_vulnerable_30' => 'stuff30' } ).scope_audit_id q = Queue.new - @distributor.spawn( @dispatcher_url, elements: ids ){ |i| q << i } + @distributor.distribute_and_run( @get_instance_info.call, elements: ids ){ |i| q << i } slave_info = q.pop slave_info.should be_true slave = @distributor.connect_to_instance( slave_info ) sleep 0.1 while slave.framework.busy? sleep 1 @master.issues.size.should == 2 + @master.issue_summaries.size.should == 2 vuln_urls = @master.issues.map { |i| i.url }.sort.uniq - exp_urls = %w(/vulnerable?vulnerable_20=stuff20 /vulnerable?vulnerable_30=stuff30) + exp_urls = %w(/vulnerable?0_vulnerable_20=stuff20 /vulnerable?9_vulnerable_30=stuff30) vuln_urls.should == exp_urls.map { |u| Arachni::Module::Utilities.normalize_url( @url + u ) }. sort.uniq end context 'and new elements appear via the trainer' do - it 'should override the restrictions' do + it 'soverride the restrictions' do @opts.audit_forms = true @opts.url = server_url_for( :auditor ) + '/train/default' url = @opts.url.to_s id = Arachni::Element::Form.new( url + '?', inputs: { 'step_1' => 'form_blah_step_1' } ).scope_audit_id q = Queue.new - @distributor.spawn( @dispatcher_url, elements: [id] ){ |i| q << i } + @distributor.distribute_and_run( @get_instance_info.call, elements: [id] ){ |i| q << i } slave_info = q.pop slave_info.should be_true slave = @distributor.connect_to_instance( slave_info ) sleep 0.1 while slave.framework.busy? sleep 1 @master.issues.size.should == 8 + @master.issue_summaries.size.should == 8 + #@master.issues.size.should == 1 #@master.issues.first.url.should == # url + "?you_made_it=to+the+end+of+the+training" end end end - context 'when called with extra page' do - it 'should include them in the audit' do + context 'when called with extra pages' do + it 'includes them in the audit' do exp_urls = [] links = [] - links << Arachni::Element::Link.new( @url + '/vulnerable?vulnerable_20=stuff20', - inputs: { 'vulnerable_20' => 'stuff20' } + links << Arachni::Element::Link.new( @url + '/vulnerable?2_vulnerable_20=stuff20', + inputs: { '2_vulnerable_20' => 'stuff20' } ) - links << Arachni::Element::Link.new( @url + '/vulnerable?vulnerable_30=stuff30', - inputs: { 'vulnerable_30' => 'stuff30' } + links << Arachni::Element::Link.new( @url + '/vulnerable?5_vulnerable_30=stuff30', + inputs: { '5_vulnerable_30' => 'stuff30' } ) exp_urls |= links.map { |l| l.url } pages = [] pages << Arachni::Page.new( url: @url, links: links ) links = [] - links << Arachni::Element::Link.new( @url + '/vulnerable?vulnerable_12=stuff12', - inputs: { 'vulnerable_12' => 'stuff12' } + links << Arachni::Element::Link.new( @url + '/vulnerable?6_vulnerable_12=stuff12', + inputs: { '6_vulnerable_12' => 'stuff12' } ) - links << Arachni::Element::Link.new( @url + '/vulnerable?vulnerable_23=stuff23', - inputs: { 'vulnerable_23' => 'stuff23' } + links << Arachni::Element::Link.new( @url + '/vulnerable?0_vulnerable_23=stuff23', + inputs: { '0_vulnerable_23' => 'stuff23' } ) exp_urls |= links.map { |l| l.url } pages << Arachni::Page.new( @@ -607,18 +679,19 @@ ) # send it somewhere that doesn't exist @opts.url = @url + '/foo' q = Queue.new - @distributor.spawn( @dispatcher_url, pages: pages ){ |i| q << i } + @distributor.distribute_and_run( @get_instance_info.call, pages: pages ){ |i| q << i } slave_info = q.pop slave_info.should be_true slave = @distributor.connect_to_instance( slave_info ) sleep 0.1 while slave.framework.busy? sleep 1 @master.issues.size.should == 4 + @master.issue_summaries.size.should == 4 vuln_urls = @master.issues.map { |i| i.url }.sort.uniq vuln_urls.should == exp_urls.sort end end