mirror of
https://github.com/inspec/inspec
synced 2024-11-22 20:53:11 +00:00
Merge pull request #5683 from inspec/nm/cassandra-resource
Add support for Cassandra DB
This commit is contained in:
commit
a79be0ca96
14 changed files with 399 additions and 0 deletions
45
docs-chef-io/content/inspec/resources/cassandradb_conf.md
Normal file
45
docs-chef-io/content/inspec/resources/cassandradb_conf.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
+++
|
||||
title = "cassandradb_conf resource"
|
||||
draft = false
|
||||
gh_repo = "inspec"
|
||||
platform = "os"
|
||||
|
||||
[menu]
|
||||
[menu.inspec]
|
||||
title = "cassandradb_conf"
|
||||
identifier = "inspec/resources/os/cassandradb_conf.md cassandradb_conf resource"
|
||||
parent = "inspec/resources/os"
|
||||
+++
|
||||
|
||||
Use the `cassandradb_conf` Chef InSpec audit resource to test the configuration of a Cassandra database, which is typically located at `$CASSANDRA_HOME/cassandra.yaml` or `$CASSANDRA_HOME\conf\cassandra.yaml` depending upon the platform.
|
||||
|
||||
## Installation
|
||||
|
||||
This resource is distributed along with Chef InSpec itself. You can use it automatically.
|
||||
|
||||
## Requirements
|
||||
|
||||
- The value of the `CASSANDRA_HOME` environment variable must be set in the system.
|
||||
|
||||
## Syntax
|
||||
|
||||
A `cassandradb_conf` resource block fetches configurations in the `cassandra.yaml` file, and then compares them with the value stated in the test:
|
||||
|
||||
describe cassandradb_conf do
|
||||
its('config item') { should eq 'value' }
|
||||
end
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this Chef InSpec audit resource.
|
||||
|
||||
### Test parameters set within the configuration file
|
||||
|
||||
describe cassandradb_conf do
|
||||
its('listen_address') { should eq 'localhost' }
|
||||
its('num_tokens') { should eq 16 }
|
||||
end
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers, please visit our [matchers page](/inspec/matchers/).
|
76
docs-chef-io/content/inspec/resources/cassandradb_session.md
Normal file
76
docs-chef-io/content/inspec/resources/cassandradb_session.md
Normal file
|
@ -0,0 +1,76 @@
|
|||
+++
|
||||
title = "cassandradb_session resource"
|
||||
draft = false
|
||||
gh_repo = "inspec"
|
||||
platform = "os"
|
||||
|
||||
[menu]
|
||||
[menu.inspec]
|
||||
title = "cassandradb_session"
|
||||
identifier = "inspec/resources/os/cassandradb_session.md cassandradb_session resource"
|
||||
parent = "inspec/resources/os"
|
||||
+++
|
||||
|
||||
Use the `cassandradb_session` Chef InSpec audit resource to test Cassandra Query Language (CQL) commands run against a Cassandra database.
|
||||
|
||||
## Availability
|
||||
|
||||
### Installation
|
||||
|
||||
This resource is distributed along with Chef InSpec itself. You can use it automatically.
|
||||
|
||||
## Syntax
|
||||
|
||||
A `cassandradb_session` resource block declares the username, password, host, and port to use for the session, and then the command to be run:
|
||||
|
||||
describe cassandradb_session(user: 'USERNAME', password: 'PASSWORD', host: 'localhost', port: 9042).query('QUERY') do
|
||||
its('value') { should eq('EXPECTED') }
|
||||
end
|
||||
|
||||
where
|
||||
|
||||
- `cassandradb_session` declares a username, password, host and port to run the query.
|
||||
- `query('QUERY')` contains the query to be run.
|
||||
- `its('value') { should eq('expected') }` compares the results of the query against the expected result in the test.
|
||||
|
||||
### Optional Parameters
|
||||
|
||||
The `cassandradb_session` InSpec resource accepts `user`, `password`, `host`, and `port` parameters.
|
||||
|
||||
In Particular:
|
||||
|
||||
#### `user`
|
||||
|
||||
Default value: `cassandra`.
|
||||
|
||||
#### `password`
|
||||
|
||||
Default value: `cassandra`.
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this Chef InSpec audit resource.
|
||||
|
||||
### Test for matching values using a Cassandra query
|
||||
|
||||
```ruby
|
||||
cql = cassandradb_session(user: 'MY_USER', password: 'PASSWORD', host: 'localhost', port: 9042)
|
||||
|
||||
describe cql.query("SELECT cluster_name FROM system.local") do
|
||||
its('output') { should match /Test Cluster/ }
|
||||
end
|
||||
```
|
||||
|
||||
### Test for matching values using a Cassandra query from a sample database
|
||||
|
||||
```ruby
|
||||
cql = cassandradb_session(user: 'MY_USER', password: 'PASSWORD', host: 'localhost', port: 9042)
|
||||
|
||||
describe cql.query("use SAMPLEDB; SELECT name FROM SAMPLETABLE") do
|
||||
its('output') { should match /Test Name/ }
|
||||
end
|
||||
```
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers, please visit our [matchers page](/inspec/matchers/).
|
|
@ -37,6 +37,9 @@ require "inspec/resources/chocolatey_package"
|
|||
require "inspec/resources/command"
|
||||
require "inspec/resources/cran"
|
||||
require "inspec/resources/cpan"
|
||||
require "inspec/resources/cassandradb_session"
|
||||
require "inspec/resources/cassandradb_conf"
|
||||
require "inspec/resources/cassandra"
|
||||
require "inspec/resources/crontab"
|
||||
require "inspec/resources/dh_params"
|
||||
require "inspec/resources/directory"
|
||||
|
|
64
lib/inspec/resources/cassandra.rb
Normal file
64
lib/inspec/resources/cassandra.rb
Normal file
|
@ -0,0 +1,64 @@
|
|||
module Inspec::Resources
|
||||
class Cassandra < Inspec.resource(1)
|
||||
name "cassandra"
|
||||
supports platform: "unix"
|
||||
supports platform: "windows"
|
||||
|
||||
desc "The 'cassandra' resource is a helper for the 'cql_conf'"
|
||||
|
||||
attr_reader :conf_path
|
||||
|
||||
def initialize
|
||||
case inspec.os[:family]
|
||||
when "debian", "redhat", "linux", "suse"
|
||||
determine_conf_dir_and_path_in_linux
|
||||
when "windows"
|
||||
determine_conf_dir_and_path_in_windows
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
"CassandraDB"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def determine_conf_dir_and_path_in_linux
|
||||
cassandra_home = inspec.os_env("CASSANDRA_HOME").content
|
||||
|
||||
if cassandra_home.nil? || cassandra_home.empty?
|
||||
warn "$CASSANDRA_HOME environment variable not set in the system"
|
||||
nil
|
||||
else
|
||||
conf_path = "#{cassandra_home}/cassandra.yaml"
|
||||
if !inspec.file(conf_path).exist?
|
||||
warn "Cassandra conf file not found in #{cassandra_home} directory."
|
||||
nil
|
||||
else
|
||||
@conf_path = conf_path
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
fail_resource "Errors reading cassandra conf file: #{e}"
|
||||
end
|
||||
|
||||
def determine_conf_dir_and_path_in_windows
|
||||
cassandra_home = inspec.os_env("CASSANDRA_HOME").content
|
||||
|
||||
if cassandra_home.nil? || cassandra_home.empty?
|
||||
warn "CASSANDRA_HOME environment variable not set in the system"
|
||||
nil
|
||||
else
|
||||
conf_path = "#{cassandra_home}\\conf\\cassandra.yaml"
|
||||
if !inspec.file(conf_path).exist?
|
||||
warn "Cassandra conf file not found in #{cassandra_home}\\conf directory."
|
||||
nil
|
||||
else
|
||||
@conf_path = conf_path
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
fail_resource "Errors reading cassandra conf file: #{e}"
|
||||
end
|
||||
end
|
||||
end
|
47
lib/inspec/resources/cassandradb_conf.rb
Normal file
47
lib/inspec/resources/cassandradb_conf.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
require "inspec/resources/json"
|
||||
require "inspec/resources/cassandra"
|
||||
|
||||
module Inspec::Resources
|
||||
class CassandradbConf < JsonConfig
|
||||
name "cassandradb_conf"
|
||||
supports platform: "unix"
|
||||
supports platform: "windows"
|
||||
desc "Use the cql_conf InSpec audit resource to test the contents of the configuration file for Cassandra DB"
|
||||
example <<~EXAMPLE
|
||||
describe cassandradb_conf do
|
||||
its('listen_address') { should eq '0.0.0.0' }
|
||||
end
|
||||
EXAMPLE
|
||||
|
||||
def initialize(conf_path = nil)
|
||||
cassandra = nil
|
||||
if conf_path.nil?
|
||||
cassandra = inspec.cassandra
|
||||
@conf_path = cassandra.conf_path
|
||||
else
|
||||
@conf_path = conf_path
|
||||
end
|
||||
|
||||
if cassandra && cassandra.resource_failed?
|
||||
raise cassandra.resource_exception_message
|
||||
elsif @conf_path.nil?
|
||||
return skip_resource "Cassandra db conf path is not set"
|
||||
end
|
||||
|
||||
super(@conf_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse(content)
|
||||
YAML.load(content)
|
||||
rescue => e
|
||||
raise Inspec::Exceptions::ResourceFailed, "Unable to parse `cassandra.yaml` file: #{e.message}"
|
||||
end
|
||||
|
||||
def resource_base_name
|
||||
"Cassandra Configuration"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
68
lib/inspec/resources/cassandradb_session.rb
Normal file
68
lib/inspec/resources/cassandradb_session.rb
Normal file
|
@ -0,0 +1,68 @@
|
|||
module Inspec::Resources
|
||||
class Lines
|
||||
attr_reader :output
|
||||
|
||||
def initialize(raw, desc)
|
||||
@output = raw
|
||||
@desc = desc
|
||||
end
|
||||
|
||||
def to_s
|
||||
@desc
|
||||
end
|
||||
end
|
||||
|
||||
class CassandradbSession < Inspec.resource(1)
|
||||
name "cassandradb_session"
|
||||
supports platform: "unix"
|
||||
supports platform: "windows"
|
||||
desc "Use the cassandradb_session InSpec resource to test commands against an Cassandra database"
|
||||
example <<~EXAMPLE
|
||||
cql = cassandradb_session(user: 'my_user', password: 'password', host: 'host', port: 'port')
|
||||
describe cql.query("SELECT cluster_name FROM system.local") do
|
||||
its('output') { should match /Test Cluster/ }
|
||||
end
|
||||
EXAMPLE
|
||||
|
||||
attr_reader :user, :password, :host, :port
|
||||
|
||||
def initialize(opts = {})
|
||||
@user = opts[:user] || "cassandra"
|
||||
@password = opts[:password] || "cassandra"
|
||||
@host = opts[:host]
|
||||
@port = opts[:port]
|
||||
end
|
||||
|
||||
def query(q)
|
||||
cassandra_cmd = create_cassandra_cmd(q)
|
||||
cmd = inspec.command(cassandra_cmd)
|
||||
out = cmd.stdout + "\n" + cmd.stderr
|
||||
if cmd.exit_status != 0 || out =~ /Unable to connect to any servers/ || out.downcase =~ /^error:.*/
|
||||
raise Inspec::Exceptions::ResourceFailed, "Cassandra query with errors: #{out}"
|
||||
else
|
||||
Lines.new(cmd.stdout.strip, "Cassandra query: #{q}")
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Cassandra DB Session"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_cassandra_cmd(q)
|
||||
# TODO: simple escape, must be handled by a library
|
||||
# that does this securely
|
||||
escaped_query = q.gsub(/\\/, "\\\\").gsub(/"/, '\\"').gsub(/\$/, '\\$')
|
||||
|
||||
# construct the query
|
||||
command = "cqlsh"
|
||||
command += " #{@host}" unless @host.nil?
|
||||
command += " #{@port}" unless @port.nil?
|
||||
command += " -u #{@user}"
|
||||
command += " -p #{@password}"
|
||||
command += " --execute '#{escaped_query}'"
|
||||
command
|
||||
end
|
||||
end
|
||||
end
|
1
test/fixtures/cmd/cassandra-connection-error
vendored
Normal file
1
test/fixtures/cmd/cassandra-connection-error
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Unable to connect to any servers
|
1
test/fixtures/cmd/cassandra-connection-success
vendored
Normal file
1
test/fixtures/cmd/cassandra-connection-success
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
\r\n cluster_name\r\n--------------\r\n Test Cluster\r\n\r\n(1 rows)\r\n\n
|
1
test/fixtures/cmd/env
vendored
1
test/fixtures/cmd/env
vendored
|
@ -1,2 +1,3 @@
|
|||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
ORACLE_HOME=/opt/oracle/product/18c/dbhomeXE
|
||||
CASSANDRA_HOME=/etc/cassandra
|
||||
|
|
1
test/fixtures/cmd/fetch-cassandra-conf-in-windows
vendored
Normal file
1
test/fixtures/cmd/fetch-cassandra-conf-in-windows
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
C:\Program Files\apache-cassandra-3.11.4-bin\apache-cassandra-3.11.4
|
6
test/fixtures/files/cassandra.yaml
vendored
Normal file
6
test/fixtures/files/cassandra.yaml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
cluster_name: 'Test Cluster'
|
||||
num_tokens: 16
|
||||
listen_address: localhost
|
||||
native_transport_port: 9042
|
||||
audit_logging_options:
|
||||
enabled: false
|
|
@ -115,6 +115,8 @@ class MockLoader
|
|||
"/etc/mongod.conf" => mockfile.call("mongod.conf"),
|
||||
"/opt/oracle/product/18c/dbhomeXE/network/admin/listener.ora" => mockfile.call("listener.ora"),
|
||||
"C:\\app\\Administrator\\product\\18.0.0\\dbhomeXE\\network\\admin\\listener.ora" => mockfile.call("listener.ora"),
|
||||
"/etc/cassandra/cassandra.yaml" => mockfile.call("cassandra.yaml"),
|
||||
"C:\\Program Files\\apache-cassandra-3.11.4-bin\\apache-cassandra-3.11.4\\conf\\cassandra.yaml" => mockfile.call("cassandra.yaml"),
|
||||
"/etc/rabbitmq/rabbitmq.config" => mockfile.call("rabbitmq.config"),
|
||||
"kitchen.yml" => mockfile.call("kitchen.yml"),
|
||||
"example.csv" => mockfile.call("example.csv"),
|
||||
|
@ -502,6 +504,7 @@ class MockLoader
|
|||
"sh -c 'type \"sqlplus\"'" => cmd.call("oracle-cmd"),
|
||||
"1998da5bc0f09bd5258fad51f45447556572b747f631661831d6fcb49269a448" => cmd.call("oracle-result"),
|
||||
"${Env:ORACLE_HOME}" => cmd.call("fetch-oracle-listener-in-windows"),
|
||||
"${Env:CASSANDRA_HOME}" => cmd.call("fetch-cassandra-conf-in-windows"),
|
||||
# nginx mock cmd
|
||||
%{nginx -V 2>&1} => cmd.call("nginx-v"),
|
||||
%{/usr/sbin/nginx -V 2>&1} => cmd.call("nginx-v"),
|
||||
|
|
33
test/unit/resources/cassandradb_conf_test.rb
Normal file
33
test/unit/resources/cassandradb_conf_test.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
require "helper"
|
||||
require "inspec/resource"
|
||||
require "inspec/resources/cassandradb_conf"
|
||||
|
||||
describe "Inspec::Resources::CassandradbConf" do
|
||||
it "verify configurations of cassandra DB in linux when conf path is passed" do
|
||||
resource = MockLoader.new(:centos7).load_resource("cassandradb_conf", "/etc/cassandra/cassandra.yaml")
|
||||
_(resource.params["listen_address"]).must_equal "localhost"
|
||||
_(resource.params["native_transport_port"]).must_equal 9042
|
||||
_(resource.params["audit_logging_options"]["enabled"]).must_equal false
|
||||
end
|
||||
|
||||
it "verify configurations of cassandra DB in windows when conf path is passed" do
|
||||
resource = MockLoader.new(:windows).load_resource("cassandradb_conf", "C:\\Program Files\\apache-cassandra-3.11.4-bin\\apache-cassandra-3.11.4\\conf\\cassandra.yaml")
|
||||
_(resource.params["listen_address"]).must_equal "localhost"
|
||||
_(resource.params["native_transport_port"]).must_equal 9042
|
||||
_(resource.params["audit_logging_options"]["enabled"]).must_equal false
|
||||
end
|
||||
|
||||
it "verify configurations of cassandra DB in linux when conf path is not passed" do
|
||||
resource = MockLoader.new(:centos7).load_resource("cassandradb_conf", nil)
|
||||
_(resource.params["listen_address"]).must_equal "localhost"
|
||||
_(resource.params["native_transport_port"]).must_equal 9042
|
||||
_(resource.params["audit_logging_options"]["enabled"]).must_equal false
|
||||
end
|
||||
|
||||
it "verify configurations of cassandra DB in windows when conf path is not passed" do
|
||||
resource = MockLoader.new(:windows).load_resource("cassandradb_conf", nil)
|
||||
_(resource.params["listen_address"]).must_equal "localhost"
|
||||
_(resource.params["native_transport_port"]).must_equal 9042
|
||||
_(resource.params["audit_logging_options"]["enabled"]).must_equal false
|
||||
end
|
||||
end
|
50
test/unit/resources/cassandradb_session_test.rb
Normal file
50
test/unit/resources/cassandradb_session_test.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
require "helper"
|
||||
require "inspec/resource"
|
||||
require "inspec/resources/cassandradb_session"
|
||||
|
||||
describe "Inspec::Resources::CassandradbSession" do
|
||||
it "verify cassandradb_session configuration" do
|
||||
resource = load_resource("cassandradb_session", host: "localhost", port: 9042)
|
||||
_(resource.resource_failed?).must_equal false
|
||||
_(resource.user).must_equal "cassandra"
|
||||
_(resource.password).must_equal "cassandra"
|
||||
_(resource.host).must_equal "localhost"
|
||||
_(resource.port).must_equal 9042
|
||||
end
|
||||
|
||||
it "success when connection is estalished" do
|
||||
resource = quick_resource(:cassandradb_session, :linux, user: "USER", password: "rightpassword", host: "localhost", port: 9042) do |cmd|
|
||||
cmd.strip!
|
||||
case cmd
|
||||
when "cqlsh localhost 9042 -u USER -p rightpassword --execute 'SELECT cluster_name FROM system.local'" then
|
||||
stdout_file "test/fixtures/cmd/cassandra-connection-success"
|
||||
else
|
||||
raise cmd.inspect
|
||||
end
|
||||
end
|
||||
|
||||
_(resource.resource_failed?).must_equal false
|
||||
query = resource.query("SELECT cluster_name FROM system.local")
|
||||
_(query.output).must_match(/Test Cluster/)
|
||||
end
|
||||
|
||||
it "fails when no connection established" do
|
||||
resource = quick_resource(:cassandradb_session, :linux, user: "USER", password: "wrongpassword", host: "localhost", port: 1234) do |cmd|
|
||||
cmd.strip!
|
||||
case cmd
|
||||
when "cqlsh localhost 1234 -u USER -p wrongpassword --execute 'SELECT cluster_name FROM system.local'" then
|
||||
stdout_file "test/fixtures/cmd/cassandra-connection-error"
|
||||
else
|
||||
raise cmd.inspect
|
||||
end
|
||||
ex = assert_raises(Inspec::Exceptions::ResourceFailed) { resource.query("SELECT cluster_name FROM system.local") }
|
||||
_(ex.message).must_include("Cassandra query with errors")
|
||||
end
|
||||
end
|
||||
|
||||
it "does not fails auth when no user or no password is provided" do
|
||||
resource = quick_resource(:cassandradb_session, :linux)
|
||||
_(resource.resource_failed?).must_equal false
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in a new issue