Move database.yml selection to Metasploit::Framework::Database

MSP-11153

Test the following paths in order and only return them if the path
exists:

1. MSF_DATABASE_CONFIG environment variable
2. ~/.msf4/database.yml
3. <project>/config/database.yml
This commit is contained in:
Luke Imhoff 2014-08-27 12:01:43 -05:00
parent ff7e0f3c19
commit 951ce15b44
No known key found for this signature in database
GPG Key ID: 5B1FB01FB33356F8
5 changed files with 772 additions and 43 deletions

View File

@ -26,30 +26,14 @@ require 'action_view/railtie'
#
require 'metasploit/framework/common_engine'
require 'msf/base/config'
require 'metasploit/framework/database'
module Metasploit
module Framework
class Application < Rails::Application
include Metasploit::Framework::CommonEngine
environment_database_yaml = ENV['MSF_DATABASE_CONFIG']
if environment_database_yaml
# DO NOT check if the path exists: if the environment variable is set, then the user meant to use this path
# and if it doesn't exist then an error should occur so the user knows the environment variable points to a
# non-existent file.
config.paths['config/database'] = environment_database_yaml
else
user_config_root = Pathname.new(Msf::Config.get_config_root)
user_database_yaml = user_config_root.join('database.yml')
# DO check if the path exists as in test environments there may be no config root, in which case the normal
# rails location, `config/database.yml`, should contain the database config.
if user_database_yaml.exist?
config.paths['config/database'] = [user_database_yaml.to_path]
end
end
config.paths['config/database'] = [Metasploit::Framework::Database.configurations_pathname.try(:to_path)]
end
end
end

View File

@ -1,14 +1,102 @@
require 'metasploit/framework'
require 'msf/base/config'
module Metasploit
module Framework
module Database
def self.configurations
YAML.load_file(configurations_pathname)
#
# CONSTANTS
#
CONFIGURATIONS_PATHNAME_PRECEDENCE = [
:environment_configurations_pathname,
:user_configurations_pathname,
:project_configurations_pathname
]
#
# Module Methods
#
# Returns first configuration pathname from {configuration_pathnames} or the overridding `:path`.
#
# @param options [Hash{Symbol=>String}]
# @option options [String] :path Path to use instead of first element of {configurations_pathnames}
# @return [Pathname] if configuration pathname exists.
# @return [nil] if configuration pathname does not exist.
def self.configurations_pathname(options={})
options.assert_valid_keys(:path)
path = options[:path]
if path.present?
pathname = Pathname.new(path)
else
pathname = configurations_pathnames.first
end
if pathname.present? && pathname.exist?
pathname
else
nil
end
end
def self.configurations_pathname
Metasploit::Framework::Application.paths['config/database'].first
# Return configuration pathnames that exist.
#
# Returns `Pathnames` in order of precedence
#
# 1. {environment_configurations_pathname}
# 2. {user_configurations_pathname}
# 3. {project_configurations_pathname}
#
# @return [Array<Pathname>]
def self.configurations_pathnames
configurations_pathnames = []
CONFIGURATIONS_PATHNAME_PRECEDENCE.each do |configurations_pathname_message|
configurations_pathname = public_send(configurations_pathname_message)
if !configurations_pathname.nil? && configurations_pathname.exist?
configurations_pathnames << configurations_pathname
end
end
configurations_pathnames
end
# Pathname to `database.yml` pointed to by `MSF_DATABASE_CONFIG` environment variable.
#
# @return [Pathname] if `MSF_DATABASE_CONFIG` is not blank.
# @return [nil] otherwise
def self.environment_configurations_pathname
msf_database_config = ENV['MSF_DATABASE_CONFIG']
if msf_database_config.blank?
msf_database_config = nil
else
msf_database_config = Pathname.new(msf_database_config)
end
msf_database_config
end
# Pathname to `database.yml` for the metasploit-framework project in `config/database.yml`.
#
# @return [Pathname]
def self.project_configurations_pathname
root = Pathname.new(__FILE__).realpath.parent.parent.parent.parent
project_configurations_pathname = root.join('config', 'database.yml')
return project_configurations_pathname
end
# Pathname to `database.yml` in the user's config directory.
#
# @return [Pathname] if the user has a `database.yml` in their config directory (`~/.msf4` by default).
# @return [nil] if the user does not have a `database.yml` in their config directory.
def self.user_configurations_pathname
Pathname.new(Msf::Config.get_config_root).realpath.join('database.yml')
end
end
end

View File

@ -14,8 +14,8 @@ require 'active_support/ordered_options'
# Project
#
require 'metasploit/framework/database'
require 'metasploit/framework/parsed_options'
require 'msf/base/config'
# Options parsed from the command line that can be used to change the
# `Metasploit::Framework::Application.config` and `Rails.env`
@ -73,15 +73,7 @@ class Metasploit::Framework::ParsedOptions::Base
options.database = ActiveSupport::OrderedOptions.new
user_config_root = Pathname.new(Msf::Config.get_config_root)
user_database_yaml = user_config_root.join('database.yml')
if user_database_yaml.exist?
options.database.config = user_database_yaml.to_path
else
options.database.config = 'config/database.yml'
end
options.database.config = Metasploit::Framework::Database.configurations_pathname.try(:to_path)
options.database.disable = false
options.database.migrations_paths = []

View File

@ -174,23 +174,18 @@ class Driver < Msf::Ui::Driver
end
if framework.db.connection_established?
puts "Connection Established!"
framework.db.after_establish_connection
else
# Look for our database configuration in the following places, in order:
# command line arguments
# environment variable
# configuration directory (usually ~/.msf3)
dbfile = opts['DatabaseYAML']
dbfile ||= ENV["MSF_DATABASE_CONFIG"]
dbfile ||= File.join(Msf::Config.get_config_root, "database.yml")
configuration_pathname = Metasploit::Framework::Database.configurations_pathname(path: opts['DatabaseYAML'])
if (dbfile and File.exists? dbfile)
if File.readable?(dbfile)
dbinfo = YAML.load(File.read(dbfile))
unless configuration_pathname.nil?
if configuration_pathname.readable?
dbinfo = YAML.load_file(configuration_pathname)
dbenv = opts['DatabaseEnv'] || Rails.env
db = dbinfo[dbenv]
else
print_error("Warning, #{dbfile} is not readable. Try running as root or chmod.")
print_error("Warning, #{configuration_pathname} is not readable. Try running as root or chmod.")
end
if not db

View File

@ -0,0 +1,670 @@
require 'spec_helper'
RSpec.describe Metasploit::Framework::Database do
context 'CONSTANTS' do
context 'CONFIGURATIONS_PATHNAME_PRECEDENCE' do
subject(:configurations_pathname_precedence) {
described_class::CONFIGURATIONS_PATHNAME_PRECEDENCE
}
it { is_expected.to match_array(
[
:environment_configurations_pathname,
:user_configurations_pathname,
:project_configurations_pathname
]
) }
end
end
context '.configurations_pathname' do
subject(:configurations_pathname) {
described_class.configurations_pathname(*arguments)
}
context 'with options' do
let(:arguments) {
[
{
path: path
}
]
}
context 'with :path' do
context 'that exists' do
let(:path) {
tempfile.path
}
let(:tempfile) {
Tempfile.new(['database', '.yml'])
}
it 'returns Pathname(path)' do
expect(configurations_pathname).to eq(Pathname.new(path))
end
end
context 'that does not exist' do
let(:path) {
'/a/configurations/path/that/does/not/exist/database.yml'
}
it { is_expected.to be_nil }
end
end
context 'without :path' do
let(:path) {
''
}
it 'calls configurations_pathnames' do
expect(described_class).to receive(:configurations_pathnames).and_call_original
configurations_pathname
end
it 'returns first pathname from configurations_pathnames' do
expect(configurations_pathname).to eq(described_class.configurations_pathnames.first)
end
end
end
context 'without options' do
let(:arguments) {
[]
}
it 'calls configurations_pathnames' do
expect(described_class).to receive(:configurations_pathnames).and_call_original
configurations_pathname
end
it 'returns first pathname from configurations_pathnames' do
expect(configurations_pathname).to eq(described_class.configurations_pathnames.first)
end
end
end
context '.configurations_pathnames' do
subject(:configurations_pathnames) {
described_class.configurations_pathnames
}
before(:each) do
allow(described_class).to receive(:environment_configurations_pathname).and_return(
environment_configurations_pathname
)
end
context 'with environment_configurations_pathname' do
context 'that exists' do
#
# lets
#
let(:environment_configurations_pathname) {
Pathname.new(environment_configurations_tempfile.path)
}
let(:environment_configurations_tempfile) {
Tempfile.new(['environment_configurations', '.database.yml'])
}
#
# Callbacks
#
before(:each) do
allow(described_class).to receive(:user_configurations_pathname).and_return(
user_configurations_pathname
)
end
context 'with user_configurations_pathname' do
context 'that exists' do
#
# lets
#
let(:user_configurations_pathname) {
Pathname.new(user_configurations_tempfile.path)
}
let(:user_configurations_tempfile) {
Tempfile.new(['user_configurations', '.database.yml'])
}
#
# Callbacks
#
before(:each) do
allow(described_class).to receive(:project_configurations_pathname).and_return(
project_configurations_pathname
)
end
context 'with project_configurations_pathname' do
context 'that exists' do
let(:project_configurations_pathname) {
Pathname.new(project_configurations_tempfile.path)
}
let(:project_configurations_tempfile) {
Tempfile.new(['project_configurations', '.database.yml'])
}
it 'is [environment_configurations_pathname, user_configurations_pathname, project_configurations_pathname]' do
expect(project_configurations_pathname).to exist
expect(configurations_pathnames).to match_array(
[
environment_configurations_pathname,
user_configurations_pathname,
project_configurations_pathname
]
)
end
end
context 'that does not exist' do
let(:project_configurations_pathname) {
Pathname.new('/metasploit-framework/does/not/exist/here/config/database.yml')
}
it 'is [environment_configurations_pathname, user_configurations_pathname]' do
expect(environment_configurations_pathname).to exist
expect(user_configurations_pathname).to exist
expect(project_configurations_pathname).not_to exist
expect(project_configurations_pathname).not_to exist
expect(configurations_pathnames).to match_array(
[
environment_configurations_pathname,
user_configurations_pathname
]
)
end
end
end
context 'without project_configurations_pathname' do
let(:project_configurations_pathname) {
nil
}
it 'is [environment_configuration_pathname, user_configurations_pathname]' do
expect(environment_configurations_pathname).to exist
expect(user_configurations_pathname).to exist
expect(configurations_pathnames).to match_array(
[
environment_configurations_pathname,
user_configurations_pathname
]
)
end
end
end
context 'with does not exist' do
#
# lets
#
let(:user_configurations_pathname) {
Pathname.new('/user/configuration/that/does/not/exist/.msf4/database.yml')
}
#
# Callbacks
#
before(:each) do
allow(described_class).to receive(:project_configurations_pathname).and_return(
project_configurations_pathname
)
end
context 'with project_configurations_pathname' do
context 'that exists' do
let(:project_configurations_pathname) {
Pathname.new(project_configurations_tempfile.path)
}
let(:project_configurations_tempfile) {
Tempfile.new(['project_configurations', '.database.yml'])
}
it 'is [environment_configurations_pathname, project_configurations_pathname]' do
expect(environment_configurations_pathname).to exist
expect(user_configurations_pathname).not_to exist
expect(project_configurations_pathname).to exist
expect(configurations_pathnames).to match_array(
[
environment_configurations_pathname,
project_configurations_pathname
]
)
end
end
context 'that does not exist' do
let(:project_configurations_pathname) {
Pathname.new('/metasploit-framework/that/does/not/exist/config/database.yml')
}
it 'is [environment_configurations_pathname]' do
expect(environment_configurations_pathname).to exist
expect(user_configurations_pathname).not_to exist
expect(project_configurations_pathname).not_to exist
expect(configurations_pathnames).to match_array(
[
environment_configurations_pathname
]
)
end
end
end
context 'without project_configurations_pathname' do
let(:project_configurations_pathname) {
nil
}
it 'is [environment_configurations_pathname]' do
expect(environment_configurations_pathname).to exist
expect(user_configurations_pathname).not_to exist
expect(project_configurations_pathname).to be_nil
expect(configurations_pathnames).to match_array(
[
environment_configurations_pathname
]
)
end
end
end
end
context 'without user_configurations_pathname' do
#
# lets
#
let(:user_configurations_pathname) {
nil
}
#
# Callbacks
#
before(:each) do
allow(described_class).to receive(:project_configurations_pathname).and_return(
project_configurations_pathname
)
end
context 'with project_configurations_pathname' do
end
context 'without project_configurations_pathname' do
let(:project_configurations_pathname) {
nil
}
it 'contains only the environment_configuration_pathname' do
expect(configurations_pathnames).to match_array([environment_configurations_pathname])
end
end
end
end
context 'that does not exist' do
end
end
context 'without environment_configurations_pathname' do
#
# lets
#
let(:environment_configurations_pathname) {
nil
}
#
# Callbacks
#
before(:each) do
allow(described_class).to receive(:user_configurations_pathname).and_return(
user_configurations_pathname
)
end
context 'with user_configurations_pathname' do
context 'that exists' do
#
# lets
#
let(:user_configurations_pathname) {
Pathname.new(user_configurations_tempfile.path)
}
let(:user_configurations_tempfile) {
Tempfile.new(['user_configurations', '.database.yml'])
}
#
# Callbacks
#
before(:each) do
allow(described_class).to receive(:project_configurations_pathname).and_return(
project_configurations_pathname
)
end
context 'with project_configurations_pathname' do
context 'that exists' do
let(:project_configurations_pathname) {
Pathname.new(project_configurations_tempfile.path)
}
let(:project_configurations_tempfile) {
Tempfile.new(['project_configurations', '.database.yml'])
}
it 'is [user_configurations_pathname, project_configurations_pathname]' do
expect(environment_configurations_pathname).to be_nil
expect(user_configurations_pathname).to exist
expect(project_configurations_pathname).to exist
expect(configurations_pathnames).to match_array(
[
user_configurations_pathname,
project_configurations_pathname
]
)
end
end
context 'that does not exist' do
let(:project_configurations_pathname) {
Pathname.new('/metasploit-framework/that/does/not/exist/config/database.yml')
}
it 'is [user_configurations_pathname]' do
expect(environment_configurations_pathname).to be_nil
expect(user_configurations_pathname).to exist
expect(project_configurations_pathname).not_to exist
expect(configurations_pathnames).to match_array(
[
user_configurations_pathname
]
)
end
end
end
context 'without project_configurations_pathname' do
let(:project_configurations_pathname) {
nil
}
it 'is [user_configurations_pathname]' do
expect(environment_configurations_pathname).to be_nil
expect(user_configurations_pathname).to exist
expect(project_configurations_pathname).to be_nil
expect(configurations_pathnames).to match_array(
[
user_configurations_pathname
]
)
end
end
end
context 'that does not exist' do
#
# lets
#
let(:user_configurations_pathname) {
Pathname.new('/user/configuration/that/does/not/exist/.msf4/database.yml')
}
#
# Callbacks
#
before(:each) do
allow(described_class).to receive(:project_configurations_pathname).and_return(
project_configurations_pathname
)
end
context 'with project_configurations_pathname' do
context 'that exists' do
let(:project_configurations_pathname) {
Pathname.new(project_configurations_tempfile.path)
}
let(:project_configurations_tempfile) {
Tempfile.new(['project_configurations', '.database.yml'])
}
it 'is [project_configurations_pathname]' do
expect(environment_configurations_pathname).to be_nil
expect(user_configurations_pathname).not_to exist
expect(project_configurations_pathname).to exist
expect(configurations_pathnames).to match_array(
[
project_configurations_pathname
]
)
end
end
context 'that does not exist' do
let(:project_configurations_pathname) {
Pathname.new('/metasploit-framework/that/does/not/exist/config/database.yml')
}
it 'is []' do
expect(environment_configurations_pathname).to be_nil
expect(user_configurations_pathname).not_to exist
expect(project_configurations_pathname).not_to exist
expect(configurations_pathnames).to eq([])
end
end
end
context 'without project_configurations_pathname' do
let(:project_configurations_pathname) {
nil
}
it 'is []' do
expect(environment_configurations_pathname).to be_nil
expect(user_configurations_pathname).not_to exist
expect(project_configurations_pathname).to be_nil
expect(configurations_pathnames).to eq([])
end
end
end
end
context 'without user_configurations_pathname' do
#
# lets
#
let(:user_configurations_pathname) {
nil
}
#
# Callbacks
#
before(:each) do
allow(described_class).to receive(:project_configurations_pathname).and_return(
project_configurations_pathname
)
end
context 'with project_configurations_pathname' do
context 'that exists' do
let(:project_configurations_pathname) {
Pathname.new(project_configurations_tempfile.path)
}
let(:project_configurations_tempfile) {
Tempfile.new(['project_configurations', '.database.yml'])
}
it 'is [project_configurations_pathname]' do
expect(environment_configurations_pathname).to be_nil
expect(user_configurations_pathname).to be_nil
expect(project_configurations_pathname).to exist
expect(configurations_pathnames).to match_array(
[
project_configurations_pathname
]
)
end
end
context 'that does not exist' do
let(:project_configurations_pathname) {
Pathname.new('/metasploit-framework/that/does/not/exist/config/database.yml')
}
it 'is []' do
expect(environment_configurations_pathname).to be_nil
expect(user_configurations_pathname).to be_nil
expect(project_configurations_pathname).not_to exist
expect(configurations_pathnames).to eq([])
end
end
end
context 'without project_configurations_pathname' do
let(:project_configurations_pathname) {
nil
}
it { is_expected.to eq([]) }
end
end
end
end
context '.environment_configurations_pathname' do
subject(:environment_configurations_pathname) {
described_class.environment_configurations_pathname
}
around(:each) do |example|
env_before = ENV.to_hash
begin
example.run
ensure
ENV.update(env_before)
end
end
context 'with MSF_DATABASE_CONFIG' do
before(:each) do
ENV['MSF_DATABASE_CONFIG'] = msf_database_config
end
context 'with blank' do
let(:msf_database_config) {
''
}
it { is_expected.to be_nil }
end
context 'without blank' do
let(:msf_database_config) {
'msf/database/config/database.yml'
}
it 'is Pathname of MSF_DATABASE_CONFIG' do
expect(environment_configurations_pathname).to eq(Pathname.new(msf_database_config))
end
end
end
context 'without MSF_DATABASE_CONFIG' do
it { is_expected.to be_nil }
end
end
context '.project_configurations_pathname' do
subject(:project_configurations_pathname) {
described_class.project_configurations_pathname
}
it 'is <metasploit-framework>/config/database.yml' do
root = Pathname.new(__FILE__).realpath.parent.parent.parent.parent.parent
expect(project_configurations_pathname).to eq(root.join('config', 'database.yml'))
end
end
context '.user_configurations_pathname' do
subject(:user_configurations_pathname) {
described_class.user_configurations_pathname
}
#
# lets
#
let(:config_root) {
Dir.mktmpdir
}
#
# Callbacks
#
around(:each) do |example|
begin
example.run
ensure
FileUtils.remove_entry_secure config_root
end
end
before(:each) do
expect(Msf::Config).to receive(:get_config_root).and_return(config_root)
end
it 'is database.yml under the user config root' do
expect(user_configurations_pathname).to eq(Pathname.new(config_root).realpath.join('database.yml'))
end
end
end