inspec-core 4.38.9 → 4.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +5 -1
- data/etc/deprecations.json +1 -1
- data/lib/inspec/base_cli.rb +11 -1
- data/lib/inspec/cached_fetcher.rb +2 -2
- data/lib/inspec/cli.rb +14 -4
- data/lib/inspec/control_eval_context.rb +64 -17
- data/lib/inspec/dsl.rb +18 -3
- data/lib/inspec/fetcher/url.rb +45 -3
- data/lib/inspec/fetcher.rb +3 -3
- data/lib/inspec/plugin/v1/registry.rb +6 -2
- data/lib/inspec/profile.rb +146 -6
- data/lib/inspec/resources/apache_conf.rb +8 -6
- data/lib/inspec/resources/cassandra.rb +64 -0
- data/lib/inspec/resources/cassandradb_conf.rb +47 -0
- data/lib/inspec/resources/cassandradb_session.rb +68 -0
- data/lib/inspec/resources/chrony_conf.rb +55 -0
- data/lib/inspec/resources/csv.rb +26 -3
- data/lib/inspec/resources/groups.rb +22 -3
- data/lib/inspec/resources/ibmdb2_conf.rb +57 -0
- data/lib/inspec/resources/ibmdb2_session.rb +69 -0
- data/lib/inspec/resources/mongodb_session.rb +88 -0
- data/lib/inspec/resources/mssql_sys_conf.rb +48 -0
- data/lib/inspec/resources/opa.rb +26 -0
- data/lib/inspec/resources/opa_api.rb +39 -0
- data/lib/inspec/resources/opa_cli.rb +43 -0
- data/lib/inspec/resources/oracle.rb +66 -0
- data/lib/inspec/resources/oracledb_conf.rb +40 -0
- data/lib/inspec/resources/oracledb_listener_conf.rb +123 -0
- data/lib/inspec/resources/oracledb_session.rb +23 -6
- data/lib/inspec/resources/postgres_session.rb +15 -10
- data/lib/inspec/resources/registry_key.rb +1 -1
- data/lib/inspec/resources/security_identifier.rb +8 -14
- data/lib/inspec/resources/security_policy.rb +4 -3
- data/lib/inspec/resources/service.rb +7 -1
- data/lib/inspec/resources/sybase_conf.rb +37 -0
- data/lib/inspec/resources/sybase_session.rb +111 -0
- data/lib/inspec/resources/users.rb +16 -2
- data/lib/inspec/resources/windows_firewall.rb +1 -1
- data/lib/inspec/resources/wmi.rb +1 -1
- data/lib/inspec/resources.rb +12 -0
- data/lib/inspec/run_data/profile.rb +0 -2
- data/lib/inspec/runner.rb +2 -0
- data/lib/inspec/utils/filter.rb +1 -1
- data/lib/inspec/version.rb +1 -1
- data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +1 -1
- data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +1 -1
- data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +1 -1
- data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +16 -15
- metadata +18 -2
@@ -0,0 +1,43 @@
|
|
1
|
+
require "inspec/resources/opa"
|
2
|
+
|
3
|
+
module Inspec::Resources
|
4
|
+
class OpaCli < Opa
|
5
|
+
name "opa_cli"
|
6
|
+
supports platform: "unix"
|
7
|
+
supports platform: "windows"
|
8
|
+
|
9
|
+
def initialize(opts = {})
|
10
|
+
@opa_executable_path = opts[:opa_executable_path] || "opa" # if this path is not provided then we will assume that it's been set in the ENV PATH
|
11
|
+
@policy = opts[:policy] || nil
|
12
|
+
@data = opts[:data] || nil
|
13
|
+
@query = opts[:query] || nil
|
14
|
+
if (@policy.nil? || @policy.empty?) || (@data.nil? || @data.empty?) || (@query.nil? || @query.empty?)
|
15
|
+
fail_resource "OPA policy, data and query are mandatory."
|
16
|
+
end
|
17
|
+
@content = load_result
|
18
|
+
super(@content)
|
19
|
+
end
|
20
|
+
|
21
|
+
def allow
|
22
|
+
@content["result"][0]["expressions"][0]["value"] if @content["result"][0]["expressions"][0]["text"].include?("allow")
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"OPA cli"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def load_result
|
32
|
+
raise Inspec::Exceptions::ResourceFailed, "#{resource_exception_message}" if resource_failed?
|
33
|
+
|
34
|
+
result = inspec.command("#{@opa_executable_path} eval -i '#{@data}' -d '#{@policy}' '#{@query}'")
|
35
|
+
if result.exit_status == 0
|
36
|
+
result.stdout.gsub("\n", "")
|
37
|
+
else
|
38
|
+
error = result.stdout + "\n" + result.stderr
|
39
|
+
raise Inspec::Exceptions::ResourceFailed, "Error while executing OPA query: #{error}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "inspec/resources/powershell"
|
2
|
+
|
3
|
+
module Inspec::Resources
|
4
|
+
class Oracle < Inspec.resource(1)
|
5
|
+
name "oracle"
|
6
|
+
supports platform: "unix"
|
7
|
+
supports platform: "windows"
|
8
|
+
|
9
|
+
desc "The 'oracle' resource is a helper for the 'oracledb_listener_conf'"
|
10
|
+
|
11
|
+
attr_reader :conf_path
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
case inspec.os[:family]
|
15
|
+
when "debian", "redhat", "linux", "suse"
|
16
|
+
determine_conf_dir_and_path_in_linux
|
17
|
+
when "windows"
|
18
|
+
determine_conf_dir_and_path_in_windows
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"OracleDB"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def determine_conf_dir_and_path_in_linux
|
29
|
+
oracle_home = inspec.os_env("ORACLE_HOME").content
|
30
|
+
|
31
|
+
if oracle_home.nil? || oracle_home.empty?
|
32
|
+
warn "$ORACLE_HOME env value not set in the system"
|
33
|
+
nil
|
34
|
+
else
|
35
|
+
conf_path = "#{oracle_home}/network/admin/listener.ora"
|
36
|
+
if !inspec.file(conf_path).exist?
|
37
|
+
warn "No oracle listener settings found in $ORACLE_HOME/network/admin directory"
|
38
|
+
nil
|
39
|
+
else
|
40
|
+
@conf_path = conf_path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
rescue => e
|
44
|
+
fail_resource "Errors reading listener settings: #{e}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def determine_conf_dir_and_path_in_windows
|
48
|
+
oracle_home = inspec.os_env("ORACLE_HOME").content
|
49
|
+
|
50
|
+
if oracle_home.nil? || oracle_home.empty?
|
51
|
+
warn "ORACLE_HOME env value not set in the system"
|
52
|
+
nil
|
53
|
+
else
|
54
|
+
conf_path = "#{oracle_home}\\network\\admin\\listener.ora"
|
55
|
+
if !inspec.file(conf_path).exist?
|
56
|
+
warn "No oracle listener settings found in ORACLE_HOME\\network\\admin directory"
|
57
|
+
nil
|
58
|
+
else
|
59
|
+
@conf_path = conf_path
|
60
|
+
end
|
61
|
+
end
|
62
|
+
rescue => e
|
63
|
+
fail_resource "Errors reading listener settings: #{e}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "inspec/resources/oracledb_session"
|
2
|
+
|
3
|
+
module Inspec::Resources
|
4
|
+
class OracledbConf < Inspec.resource(1)
|
5
|
+
name "oracledb_conf"
|
6
|
+
supports platform: "unix"
|
7
|
+
supports platform: "windows"
|
8
|
+
desc "Use the oracledb_conf InSpec audit resource to test the database settings for Oracle DB"
|
9
|
+
example <<~EXAMPLE
|
10
|
+
describe oracledb_conf(user: 'USER', password: 'PASSWORD') do
|
11
|
+
its("audit_sys_operations") { should cmp "true" }
|
12
|
+
its("sql92_security") { should cmp "true" }
|
13
|
+
end
|
14
|
+
EXAMPLE
|
15
|
+
|
16
|
+
attr_reader :oracledb_session
|
17
|
+
|
18
|
+
def initialize(opts = {})
|
19
|
+
@oracledb_session = inspec.oracledb_session(opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(name)
|
23
|
+
setting = name.to_s.upcase
|
24
|
+
determine_database_setting(setting)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"Oracle DB Configuration"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def determine_database_setting(setting)
|
34
|
+
sql_query = oracledb_session.query("SELECT UPPER(VALUE) AS UPPER_VALUE FROM V$SYSTEM_PARAMETER WHERE UPPER(NAME) = '#{setting}'")
|
35
|
+
sql_query.row(0).column("UPPER_VALUE").value
|
36
|
+
rescue => e
|
37
|
+
raise Inspec::Exceptions::ResourceFailed, "Errors fetching database settings for Oracle database: #{e}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require "inspec/utils/object_traversal"
|
2
|
+
require "inspec/utils/simpleconfig"
|
3
|
+
require "inspec/utils/find_files"
|
4
|
+
require "inspec/utils/file_reader"
|
5
|
+
require "inspec/resources/oracle"
|
6
|
+
|
7
|
+
module Inspec::Resources
|
8
|
+
class OracledbListenerConf < Inspec.resource(1)
|
9
|
+
name "oracledb_listener_conf"
|
10
|
+
supports platform: "unix"
|
11
|
+
supports platform: "windows"
|
12
|
+
desc "Use the oracledb_listener_conf InSpec audit resource to test the listener settings for Oracle DB"
|
13
|
+
example <<~EXAMPLE
|
14
|
+
describe oracledb_listener_conf do
|
15
|
+
its('DEFAULT_SERVICE_LISTENER') { should eq 'XE' }
|
16
|
+
end
|
17
|
+
EXAMPLE
|
18
|
+
|
19
|
+
include FindFiles
|
20
|
+
include FileReader
|
21
|
+
include ObjectTraverser
|
22
|
+
|
23
|
+
def initialize(conf_path = nil)
|
24
|
+
oracle = nil
|
25
|
+
if conf_path.nil?
|
26
|
+
oracle = inspec.oracle
|
27
|
+
@conf_path = oracle.conf_path
|
28
|
+
else
|
29
|
+
@conf_path = conf_path
|
30
|
+
end
|
31
|
+
|
32
|
+
if oracle && oracle.resource_failed?
|
33
|
+
raise oracle.resource_exception_message
|
34
|
+
elsif @conf_path.nil?
|
35
|
+
return skip_resource "Oracle Listener conf path is not set"
|
36
|
+
end
|
37
|
+
|
38
|
+
@conf_dir = File.expand_path(File.dirname(@conf_path))
|
39
|
+
@files_contents = {}
|
40
|
+
@content = nil
|
41
|
+
@params = nil
|
42
|
+
read_content
|
43
|
+
end
|
44
|
+
|
45
|
+
def content
|
46
|
+
@content ||= read_content
|
47
|
+
end
|
48
|
+
|
49
|
+
def params(*opts)
|
50
|
+
@params || read_content
|
51
|
+
res = @params
|
52
|
+
opts.each do |opt|
|
53
|
+
res = res[opt] unless res.nil?
|
54
|
+
end
|
55
|
+
res
|
56
|
+
end
|
57
|
+
|
58
|
+
def value(key)
|
59
|
+
extract_value(key, @params)
|
60
|
+
end
|
61
|
+
|
62
|
+
def method_missing(*keys)
|
63
|
+
keys.shift if keys.is_a?(Array) && keys[0] == :[]
|
64
|
+
param = value(keys)
|
65
|
+
return nil if param.nil?
|
66
|
+
# extract first value if we have only one value in array
|
67
|
+
return param[0] if param.length == 1
|
68
|
+
|
69
|
+
param
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
"Oracle Listener Configuration"
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def read_content
|
79
|
+
@content = ""
|
80
|
+
@params = {}
|
81
|
+
|
82
|
+
to_read = [@conf_path]
|
83
|
+
until to_read.empty?
|
84
|
+
base_dir = File.dirname(to_read[0])
|
85
|
+
raw_conf = read_file(to_read[0])
|
86
|
+
@content += raw_conf
|
87
|
+
|
88
|
+
opts = {
|
89
|
+
assignment_regex: /^\s*([^=]*?)\s*=\s*[']?\s*(.*?)\s*[']?\s*$/,
|
90
|
+
}
|
91
|
+
params = SimpleConfig.new(raw_conf, opts).params
|
92
|
+
@params.merge!(params)
|
93
|
+
|
94
|
+
to_read = to_read.drop(1)
|
95
|
+
# see if there is more config files to include
|
96
|
+
|
97
|
+
to_read += include_files(params, base_dir).find_all do |fp|
|
98
|
+
not @files_contents.key? fp
|
99
|
+
end
|
100
|
+
end
|
101
|
+
@content
|
102
|
+
end
|
103
|
+
|
104
|
+
def include_files(params, base_dir)
|
105
|
+
include_files = Array(params["include"]) || []
|
106
|
+
include_files += Array(params["include_if_exists"]) || []
|
107
|
+
include_files.map! do |f|
|
108
|
+
Pathname.new(f).absolute? ? f : File.join(base_dir, f)
|
109
|
+
end
|
110
|
+
|
111
|
+
dirs = Array(params["include_dir"]) || []
|
112
|
+
dirs.each do |dir|
|
113
|
+
dir = File.join(base_dir, dir) if dir[0] != "/"
|
114
|
+
include_files += find_files(dir, depth: 1, type: "file")
|
115
|
+
end
|
116
|
+
include_files
|
117
|
+
end
|
118
|
+
|
119
|
+
def read_file(path)
|
120
|
+
@files_contents[path] ||= read_file_content(path)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -42,6 +42,7 @@ module Inspec::Resources
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def query(sql)
|
45
|
+
raise Inspec::Exceptions::ResourceSkipped, "#{resource_exception_message}" if resource_skipped?
|
45
46
|
raise Inspec::Exceptions::ResourceFailed, "#{resource_exception_message}" if resource_failed?
|
46
47
|
|
47
48
|
if @sqlcl_bin && inspec.command(@sqlcl_bin).exist?
|
@@ -78,7 +79,14 @@ module Inspec::Resources
|
|
78
79
|
# using a db_role
|
79
80
|
# su, using a db_role
|
80
81
|
def command_builder(format_options, query)
|
81
|
-
|
82
|
+
if @db_role.nil? || @su_user.nil?
|
83
|
+
verified_query = verify_query(query)
|
84
|
+
else
|
85
|
+
escaped_query = query.gsub(/\\\\/, "\\").gsub(/"/, '\\"')
|
86
|
+
escaped_query = escaped_query.gsub("$", '\\$') unless escaped_query.include? "\\$"
|
87
|
+
verified_query = verify_query(escaped_query)
|
88
|
+
end
|
89
|
+
|
82
90
|
sql_prefix, sql_postfix = "", ""
|
83
91
|
if inspec.os.windows?
|
84
92
|
sql_prefix = %{@'\n#{format_options}\n#{verified_query}\nEXIT\n'@ | }
|
@@ -87,11 +95,14 @@ module Inspec::Resources
|
|
87
95
|
end
|
88
96
|
|
89
97
|
if @db_role.nil?
|
90
|
-
|
98
|
+
%{#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service}#{sql_postfix}}
|
91
99
|
elsif @su_user.nil?
|
92
|
-
|
100
|
+
%{#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service} as #{@db_role}#{sql_postfix}}
|
93
101
|
else
|
94
|
-
|
102
|
+
# oracle_query_string is echoed to be able to extract the query output clearly
|
103
|
+
# su - su_user in certain versions of oracle returns a message
|
104
|
+
# Example of msg with query output: The Oracle base remains unchanged with value /oracle\n\nVALUE\n3\n
|
105
|
+
%{su - #{@su_user} -c "echo 'oracle_query_string'; env ORACLE_SID=#{@service} #{@bin} / as #{@db_role}#{sql_postfix}"}
|
95
106
|
end
|
96
107
|
end
|
97
108
|
|
@@ -101,9 +112,15 @@ module Inspec::Resources
|
|
101
112
|
end
|
102
113
|
|
103
114
|
def parse_csv_result(stdout)
|
104
|
-
output = stdout.
|
115
|
+
output = stdout.split("oracle_query_string")[-1]
|
116
|
+
# comma_query_sub replaces the csv delimiter "," in the output.
|
117
|
+
# Handles CSV parsing of data like this (DROP,3) etc
|
118
|
+
output = output.sub(/\r/, "").strip.gsub(",", "comma_query_sub")
|
105
119
|
converter = ->(header) { header.downcase }
|
106
|
-
CSV.parse(output, headers: true, header_converters: converter).map
|
120
|
+
CSV.parse(output, headers: true, header_converters: converter).map do |row|
|
121
|
+
revised_row = row.entries.flatten.map { |entry| entry.gsub("comma_query_sub", ",") }
|
122
|
+
Hashie::Mash.new([revised_row].to_h)
|
123
|
+
end
|
107
124
|
end
|
108
125
|
end
|
109
126
|
end
|
@@ -40,14 +40,13 @@ module Inspec::Resources
|
|
40
40
|
end
|
41
41
|
EXAMPLE
|
42
42
|
|
43
|
-
def initialize(user, pass, host = nil, port = nil)
|
43
|
+
def initialize(user, pass, host = nil, port = nil, socket_path = nil)
|
44
44
|
@user = user || "postgres"
|
45
45
|
@pass = pass
|
46
46
|
@host = host || "localhost"
|
47
47
|
@port = port || 5432
|
48
|
+
@socket_path = socket_path
|
48
49
|
raise Inspec::Exceptions::ResourceFailed, "Can't run PostgreSQL SQL checks without authentication." if @user.nil? || @pass.nil?
|
49
|
-
|
50
|
-
test_connection
|
51
50
|
end
|
52
51
|
|
53
52
|
def query(query, db = [])
|
@@ -65,20 +64,26 @@ module Inspec::Resources
|
|
65
64
|
|
66
65
|
private
|
67
66
|
|
68
|
-
def test_connection
|
69
|
-
query("select now()\;")
|
70
|
-
end
|
71
|
-
|
72
67
|
def escaped_query(query)
|
73
68
|
Shellwords.escape(query)
|
74
69
|
end
|
75
70
|
|
76
71
|
def create_psql_cmd(query, db = [])
|
77
72
|
dbs = db.map { |x| "#{x}" }.join(" ")
|
78
|
-
|
79
|
-
|
73
|
+
|
74
|
+
if @socket_path && !inspec.os.windows?
|
75
|
+
# Socket path and empty host in the connection string establishes socket connection
|
76
|
+
# Socket connection only enabled for non-windows platforms
|
77
|
+
# Windows does not support unix domain sockets
|
78
|
+
"psql -d postgresql://#{@user}:#{@pass}@/#{dbs}?host=#{@socket_path} -A -t -w -c #{escaped_query(query)}"
|
80
79
|
else
|
81
|
-
|
80
|
+
# Host in connection string establishes tcp/ip connection
|
81
|
+
if inspec.os.windows?
|
82
|
+
warn "Socket based connection not supported in windows, connecting using host" if @socket_path
|
83
|
+
"psql -d postgresql://#{@user}:#{@pass}@#{@host}:#{@port}/#{dbs} -A -t -w -c \"#{query}\""
|
84
|
+
else
|
85
|
+
"psql -d postgresql://#{@user}:#{@pass}@#{@host}:#{@port}/#{dbs} -A -t -w -c #{escaped_query(query)}"
|
86
|
+
end
|
82
87
|
end
|
83
88
|
end
|
84
89
|
end
|
@@ -105,7 +105,7 @@ module Inspec::Resources
|
|
105
105
|
children_keys(@options[:path], filter)
|
106
106
|
end
|
107
107
|
|
108
|
-
# returns nil, if not
|
108
|
+
# returns nil, if not existent or value
|
109
109
|
def method_missing(*keys)
|
110
110
|
# allow the use of array syntax in an `its` block so that users
|
111
111
|
# can use it to query for keys with . characters in them
|
@@ -57,14 +57,14 @@ module Inspec::Resources
|
|
57
57
|
@sids = {}
|
58
58
|
case @type
|
59
59
|
when :group
|
60
|
-
sid_data =
|
60
|
+
sid_data = cim_results(:group)
|
61
61
|
when :user
|
62
|
-
sid_data =
|
62
|
+
sid_data = cim_results(:user)
|
63
63
|
when :unspecified
|
64
64
|
# try group first, then user
|
65
|
-
sid_data =
|
65
|
+
sid_data = cim_results(:group)
|
66
66
|
if sid_data.empty?
|
67
|
-
sid_data =
|
67
|
+
sid_data = cim_results(:user)
|
68
68
|
end
|
69
69
|
else
|
70
70
|
raise "Unhandled entity type '#{@type}'"
|
@@ -72,20 +72,14 @@ module Inspec::Resources
|
|
72
72
|
sid_data.each { |sid| @sids[sid[1]] = sid[2] }
|
73
73
|
end
|
74
74
|
|
75
|
-
def
|
76
|
-
query = "wmic "
|
75
|
+
def cim_results(type)
|
77
76
|
case type
|
78
77
|
when :group
|
79
|
-
|
78
|
+
cmd = "Get-CimInstance -ClassName Win32_Account | Select-Object -Property Domain, Name, SID | Where-Object { $_.Name -eq '#{@name}' -and { $_.SIDType -eq 4 -or $_.SIDType -eq 5 } } | ConvertTo-Csv -NoTypeInformation"
|
80
79
|
when :user
|
81
|
-
|
80
|
+
cmd = "Get-CimInstance -ClassName Win32_Account | Select-Object -Property Domain, Name, SID, SIDType | Where-Object { $_.Name -eq '#{@name}' -and $_.SIDType -eq 1 } | ConvertTo-Csv -NoTypeInformation"
|
82
81
|
end
|
83
|
-
|
84
|
-
# Example output:
|
85
|
-
# inspec> command("wmic useraccount where 'Name=\"Administrator\"' get Name\",\"SID /format:csv").stdout
|
86
|
-
# => "\r\n\r\nNode,Name,SID\r\n\r\nComputer1,Administrator,S-1-5-21-650485088-1194226989-968533923-500\r\n\r\n"
|
87
|
-
# Remove the \r characters, split on \n\n, ignore the CSV header row
|
88
|
-
inspec.command(query).stdout.strip.tr("\r", "").split("\n\n")[1..-1].map { |entry| entry.split(",") }
|
82
|
+
inspec.command(cmd).stdout.strip.gsub("\"", "").tr("\r", "").split("\n")[1..-1].map { |entry| entry.split(",") }
|
89
83
|
end
|
90
84
|
end
|
91
85
|
end
|
@@ -147,7 +147,7 @@ module Inspec::Resources
|
|
147
147
|
|
148
148
|
# extracts the values, this methods detects:
|
149
149
|
# numbers and SIDs and optimizes them for further usage
|
150
|
-
def extract_value(val)
|
150
|
+
def extract_value(key, val)
|
151
151
|
if val =~ /^\d+$/
|
152
152
|
val.to_i
|
153
153
|
# special handling for SID array
|
@@ -166,14 +166,15 @@ module Inspec::Resources
|
|
166
166
|
elsif !(m = /^\"(.*)\"$/.match(val)).nil?
|
167
167
|
m[1]
|
168
168
|
else
|
169
|
-
|
169
|
+
# When there is Registry Values we are not spliting the value for backward compatibility
|
170
|
+
key.include?("\\") ? val : val.split(",")
|
170
171
|
end
|
171
172
|
end
|
172
173
|
|
173
174
|
def convert_hash(hash)
|
174
175
|
new_hash = {}
|
175
176
|
hash.each do |k, v|
|
176
|
-
v.is_a?(Hash) ? value = convert_hash(v) : value = extract_value(v)
|
177
|
+
v.is_a?(Hash) ? value = convert_hash(v) : value = extract_value(k, v)
|
177
178
|
new_hash[k.strip] = value
|
178
179
|
end
|
179
180
|
new_hash
|
@@ -141,7 +141,7 @@ module Inspec::Resources
|
|
141
141
|
elsif version > 0
|
142
142
|
SysV.new(inspec, service_ctl || "/usr/sbin/service")
|
143
143
|
end
|
144
|
-
when "redhat", "fedora", "centos", "oracle", "cloudlinux", "scientific"
|
144
|
+
when "redhat", "fedora", "centos", "oracle", "cloudlinux", "scientific", "rocky", "almalinux"
|
145
145
|
version = os[:release].to_i
|
146
146
|
|
147
147
|
systemd = ((platform != "fedora" && version >= 7) ||
|
@@ -152,6 +152,12 @@ module Inspec::Resources
|
|
152
152
|
else
|
153
153
|
SysV.new(inspec, service_ctl || "/sbin/service")
|
154
154
|
end
|
155
|
+
when "alibaba"
|
156
|
+
if os[:release].to_i >= 3
|
157
|
+
Systemd.new(inspec, service_ctl)
|
158
|
+
else
|
159
|
+
SysV.new(inspec, service_ctl || "/sbin/service")
|
160
|
+
end
|
155
161
|
when "wrlinux"
|
156
162
|
SysV.new(inspec, service_ctl)
|
157
163
|
when "mac_os_x", "darwin"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "inspec/resources/sybase_session"
|
2
|
+
|
3
|
+
module Inspec::Resources
|
4
|
+
class SybaseConf < Inspec.resource(1)
|
5
|
+
name "sybase_conf"
|
6
|
+
supports platform: "unix"
|
7
|
+
# supports platform: "windows" # TODO
|
8
|
+
desc "Use the sybase_conf InSpec resource to test Sybase config settings"
|
9
|
+
example <<~EXAMPLE
|
10
|
+
describe sybase_conf("max memory", password: 'password', server: 'SYBASE') do
|
11
|
+
its("run_value") { should cmp 180224 }
|
12
|
+
end
|
13
|
+
EXAMPLE
|
14
|
+
|
15
|
+
attr_reader :conf_param, :sql_query
|
16
|
+
def initialize(conf_param_name, opts = {})
|
17
|
+
@conf_param = conf_param_name
|
18
|
+
opts[:username] ||= "sa"
|
19
|
+
opts[:database] ||= "master"
|
20
|
+
sql_session = inspec.sybase_session(opts)
|
21
|
+
@sql_query = sql_session.query("sp_configure \"#{conf_param}\"")
|
22
|
+
end
|
23
|
+
|
24
|
+
def run_value
|
25
|
+
sql_query.row(0).column("Run Value").value
|
26
|
+
end
|
27
|
+
|
28
|
+
def config_value
|
29
|
+
sql_query.row(0).column("Config Value").value
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"Sybase Conf #{conf_param}"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require "inspec/resources/command"
|
2
|
+
require "inspec/utils/database_helpers"
|
3
|
+
require "hashie/mash"
|
4
|
+
require "csv" unless defined?(CSV)
|
5
|
+
require "tempfile" unless defined?(Tempfile)
|
6
|
+
|
7
|
+
module Inspec::Resources
|
8
|
+
# STABILITY: Experimental
|
9
|
+
# This resource needs further testing and refinement
|
10
|
+
#
|
11
|
+
class SybaseSession < Inspec.resource(1)
|
12
|
+
name "sybase_session"
|
13
|
+
supports platform: "unix"
|
14
|
+
# supports platform: "windows" # TODO
|
15
|
+
desc "Use the sybase_session InSpec resource to test commands against an Sybase database"
|
16
|
+
example <<~EXAMPLE
|
17
|
+
sql = sybase_session(username: 'my_user', password: 'password', server: 'SYBASE', database: 'pubs2')
|
18
|
+
describe sql.query(\"SELECT * FROM authors\").row(0).column('au_lname') do
|
19
|
+
its('value') { should eq 'Smith' }
|
20
|
+
end
|
21
|
+
EXAMPLE
|
22
|
+
|
23
|
+
# TODO: allow to set -I interfaces file
|
24
|
+
# TODO: allow to customize -s column separator
|
25
|
+
attr_reader :bin, :col_sep, :database, :password, :server, :sybase_home, :username
|
26
|
+
|
27
|
+
def initialize(opts = {})
|
28
|
+
@username = opts[:username]
|
29
|
+
@password = opts[:password]
|
30
|
+
@database = opts[:database]
|
31
|
+
@server = opts[:server]
|
32
|
+
@sybase_home = opts[:sybase_home] || "/opt/sap"
|
33
|
+
@bin = opts[:bin] || "isql"
|
34
|
+
@col_sep = "|"
|
35
|
+
|
36
|
+
fail_resource "Can't run Sybase checks without authentication" unless username && password
|
37
|
+
fail_resource "You must provide a server name for the session" unless server
|
38
|
+
fail_resource "You must provide a database name for the session" unless database
|
39
|
+
fail_resource "Cannot find #{bin} CLI tool" unless inspec.command(bin).exist?
|
40
|
+
end
|
41
|
+
|
42
|
+
def query(sql)
|
43
|
+
# We must write the SQl to a temp file on the remote target
|
44
|
+
# try to get a temp path
|
45
|
+
sql_file_path = upload_sql_file(sql)
|
46
|
+
|
47
|
+
# isql reuires that we have a matching locale set, but does not support C.UTF-8. en_US.UTF-8 is the least evil.
|
48
|
+
command = "LANG=en_US.UTF-8 SYBASE=#{sybase_home} #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}"
|
49
|
+
isql_cmd = inspec.command(command)
|
50
|
+
|
51
|
+
# Check for isql errors
|
52
|
+
res = isql_cmd.exit_status
|
53
|
+
raise Inspec::Exceptions::ResourceFailed.new("isql exited with code #{res} and stderr '#{isql_cmd.stderr}', stdout '#{isql_cmd.stdout}'") unless res == 0
|
54
|
+
# isql is ill-behaved, and returns 0 on error
|
55
|
+
raise Inspec::Exceptions::ResourceFailed.new("isql exited with error '#{isql_cmd.stderr}', stdout '#{isql_cmd.stdout}'") unless isql_cmd.stderr == ""
|
56
|
+
# check stdout for error messages when stderr is empty "Msg 102, Level 15, State 181:\nServer 'SYBASE', Line 1:\nIncorrect syntax near '.'.\n"
|
57
|
+
raise Inspec::Exceptions::ResourceFailed.new("isql exited with error #{isql_cmd.stdout}") if isql_cmd.stdout.match?(/Msg\s\d+,\sLevel\s\d+,\sState\s\d+/)
|
58
|
+
|
59
|
+
# Clean up temporary file
|
60
|
+
rm_cmd = inspec.command("rm #{sql_file_path}")
|
61
|
+
res = rm_cmd.exit_status # TODO: handle
|
62
|
+
raise Inspec::Exceptions::ResourceFailed.new("Unable to delete temproary SQL input file at #{sql_file_path}: #{rm_cmd.stderr}") unless res == 0
|
63
|
+
|
64
|
+
DatabaseHelper::SQLQueryResult.new(isql_cmd, parse_csv_result(isql_cmd.stdout))
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
"Sybase Session"
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def parse_csv_result(stdout)
|
74
|
+
output = stdout.gsub(/\r/, "").strip
|
75
|
+
lines = output.lines
|
76
|
+
# Remove second row (all dashes) and last 2 rows (blank and summary lines)
|
77
|
+
trimmed_output = ([lines[0]] << lines.slice(2..-3)).join("")
|
78
|
+
header_converter = Proc.new do |header|
|
79
|
+
# This is here to suppress a warning from Hashie::Mash when it encounters a
|
80
|
+
# header column that ends up with the name "default", which happens when using the
|
81
|
+
# sybase_conf resource. It does mean that aly query whose output field includes the name
|
82
|
+
# Default (exactly) will get renamed to default_value, but that seems unlikely.
|
83
|
+
if header.match?(/^Default\s+$/)
|
84
|
+
"default_value"
|
85
|
+
else
|
86
|
+
header.downcase.strip
|
87
|
+
end
|
88
|
+
end
|
89
|
+
field_converter = ->(field) { field&.strip }
|
90
|
+
CSV.parse(trimmed_output, headers: true, header_converters: header_converter, converters: field_converter, col_sep: col_sep).map { |row| Hashie::Mash.new(row.to_h) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def upload_sql_file(sql)
|
94
|
+
remote_temp_dir = "/tmp"
|
95
|
+
remote_file_path = nil
|
96
|
+
local_temp_file = Tempfile.new(["sybase", ".sql"])
|
97
|
+
begin
|
98
|
+
local_temp_file.write("#{sql}\n")
|
99
|
+
local_temp_file.write("go\n")
|
100
|
+
local_temp_file.flush
|
101
|
+
filename = File.basename(local_temp_file.path)
|
102
|
+
remote_file_path = "#{remote_temp_dir}/#{filename}"
|
103
|
+
inspec.backend.upload([local_temp_file.path], remote_temp_dir)
|
104
|
+
ensure
|
105
|
+
local_temp_file.close
|
106
|
+
local_temp_file.unlink
|
107
|
+
end
|
108
|
+
remote_file_path
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -204,7 +204,9 @@ module Inspec::Resources
|
|
204
204
|
alias group groupname
|
205
205
|
|
206
206
|
def groups
|
207
|
-
|
207
|
+
unless identity.nil?
|
208
|
+
inspec.os.windows? ? UserGroups.new(identity[:groups]) : identity[:groups]
|
209
|
+
end
|
208
210
|
end
|
209
211
|
|
210
212
|
def home
|
@@ -314,6 +316,18 @@ module Inspec::Resources
|
|
314
316
|
end
|
315
317
|
end
|
316
318
|
|
319
|
+
# Class defined to compare for groups without case-sensitivity
|
320
|
+
class UserGroups < Array
|
321
|
+
def initialize(user_groups)
|
322
|
+
@user_groups = user_groups
|
323
|
+
super
|
324
|
+
end
|
325
|
+
|
326
|
+
def include?(group)
|
327
|
+
!(@user_groups.select { |user_group| user_group.casecmp?(group) }.empty?)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
317
331
|
# This is an abstract class that every user provoider has to implement.
|
318
332
|
# A user provider implements a system abstracts and helps the InSpec resource
|
319
333
|
# hand-over system specific behavior to those providers
|
@@ -622,7 +636,7 @@ module Inspec::Resources
|
|
622
636
|
name, _domain = parse_windows_account(username)
|
623
637
|
return if collect_user_details.nil?
|
624
638
|
|
625
|
-
res = collect_user_details.select { |user| user[:username]
|
639
|
+
res = collect_user_details.select { |user| user[:username].casecmp? name }
|
626
640
|
res[0] unless res.empty?
|
627
641
|
end
|
628
642
|
|