2016-07-17 18:18:25 +00:00
|
|
|
# encoding: utf-8
|
2017-06-29 15:01:32 +00:00
|
|
|
# author: Nolan Davidson
|
2016-07-17 18:18:25 +00:00
|
|
|
# author: Christoph Hartmann
|
|
|
|
# author: Dominik Richter
|
|
|
|
|
2017-06-29 15:01:32 +00:00
|
|
|
require 'hashie/mash'
|
|
|
|
require 'utils/database_helpers'
|
|
|
|
|
2016-07-17 18:18:25 +00:00
|
|
|
module Inspec::Resources
|
2017-06-29 15:01:32 +00:00
|
|
|
# STABILITY: Experimental
|
|
|
|
# This resource needs further testing and refinement
|
|
|
|
#
|
|
|
|
# This requires the `sqlcmd` tool available on platform
|
|
|
|
# @see https://docs.microsoft.com/en-us/sql/relational-databases/scripting/sqlcmd-use-the-utility
|
|
|
|
# @see https://docs.microsoft.com/en-us/sql/linux/sql-server-linux-connect-and-query-sqlcmd
|
2016-07-17 18:18:25 +00:00
|
|
|
class MssqlSession < Inspec.resource(1)
|
|
|
|
name 'mssql_session'
|
|
|
|
desc 'Use the mssql_session InSpec audit resource to test SQL commands run against a MS Sql Server database.'
|
|
|
|
example "
|
2017-05-02 14:35:54 +00:00
|
|
|
# Using SQL authentication
|
|
|
|
sql = mssql_session(user: 'myuser', pass: 'mypassword')
|
2017-06-29 15:01:32 +00:00
|
|
|
describe sql.query('SELECT * FROM table').row(0).column('columnname') do
|
|
|
|
its('value') { should cmp == 1 }
|
2017-05-02 14:35:54 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Passing no credentials to mssql_session forces it to use Windows authentication
|
|
|
|
sql_windows_auth = mssql_session
|
2017-06-29 15:01:32 +00:00
|
|
|
describe sql.query(\"SELECT SERVERPROPERTY('IsIntegratedSecurityOnly') as \\\"login_mode\\\";\").row(0).column('login_mode') do
|
|
|
|
its('value') { should_not be_empty }
|
|
|
|
its('value') { should cmp == 1 }
|
2016-07-17 18:18:25 +00:00
|
|
|
end
|
|
|
|
"
|
|
|
|
|
2017-06-29 15:01:32 +00:00
|
|
|
attr_reader :user, :password, :host
|
2017-05-02 14:35:54 +00:00
|
|
|
def initialize(opts = {})
|
|
|
|
@user = opts[:user]
|
2017-06-29 15:01:32 +00:00
|
|
|
@password = opts[:password] || opts[:pass]
|
|
|
|
if opts[:pass]
|
|
|
|
warn '[DEPRECATED] use `password` option to supply password instead of `pass`'
|
|
|
|
end
|
2017-05-02 14:35:54 +00:00
|
|
|
@host = opts[:host] || 'localhost'
|
|
|
|
@instance = opts[:instance]
|
2017-06-29 15:01:32 +00:00
|
|
|
|
|
|
|
# check if sqlcmd is available
|
|
|
|
return skip_resource('sqlcmd is missing') if !inspec.command('sqlcmd').exist?
|
|
|
|
# check that database is reachable
|
|
|
|
return skip_resource("Can't connect to the MS SQL Server.") if !test_connection
|
2016-07-17 18:18:25 +00:00
|
|
|
end
|
|
|
|
|
2016-07-17 18:22:04 +00:00
|
|
|
def query(q)
|
2017-06-29 15:01:32 +00:00
|
|
|
escaped_query = q.gsub(/\\/, '\\\\').gsub(/"/, '\\"').gsub(/\$/, '\\$')
|
|
|
|
# surpress 'x rows affected' in SQLCMD with 'set nocount on;'
|
|
|
|
cmd_string = "sqlcmd -Q \"set nocount on; #{escaped_query}\" -W -w 1024 -s ','"
|
2017-09-18 19:49:20 +00:00
|
|
|
cmd_string += " -U '#{@user}' -P '#{@password}'" unless @user.nil? || @password.nil?
|
2017-05-02 14:35:54 +00:00
|
|
|
if @instance.nil?
|
2017-09-18 19:49:20 +00:00
|
|
|
cmd_string += " -S '#{@host}'"
|
2017-05-02 14:35:54 +00:00
|
|
|
else
|
2017-09-18 19:49:20 +00:00
|
|
|
cmd_string += " -S '#{@host}\\#{@instance}'"
|
2017-05-02 14:35:54 +00:00
|
|
|
end
|
|
|
|
cmd = inspec.command(cmd_string)
|
|
|
|
out = cmd.stdout + "\n" + cmd.stderr
|
2017-06-29 15:01:32 +00:00
|
|
|
if cmd.exit_status != 0 || out =~ /Sqlcmd: Error/
|
|
|
|
# TODO: we need to throw an exception here
|
|
|
|
# change once https://github.com/chef/inspec/issues/1205 is in
|
|
|
|
warn "Could not execute the sql query #{out}"
|
|
|
|
DatabaseHelper::SQLQueryResult.new(cmd, Hashie::Mash.new({}))
|
|
|
|
else
|
|
|
|
DatabaseHelper::SQLQueryResult.new(cmd, parse_csv_result(cmd))
|
2017-05-02 14:35:54 +00:00
|
|
|
end
|
2016-07-17 18:18:25 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def to_s
|
2017-05-02 14:35:54 +00:00
|
|
|
'MSSQL session'
|
2016-07-17 18:18:25 +00:00
|
|
|
end
|
2017-06-29 15:01:32 +00:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def test_connection
|
|
|
|
!query('select getdate()').empty?
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse_csv_result(cmd)
|
|
|
|
require 'csv'
|
|
|
|
table = CSV.parse(cmd.stdout, { headers: true })
|
|
|
|
|
|
|
|
# remove first row, since it will be a seperator line
|
|
|
|
table.delete(0)
|
|
|
|
|
|
|
|
# convert to hash
|
|
|
|
headers = table.headers
|
|
|
|
|
|
|
|
results = table.map { |row|
|
|
|
|
res = {}
|
|
|
|
headers.each { |header|
|
|
|
|
res[header.downcase] = row[header]
|
|
|
|
}
|
|
|
|
Hashie::Mash.new(res)
|
|
|
|
}
|
|
|
|
results
|
|
|
|
end
|
2016-07-17 18:18:25 +00:00
|
|
|
end
|
|
|
|
end
|