Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/spec/integration/msfmcpd/config_loading_spec.rb
70330 views
1
# frozen_string_literal: true
2
3
require 'msf/core/mcp'
4
require 'webmock/rspec'
5
require 'tempfile'
6
7
RSpec.describe 'Configuration Loading and Validation Integration' do
8
let(:file_fixtures_path) { File.join(Msf::Config.install_root, 'spec', 'file_fixtures') }
9
let(:valid_messagepack_path) { File.join(file_fixtures_path, 'config_files', 'msfmcpd', 'valid_messagepack.yaml') }
10
let(:valid_jsonrpc_path) { File.join(file_fixtures_path, 'config_files', 'msfmcpd', 'valid_jsonrpc.yaml') }
11
12
before do
13
WebMock.disable_net_connect!(allow_localhost: false)
14
end
15
16
after do
17
WebMock.allow_net_connect!
18
end
19
20
describe 'Application Initialization with Configuration' do
21
let(:output) { StringIO.new }
22
let(:argv) { ['--config', valid_messagepack_path] }
23
24
it 'successfully initializes all components with valid MessagePack config' do
25
app = Msf::MCP::Application.new(argv, output: output)
26
27
# Stub Metasploit authentication
28
stub_request(:post, 'https://localhost:55553/api/')
29
.to_return(
30
status: 200,
31
body: MessagePack.pack({
32
'result' => 'success',
33
'token' => 'fake_token_123'
34
})
35
)
36
37
# Execute initialization steps (before start)
38
app.send(:parse_arguments)
39
app.send(:load_configuration)
40
app.send(:validate_configuration)
41
app.send(:initialize_rate_limiter)
42
app.send(:initialize_metasploit_client)
43
44
# Verify components are initialized with config values
45
expect(app.config[:msf_api][:type]).to eq('messagepack')
46
expect(app.config[:msf_api][:host]).to eq('localhost')
47
expect(app.config[:msf_api][:port]).to eq(55553)
48
49
# Verify rate limiter is configured from config
50
expect(app.rate_limiter).to be_a(Msf::MCP::Security::RateLimiter)
51
expect(app.rate_limiter.instance_variable_get(:@requests_per_minute)).to eq(60)
52
expect(app.rate_limiter.instance_variable_get(:@burst_size)).to eq(10)
53
54
# Verify Metasploit client is created with config values
55
expect(app.msf_client).to be_a(Msf::MCP::Metasploit::Client)
56
end
57
58
it 'successfully initializes all components with valid JSON-RPC config' do
59
argv = ['--config', valid_jsonrpc_path]
60
app = Msf::MCP::Application.new(argv, output: output)
61
62
# Execute initialization steps
63
app.send(:parse_arguments)
64
app.send(:load_configuration)
65
app.send(:validate_configuration)
66
app.send(:initialize_rate_limiter)
67
app.send(:initialize_metasploit_client)
68
69
# Verify config is loaded correctly
70
expect(app.config[:msf_api][:type]).to eq('json-rpc')
71
expect(app.config[:msf_api][:port]).to eq(8081)
72
expect(app.config[:msf_api][:token]).to eq('test_bearer_token_12345')
73
74
# Verify components are initialized
75
expect(app.rate_limiter).to be_a(Msf::MCP::Security::RateLimiter)
76
expect(app.msf_client).to be_a(Msf::MCP::Metasploit::Client)
77
end
78
79
it 'applies defaults and starts successfully with minimal config' do
80
minimal_config = {
81
msf_api: {
82
type: 'messagepack',
83
user: 'msf',
84
password: 'pass'
85
}
86
}
87
88
config_file = Tempfile.new(['minimal_config', '.yaml'])
89
# Dirty hack to make sure the config hash keys are strings and not symbols.
90
config_file.write(YAML.dump(JSON.parse(minimal_config.to_json)))
91
config_file.flush
92
93
app = Msf::MCP::Application.new(['--config', config_file.path], output: output)
94
95
# Stub authentication
96
stub_request(:post, 'https://localhost:55553/api/')
97
.to_return(
98
status: 200,
99
body: MessagePack.pack({
100
'result' => 'success',
101
'token' => 'token'
102
})
103
)
104
105
app.send(:parse_arguments)
106
app.send(:load_configuration)
107
app.send(:validate_configuration)
108
109
# Verify defaults were applied
110
expect(app.config[:msf_api][:host]).to eq('localhost')
111
expect(app.config[:msf_api][:port]).to eq(55553)
112
expect(app.config[:rate_limit][:requests_per_minute]).to eq(60)
113
expect(app.config[:logging][:level]).to eq('INFO')
114
115
# Verify can proceed with initialization
116
expect {
117
app.send(:initialize_rate_limiter)
118
app.send(:initialize_metasploit_client)
119
}.not_to raise_error
120
121
config_file.close
122
config_file.unlink
123
end
124
125
it 'fails gracefully when config has invalid port' do
126
invalid_config = YAML.safe_load_file(valid_messagepack_path)
127
invalid_config['msf_api']['port'] = 999999
128
129
config_file = Tempfile.new(['invalid_port', '.yaml'])
130
config_file.write(YAML.dump(invalid_config))
131
config_file.flush
132
133
app = Msf::MCP::Application.new(['--config', config_file.path], output: output)
134
135
expect {
136
app.run
137
}.to raise_error(SystemExit)
138
139
expect(output.string).to include('Configuration validation failed')
140
expect(output.string).to include('port must be between')
141
142
config_file.close
143
config_file.unlink
144
end
145
146
it 'fails gracefully when config is missing required authentication on remote host' do
147
invalid_config = {
148
msf_api: {
149
type: 'messagepack',
150
host: '192.168.1.100',
151
port: 55553,
152
auto_start_rpc: false
153
}
154
}
155
156
config_file = Tempfile.new(['missing_auth', '.yaml'])
157
# Dirty hack to make sure the config hash keys are strings and not symbols.
158
config_file.write(YAML.dump(JSON.parse(invalid_config.to_json)))
159
config_file.flush
160
161
app = Msf::MCP::Application.new(['--config', config_file.path], output: output)
162
163
expect {
164
app.run
165
}.to raise_error(SystemExit)
166
167
expect(output.string).to include('Configuration validation failed')
168
expect(output.string).to match(/user|password/)
169
170
config_file.close
171
config_file.unlink
172
end
173
174
it 'prevents application startup with invalid API type' do
175
invalid_config = YAML.safe_load_file(valid_messagepack_path)
176
invalid_config['msf_api']['type'] = 'soap'
177
178
config_file = Tempfile.new(['invalid_type', '.yaml'])
179
config_file.write(YAML.dump(invalid_config))
180
config_file.flush
181
182
app = Msf::MCP::Application.new(['--config', config_file.path], output: output)
183
184
expect {
185
app.run
186
}.to raise_error(SystemExit)
187
188
expect(output.string).to include('Configuration validation failed')
189
expect(output.string).to include('msf_api.type')
190
191
config_file.close
192
config_file.unlink
193
end
194
end
195
196
describe 'Environment Variable Override Integration' do
197
let(:output) { StringIO.new }
198
199
after do
200
# Clean up ENV
201
%w[MSF_API_HOST MSF_API_PORT MSF_API_TYPE MSF_API_USER MSF_API_PASSWORD MSF_API_TOKEN].each { |key| ENV.delete(key) }
202
end
203
204
it 'ENV override changes which Metasploit host client connects to' do
205
ENV['MSF_API_HOST'] = '192.168.1.100'
206
207
app = Msf::MCP::Application.new(['--config', valid_messagepack_path], output: output)
208
209
app.send(:parse_arguments)
210
app.send(:load_configuration)
211
app.send(:validate_configuration)
212
app.send(:initialize_metasploit_client)
213
214
# Verify client was created with ENV-overridden host
215
expect(app.config[:msf_api][:host]).to eq('192.168.1.100')
216
217
# Verify that making an API call would use the overridden host
218
stub_request(:post, 'https://192.168.1.100:55553/api/')
219
.to_return(
220
status: 200,
221
body: MessagePack.pack({
222
'result' => 'success',
223
'token' => 'token_123'
224
})
225
)
226
227
expect {
228
app.msf_client.authenticate('test_user', 'test_password')
229
}.not_to raise_error
230
231
# Verify the request was made to the correct host
232
expect(WebMock).to have_requested(:post, 'https://192.168.1.100:55553/api/').once
233
end
234
235
it 'ENV override changes authentication credentials used' do
236
ENV['MSF_API_USER'] = 'env_override_user'
237
ENV['MSF_API_PASSWORD'] = 'env_override_pass'
238
239
app = Msf::MCP::Application.new(['--config', valid_messagepack_path], output: output)
240
241
# Stub authentication - accept any body since MessagePack matching is problematic
242
stub_request(:post, 'https://localhost:55553/api/')
243
.to_return(
244
status: 200,
245
body: MessagePack.pack({
246
'result' => 'success',
247
'token' => 'env_token'
248
})
249
)
250
251
app.send(:parse_arguments)
252
app.send(:load_configuration)
253
app.send(:validate_configuration)
254
app.send(:initialize_metasploit_client)
255
256
# Verify ENV vars override config file
257
expect(app.config[:msf_api][:user]).to eq('env_override_user')
258
expect(app.config[:msf_api][:password]).to eq('env_override_pass')
259
260
# Verify authentication uses ENV credentials
261
app.send(:authenticate_metasploit)
262
263
expect(WebMock).to have_requested(:post, 'https://localhost:55553/api/').once
264
end
265
266
it 'ENV override changes API type from MessagePack to JSON-RPC' do
267
ENV['MSF_API_TYPE'] = 'json-rpc'
268
ENV['MSF_API_PORT'] = '8081'
269
ENV['MSF_API_TOKEN'] = 'env_token_123' # JSON-RPC requires token
270
271
app = Msf::MCP::Application.new(['--config', valid_messagepack_path], output: output)
272
273
app.send(:parse_arguments)
274
app.send(:load_configuration)
275
app.send(:validate_configuration)
276
app.send(:initialize_metasploit_client)
277
278
# Verify type was overridden
279
expect(app.config[:msf_api][:type]).to eq('json-rpc')
280
expect(app.config[:msf_api][:port]).to eq(8081)
281
expect(app.config[:msf_api][:token]).to eq('env_token_123')
282
283
# Verify client is JSON-RPC client (not MessagePack)
284
underlying_client = app.msf_client.instance_variable_get(:@client)
285
expect(underlying_client).to be_a(Msf::MCP::Metasploit::JsonRpcClient)
286
end
287
end
288
289
describe 'CLI Flag Override Integration' do
290
let(:output) { StringIO.new }
291
292
it 'CLI --user and --password flags override config file authentication' do
293
app = Msf::MCP::Application.new(
294
['--config', valid_messagepack_path, '--user', 'cli_user', '--password', 'cli_pass'],
295
output: output
296
)
297
298
# Stub authentication - accept any body
299
stub_request(:post, 'https://localhost:55553/api/')
300
.to_return(
301
status: 200,
302
body: MessagePack.pack({
303
'result' => 'success',
304
'token' => 'cli_token'
305
})
306
)
307
308
app.send(:parse_arguments)
309
app.send(:load_configuration)
310
311
# Verify CLI args override config file
312
expect(app.config[:msf_api][:user]).to eq('cli_user')
313
expect(app.config[:msf_api][:password]).to eq('cli_pass')
314
315
app.send(:validate_configuration)
316
app.send(:initialize_metasploit_client)
317
app.send(:authenticate_metasploit)
318
319
# Verify authentication was called
320
expect(WebMock).to have_requested(:post, 'https://localhost:55553/api/').once
321
end
322
end
323
end
324
325