Merge pull request #4654 from inspec/mj/oracle

oracledb_session fixes
This commit is contained in:
Miah Johnson 2019-10-31 14:13:56 -07:00 committed by GitHub
commit 7a68d58904
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 104 additions and 56 deletions

View file

@ -1,5 +1,4 @@
require "inspec/resources/command"
require "hashie/mash"
require "inspec/utils/database_helpers"
require "htmlentities"
require "rexml/document"
@ -21,8 +20,9 @@ module Inspec::Resources
end
EXAMPLE
attr_reader :user, :password, :host, :service, :as_os_user, :as_db_role
# rubocop:disable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity
attr_reader :bin, :db_role, :host, :password, :port, :service,
:su_user, :user
def initialize(opts = {})
@user = opts[:user]
@password = opts[:password] || opts[:pass]
@ -30,60 +30,35 @@ module Inspec::Resources
Inspec.deprecate(:oracledb_session_pass_option, "The oracledb_session `pass` option is deprecated. Please use `password`.")
end
@bin = "sqlplus"
@host = opts[:host] || "localhost"
@port = opts[:port] || "1521"
@service = opts[:service]
# connection as sysdba stuff
return skip_resource "Option 'as_os_user' not available in Windows" if inspec.os.windows? && opts[:as_os_user]
@su_user = opts[:as_os_user]
@db_role = opts[:as_db_role]
# we prefer sqlci although it is way slower than sqlplus, but it understands csv properly
@sqlcl_bin = "sql" unless opts.key?(:sqlplus_bin) # don't use it if user specified sqlplus_bin option
@sqlcl_bin = opts[:sqlcl_bin] || nil
@sqlplus_bin = opts[:sqlplus_bin] || "sqlplus"
return fail_resource "Can't run Oracle checks without authentication" if @su_user.nil? && (@user.nil? || @password.nil?)
return fail_resource "You must provide a service name for the session" if @service.nil?
skip_resource "Option 'as_os_user' not available in Windows" if inspec.os.windows? && su_user
fail_resource "Can't run Oracle checks without authentication" unless su_user && (user || password)
fail_resource "You must provide a service name for the session" unless service
end
def query(q)
escaped_query = q.gsub(/\\/, '\\\\').gsub(/"/, '\\"')
# escape tables with $
escaped_query = escaped_query.gsub("$", '\\$')
p = nil
# use sqlplus if sqlcl is not available
def query(sql)
if @sqlcl_bin && inspec.command(@sqlcl_bin).exist?
bin = @sqlcl_bin
opts = "set sqlformat csv\nSET FEEDBACK OFF"
p = :parse_csv_result
@bin = @sqlcl_bin
format_options = "set sqlformat csv\nSET FEEDBACK OFF"
parser = :parse_csv_result
else
bin = @sqlplus_bin
opts = "SET MARKUP HTML ON\nSET PAGESIZE 32000\nSET FEEDBACK OFF"
p = :parse_html_result
@bin = "#{@sqlplus_bin} -S"
format_options = "SET MARKUP HTML ON\nSET PAGESIZE 32000\nSET FEEDBACK OFF"
parser = :parse_html_result
end
query = verify_query(escaped_query)
query += ";" unless query.end_with?(";")
if @db_role.nil?
command = %{#{bin} "#{@user}"/"#{@password}"@#{@host}:#{@port}/#{@service} <<EOC\n#{opts}\n#{query}\nEXIT\nEOC}
elsif @su_user.nil?
command = %{#{bin} "#{@user}"/"#{@password}"@#{@host}:#{@port}/#{@service} as #{@db_role} <<EOC\n#{opts}\n#{query}\nEXIT\nEOC}
else
command = %{su - #{@su_user} -c "env ORACLE_SID=#{@service} #{bin} / as #{@db_role} <<EOC\n#{opts}\n#{query}\nEXIT\nEOC"}
end
cmd = inspec.command(command)
command = command_builder(format_options, sql)
inspec_cmd = inspec.command(command)
out = cmd.stdout + "\n" + cmd.stderr
if out.downcase =~ /^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({}))
end
DatabaseHelper::SQLQueryResult.new(cmd, send(p, cmd.stdout))
DatabaseHelper::SQLQueryResult.new(inspec_cmd, send(parser,
inspec_cmd.stdout))
end
def to_s
@ -92,9 +67,30 @@ module Inspec::Resources
private
# 3 commands
# regular user password
# using a db_role
# su, using a db_role
def command_builder(format_options, query)
verified_query = verify_query(query)
sql_prefix, sql_postfix = "", ""
if inspec.os.windows?
sql_prefix = %{@'\n#{format_options}\n#{verified_query}\nEXIT\n'@ | }
else
sql_postfix = %{ <<'EOC'\n#{format_options}\n#{verified_query}\nEXIT\nEOC}
end
if @db_role.nil?
%{#{sql_prefix}#{bin} "#{user}"/"#{password}"@#{host}:#{port}/#{@service}#{sql_postfix}}
elsif @su_user.nil?
%{#{sql_prefix}#{bin} "#{user}"/"#{password}"@#{host}:#{port}/#{@service} as #{@db_role}#{sql_postfix}}
else
%{su - #{@su_user} -c "env ORACLE_SID=#{@service} #{bin} / as #{@db_role}#{sql_postfix}}
end
end
def verify_query(query)
# ensure we have a ; at the end
query + ";" unless query.strip.end_with?(";")
query += ";" unless query.strip.end_with?(";")
query
end
@ -115,7 +111,7 @@ module Inspec::Resources
results
end
def parse_html_result(stdout) # rubocop:disable Metrics/AbcSize
def parse_html_result(stdout)
result = stdout
# make oracle html valid html by removing the p tag, it does not include a closing tag
result = result.gsub("<p>", "").gsub("</p>", "").gsub("<br>", "")

View file

@ -3,18 +3,70 @@ require "inspec/resource"
require "inspec/resources/oracledb_session"
describe "Inspec::Resources::OracledbSession" do
it "verify oracledb_session configuration" do
resource = load_resource("oracledb_session", user: "SYSTEM", password: "supersecurepass", host: "localhost", service: "ORCL.localdomain")
_(resource.user).must_equal "SYSTEM"
_(resource.password).must_equal "supersecurepass"
_(resource.host).must_equal "localhost"
_(resource.service).must_equal "ORCL.localdomain"
end
it "sqlplus Linux" do
resource = quick_resource(:oracledb_session, :linux, user: "USER", password: "password", host: "localhost", service: "ORCL", port: 1527, sqlplus_bin: "/bin/sqlplus") do |cmd|
cmd.strip!
case cmd
when "/bin/sqlplus -S \"USER\"/\"password\"@localhost:1527/ORCL <<'EOC'\nSET MARKUP HTML ON\nSET PAGESIZE 32000\nSET FEEDBACK OFF\nSELECT NAME AS VALUE FROM v$database;\nEXIT\nEOC" then
stdout_file "test/unit/mock/cmd/oracle-result"
else
raise cmd.inspect
end
end
it "run a SQL query" do
resource = load_resource("oracledb_session", user: "SYSTEM", password: "supersecurepass", host: "127.0.0.1", service: "ORCL.localdomain", port: 1527)
_(resource.resource_skipped?).must_equal false
query = resource.query("SELECT NAME AS VALUE FROM v$database;")
_(query.size).must_equal 1
_(query.row(0).column("value").value).must_equal "ORCL"
end
it "sqlplus Windows" do
resource = quick_resource(:oracledb_session, :windows, user: "USER", password: "password", host: "localhost", service: "ORCL", port: 1527, sqlplus_bin: "C:/sqlplus.exe") do |cmd|
cmd.strip!
case cmd
when "@'\nSET MARKUP HTML ON\nSET PAGESIZE 32000\nSET FEEDBACK OFF\nSELECT NAME AS VALUE FROM v$database;\nEXIT\n'@ | C:/sqlplus.exe -S \"USER\"/\"password\"@localhost:1527/ORCL" then
stdout_file "test/unit/mock/cmd/oracle-result"
else
raise cmd.inspect
end
end
_(resource.resource_skipped?).must_equal false
query = resource.query("SELECT NAME AS VALUE FROM v$database;")
_(query.size).must_equal 1
_(query.row(0).column("value").value).must_equal "ORCL"
end
it "skipped when sqlplus Windows as_os_user" do
resource = quick_resource(:oracledb_session, :windows, user: "USER", password: "password", host: "localhost", service: "ORCL", port: 1527, sqlplus_bin: "C:/sqlplus.exe", as_os_user: "Administrator")
_(resource.resource_skipped?).must_equal true
_(resource.resource_exception_message).must_equal "Option 'as_os_user' not available in Windows"
end
it "fails when no user, password, or su" do
resource = quick_resource(:oracledb_session, :windows, host: "localhost", service: "ORCL", port: 1527, sqlplus_bin: "C:/sqlplus.exe")
_(resource.resource_failed?).must_equal true
_(resource.resource_exception_message).must_equal "Can't run Oracle checks without authentication"
end
it "fails when no service name is provided" do
resource = quick_resource(:oracledb_session, :windows, user: "USER", password: "password", host: "localhost", port: 1527, sqlplus_bin: "C:/sqlplus.exe")
_(resource.resource_failed?).must_equal true
_(resource.resource_exception_message).must_equal "You must provide a service name for the session"
end
it "verify oracledb_session configuration" do
resource = quick_resource(:oracledb_session, :linux, user: "USER", password: "password", host: "localhost", service: "ORCL", as_db_role: "dbrole", as_os_user: "osuser")
_(resource.user).must_equal "USER"
_(resource.password).must_equal "password"
_(resource.host).must_equal "localhost"
_(resource.port).must_equal "1521"
_(resource.service).must_equal "ORCL"
_(resource.db_role).must_equal "dbrole"
_(resource.su_user).must_equal "osuser"
_(resource.bin).must_equal "sqlplus"
end
end