Path: blob/master/spec/integration/msfmcpd/config_loading_spec.rb
70330 views
# frozen_string_literal: true12require 'msf/core/mcp'3require 'webmock/rspec'4require 'tempfile'56RSpec.describe 'Configuration Loading and Validation Integration' do7let(:file_fixtures_path) { File.join(Msf::Config.install_root, 'spec', 'file_fixtures') }8let(:valid_messagepack_path) { File.join(file_fixtures_path, 'config_files', 'msfmcpd', 'valid_messagepack.yaml') }9let(:valid_jsonrpc_path) { File.join(file_fixtures_path, 'config_files', 'msfmcpd', 'valid_jsonrpc.yaml') }1011before do12WebMock.disable_net_connect!(allow_localhost: false)13end1415after do16WebMock.allow_net_connect!17end1819describe 'Application Initialization with Configuration' do20let(:output) { StringIO.new }21let(:argv) { ['--config', valid_messagepack_path] }2223it 'successfully initializes all components with valid MessagePack config' do24app = Msf::MCP::Application.new(argv, output: output)2526# Stub Metasploit authentication27stub_request(:post, 'https://localhost:55553/api/')28.to_return(29status: 200,30body: MessagePack.pack({31'result' => 'success',32'token' => 'fake_token_123'33})34)3536# Execute initialization steps (before start)37app.send(:parse_arguments)38app.send(:load_configuration)39app.send(:validate_configuration)40app.send(:initialize_rate_limiter)41app.send(:initialize_metasploit_client)4243# Verify components are initialized with config values44expect(app.config[:msf_api][:type]).to eq('messagepack')45expect(app.config[:msf_api][:host]).to eq('localhost')46expect(app.config[:msf_api][:port]).to eq(55553)4748# Verify rate limiter is configured from config49expect(app.rate_limiter).to be_a(Msf::MCP::Security::RateLimiter)50expect(app.rate_limiter.instance_variable_get(:@requests_per_minute)).to eq(60)51expect(app.rate_limiter.instance_variable_get(:@burst_size)).to eq(10)5253# Verify Metasploit client is created with config values54expect(app.msf_client).to be_a(Msf::MCP::Metasploit::Client)55end5657it 'successfully initializes all components with valid JSON-RPC config' do58argv = ['--config', valid_jsonrpc_path]59app = Msf::MCP::Application.new(argv, output: output)6061# Execute initialization steps62app.send(:parse_arguments)63app.send(:load_configuration)64app.send(:validate_configuration)65app.send(:initialize_rate_limiter)66app.send(:initialize_metasploit_client)6768# Verify config is loaded correctly69expect(app.config[:msf_api][:type]).to eq('json-rpc')70expect(app.config[:msf_api][:port]).to eq(8081)71expect(app.config[:msf_api][:token]).to eq('test_bearer_token_12345')7273# Verify components are initialized74expect(app.rate_limiter).to be_a(Msf::MCP::Security::RateLimiter)75expect(app.msf_client).to be_a(Msf::MCP::Metasploit::Client)76end7778it 'applies defaults and starts successfully with minimal config' do79minimal_config = {80msf_api: {81type: 'messagepack',82user: 'msf',83password: 'pass'84}85}8687config_file = Tempfile.new(['minimal_config', '.yaml'])88# Dirty hack to make sure the config hash keys are strings and not symbols.89config_file.write(YAML.dump(JSON.parse(minimal_config.to_json)))90config_file.flush9192app = Msf::MCP::Application.new(['--config', config_file.path], output: output)9394# Stub authentication95stub_request(:post, 'https://localhost:55553/api/')96.to_return(97status: 200,98body: MessagePack.pack({99'result' => 'success',100'token' => 'token'101})102)103104app.send(:parse_arguments)105app.send(:load_configuration)106app.send(:validate_configuration)107108# Verify defaults were applied109expect(app.config[:msf_api][:host]).to eq('localhost')110expect(app.config[:msf_api][:port]).to eq(55553)111expect(app.config[:rate_limit][:requests_per_minute]).to eq(60)112expect(app.config[:logging][:level]).to eq('INFO')113114# Verify can proceed with initialization115expect {116app.send(:initialize_rate_limiter)117app.send(:initialize_metasploit_client)118}.not_to raise_error119120config_file.close121config_file.unlink122end123124it 'fails gracefully when config has invalid port' do125invalid_config = YAML.safe_load_file(valid_messagepack_path)126invalid_config['msf_api']['port'] = 999999127128config_file = Tempfile.new(['invalid_port', '.yaml'])129config_file.write(YAML.dump(invalid_config))130config_file.flush131132app = Msf::MCP::Application.new(['--config', config_file.path], output: output)133134expect {135app.run136}.to raise_error(SystemExit)137138expect(output.string).to include('Configuration validation failed')139expect(output.string).to include('port must be between')140141config_file.close142config_file.unlink143end144145it 'fails gracefully when config is missing required authentication on remote host' do146invalid_config = {147msf_api: {148type: 'messagepack',149host: '192.168.1.100',150port: 55553,151auto_start_rpc: false152}153}154155config_file = Tempfile.new(['missing_auth', '.yaml'])156# Dirty hack to make sure the config hash keys are strings and not symbols.157config_file.write(YAML.dump(JSON.parse(invalid_config.to_json)))158config_file.flush159160app = Msf::MCP::Application.new(['--config', config_file.path], output: output)161162expect {163app.run164}.to raise_error(SystemExit)165166expect(output.string).to include('Configuration validation failed')167expect(output.string).to match(/user|password/)168169config_file.close170config_file.unlink171end172173it 'prevents application startup with invalid API type' do174invalid_config = YAML.safe_load_file(valid_messagepack_path)175invalid_config['msf_api']['type'] = 'soap'176177config_file = Tempfile.new(['invalid_type', '.yaml'])178config_file.write(YAML.dump(invalid_config))179config_file.flush180181app = Msf::MCP::Application.new(['--config', config_file.path], output: output)182183expect {184app.run185}.to raise_error(SystemExit)186187expect(output.string).to include('Configuration validation failed')188expect(output.string).to include('msf_api.type')189190config_file.close191config_file.unlink192end193end194195describe 'Environment Variable Override Integration' do196let(:output) { StringIO.new }197198after do199# Clean up ENV200%w[MSF_API_HOST MSF_API_PORT MSF_API_TYPE MSF_API_USER MSF_API_PASSWORD MSF_API_TOKEN].each { |key| ENV.delete(key) }201end202203it 'ENV override changes which Metasploit host client connects to' do204ENV['MSF_API_HOST'] = '192.168.1.100'205206app = Msf::MCP::Application.new(['--config', valid_messagepack_path], output: output)207208app.send(:parse_arguments)209app.send(:load_configuration)210app.send(:validate_configuration)211app.send(:initialize_metasploit_client)212213# Verify client was created with ENV-overridden host214expect(app.config[:msf_api][:host]).to eq('192.168.1.100')215216# Verify that making an API call would use the overridden host217stub_request(:post, 'https://192.168.1.100:55553/api/')218.to_return(219status: 200,220body: MessagePack.pack({221'result' => 'success',222'token' => 'token_123'223})224)225226expect {227app.msf_client.authenticate('test_user', 'test_password')228}.not_to raise_error229230# Verify the request was made to the correct host231expect(WebMock).to have_requested(:post, 'https://192.168.1.100:55553/api/').once232end233234it 'ENV override changes authentication credentials used' do235ENV['MSF_API_USER'] = 'env_override_user'236ENV['MSF_API_PASSWORD'] = 'env_override_pass'237238app = Msf::MCP::Application.new(['--config', valid_messagepack_path], output: output)239240# Stub authentication - accept any body since MessagePack matching is problematic241stub_request(:post, 'https://localhost:55553/api/')242.to_return(243status: 200,244body: MessagePack.pack({245'result' => 'success',246'token' => 'env_token'247})248)249250app.send(:parse_arguments)251app.send(:load_configuration)252app.send(:validate_configuration)253app.send(:initialize_metasploit_client)254255# Verify ENV vars override config file256expect(app.config[:msf_api][:user]).to eq('env_override_user')257expect(app.config[:msf_api][:password]).to eq('env_override_pass')258259# Verify authentication uses ENV credentials260app.send(:authenticate_metasploit)261262expect(WebMock).to have_requested(:post, 'https://localhost:55553/api/').once263end264265it 'ENV override changes API type from MessagePack to JSON-RPC' do266ENV['MSF_API_TYPE'] = 'json-rpc'267ENV['MSF_API_PORT'] = '8081'268ENV['MSF_API_TOKEN'] = 'env_token_123' # JSON-RPC requires token269270app = Msf::MCP::Application.new(['--config', valid_messagepack_path], output: output)271272app.send(:parse_arguments)273app.send(:load_configuration)274app.send(:validate_configuration)275app.send(:initialize_metasploit_client)276277# Verify type was overridden278expect(app.config[:msf_api][:type]).to eq('json-rpc')279expect(app.config[:msf_api][:port]).to eq(8081)280expect(app.config[:msf_api][:token]).to eq('env_token_123')281282# Verify client is JSON-RPC client (not MessagePack)283underlying_client = app.msf_client.instance_variable_get(:@client)284expect(underlying_client).to be_a(Msf::MCP::Metasploit::JsonRpcClient)285end286end287288describe 'CLI Flag Override Integration' do289let(:output) { StringIO.new }290291it 'CLI --user and --password flags override config file authentication' do292app = Msf::MCP::Application.new(293['--config', valid_messagepack_path, '--user', 'cli_user', '--password', 'cli_pass'],294output: output295)296297# Stub authentication - accept any body298stub_request(:post, 'https://localhost:55553/api/')299.to_return(300status: 200,301body: MessagePack.pack({302'result' => 'success',303'token' => 'cli_token'304})305)306307app.send(:parse_arguments)308app.send(:load_configuration)309310# Verify CLI args override config file311expect(app.config[:msf_api][:user]).to eq('cli_user')312expect(app.config[:msf_api][:password]).to eq('cli_pass')313314app.send(:validate_configuration)315app.send(:initialize_metasploit_client)316app.send(:authenticate_metasploit)317318# Verify authentication was called319expect(WebMock).to have_requested(:post, 'https://localhost:55553/api/').once320end321end322end323324325