Path: blob/master/spec/integration/msfmcpd/tool_execution_db_spec.rb
70330 views
# frozen_string_literal: true12require 'msf/core/mcp'3require 'webmock/rspec'45RSpec.describe 'Tool Execution End-to-End - Database Queries' do6# Disable real HTTP connections for integration tests7before(:all) do8WebMock.disable_net_connect!(allow_localhost: false)9end1011after(:all) do12WebMock.allow_net_connect!13end1415let(:host) { 'localhost' }16let(:port) { 55553 }17let(:endpoint) { '/api/' }18let(:api_url) { "https://#{host}:#{port}#{endpoint}" }19let(:user) { 'test_user' }20let(:password) { 'test_password' }2122describe 'Database Query Integration with HTTP' do23it 'executes host query through complete HTTP request flow' do24# Stub authentication endpoint25stub_request(:post, api_url)26.with(body: ['auth.login', user, password].to_msgpack)27.to_return(28status: 200,29body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,30headers: { 'Content-Type' => 'binary/message-pack' }31)3233# Stub db.hosts endpoint with realistic response34hosts_stub = stub_request(:post, api_url)35.with(body: ['db.hosts', 'test_token', { workspace: 'default' }].to_msgpack)36.to_return(37status: 200,38body: {39'hosts' => [40{41'address' => '192.168.1.100',42'mac' => '00:11:22:33:44:55',43'name' => 'server01',44'os_name' => 'Linux',45'os_flavor' => 'Ubuntu',46'state' => 'alive',47'created_at' => 1609459200,48'updated_at' => 164099520049},50{51'address' => '192.168.1.101',52'mac' => '00:11:22:33:44:56',53'name' => 'server02',54'os_name' => 'Windows',55'os_flavor' => 'Server 2019',56'state' => 'alive',57'created_at' => 1609459300,58'updated_at' => 164099530059}60]61}.to_msgpack,62headers: { 'Content-Type' => 'binary/message-pack' }63)6465# Create rate limiter66limiter = Msf::MCP::Security::RateLimiter.new(requests_per_minute: 60, burst_size: 10)6768# Create authenticated client69client = Msf::MCP::Metasploit::MessagePackClient.new(70host: host,71port: port,72endpoint: endpoint73)74client.authenticate(user, password)7576# Create server context77server_context = {78msf_client: client,79rate_limiter: limiter80}8182# Execute host query through complete stack83result = Msf::MCP::Tools::HostInfo.call(84workspace: 'default',85server_context: server_context86)8788# Verify HTTP request was made89expect(hosts_stub).to have_been_requested.once9091# Verify MCP response structure92expect(result).to be_a(MCP::Tool::Response)93expect(result.content).to be_an(Array)94expect(result.content.first[:type]).to eq('text')9596# Verify data transformation occurred correctly97data = result.structured_content[:data]98expect(data).to be_an(Array)99expect(data.length).to eq(2)100expect(data.first[:address]).to eq('192.168.1.100')101expect(data.first[:hostname]).to eq('server01')102expect(data.first[:os_name]).to eq('Linux')103104# Verify timestamps transformed to ISO 8601105expect(data.first[:created_at]).to eq('2021-01-01T00:00:00Z')106expect(data.first[:updated_at]).to eq('2022-01-01T00:00:00Z')107108# Verify metadata109metadata = result.structured_content[:metadata]110expect(metadata[:workspace]).to eq('default')111expect(metadata[:total_items]).to eq(2)112expect(metadata[:returned_items]).to eq(2)113end114115it 'applies filters correctly through HTTP request' do116# Stub authentication117stub_request(:post, api_url)118.with(body: ['auth.login', user, password].to_msgpack)119.to_return(120status: 200,121body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,122headers: { 'Content-Type' => 'binary/message-pack' }123)124125# Stub db.hosts with filters126hosts_stub = stub_request(:post, api_url)127.with(body: ['db.hosts', 'test_token', { workspace: 'default', addresses: '192.168.1.0/24', only_up: true }].to_msgpack)128.to_return(129status: 200,130body: {131'hosts' => [132{133'address' => '192.168.1.100',134'mac' => '00:11:22:33:44:55',135'name' => 'filtered_host',136'state' => 'alive',137'created_at' => 1609459200138}139]140}.to_msgpack,141headers: { 'Content-Type' => 'binary/message-pack' }142)143144limiter = Msf::MCP::Security::RateLimiter.new(requests_per_minute: 60, burst_size: 10)145client = Msf::MCP::Metasploit::MessagePackClient.new(146host: host,147port: port,148endpoint: endpoint149)150client.authenticate(user, password)151152server_context = {153msf_client: client,154rate_limiter: limiter155}156157# Execute query with filters158result = Msf::MCP::Tools::HostInfo.call(159workspace: 'default',160addresses: '192.168.1.0/24',161only_up: true,162server_context: server_context163)164165# Verify HTTP request with filters was made166expect(hosts_stub).to have_been_requested.once167168# Verify filtered results169expect(result).to be_a(MCP::Tool::Response)170data = result.structured_content[:data]171expect(data.length).to eq(1)172expect(data.first[:address]).to eq('192.168.1.100')173end174175it 'executes service query with multiple filters through HTTP' do176# Stub authentication177stub_request(:post, api_url)178.with(body: ['auth.login', user, password].to_msgpack)179.to_return(180status: 200,181body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,182headers: { 'Content-Type' => 'binary/message-pack' }183)184185# Stub db.services with filters186# Note: MessagePack hash key order may vary, so we match any request to db.services187services_stub = stub_request(:post, api_url)188.with { |request|189body = MessagePack.unpack(request.body)190body[0] == 'db.services' && body[1] == 'test_token' &&191body[2].is_a?(Hash) && body[2]['workspace'] == 'default'192}193.to_return(194status: 200,195body: {196'services' => [197{198'host' => '192.168.1.100',199'port' => 445,200'proto' => 'tcp',201'name' => 'microsoft-ds',202'state' => 'open',203'created_at' => 1609459200,204'updated_at' => 1640995200205}206]207}.to_msgpack,208headers: { 'Content-Type' => 'binary/message-pack' }209)210211limiter = Msf::MCP::Security::RateLimiter.new(requests_per_minute: 60, burst_size: 10)212client = Msf::MCP::Metasploit::MessagePackClient.new(213host: host,214port: port,215endpoint: endpoint216)217client.authenticate(user, password)218219server_context = {220msf_client: client,221rate_limiter: limiter222}223224# Execute service query with multiple filters225result = Msf::MCP::Tools::ServiceInfo.call(226workspace: 'default',227host: '192.168.1.100',228ports: '445',229protocol: 'tcp',230server_context: server_context231)232233# Verify HTTP request with all filters was made234expect(services_stub).to have_been_requested.once235236# Verify results237expect(result).to be_a(MCP::Tool::Response)238data = result.structured_content[:data]239expect(data.length).to eq(1)240expect(data.first[:host_address]).to eq('192.168.1.100')241expect(data.first[:port]).to eq(445)242expect(data.first[:protocol]).to eq('tcp')243expect(data.first[:name]).to eq('microsoft-ds')244end245246it 'handles pagination correctly across HTTP boundary' do247# Stub authentication248stub_request(:post, api_url)249.with(body: ['auth.login', user, password].to_msgpack)250.to_return(251status: 200,252body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,253headers: { 'Content-Type' => 'binary/message-pack' }254)255256# Stub db.hosts with multiple results257hosts_stub = stub_request(:post, api_url)258.with(body: ['db.hosts', 'test_token', { workspace: 'default' }].to_msgpack)259.to_return(260status: 200,261body: {262'hosts' => [263{ 'address' => '192.168.1.1', 'name' => 'host1', 'state' => 'alive', 'created_at' => 1609459200 },264{ 'address' => '192.168.1.2', 'name' => 'host2', 'state' => 'alive', 'created_at' => 1609459300 },265{ 'address' => '192.168.1.3', 'name' => 'host3', 'state' => 'alive', 'created_at' => 1609459400 },266{ 'address' => '192.168.1.4', 'name' => 'host4', 'state' => 'alive', 'created_at' => 1609459500 },267{ 'address' => '192.168.1.5', 'name' => 'host5', 'state' => 'alive', 'created_at' => 1609459600 }268]269}.to_msgpack,270headers: { 'Content-Type' => 'binary/message-pack' }271)272273limiter = Msf::MCP::Security::RateLimiter.new(requests_per_minute: 60, burst_size: 10)274client = Msf::MCP::Metasploit::MessagePackClient.new(275host: host,276port: port,277endpoint: endpoint278)279client.authenticate(user, password)280281server_context = {282msf_client: client,283rate_limiter: limiter284}285286# Execute query with pagination (offset=1, limit=2 means items 2 and 3)287result = Msf::MCP::Tools::HostInfo.call(288workspace: 'default',289limit: 2,290offset: 1,291server_context: server_context292)293294# Verify HTTP request was made295expect(hosts_stub).to have_been_requested.once296297# Verify pagination applied correctly298expect(result).to be_a(MCP::Tool::Response)299data = result.structured_content[:data]300expect(data.length).to eq(2)301expect(data.first[:address]).to eq('192.168.1.2')302expect(data.last[:address]).to eq('192.168.1.3')303304# Verify pagination metadata305metadata = result.structured_content[:metadata]306expect(metadata[:limit]).to eq(2)307expect(metadata[:offset]).to eq(1)308expect(metadata[:total_items]).to eq(5)309expect(metadata[:returned_items]).to eq(2)310end311end312end313314315