diff --git a/controls/V-81875.rb b/controls/V-81875.rb index da8a4d8..043f3ff 100644 --- a/controls/V-81875.rb +++ b/controls/V-81875.rb @@ -74,7 +74,13 @@ tag "documentable": false tag "severity_override_guidance": false - describe yaml(input('mongod_conf')) do - its(%w{net ssl FIPSMode}) { should cmp 'true' } + if input('is_sensitive') + describe yaml(input('mongod_conf')) do + its(%w{net ssl FIPSMode}) { should cmp 'true' } + end + else + describe 'The system is not a classified environment, therefore for this control is NA' do + skip 'The system is not a classified environment, therefore for this control is NA' + end end end diff --git a/controls/V-81887.rb b/controls/V-81887.rb index 5695a4e..bd01355 100644 --- a/controls/V-81887.rb +++ b/controls/V-81887.rb @@ -43,11 +43,14 @@ tag "documentable": false tag "severity_override_guidance": false + mongodb_service_account = input('mongodb_service_account') + mongodb_service_group = input('mongodb_service_group') + if file(input('mongod_conf')).exist? describe file(input('mongod_conf')) do it { should_not be_more_permissive_than('0755') } - its('owner') { should be_in input('mongodb_service_account') } - its('group') { should be_in input('mongodb_service_group') } + its('owner') { should be_in mongodb_service_account } + its('group') { should be_in mongodb_service_group } end else describe 'This control must be reviewed manually because the configuration @@ -60,8 +63,8 @@ if file(input('mongo_data_dir')).exist? describe directory(input('mongo_data_dir')) do it { should_not be_more_permissive_than('0755') } - its('owner') { should be_in input('mongodb_service_account') } - its('group') { should be_in input('mongodb_service_group') } + its('owner') { should be_in mongodb_service_account } + its('group') { should be_in mongodb_service_group } end else describe 'This control must be reviewed manually because the Mongodb data diff --git a/inputs.yml b/inputs.yml index 002c1f7..10475f6 100644 --- a/inputs.yml +++ b/inputs.yml @@ -55,3 +55,4 @@ accountAdmin01_allowed_role: ['[ ] } '] +is_sensitive: true diff --git a/inspec.yml b/inspec.yml index a7d887c..78393c3 100644 --- a/inspec.yml +++ b/inspec.yml @@ -134,3 +134,8 @@ inputs: description: Mongodb Service Group type: array value: ["mongodb", "mongod"] + + - name: is_sensitive + description: MongoDB is deployed in a classified environment + type: boolean + value: true diff --git a/libraries/mongo_command.rb b/libraries/mongo_command.rb new file mode 100644 index 0000000..ed75fa1 --- /dev/null +++ b/libraries/mongo_command.rb @@ -0,0 +1,116 @@ +require 'json' + +class MongoCommand < Inspec.resource(1) + name 'mongo_command' + desc 'Runs a MongoDB command using the mongo CLI against a given database (default database: admin)' + example <<-EOL + describe mongo_command('db.showRoles()') do + its('params.length') { should be > 0 } + end + EOL + + attr_reader :command, :database, :params + + def initialize(command, options = {}) + @command = command + @username = options[:username] + @password = options[:password] + @database = options.fetch(:database, 'admin') + @allow_auth_errors = options.fetch(:allow_auth_errors, false) + @tls = options.fetch(:tls, true) + @verify_ssl = options.fetch(:verify_ssl, true) + + check_for_cli_command + @inspec_command = run_mongo_command(command) + @params = parse(@inspec_command.stdout) + end + + def stdout + @inspec_command.stdout + end + + def stderr + @inspec_command.stderr + end + + def to_s + str = "MongoDB Command (#{@command}" + str += ", username: #{@username}" unless @username.nil? + str += ", password: " unless @password.nil? + str += ')' + + str + end + + private + + def parse(output) + # return right away if stdout is nil + return [] if output.nil? + + # strip any network warnings from the output + # Unfortunately, it appears the --sslAllowInvalidHostnames doesn't actually squelch + # any warnings, even when using --quiet mode + output_lines = output.lines.delete_if { |line| line.include?(' W NETWORK ')} + output_lines = output.lines.delete_if { |line| line.include?('UUID(')} + + # if, after removing any network warnings, there are no lines to process, + # we received no command output. + return [] if output_lines.empty? + + # put our output back together as a string + output = output_lines.join + + # skip the whole resource if we could not run the command at all + return skip_resource "User is not authorized to run command #{command}" if + is_auth_error?(output) && !auth_errors_allowed? + + # if the output indicates there's an authorization error, and we allow auth + # errors, we won't throw an exception, just set the params to an empty array. + return [] if is_auth_error?(output) && auth_errors_allowed? + + # At this point, we should have parseable JSON we can use and no auth errors. + # Let's read it in. + JSON.parse(output.to_s) + rescue JSON::ParserError => e + skip_resource "Unable to parse JSON response from mongo client: #{e.message}" unless @allow_auth_errors + [] + end + + def check_for_cli_command + check_command = inspec.command(format_command("db.version()")) + if check_command.exit_status != 0 + skip_resource "Unable to run mongo commands: #{check_command.stderr}" + end + end + + def run_mongo_command(command) + inspec.command(format_command(command)) + end + + def tls_verify_disabled? + ['false', false].include?(@verify_ssl) + end + + def tls_disabled? + ['false', false].include?(@tls) + end + + def is_auth_error?(output) + output.include?('Error: not authorized') + end + + def auth_errors_allowed? + @allow_auth_errors == true + end + + def format_command(command) + command = %{echo "#{command}" | mongo --quiet #{database}} + command += " --tls" unless tls_disabled? + command += " --username #{@username}" unless @username.nil? + command += " --password #{@password}" unless @password.nil? + command += " --tlsAllowInvalidCertificates" if tls_verify_disabled? && tls_disabled? + command += " --tlsAllowInvalidHostnames" if tls_verify_disabled? && tls_disabled? + command + end +end \ No newline at end of file