api: Complete overhaul of resources and backends

* Created general plugin system
* Add backend plugins: Specinfra and Mock
* Add resource plugins: All Vulcanosec resources
* Add integration testing suite
* Removed Serverspec as a dependency

This will be ready once the remaining resources from Serverspec are integrated.
So far command and file have been largely migrated. See unit and integration tests.
This commit is contained in:
Dominik Richter 2015-09-03 16:18:07 +02:00
commit d769659d48
56 changed files with 1406 additions and 673 deletions

4
.tests.yaml Normal file
View file

@ -0,0 +1,4 @@
images:
- ubuntu-1204-20150612
- ubuntu-1404-20150320
- ubuntu-latest

24
Rakefile Normal file
View file

@ -0,0 +1,24 @@
require 'rake/testtask'
task :default => :test
Rake::TestTask.new do |t|
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.warning = true
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
namespace :test do
task :isolated do
Dir.glob("test/**/*_test.rb").all? do |file|
sh(Gem.ruby, '-w', '-Ilib:test', file)
end or raise "Failures"
end
task :integration do
tests = Dir["test/resource/*.rb"]
return if tests.empty?
sh(Gem.ruby, 'test/docker.rb', *tests)
end
end

View file

@ -45,9 +45,9 @@ class VulcanoCLI < Thor
desc "exec PATHS", "run all test files" desc "exec PATHS", "run all test files"
option :id, type: :string, option :id, type: :string,
desc: 'Attach a profile ID to all test results' desc: 'Attach a profile ID to all test results'
option :target, type: :string, default: nil, option :target, aliases: :t, type: :string, default: nil,
desc: 'Simple targeting option using URIs, e.g. ssh://user:pass@host:port' desc: 'Simple targeting option using URIs, e.g. ssh://user:pass@host:port'
option :backend, type: :string, default: nil, option :backend, aliases: :b, type: :string, default: nil,
desc: 'Choose a backend: exec (run locally), ssh, winrm.' desc: 'Choose a backend: exec (run locally), ssh, winrm.'
option :host, type: :string, option :host, type: :string,
desc: 'Specify a remote host which is tested.' desc: 'Specify a remote host which is tested.'
@ -69,10 +69,13 @@ class VulcanoCLI < Thor
desc: 'Allow remote scans with WinRM to run on self-signed certificates.' desc: 'Allow remote scans with WinRM to run on self-signed certificates.'
option :winrm_ssl, type: :boolean, default: false, option :winrm_ssl, type: :boolean, default: false,
desc: 'Configure WinRM scans to run via SSL instead of pure HTTP.' desc: 'Configure WinRM scans to run via SSL instead of pure HTTP.'
def exec(*resources) option :format, type: :string, default: 'progress'
def exec(*tests)
runner = Vulcano::Runner.new(options[:id], options) runner = Vulcano::Runner.new(options[:id], options)
runner.add_resources(resources) runner.add_tests(tests)
runner.run runner.run
rescue RuntimeError => e
puts e.message
end end
end end

View file

@ -5,12 +5,12 @@
require 'utils/simpleconfig' require 'utils/simpleconfig'
require 'utils/find_files' require 'utils/find_files'
class ApacheConf class ApacheConf < Vulcano.resource(1)
name 'apache_conf'
def initialize( conf_path ) def initialize( conf_path )
@runner = Specinfra::Runner
@conf_path = conf_path @conf_path = conf_path
@conf_dir = File.expand_path(File.dirname @conf_path) @conf_dir = File.dirname(@conf_path)
@files_contents = {} @files_contents = {}
@content = nil @content = nil
@params = nil @params = nil
@ -31,13 +31,13 @@ class ApacheConf
end end
def filter_comments data def filter_comments data
contents = "" content = ""
data.each_line do |line| data.each_line do |line|
if (!line.match(/^\s*#/)) then if (!line.match(/^\s*#/)) then
contents << line content << line
end end
end end
return contents return content
end end
def read_content def read_content
@ -45,11 +45,13 @@ class ApacheConf
@params = {} @params = {}
# skip if the main configuration file doesn't exist # skip if the main configuration file doesn't exist
if !@runner.check_file_is_file(@conf_path) file = vulcano.file(@conf_path)
if !file.file?
return skip_resource "Can't find file \"#{@conf_path}\"" return skip_resource "Can't find file \"#{@conf_path}\""
end end
raw_conf = read_file(@conf_path)
if raw_conf.empty? && @runner.get_file_size(@conf_path).stdout.strip.to_i > 0 raw_conf = file.content
if raw_conf.empty? && file.size > 0
return skip_resource("Can't read file \"#{@conf_path}\"") return skip_resource("Can't read file \"#{@conf_path}\"")
end end
@ -100,6 +102,6 @@ class ApacheConf
end end
def read_file(path) def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout @files_contents[path] ||= vulcano.file(path).content
end end
end end

View file

@ -3,14 +3,14 @@
# license: All rights reserved # license: All rights reserved
## Advanced Auditing ## Advanced Auditing
# As soon as you start applying Advanced Audit Configuration Policy, legacy policies will be completely ignored. # As soon as you start applying Advanced Audit Configuration Policy, legacy policies will be completely ignored.
# reference: https://technet.microsoft.com/en-us/library/cc753632.aspx # reference: https://technet.microsoft.com/en-us/library/cc753632.aspx
# use: # use:
# - list all categories: Auditpol /list /subcategory:* /r # - list all categories: Auditpol /list /subcategory:* /r
# - list parameters: Auditpol /get /category:"System" /subcategory:"IPsec Driver" # - list parameters: Auditpol /get /category:"System" /subcategory:"IPsec Driver"
# - list specific parameter: Auditpol /get /subcategory:"IPsec Driver" # - list specific parameter: Auditpol /get /subcategory:"IPsec Driver"
# #
# @link: http://blogs.technet.com/b/askds/archive/2011/03/11/getting-the-effective-audit-policy-in-windows-7-and-2008-r2.aspx # @link: http://blogs.technet.com/b/askds/archive/2011/03/11/getting-the-effective-audit-policy-in-windows-7-and-2008-r2.aspx
=begin =begin
Category/Subcategory,GUID Category/Subcategory,GUID
@ -92,9 +92,8 @@ Further information is available at: https://msdn.microsoft.com/en-us/library/dd
=end =end
include Serverspec::Type class AuditPolicy < Vulcano.resource(1)
name 'audit_policy'
class AuditPolicy < Serverspec::Type::Base
def method_missing(method) def method_missing(method)
key = method.to_s key = method.to_s
@ -102,12 +101,11 @@ class AuditPolicy < Serverspec::Type::Base
# expected result: # expected result:
# Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting # Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting
# WIN-MB8NINQ388J,System,Kerberos Authentication Service,{0CCE9242-69AE-11D9-BED3-505054503030},No Auditing, # WIN-MB8NINQ388J,System,Kerberos Authentication Service,{0CCE9242-69AE-11D9-BED3-505054503030},No Auditing,
command_result ||= @runner.run_command("Auditpol /get /subcategory:'#{key}' /r") result ||= vulcano.run_command("Auditpol /get /subcategory:'#{key}' /r").stdout
result = command_result.stdout
# find line # find line
target = nil target = nil
result.each_line {|s| result.each_line {|s|
target = s.strip if s.match(/\b.*#{key}.*\b/) target = s.strip if s.match(/\b.*#{key}.*\b/)
} }
@ -115,7 +113,7 @@ class AuditPolicy < Serverspec::Type::Base
if target != nil if target != nil
# split csv values and return value # split csv values and return value
value = target.split(',')[4] value = target.split(',')[4]
else else
value = nil value = nil
end end
@ -123,13 +121,7 @@ class AuditPolicy < Serverspec::Type::Base
end end
def to_s def to_s
%Q[Windows Advanced Auditing] 'Windows Advanced Auditing'
end end
end end
module Serverspec::Type
def audit_policy()
AuditPolicy.new()
end
end

View file

@ -4,10 +4,10 @@
require 'utils/simpleconfig' require 'utils/simpleconfig'
class AuditDaemonConf < Vulcano::Resource class AuditDaemonConf < Vulcano.resource(1)
name 'audit_daemon_conf'
def initialize def initialize
@runner = Specinfra::Runner
@conf_path = '/etc/audit/auditd.conf' @conf_path = '/etc/audit/auditd.conf'
@files_contents = {} @files_contents = {}
@content = nil @content = nil
@ -26,27 +26,19 @@ class AuditDaemonConf < Vulcano::Resource
def read_content def read_content
# read the file # read the file
if !@runner.check_file_is_file(@conf_path) file = vulcano.file(@conf_path)
if !file.file?
return skip_resource "Can't find file \"#{@conf_path}\"" return skip_resource "Can't find file \"#{@conf_path}\""
end end
@content = read_file(@conf_path)
if @content.empty? && @runner.get_file_size(@conf_path).stdout.strip.to_i > 0 @content = file.content
if @content.empty? && file.size > 0
return skip_resource "Can't read file \"#{@conf_path}\"" return skip_resource "Can't read file \"#{@conf_path}\""
end end
# parse the file # parse the file
@params = SimpleConfig.new(@content, @params = SimpleConfig.new(@content,
multiple_values: false multiple_values: false
).params ).params
@content
end end
def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout
end
end end
module Serverspec::Type
def audit_daemon_conf()
AuditDaemonConf.new()
end
end

View file

@ -2,25 +2,24 @@
# copyright: 2015, Vulcano Security GmbH # copyright: 2015, Vulcano Security GmbH
# license: All rights reserved # license: All rights reserved
include Serverspec::Type class AuditDaemonRules < Vulcano.resource(1)
name 'audit_daemon_rules'
class AuditDaemonRules < Vulcano::Resource
def initialize def initialize
@runner = Specinfra::Runner @content = vulcano.run_command("/sbin/auditctl -l").stdout.chomp
@command_result ||= @runner.run_command("/sbin/auditctl -l")
@content = @command_result.stdout.chomp
@opts = { @opts = {
assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/, assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
multiple_values: true multiple_values: true
} }
@params = SimpleConfig.new(@content, @opts).params end
def params
@params ||= SimpleConfig.new(@content, @opts).params
end end
def method_missing name def method_missing name
@params[name.to_s] params[name.to_s]
end end
def status name def status name
@ -28,7 +27,7 @@ class AuditDaemonRules < Vulcano::Resource
assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/, assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
multiple_values: false multiple_values: false
} }
@status_content ||= @runner.run_command("/sbin/auditctl -s").stdout.chomp @status_content ||= vulcano.run_command("/sbin/auditctl -s").stdout.chomp
@status_params = SimpleConfig.new(@status_content, @status_opts).params @status_params = SimpleConfig.new(@status_content, @status_opts).params
status = @status_params["AUDIT_STATUS"] status = @status_params["AUDIT_STATUS"]
if (status == nil) then return nil end if (status == nil) then return nil end
@ -38,13 +37,7 @@ class AuditDaemonRules < Vulcano::Resource
end end
def to_s def to_s
%Q[AuditDaemonRules] 'Audit Daemon Rules'
end end
end end
module Serverspec::Type
def audit_daemon_rules()
AuditDaemonRules.new()
end
end

View file

@ -1,14 +1,32 @@
# encoding: utf-8
# copyright: 2015, Vulcano Security GmbH # copyright: 2015, Vulcano Security GmbH
# encoding: utf-8
# license: All rights reserved # license: All rights reserved
module Serverspec::Type class Command < Vulcano.resource(1)
class Command < Base name 'command'
# Check if a given command (executable) exists def initialize(cmd)
# in the default path @command = cmd
def exists?
cmd = @name
Command.new("type \"#{cmd}\" > /dev/null").exit_status == 0
end
end end
end
def result
@result ||= vulcano.run_command(@command)
end
def stdout
result.stdout
end
def stderr
result.stderr
end
def exit_status
result.exit_status.to_i
end
def exists?
res = vulcano.run_command("type \"#{@command}\" > /dev/null")
res.exit_status.to_i == 0
end
end

View file

@ -0,0 +1,9 @@
# encoding: utf-8
require 'resources/file'
module Vulcano::Resources
class Directory < File
name 'directory'
end
end

View file

@ -2,40 +2,31 @@
# copyright: 2015, Vulcano Security GmbH # copyright: 2015, Vulcano Security GmbH
# license: All rights reserved # license: All rights reserved
include Serverspec::Type class OsEnv < Vulcano.resource(1)
name 'os_env'
class EnvironmentVariable < Serverspec::Type::Base attr_reader :content
def initialize(field)
def method_missing(method) @command_result = vulcano.run_command("su - root -c 'echo $#{name}'")
@command_result ||= @runner.run_command("su - root -c 'echo $#{name}'") @content = @command_result.stdout.chomp
end
def content
command_result.stdout.chomp
end end
def split def split
# -1 is required to catch cases like dir1::dir2: # -1 is required to catch cases like dir1::dir2:
# where we have a trailing : # where we have a trailing :
command_result.stdout.chomp.split(':', -1) @content.split(':', -1)
end end
def stderr def stderr
command_result.stderr @command_result.stderr
end end
def exit_status def exit_status
command_result.exit_status.to_i @command_result.exit_status.to_i
end end
def to_s def to_s
%Q[Environment Variable] "Environment variable #{field}"
end end
end end
module Serverspec::Type
def os_env(name)
EnvironmentVariable.new(name)
end
end

View file

@ -8,24 +8,17 @@
# - gid # - gid
# - group list, comma seperated list # - group list, comma seperated list
include Serverspec::Type class EtcGroup < Vulcano.resource(1)
name 'etc_group'
class EtcGroup < Serverspec::Type::File attr_accessor :gid, :entries
def initialize(path = nil)
attr_accessor :gid @path = path || '/etc/group'
@entries = parse(@path)
end
def to_s def to_s
%Q[/etc/group] @path
end
def parse
content().split("\n").map do |line|
line.split(':')
end
end
def entries
@entries ||= parse
end end
def groups def groups
@ -61,10 +54,13 @@ class EtcGroup < Serverspec::Type::File
self self
end end
end private
module Serverspec::Type def parse(path)
def etc_group(path = nil) @content = vulcano.file(path).content
EtcGroup.new(path || '/etc/group') @content.split("\n").map do |line|
line.split(':')
end
end end
end
end

View file

@ -2,12 +2,68 @@
# copyright: 2015, Vulcano Security GmbH # copyright: 2015, Vulcano Security GmbH
# license: All rights reserved # license: All rights reserved
module Serverspec::Type module Vulcano::Resources
class File < Base class File < Vulcano.resource(1)
# Overwrite the to_s method to show path name 'file'
# instead of type
def to_s def initialize(path)
%Q.Path "#{@name}". @path = path
@file = vulcano.file(@path)
end end
%w{
type exists? file? block_device? character_device? socket? directory?
symlink? pipe?
mode mode? owner owned_by? group grouped_into? link_target linked_to?
content mtime size selinux_label
mounted? immutable? product_version file_version version?
md5sum sha256sum
}.each do |m|
define_method m.to_sym do |*args|
@file.method(m.to_sym).call(*args)
end
end
def contain(pattern, from, to)
raise ' not yet implemented '
end
def readable?(by_owner, by_user)
if by_user.nil?
m = unix_mode_mask(by_owner, 'r') ||
raise("#{by_owner} is not a valid unix owner.")
( @file.mask & m ) != 0
else
# TODO: REMOVE THIS FALLBACK
Specinfra::Runner.check_file_is_accessible_by_user(@path, by_user, 'r')
end
end
def writable?(by_owner, by_user)
if by_user.nil?
m = unix_mode_mask(by_owner, 'w') ||
raise("#{by_owner} is not a valid unix owner.")
( @file.mask & m ) != 0
else
# TODO: REMOVE THIS FALLBACK
Specinfra::Runner.check_file_is_accessible_by_user(@path, by_user, 'w')
end
end
def executable?(by_owner, by_user)
if by_user.nil?
m = unix_mode_mask(by_owner, 'x') ||
raise("#{by_owner} is not a valid unix owner.")
( @file.mask & m ) != 0
else
# TODO: REMOVE THIS FALLBACK
Specinfra::Runner.check_file_is_accessible_by_user(@path, by_user, 'x')
end
end
def to_s
"Path '#{@path}'"
end
end end
end end

View file

@ -4,8 +4,6 @@
require 'json' require 'json'
include Serverspec::Type
# return JSON object # return JSON object
def gpo (policy_path, policy_name) def gpo (policy_path, policy_name)
file = ::File.read(::File.join ::File.dirname(__FILE__), "gpo.json") file = ::File.read(::File.join ::File.dirname(__FILE__), "gpo.json")
@ -15,12 +13,13 @@ def gpo (policy_path, policy_name)
end end
# Group Policy # Group Policy
class GroupPolicy < Serverspec::Type::Base class GroupPolicy < Vulcano.resource(1)
name 'group_policy'
def getRegistryValue(entry) def getRegistryValue(entry)
keys = entry['registry_information'][0] keys = entry['registry_information'][0]
cmd = "(Get-Item 'Registry::#{keys['path']}').GetValue('#{keys['key']}')" cmd = "(Get-Item 'Registry::#{keys['path']}').GetValue('#{keys['key']}')"
command_result ||= @runner.run_command(cmd) command_result ||= vulcano.run_command(cmd)
val = { :exit_code => command_result.exit_status.to_i, :data => command_result.stdout } val = { :exit_code => command_result.exit_status.to_i, :data => command_result.stdout }
val val
end end
@ -52,9 +51,3 @@ class GroupPolicy < Serverspec::Type::Base
end end
end end
module Serverspec::Type
def group_policy(policy_path)
GroupPolicy.new(policy_path)
end
end

View file

@ -4,10 +4,10 @@
require 'utils/simpleconfig' require 'utils/simpleconfig'
class InetdConf < Vulcano::Resource class InetdConf < Vulcano.resource(1)
name 'inetd_config'
def initialize(path) def initialize(path)
@runner = Specinfra::Runner
@conf_path = path @conf_path = path
@files_contents = {} @files_contents = {}
@content = nil @content = nil
@ -26,11 +26,12 @@ class InetdConf < Vulcano::Resource
def read_content def read_content
# read the file # read the file
if !@runner.check_file_is_file(@conf_path) file = vulcano.file(@conf_path)
if !file.file?
return skip_resource "Can't find file \"#{@conf_path}\"" return skip_resource "Can't find file \"#{@conf_path}\""
end end
@content = read_file(@conf_path) @content = file.content
if @content.empty? && @runner.get_file_size(@conf_path).stdout.strip.to_i > 0 if @content.empty? && file.size > 0
return skip_resource "Can't read file \"#{@conf_path}\"" return skip_resource "Can't read file \"#{@conf_path}\""
end end
# parse the file # parse the file
@ -42,15 +43,4 @@ class InetdConf < Vulcano::Resource
@content @content
end end
def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout
end
end end
module Serverspec::Type
def inetd_conf(path = nil)
@inetd_conf ||= {}
dpath = path || '/etc/inetd.conf'
@inetd_conf[dpath] = InetdConf.new(dpath)
end
end

View file

@ -4,10 +4,10 @@
require 'utils/simpleconfig' require 'utils/simpleconfig'
class LimitsConf < Vulcano::Resource class LimitsConf < Vulcano.resource(1)
name 'limits_conf'
def initialize(path) def initialize(path)
@runner = Specinfra::Runner
@conf_path = path @conf_path = path
@files_contents = {} @files_contents = {}
@content = nil @content = nil
@ -26,11 +26,12 @@ class LimitsConf < Vulcano::Resource
def read_content def read_content
# read the file # read the file
if !@runner.check_file_is_file(@conf_path) file = vulcano.file(@conf_path)
if !file.file?
return skip_resource "Can't find file \"#{@conf_path}\"" return skip_resource "Can't find file \"#{@conf_path}\""
end end
@content = read_file(@conf_path) @content = file.content
if @content.empty? && @runner.get_file_size(@conf_path).stdout.strip.to_i > 0 if @content.empty? && file.size > 0
return skip_resource "Can't read file \"#{@conf_path}\"" return skip_resource "Can't read file \"#{@conf_path}\""
end end
# parse the file # parse the file
@ -42,15 +43,4 @@ class LimitsConf < Vulcano::Resource
@content @content
end end
def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout
end
end end
module Serverspec::Type
def limits_conf(path = nil)
@limits_conf ||= {}
dpath = path || '/etc/security/limits.conf'
@limits_conf[dpath] ||= LimitsConf.new(dpath)
end
end

View file

@ -4,10 +4,10 @@
require 'utils/simpleconfig' require 'utils/simpleconfig'
class LoginDef < Vulcano::Resource class LoginDef < Vulcano.resource(1)
name 'login_defs'
def initialize(path = nil) def initialize(path = nil)
@runner = Specinfra::Runner
@conf_path = path || '/etc/login.defs' @conf_path = path || '/etc/login.defs'
@files_contents = {} @files_contents = {}
@content = nil @content = nil
@ -26,11 +26,12 @@ class LoginDef < Vulcano::Resource
def read_content def read_content
# read the file # read the file
if !@runner.check_file_is_file(@conf_path) file = vulcano.file(@conf_path)
if !file.file?
return skip_resource "Can't find file \"#{@conf_path}\"" return skip_resource "Can't find file \"#{@conf_path}\""
end end
@content = read_file(@conf_path) @content = file.content
if @content.empty? && @runner.get_file_size(@conf_path).stdout.strip.to_i > 0 if @content.empty? && file.size > 0
return skip_resource "Can't read file \"#{@conf_path}\"" return skip_resource "Can't read file \"#{@conf_path}\""
end end
# parse the file # parse the file
@ -41,13 +42,4 @@ class LoginDef < Vulcano::Resource
@content @content
end end
def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout
end
end end
module Serverspec::Type
def login_def(path = nil)
LoginDef.new(path)
end
end

View file

@ -2,7 +2,9 @@
# copyright: 2015, Vulcano Security GmbH # copyright: 2015, Vulcano Security GmbH
# license: All rights reserved # license: All rights reserved
class Mysql class Mysql < Vulcano.resource(1)
name 'mysql'
attr_reader :package, :service, :conf_dir, :conf_path, :data_dir, :log_dir, :log_path, :log_group, :log_dir_group attr_reader :package, :service, :conf_dir, :conf_path, :data_dir, :log_dir, :log_path, :log_group, :log_dir_group
def initialize def initialize
# set OS-dependent filenames and paths # set OS-dependent filenames and paths
@ -55,9 +57,3 @@ class Mysql
end end
end end
end end
module Serverspec::Type
def mysql
@mysql ||= Mysql.new()
end
end

View file

@ -8,16 +8,17 @@ require 'resources/mysql'
class MysqlConfEntry class MysqlConfEntry
def initialize( path, params ) def initialize( path, params )
@runner = Specinfra::Runner
@params = params @params = params
@path = path @path = path
end end
def method_missing name, *args def method_missing name, *args
k = name.to_s k = name.to_s
res = @params[k] res = @params[k]
return true if res.nil? && @params.key?(k) return true if res.nil? && @params.key?(k)
@params[k] @params[k]
end end
def to_s def to_s
group = ' ' group = ' '
group = "[#{@path.join('][')}] " unless @path.nil? or @path.empty? group = "[#{@path.join('][')}] " unless @path.nil? or @path.empty?
@ -25,10 +26,10 @@ class MysqlConfEntry
end end
end end
class MysqlConf < Vulcano::Resource class MysqlConf < Vulcano.resource(1)
name 'mysql_conf'
def initialize( conf_path ) def initialize( conf_path )
@runner = Specinfra::Runner
@conf_path = conf_path @conf_path = conf_path
@files_contents = {} @files_contents = {}
@content = nil @content = nil
@ -59,11 +60,11 @@ class MysqlConf < Vulcano::Resource
@params = {} @params = {}
# skip if the main configuration file doesn't exist # skip if the main configuration file doesn't exist
if !@runner.check_file_is_file(@conf_path) if !vulcano.file(@conf_path).file?
return skip_resource "Can't find file \"#{@conf_path}\"" return skip_resource "Can't find file \"#{@conf_path}\""
end end
raw_conf = read_file(@conf_path) raw_conf = read_file(@conf_path)
if raw_conf.empty? && @runner.get_file_size(@conf_path).stdout.strip.to_i > 0 if raw_conf.empty? && vulcano.file(@conf_path).size > 0
return skip_resource("Can't read file \"#{@conf_path}\"") return skip_resource("Can't read file \"#{@conf_path}\"")
end end
@ -91,14 +92,6 @@ class MysqlConf < Vulcano::Resource
end end
def read_file(path) def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout @files_contents[path] ||= vulcano.file(path).content
end end
end end
module Serverspec::Type
def mysql_conf(path = nil)
@mysql_conf ||= {}
dpath = path || mysql.conf_path
@mysql_conf[dpath] ||= MysqlConf.new( dpath )
end
end

View file

@ -4,11 +4,12 @@
$__SCOPE = self $__SCOPE = self
class MysqlSession < Vulcano::Resource class MysqlSession < Vulcano.resource(1)
name 'mysql_session'
def initialize user, pass def initialize user, pass
@user = user @user = user
@pass = pass @pass = pass
@runner = Specinfra::Runner
initialize_fallback if user.nil? or pass.nil? initialize_fallback if user.nil? or pass.nil?
skip_resource("Can't run MySQL SQL checks without authentication") if @user.nil? or @pass.nil? skip_resource("Can't run MySQL SQL checks without authentication") if @user.nil? or @pass.nil?
end end
@ -18,7 +19,7 @@ class MysqlSession < Vulcano::Resource
# that does this securely # that does this securely
escaped_query = query.gsub(/\\/, '\\\\').gsub(/"/,'\\"').gsub(/\$/,'\\$') escaped_query = query.gsub(/\\/, '\\\\').gsub(/"/,'\\"').gsub(/\$/,'\\$')
# run the query # run the query
cmd = Serverspec::Type::Command.new("mysql -u#{@user} -p#{@pass} #{db} -s -e \"#{escaped_query}\"") cmd = vulcano.run_command("mysql -u#{@user} -p#{@pass} #{db} -s -e \"#{escaped_query}\"")
out = cmd.stdout + "\n" + cmd.stderr out = cmd.stdout + "\n" + cmd.stderr
if out =~ /Can't connect to .* MySQL server/ or if out =~ /Can't connect to .* MySQL server/ or
out.downcase =~ /^error/ out.downcase =~ /^error/
@ -33,7 +34,7 @@ class MysqlSession < Vulcano::Resource
def initialize_fallback def initialize_fallback
# support debian mysql administration login # support debian mysql administration login
debian = @runner.run_command("test -f /etc/mysql/debian.cnf && cat /etc/mysql/debian.cnf").stdout debian = vulcano.run_command("test -f /etc/mysql/debian.cnf && cat /etc/mysql/debian.cnf").stdout
unless debian.empty? unless debian.empty?
user = debian.match(/^\s*user\s*=\s*([^ ]*)\s*$/) user = debian.match(/^\s*user\s*=\s*([^ ]*)\s*$/)
pass = debian.match(/^\s*password\s*=\s*([^ ]*)\s*$/) pass = debian.match(/^\s*password\s*=\s*([^ ]*)\s*$/)
@ -44,9 +45,3 @@ class MysqlSession < Vulcano::Resource
end end
end end
end end
module Serverspec::Type
def mysql_session( user=nil, password=nil )
MysqlSession.new(user, password)
end
end

View file

@ -4,10 +4,10 @@
require 'utils/simpleconfig' require 'utils/simpleconfig'
class NtpConf < Vulcano::Resource class NtpConf < Vulcano.resource(1)
name 'ntp_conf'
def initialize(path = nil) def initialize(path = nil)
@runner = Specinfra::Runner
@conf_path = path || '/etc/ntp.conf' @conf_path = path || '/etc/ntp.conf'
@files_contents = {} @files_contents = {}
@content = nil @content = nil
@ -26,11 +26,11 @@ class NtpConf < Vulcano::Resource
def read_content def read_content
# read the file # read the file
if !@runner.check_file_is_file(@conf_path) if !vulcano.file(@conf_path).file?
return skip_resource "Can't find file \"#{@conf_path}\"" return skip_resource "Can't find file \"#{@conf_path}\""
end end
@content = read_file(@conf_path) @content = vulcano.file(@conf_path).content
if @content.empty? && @runner.get_file_size(@conf_path).stdout.strip.to_i > 0 if @content.empty? && vulcano.file(@conf_path).size > 0
return skip_resource "Can't read file \"#{@conf_path}\"" return skip_resource "Can't read file \"#{@conf_path}\""
end end
# parse the file # parse the file
@ -41,13 +41,4 @@ class NtpConf < Vulcano::Resource
@content @content
end end
def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout
end
end end
module Serverspec::Type
def ntp_conf(path = nil)
NtpConf.new(path)
end
end

View file

@ -11,13 +11,13 @@
# } # }
# describe parse_config(audit, options ) do # describe parse_config(audit, options ) do
class PConfig < Vulcano::Resource class PConfig < Vulcano.resource(1)
name 'parse_config'
def initialize ( content=nil, useropts = {} ) def initialize ( content=nil, useropts = {} )
default_options = {} default_options = {}
@opts = default_options.merge(useropts) @opts = default_options.merge(useropts)
@runner = Specinfra::Runner
@content = content @content = content
@files_contents = {} @files_contents = {}
@params = nil @params = nil
@ -40,11 +40,11 @@ class PConfig < Vulcano::Resource
@conf_path = conf_path @conf_path = conf_path
# read the file # read the file
if !@runner.check_file_is_file(conf_path) if !vulcano.file(conf_path).file?
return skip_resource "Can't find file \"#{conf_path}\"" return skip_resource "Can't find file \"#{conf_path}\""
end end
@content = read_file(conf_path) @content = read_file(conf_path)
if @content.empty? && @runner.get_file_size(conf_path).stdout.strip.to_i > 0 if @content.empty? && vulcano.file(conf_path).size > 0
return skip_resource "Can't read file \"#{conf_path}\"" return skip_resource "Can't read file \"#{conf_path}\""
end end
@ -52,7 +52,7 @@ class PConfig < Vulcano::Resource
end end
def read_file(path) def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout @files_contents[path] ||= vulcano.file(path).content
end end
def read_content def read_content
@ -62,14 +62,11 @@ class PConfig < Vulcano::Resource
end end
end end
module Serverspec::Type class PConfigFile < PConfig
def parse_config(content, opts={}) name 'parse_config_file'
PConfig.new(content, opts)
end
def parse_config_file(file, opts={}) def initialize(path, opts)
p = PConfig.new(nil, opts) super(nil, opts)
p.parse_file(file) parse_file(path)
p
end end
end end

View file

@ -11,20 +11,24 @@
# - home directory # - home directory
# - command # - command
include Serverspec::Type class Passwd < Vulcano.resource(1)
name 'passwd'
class Passwd < Serverspec::Type::File
attr_accessor :uid attr_accessor :uid
def initialize(path = nil, uid: nil)
@path = path || '/etc/passwd'
@content = vulcano.file(@path).content
@parsed = parse(@content)
end
def to_s def to_s
%Q[/etc/passwd] @path
end end
def determine_uid () def determine_uid ()
parsed = parse()
uids = Array.new uids = Array.new
parsed.each {|x| @parsed.each {|x|
if ( x.at(2) == "#{@uid}") then if ( x.at(2) == "#{@uid}") then
uids.push(x.at(0)) uids.push(x.at(0))
end end
@ -43,8 +47,7 @@ class Passwd < Serverspec::Type::File
end end
def map_data (id) def map_data (id)
parsed = parse() @parsed.map {|x|
parsed.map {|x|
x.at(id) x.at(id)
} }
end end
@ -66,8 +69,7 @@ class Passwd < Serverspec::Type::File
end end
def users def users
parsed = parse() @parsed.map {|x|
parsed.map {|x|
{ {
"name" => x.at(0), "name" => x.at(0),
"password" => x.at(1), "password" => x.at(1),
@ -80,20 +82,12 @@ class Passwd < Serverspec::Type::File
} }
end end
def parse private
entries = Array.new
content().split("\n").each do |line| def parse(content)
entries.push(line.split(':')) content.split("\n").map do |line|
line.split(':')
end end
entries
end end
end end
module Serverspec::Type
def passwd(uid=nil)
i = Passwd.new('/etc/passwd')
i.uid = uid
i
end
end

View file

@ -2,14 +2,16 @@
# copyright: 2015, Vulcano Security GmbH # copyright: 2015, Vulcano Security GmbH
# license: All rights reserved # license: All rights reserved
class Postgres class Postgres < Vulcano.resource(1)
name 'postgres'
attr_reader :service, :data_dir, :conf_dir, :conf_path attr_reader :service, :data_dir, :conf_dir, :conf_path
def initialize def initialize
case os[:family] case os[:family]
when 'ubuntu', 'debian' when 'ubuntu', 'debian'
@service = 'postgresql' @service = 'postgresql'
@data_dir = '/var/lib/postgresql' @data_dir = '/var/lib/postgresql'
@version = command('ls /etc/postgresql/').stdout.chomp @version = vulcano.run_command('ls /etc/postgresql/').stdout.chomp
@conf_dir = "/etc/postgresql/#{@version}/main" @conf_dir = "/etc/postgresql/#{@version}/main"
@conf_path = File.join @conf_dir, 'postgresql.conf' @conf_path = File.join @conf_dir, 'postgresql.conf'
@ -27,9 +29,3 @@ class Postgres
end end
end end
end end
module Serverspec::Type
def postgres
@postgres ||= Postgres.new()
end
end

View file

@ -6,10 +6,10 @@ require 'utils/simpleconfig'
require 'utils/find_files' require 'utils/find_files'
require 'resources/postgres' require 'resources/postgres'
class PostgresConf class PostgresConf < Vulcano.resource(1)
name 'postgres_conf'
def initialize( conf_path ) def initialize( conf_path )
@runner = Specinfra::Runner
@conf_path = conf_path @conf_path = conf_path
@conf_dir = File.expand_path(File.dirname @conf_path) @conf_dir = File.expand_path(File.dirname @conf_path)
@files_contents = {} @files_contents = {}
@ -36,11 +36,11 @@ class PostgresConf
@params = {} @params = {}
# skip if the main configuration file doesn't exist # skip if the main configuration file doesn't exist
if !@runner.check_file_is_file(@conf_path) if !vulcano.file(@conf_path).file?
return skip_resource "Can't find file \"#{@conf_path}\"" return skip_resource "Can't find file \"#{@conf_path}\""
end end
raw_conf = read_file(@conf_path) raw_conf = read_file(@conf_path)
if raw_conf.empty? && @runner.get_file_size(@conf_path).stdout.strip.to_i > 0 if raw_conf.empty? && vulcano.file(@conf_path).size > 0
return skip_resource("Can't read file \"#{@conf_path}\"") return skip_resource("Can't read file \"#{@conf_path}\"")
end end
@ -70,15 +70,6 @@ class PostgresConf
end end
def read_file(path) def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout @files_contents[path] ||= vulcano.file(path).content
end end
end end
module Serverspec::Type
def postgres_conf(path = nil)
@postgres_conf ||= {}
dpath = path || postgres.conf_path
@postgres_conf[dpath] ||= PostgresConf.new( dpath )
end
end

View file

@ -2,25 +2,22 @@
# copyright: 2015, Vulcano Security GmbH # copyright: 2015, Vulcano Security GmbH
# license: All rights reserved # license: All rights reserved
module Serverspec end class Lines
module Serverspec::Type def initialize raw, desc
class Lines @raw = raw
def initialize raw, desc @desc = desc
@raw = raw end
@desc = desc
end
def output def output
@raw @raw
end end
def lines def lines
@raw.split("\n") @raw.split("\n")
end end
def to_s def to_s
@desc @desc
end
end end
end end
@ -36,7 +33,7 @@ class PostgresSession
# that does this securely # that does this securely
escaped_query = query.gsub(/\\/, '\\\\').gsub(/"/,'\\"').gsub(/\$/,'\\$') escaped_query = query.gsub(/\\/, '\\\\').gsub(/"/,'\\"').gsub(/\$/,'\\$')
# run the query # run the query
cmd = Serverspec::Type::Command.new("PGPASSWORD='#{@pass}' psql -U #{@user} #{dbs} -c \"#{escaped_query}\"") cmd = vulcano.run_command("PGPASSWORD='#{@pass}' psql -U #{@user} #{dbs} -c \"#{escaped_query}\"")
out = cmd.stdout + "\n" + cmd.stderr out = cmd.stdout + "\n" + cmd.stderr
if out =~ /could not connect to .*/ or if out =~ /could not connect to .*/ or
out.downcase =~ /^error/ out.downcase =~ /^error/
@ -51,15 +48,9 @@ class PostgresSession
sub(/(.*\n)+([-]+[+])*[-]+\n/,''). sub(/(.*\n)+([-]+[+])*[-]+\n/,'').
# remove the tail # remove the tail
sub(/\n[^\n]*\n\n$/,'') sub(/\n[^\n]*\n\n$/,'')
l = Serverspec::Type::Lines.new(lines.strip, "PostgreSQL query: #{query}") l = Lines.new(lines.strip, "PostgreSQL query: #{query}")
RSpec.__send__( 'describe', l, &block ) RSpec.__send__( 'describe', l, &block )
end end
end end
end end
module Serverspec::Type
def postgres_session( user, password )
PostgresSession.new(user, password)
end
end

View file

@ -2,17 +2,19 @@
# copyright: 2015, Vulcano Security GmbH # copyright: 2015, Vulcano Security GmbH
# license: All rights reserved # license: All rights reserved
include Serverspec::Type class Processes < Vulcano.resource(1)
name 'processes'
class Processes < Serverspec::Type::Base attr_reader :list
def initialize grep def initialize(grep)
# turn into a regexp if it isn't one yet # turn into a regexp if it isn't one yet
if grep.class == String if grep.class == String
grep = '(/[^/]*)*'+grep if grep[0] != '/' grep = '(/[^/]*)*'+grep if grep[0] != '/'
grep = Regexp.new('^'+grep+'(\s|$)') grep = Regexp.new('^'+grep+'(\s|$)')
end end
# get all running processes # get all running processes
cmd = Serverspec::Type::Command.new('ps aux') cmd = vulcano.run_command('ps aux')
all = cmd.stdout.split("\n")[1..-1] all = cmd.stdout.split("\n")[1..-1]
all_cmds = all.map do |line| all_cmds = all.map do |line|
# user 32296 0.0 0.0 42592 7972 pts/15 Ss+ Apr06 0:00 zsh # user 32296 0.0 0.0 42592 7972 pts/15 Ss+ Apr06 0:00 zsh
@ -37,10 +39,5 @@ class Processes < Serverspec::Type::Base
hm[:command] =~ grep hm[:command] =~ grep
end end
end end
end
module Serverspec::Type end
def processes( grep )
Processes.new(grep)
end
end

View file

@ -2,14 +2,20 @@
# copyright: 2015, Vulcano Security GmbH # copyright: 2015, Vulcano Security GmbH
# license: All rights reserved # license: All rights reserved
include Serverspec::Type
require 'json' require 'json'
# Registry Key Helper class RegistryKey < Vulcano.resource(1)
class RegistryKey < Serverspec::Type::Base name 'registry_key'
attr_accessor :reg_key attr_accessor :reg_key
def initialize(name, reg_key = nil)
# if we have one parameter, we use it as name
reg_key = name if reg_key == nil
@name = name
@reg_key = reg_key
end
def getRegistryValue(path, key) def getRegistryValue(path, key)
cmd = "(Get-Item 'Registry::#{path}').GetValue('#{key}')" cmd = "(Get-Item 'Registry::#{path}').GetValue('#{key}')"
command_result ||= @runner.run_command(cmd) command_result ||= @runner.run_command(cmd)
@ -42,17 +48,3 @@ class RegistryKey < Serverspec::Type::Base
end end
end end
module Serverspec::Type
def registry_key(name, reg_key=nil)
# if we have one parameter, we use it as name
if reg_key == nil
reg_key = name
end
# initialize variable
i = RegistryKey.new(name)
i.reg_key = reg_key
i
end
end

View file

@ -1,40 +0,0 @@
# encoding: utf-8
# copyright: 2015, Vulcano Security GmbH
# license: All rights reserved
require 'resources/apache_conf'
require 'resources/audit_policy'
require 'resources/auditd_conf'
require 'resources/auditd_rules'
require 'resources/command'
require 'resources/env'
require 'resources/etc_group'
require 'resources/file'
require 'resources/group_policy'
require 'resources/inetd_conf'
require 'resources/limits_conf'
require 'resources/login_def'
require 'resources/mysql'
require 'resources/mysql_conf'
require 'resources/mysql_session'
require 'resources/ntp_conf'
require 'resources/parse_config'
require 'resources/passwd'
require 'resources/postgres'
require 'resources/postgres_conf'
require 'resources/postgres_session'
require 'resources/processes'
require 'resources/registry_key'
require 'resources/security_policy'
require 'resources/ssh_conf'
# extend serverspec types
module Serverspec
module Type
def directory(name)
Directory.new(name)
end
end
end

View file

@ -4,14 +4,13 @@
# secedit /export /cfg secpol.cfg # secedit /export /cfg secpol.cfg
# #
# @link http://www.microsoft.com/en-us/download/details.aspx?id=25250 # @link http://www.microsoft.com/en-us/download/details.aspx?id=25250
# #
# In Windows, some security options are managed differently that the local GPO # In Windows, some security options are managed differently that the local GPO
# All local GPO parameters can be examined via Registry, but not all security # All local GPO parameters can be examined via Registry, but not all security
# parameters. Therefore we need a combination of Registry and secedit output # parameters. Therefore we need a combination of Registry and secedit output
include Serverspec::Type class SecurityPolicy < Vulcano.resource(1)
name 'security_policy'
class SecurityPolicy < Serverspec::Type::Base
# static variable, shared across all instances # static variable, shared across all instances
@@loaded = false @@loaded = false
@ -46,7 +45,7 @@ class SecurityPolicy < Serverspec::Type::Base
# find line with key # find line with key
key = method.to_s key = method.to_s
target = "" target = ""
@@policy.each_line {|s| @@policy.each_line {|s|
target = s.strip if s.match(/\b#{key}\s*=\s*(.*)\b/) target = s.strip if s.match(/\b#{key}\s*=\s*(.*)\b/)
} }
@ -57,9 +56,9 @@ class SecurityPolicy < Serverspec::Type::Base
val = result[:value] val = result[:value]
val = val.to_i if val.match(/^\d+$/) val = val.to_i if val.match(/^\d+$/)
else else
# TODO we may need to return skip or failure if the # TODO we may need to return skip or failure if the
# requested value is not available # requested value is not available
val = nil val = nil
end end
val val
@ -70,9 +69,3 @@ class SecurityPolicy < Serverspec::Type::Base
end end
end end
module Serverspec::Type
def security_policy()
SecurityPolicy.new()
end
end

View file

@ -4,16 +4,12 @@
require 'utils/simpleconfig' require 'utils/simpleconfig'
class SshConf < Vulcano::Resource class SshConf < Vulcano.resource(1)
name 'ssh_config'
def initialize( conf_path, type = nil ) def initialize( conf_path = nil, type = nil )
@runner = Specinfra::Runner @conf_path = conf_path || '/etc/ssh/ssh_config'
@conf_path = conf_path typename = ( @conf_path.include?('sshd') ? 'Server' : 'Client' )
@conf_dir = File.expand_path(File.dirname @conf_path)
@files_contents = {}
@content = nil
@params = nil
typename = ( conf_path.include?('sshd') ? 'Server' : 'Client' )
@type = type || "SSH #{typename} configuration #{conf_path}" @type = type || "SSH #{typename} configuration #{conf_path}"
read_content read_content
end end
@ -23,11 +19,10 @@ class SshConf < Vulcano::Resource
end end
def content def content
@content ||= read_content @conf.content
end end
def params *opts def params *opts
@params || read_content
res = @params res = @params
opts.each do |opt| opts.each do |opt|
res = res[opt] unless res.nil? res = res[opt] unless res.nil?
@ -36,40 +31,35 @@ class SshConf < Vulcano::Resource
end end
def method_missing name def method_missing name
@params || read_content
@params[name.to_s] @params[name.to_s]
end end
private
def read_content def read_content
@conf = vulcano.file(@conf_path)
# read the file # read the file
if !@runner.check_file_is_file(@conf_path) if !@conf.file?
return skip_resource "Can't find file \"#{@conf_path}\"" return skip_resource "Can't find file \"#{@conf_path}\""
end end
@content = read_file(@conf_path)
if @content.empty? && @runner.get_file_size(@conf_path).stdout.strip.to_i > 0 if @conf.content.empty? && @conf.size > 0
return skip_resource "Can't read file \"#{@conf_path}\"" return skip_resource "Can't read file \"#{@conf_path}\""
end end
# parse the file # parse the file
@params = SimpleConfig.new(@content, @params = SimpleConfig.new(@conf.content,
assignment_re: /^\s*(\S+?)\s+(.*?)\s*$/, assignment_re: /^\s*(\S+?)\s+(.*?)\s*$/,
multiple_values: false multiple_values: false
).params ).params
@content
end end
def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout
end
end end
module Serverspec::Type class SshdConf < SshConf
def ssh_config( path = nil ) name 'sshd_config'
@ssh_config ||= {}
dpath = path || '/etc/ssh/ssh_config'
@ssh_config[dpath] ||= SshConf.new(dpath)
end
def sshd_config( path = nil ) def initialize(path = nil)
ssh_config( path || '/etc/ssh/sshd_config' ) super(path || '/etc/ssh/sshd_config')
end end
end end

View file

@ -45,7 +45,7 @@ if os[:family] == 'windows'
release = versions[version] release = versions[version]
end end
# hijack os-detection from serverspec # print OS detection infos
puts JSON.dump({ puts JSON.dump({
os_family: os[:family], os_family: os[:family],
os_release: release || os[:release], os_release: release || os[:release],

View file

@ -1,19 +1,8 @@
# Copyright 2014 Dominik Richter. All rights reserved. # Copyright 2014 Dominik Richter. All rights reserved.
# Spec file for Vulcano specs # Spec file for Vulcano specs
module Serverspec
end
# Get types # Get types
module DummyServerspecTypes module DummyTestTypes
sgem = Gem::Specification.find_by_name("serverspec")
types = Dir[File.join sgem.gem_dir, 'lib', 'serverspec', 'type', '*'].
map{|x| File.basename(x).sub(/\.rb$/,'')}
types.each do |name|
define_method name do |*arg|
end
end
# a few commands with special handling # a few commands with special handling
def describe *args; end def describe *args; end
def context *args; end def context *args; end

View file

@ -9,12 +9,12 @@ libdir = File.dirname(__FILE__)
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
require 'vulcano/version' require 'vulcano/version'
require 'vulcano/backend'
require 'vulcano/resource' require 'vulcano/resource'
require 'vulcano/rspec_json_formatter' require 'vulcano/rspec_json_formatter'
require 'vulcano/rule' require 'vulcano/rule'
require 'vulcano/runner' require 'vulcano/runner'
require 'resources/resources'
require 'matchers/matchers' require 'matchers/matchers'
# Dummy module for handling additional attributes # Dummy module for handling additional attributes

View file

@ -1,6 +1,41 @@
# encoding: utf-8 # encoding: utf-8
require 'vulcano/backend/core' require 'uri'
require 'vulcano/backend/docker' require 'vulcano/plugins'
require 'vulcano/backend/exec'
require 'vulcano/backend/ssh' module Vulcano
require 'vulcano/backend/winrm' class Backend
# Expose all registered backends
def self.registry
@registry ||= {}
end
# Resolve target configuration in URI-scheme into
# all respective fields and merge with existing configuration.
# e.g. ssh://bob@remote => backend: ssh, user: bob, host: remote
def self.target_config( config )
conf = config.dup
return conf if conf['target'].to_s.empty?
uri = URI::parse(conf['target'].to_s)
conf['backend'] = conf['backend'] || uri.scheme
conf['host'] = conf['host'] || uri.host
conf['port'] = conf['port'] || uri.port
conf['user'] = conf['user'] || uri.user
conf['password'] = conf['password'] || uri.password
# return the updated config
conf
end
end
def self.backend(version = 1)
if version != 1
raise "Only backend version 1 is supported!"
end
Vulcano::Plugins::Backend
end
end
require 'vulcano/backend/mock'
require 'vulcano/backend/specinfra'

View file

@ -1,42 +0,0 @@
# encoding: utf-8
require 'utils/modulator'
module Vulcano
class Backend
extend Modulator
def initialize(conf)
@conf = conf
end
def resolve_target_options
return if @conf[:target].to_s.empty?
uri = URI::parse(@conf[:target].to_s)
@conf[:backend] = @conf[:backend] || uri.scheme
@conf[:host] = @conf[:host] || uri.host
@conf[:port] = @conf[:port] || uri.port
@conf[:user] = @conf[:user] || uri.user
@conf[:password] = @conf[:password] || uri.password
end
def configure_shared_options
Specinfra::Backend::Cmd.send(:include, Specinfra::Helper::Set)
si = Specinfra.configuration
si.os = nil
if @conf['disable_sudo']
si.disable_sudo = true
else
si.sudo_password = @conf['sudo_password']
si.sudo_options = @conf['sudo_options']
end
end
def configure_target
t = @conf[:backend] || 'exec'
m = Vulcano::Backend.modules[t]
raise "Don't understand backend '#{t}'" if m.nil?
m.configure(@conf)
end
end
end

View file

@ -1,13 +0,0 @@
# encoding: utf-8
module Vulcano::Backend::Docker
def self.configure(conf)
host = conf['host'].to_s
Specinfra.configuration.backend = :docker
Specinfra.configuration.docker_container = host
end
end
Vulcano::Backend.add_module('docker', Vulcano::Backend::Docker)

View file

@ -1,11 +0,0 @@
# encoding: utf-8
module Vulcano::Backend::Exec
def self.configure(conf)
Specinfra.configuration.backend = :exec
end
end
Vulcano::Backend.add_module('exec', Vulcano::Backend::Exec)

View file

@ -0,0 +1,75 @@
# encoding: utf-8
module Vulcano::Backends
class Mock < Vulcano.backend(1)
name 'mock'
def initialize( conf )
@conf = conf
@files = {}
end
def file(path)
puts "--> get file #{path}"
@files[path] ||= File.new(self, path)
end
def run_command(cmd)
Command.new(self, cmd)
end
def to_s
'Mock Backend Runner'
end
end
class Mock
class File
def initialize(runtime, path)
@path = path
@exists = (rand < 0.8) ? true : false
@is_file = (@exists && rand < 0.7) ? true : false
@size = 0
@content = ''
if @exists && @is_file
@size = ( rand ** 3 * 1000 ).to_i
@size = 0 if rand < 0.2
end
if @size > 0
@content = (0...50).map { ('a'..'z').to_a[rand(26)] }.join
end
end
def size
puts "----> get file #{@path} size: #{@size}"
@size
end
def content
puts "----> get file #{@path} content: #{@content}"
@content
end
def file?
puts "----> is file #{@path} a file? #{@is_file}"
@is_file
end
def exists?
puts "----> does file #{@path} exist? #{@exists}"
@exists
end
end
class Command
attr_reader :stdout, :stderr, :exit_status
def initialize(runtime, cmd)
@exit_code = (rand < 0.7) ? 0 : (100 * rand).to_i
@stdout = (0...50).map { ('a'..'z').to_a[rand(26)] }.join
@stderr = (0...50).map { ('a'..'z').to_a[rand(26)] }.join
end
end
end
end

View file

@ -0,0 +1,250 @@
# encoding: utf-8
require 'shellwords'
module Vulcano::Backends
class SpecinfraHelper < Vulcano.backend(1)
name 'specinfra'
def initialize(conf)
@conf = conf
@files = {}
type = @conf['backend'].to_s
reset_backend(type)
configure_shared_options
# configure the given backend, if we can handle it
# e.g. backend = exec ==> try to call configure_exec
# if we don't support it, error out
m = "configure_#{type}"
if self.respond_to?(m.to_sym)
self.send(m)
else
raise "Cannot configure Specinfra backend #{type}: it isn't supported yet."
end
end
def file(path)
@files[path] ||= File.new(path)
end
def run_command(cmd)
Specinfra::Runner.run_command(cmd)
end
def to_s
'SpecInfra Backend Runner'
end
def reset_backend(type)
# may be less nice, but avoid eval...
case type
when 'exec'
Specinfra::Backend::Exec.instance_variable_set(:@instance, nil)
when 'docker'
Specinfra::Backend::Docker.instance_variable_set(:@instance, nil)
when 'ssh'
Specinfra::Backend::Ssh.instance_variable_set(:@instance, nil)
when 'winrm'
Specinfra::Backend::Winrm.instance_variable_set(:@instance, nil)
end
end
def configure_shared_options
Specinfra::Backend::Cmd.send(:include, Specinfra::Helper::Set)
si = Specinfra.configuration
si.os = nil
if @conf['disable_sudo']
si.disable_sudo = true
else
si.sudo_password = @conf['sudo_password']
si.sudo_options = @conf['sudo_options']
end
end
def configure_docker
host = @conf['host'].to_s
Specinfra.configuration.backend = :docker
Specinfra.configuration.docker_container = host
end
def configure_exec
Specinfra.configuration.backend = :exec
end
def configure_ssh
si = Specinfra.configuration
si.backend = :ssh
si.request_pty = true
host = @conf['host'].to_s
RSpec.configuration.host = host
ssh_opts = {
port: @conf['port'] || 22,
auth_methods: ['none'],
user_known_hosts_file: "/dev/null",
global_known_hosts_file: "/dev/null",
number_of_password_prompts: 0,
user: @conf['user'],
password: @conf['password'],
keys: [@conf['key_file']].compact,
}
if host.empty?
raise "You must configure a target host."
end
unless ssh_opts[:port] > 0
raise "Port must be > 0 (not #{ssh_opts[:port]})"
end
if ssh_opts[:user].to_s.empty?
raise "User must not be empty."
end
unless ssh_opts[:keys].empty?
ssh_opts[:auth_methods].push('publickey')
ssh_opts[:keys_only] = true if ssh_opts[:password].nil?
end
unless ssh_opts[:password].nil?
ssh_opts[:auth_methods].push('password')
end
if ssh_opts[:keys].empty? and ssh_opts[:password].nil?
raise "You must configure at least one authentication method" +
": Password or key."
end
si.ssh_options = ssh_opts
end
def configure_winrm
si = Specinfra.configuration
si.backend = :winrm
si.os = { family: 'windows'}
# common options
host = conf['host'].to_s
port = conf['port']
user = conf['user'].to_s
pass = conf['password'].to_s
# SSL configuration
if conf['winrm_ssl']
scheme = 'https'
port = port || 5986
else
scheme = 'http'
port = port || 5985
end
# validation
if host.empty?
raise "You must configure a target host."
end
unless port > 0
raise "Port must be > 0 (not #{port})"
end
if user.empty?
raise "You must configure a WinRM user for login."
end
if pass.empty?
raise "You must configure a WinRM password."
end
# create the connection
endpoint = "#{scheme}://#{host}:#{port}/wsman"
winrm = ::WinRM::WinRMWebService.new(
endpoint,
:ssl,
user: user,
pass: pass,
basic_auth_only: true,
no_ssl_peer_verification: conf['winrm_self_signed'],
)
si.winrm = winrm
end
end
class SpecinfraHelper
class File < FileCommon
TYPES = {
socket: 00140000,
symlink: 00120000,
file: 00100000,
block_device: 00060000,
directory: 00040000,
character_device: 00020000,
pipe: 00010000,
}
def initialize(path)
@path = path
end
def type
path = Shellwords.escape(@path)
raw_type = Specinfra::Runner.run_command("stat -c %f #{path}").stdout
tmask = raw_type.to_i(16)
res = TYPES.find{|x, mask| mask & tmask == mask}
return :unknown if res.nil?
res[0]
end
def exists?
Specinfra::Runner.check_file_exists(@path)
end
def mode
Specinfra::Runner.get_file_mode(@path).stdout.to_i(8)
end
def owner
Specinfra::Runner.get_file_owner_user(@path).stdout.strip
end
def group
Specinfra::Runner.get_file_owner_group(@path).stdout.strip
end
def link_target
path = Shellwords.escape(@path)
Specinfra::Runner.run_command("readlink #{path}").stdout.strip
end
def content
Specinfra::Runner.get_file_content(@path).stdout
end
def mtime
Specinfra::Runner.get_file_mtime(@path).stdout.strip
end
def size
Specinfra::Runner.get_file_size(@path).stdout.strip.to_i
end
def selinux_label
Specinfra::Runner.get_file_selinuxlabel(@path).stdout.strip
end
def mounted?(opts = {}, only_with = nil)
Specinfra::Runner.check_file_is_mounted(@path, opts, only_with)
end
def immutable?
Specinfra::Runner.get_file_immutable(@path)
end
def product_version
Specinfra::Runner.run_command("(Get-Command '#{@path}').FileVersionInfo.ProductVersion").stdout.strip
end
def file_version
Specinfra::Runner.run_command("(Get-Command '#{@path}').FileVersionInfo.FileVersion").stdout.strip
end
end
end
end

View file

@ -1,51 +0,0 @@
# encoding: utf-8
module Vulcano::Backend::SSH
def self.configure(conf)
si = Specinfra.configuration
si.backend = :ssh
si.request_pty = true
host = conf['host'].to_s
RSpec.configuration.host = host
ssh_opts = {
port: conf['port'] || 22,
auth_methods: ['none'],
user_known_hosts_file: "/dev/null",
global_known_hosts_file: "/dev/null",
number_of_password_prompts: 0,
user: conf['user'],
password: conf['password'],
keys: [conf['key_file']].compact,
}
if host.empty?
raise "You must configure a target host."
end
unless ssh_opts[:port] > 0
raise "Port must be > 0 (not #{ssh_opts[:port]})"
end
if ssh_opts[:user].to_s.empty?
raise "User must not be empty."
end
unless ssh_opts[:keys].empty?
ssh_opts[:auth_methods].push('publickey')
ssh_opts[:keys_only] = true if ssh_opts[:password].nil?
end
unless ssh_opts[:password].nil?
ssh_opts[:auth_methods].push('password')
end
if ssh_opts[:keys].empty? and ssh_opts[:password].nil?
raise "You must configure at least one authentication method" +
": Password or key."
end
si.ssh_options = ssh_opts
end
end
Vulcano::Backend.add_module('ssh', Vulcano::Backend::SSH)

View file

@ -1,55 +0,0 @@
# encoding: utf-8
require 'winrm'
module Vulcano::Backend::WinRM
def self.configure(conf)
si = Specinfra.configuration
si.backend = :winrm
si.os = { family: 'windows'}
# common options
host = conf['host'].to_s
port = conf['port']
user = conf['user'].to_s
pass = conf['password'].to_s
# SSL configuration
if conf['winrm_ssl']
scheme = 'https'
port = port || 5986
else
scheme = 'http'
port = port || 5985
end
# validation
if host.empty?
raise "You must configure a target host."
end
unless port > 0
raise "Port must be > 0 (not #{port})"
end
if user.empty?
raise "You must configure a WinRM user for login."
end
if pass.empty?
raise "You must configure a WinRM password."
end
# create the connection
endpoint = "#{scheme}://#{host}:#{port}/wsman"
winrm = ::WinRM::WinRMWebService.new(
endpoint,
:ssl,
user: user,
pass: pass,
basic_auth_only: true,
no_ssl_peer_verification: conf['winrm_self_signed'],
)
si.winrm = winrm
end
end
Vulcano::Backend.add_module('winrm', Vulcano::Backend::WinRM)

8
lib/vulcano/plugins.rb Normal file
View file

@ -0,0 +1,8 @@
# encoding: utf-8
module Vulcano
module Plugins
autoload :Resource, 'vulcano/plugins/resource'
autoload :Backend, 'vulcano/plugins/backend'
end
end

View file

@ -0,0 +1,130 @@
# encoding: utf-8
require 'digest'
module Vulcano::Plugins
class Backend
def self.name( name )
Vulcano::Plugins::Backend.__register(name, self)
end
def self.__register(id, obj)
# raise errors for all missing methods
%w{ file run_command os }.each do |m|
next if obj.public_method_defined?(m.to_sym)
obj.send(:define_method, m.to_sym) do |*args|
raise NotImplementedError.new("Backend must implement the #{m}() method.")
end
end
Vulcano::Backend.registry[id] = obj
end
class FileCommon
# interface methods: these fields should be implemented by every
# backend File
%w{
exists? mode owner group link_target content mtime size
selinux_label product_version file_version
}
def type
:unknown
end
# The following methods can be overwritten by a derived class
# if desired, to e.g. achieve optimizations.
def md5sum
res = Digest::MD5.new
res.update(content)
res.hexdigest
end
def sha256sum
res = Digest::SHA256.new
res.update(content)
res.hexdigest
end
# Additional methods for convenience
def file?
type == :file
end
def block_device?
type == :block_device
end
def character_device?
type == :character_device
end
def socket?
type == :socket
end
def directory?
type == :directory
end
def symlink?
type == :symlink
end
def pipe?
type == :pipe?
end
def mode?(mode)
mode == mode
end
def owned_by?(owner)
owner == owner
end
def grouped_into?(group)
group == group
end
def linked_to?(dst)
link_target == dst
end
def version?(version)
product_version == version or
file_version == version
end
# helper methods provided to any implementing class
private
UNIX_MODE_OWNERS = {
owner: 00700,
group: 00070,
other: 00007,
}
UNIX_MODE_TYPES = {
r: 00444,
w: 00222,
x: 00111,
}
def unix_mode_mask(owner, type)
o = UNIX_MODE_OWNERS[owner.to_sym]
return nil if o.nil?
t = UNIX_MODE_TYPES[type.to_sym]
return nil if t.nil?
t & o
end
end
end
end

View file

@ -0,0 +1,39 @@
# encoding: utf-8
module Vulcano
module Plugins
class Resource
def self.name( name )
Vulcano::Plugins::Resource.__register(name, self)
end
def self.__register(name, obj)
cl = Class.new(obj) do
# add some common methods
include Vulcano::Plugins::ResourceCommon
def initialize(backend, *args)
# attach the backend to this instance
self.class.send(:define_method, :vulcano){backend}
# call the resource initializer
super(*args)
end
end
# add the resource to the registry by name
Vulcano::Resource.registry[name] = cl
end
end
module ResourceCommon
def resource_skipped
@resource_skipped
end
def skip_resource message
@resource_skipped = message
end
end
end
end

View file

@ -0,0 +1,72 @@
require 'vulcano/backend'
module Vulcano
class ProfileContext
attr_reader :rules, :only_ifs
def initialize(profile_id, backend, profile_registry: {}, only_ifs: [])
if backend.nil?
raise "ProfileContext is initiated with a backend == nil. " +
"This is a backend error which must be fixed upstream."
end
@profile_id = profile_id
@rules = profile_registry
@only_ifs = only_ifs
__CTX = self
# This is the heart of the profile context
# An instantiated object which has all resources registered to it
# and exposes them to the a test file.
ctx = Class.new do
include Vulcano::DSL
define_method :__register_rule do |*args|
__CTX.register_rule(*args)
end
define_method :__unregister_rule do |*args|
__CTX.unregister_rule(*args)
end
Vulcano::Resource.registry.each do |id,r|
define_method id.to_sym do |*args|
r.new(backend, *args)
end
end
def to_s
'Profile Context Run'
end
end
@profile_context = ctx.new
end
def load(content, source, line)
@profile_context.instance_eval(content, source, line)
end
def unregister_rule id
full_id = VulcanoBaseRule::full_id(@profile_id, id)
@rules[full_id] = nil
end
def register_rule r
# get the full ID
full_id = VulcanoBaseRule::full_id(@profile_id, r)
if full_id.nil?
# TODO error
return
end
# add the rule to the registry
existing = @rules[full_id]
if existing.nil?
@rules[full_id] = r
else
VulcanoBaseRule::merge(existing, r)
end
end
end
end

View file

@ -1,17 +1,46 @@
# encoding: utf-8 # encoding: utf-8
# copyright: 2015, Vulcano Security GmbH # copyright: 2015, Vulcano Security GmbH
# license: All rights reserved # license: All rights reserved
require 'vulcano/plugins'
module Vulcano module Vulcano
class Resource class Resource
def self.registry
def resource_skipped @registry ||= {}
@resource_skipped
end end
def skip_resource message
@resource_skipped = message
end
end end
end
def self.resource(version)
if version != 1
raise "Only resource version 1 is supported!"
end
Vulcano::Plugins::Resource
end
end
require 'resources/apache_conf'
require 'resources/audit_policy'
require 'resources/auditd_conf'
require 'resources/auditd_rules'
require 'resources/command'
require 'resources/directory'
require 'resources/env'
require 'resources/etc_group'
require 'resources/file'
require 'resources/group_policy'
require 'resources/inetd_conf'
require 'resources/limits_conf'
require 'resources/login_def'
require 'resources/mysql'
require 'resources/mysql_conf'
require 'resources/mysql_session'
require 'resources/ntp_conf'
require 'resources/parse_config'
require 'resources/passwd'
require 'resources/postgres'
require 'resources/postgres_conf'
require 'resources/postgres_session'
require 'resources/processes'
require 'resources/registry_key'
require 'resources/security_policy'
require 'resources/ssh_conf'

View file

@ -2,12 +2,9 @@
# copyright: 2015, Dominik Richter # copyright: 2015, Dominik Richter
# license: All rights reserved # license: All rights reserved
require 'vulcano/base_rule' require 'vulcano/base_rule'
require 'serverspec'
module Vulcano module Vulcano
class Rule < VulcanoBaseRule class Rule < VulcanoBaseRule
include Serverspec::Helper::Type
extend Serverspec::Helper::Type
include RSpec::Core::DSL include RSpec::Core::DSL
# Override RSpec methods to add # Override RSpec methods to add
@ -173,43 +170,6 @@ module Vulcano::DSL
end end
module Vulcano
class ProfileContext
include Serverspec::Helper::Type
extend Serverspec::Helper::Type
include Vulcano::DSL
def initialize profile_id, profile_registry, only_ifs
@profile_id = profile_id
@rules = profile_registry
@only_ifs = only_ifs
end
def __unregister_rule id
full_id = VulcanoBaseRule::full_id(@profile_id, id)
@rules[full_id] = nil
end
def __register_rule r
# get the full ID
full_id = VulcanoBaseRule::full_id(@profile_id, r)
if full_id.nil?
# TODO error
return
end
# add the rule to the registry
existing = @rules[full_id]
if existing.nil?
@rules[full_id] = r
else
VulcanoBaseRule::merge(existing, r)
end
end
end
end
module Vulcano::GlobalDSL module Vulcano::GlobalDSL
def __register_rule r def __register_rule r
# make sure the profile id is attached to the rule # make sure the profile id is attached to the rule

View file

@ -5,15 +5,13 @@
require 'uri' require 'uri'
require 'vulcano/backend' require 'vulcano/backend'
require 'vulcano/targets' require 'vulcano/targets'
require 'vulcano/profile_context'
# spec requirements # spec requirements
require 'rspec' require 'rspec'
require 'rspec/its' require 'rspec/its'
require 'specinfra' require 'specinfra'
require 'specinfra/helper' require 'specinfra/helper'
require 'specinfra/helper/set' require 'specinfra/helper/set'
require 'serverspec/helper'
require 'serverspec/matcher'
require 'serverspec/subject'
require 'vulcano/rspec_json_formatter' require 'vulcano/rspec_json_formatter'
module Vulcano module Vulcano
@ -23,37 +21,67 @@ module Vulcano
def initialize(profile_id, conf) def initialize(profile_id, conf)
@rules = [] @rules = []
@profile_id = profile_id @profile_id = profile_id
@conf = conf.dup @conf = Vulcano::Backend.target_config(normalize_map(conf))
# RSpec.configuration.output_stream = $stdout # global reset
# RSpec.configuration.error_stream = $stderr RSpec.world.reset
RSpec.configuration.add_formatter(:json)
# specinfra configure_output
backend = Vulcano::Backend.new(@conf) configure_backend
backend.resolve_target_options
backend.configure_shared_options
backend.configure_target
end end
def add_resources(resources) def normalize_map(hm)
items = resources.map do |resource| res = {}
Vulcano::Targets.resolve(resource) hm.each{|k,v|
res[k.to_s] = v
}
res
end
def configure_output
# RSpec.configuration.output_stream = $stdout
# RSpec.configuration.error_stream = $stderr
RSpec.configuration.add_formatter(@conf['format'] || 'progress')
end
def configure_backend
backend_name = ( @conf['backend'] ||= 'exec' )
# @TODO all backends except for mock revert to specinfra for now
unless %w{ mock }.include? backend_name
backend_class = Vulcano::Backend.registry['specinfra']
else
backend_class = Vulcano::Backend.registry[backend_name]
end end
# Return on failure
if backend_class.nil?
raise "Can't find command backend '#{backend_name}'."
end
# create the backend based on the config
@backend = backend_class.new(@conf)
end
def add_tests(tests)
# retrieve the raw ruby code of all tests
items = tests.map do |test|
Vulcano::Targets.resolve(test)
end
# add all tests (raw) to the runtime
items.flatten.each do |item| items.flatten.each do |item|
add_content(item[:content], item[:ref], item[:line]) add_content(item[:content], item[:ref], item[:line])
end end
end end
def add_content(content, source, line = nil) def add_content(content, source, line = nil)
ctx = Vulcano::ProfileContext.new(@profile_id, {}, []) ctx = Vulcano::ProfileContext.new(@profile_id, @backend)
# evaluate all tests # evaluate all tests
ctx.instance_eval(content, source, line || 1) ctx.load(content, source, line || 1)
# process the resulting rules # process the resulting rules
rules = ctx.instance_variable_get(:@rules) ctx.rules.each do |rule_id, rule|
rules.each do |rule_id, rule|
#::Vulcano::DSL.execute_rule(rule, profile_id) #::Vulcano::DSL.execute_rule(rule, profile_id)
checks = rule.instance_variable_get(:@checks) checks = rule.instance_variable_get(:@checks)
checks.each do |m,a,b| checks.each do |m,a,b|

70
test/docker.rb Normal file
View file

@ -0,0 +1,70 @@
require 'docker'
require 'yaml'
require_relative '../lib/vulcano'
tests = ARGV
if tests.empty?
puts 'Nothing to do.'
exit 0
end
class DockerTester
def initialize(tests)
@tests = tests
@images = docker_images_by_tag
@conf = tests_conf
end
def run
# test all images
@conf['images'].each{|n|
test_image(n)
}.all? or raise "Test failures"
end
def docker_images_by_tag
# get all docker image tags
images = {}
Docker::Image.all.map do |img|
Array(img.info['RepoTags']).each do |tag|
images[tag] = img
end
end
images
end
def tests_conf
# get the test configuration
conf_path = File::join(File::dirname(__FILE__), '..', '.tests.yaml')
raise "Can't find tests config in #{conf_path}" unless File::file?(conf_path)
conf = YAML.load(File::read(conf_path))
end
def test_container(container_id)
opts = { 'target' => "docker://#{container_id}" }
runner = Vulcano::Runner.new(nil, opts)
runner.add_tests(@tests)
runner.run
end
def test_image(name)
dname = "docker-#{name}:latest"
image = @images[dname]
raise "Can't find docker image #{dname}" if image.nil?
container = Docker::Container.create(
'Cmd' => [ '/bin/bash' ],
'Image' => image.id,
'OpenStdin' => true,
)
container.start
res = test_container(container.id)
container.kill
container.delete(force: true)
res
end
end
DockerTester.new(tests).run

4
test/helper.rb Normal file
View file

@ -0,0 +1,4 @@
require 'minitest/autorun'
require 'minitest/spec'
require 'vulcano/backend'

30
test/resource/command.rb Normal file
View file

@ -0,0 +1,30 @@
describe command('echo hello') do
its(:stdout) { should eq "hello\n" }
its(:stderr) { should eq "" }
its(:exit_status) { should eq 0 }
end
describe command('>&2 echo error') do
its(:stdout) { should eq "" }
its(:stderr) { should eq "error\n" }
its(:exit_status) { should eq 0 }
end
describe command('exit 123') do
its(:stdout) { should eq "" }
its(:stderr) { should eq "" }
its(:exit_status) { should eq 123 }
end
describe command('/bin/sh').exists? do
it { should eq true }
end
describe command('sh').exists? do
it { should eq true }
end
describe command('this is not existing').exists? do
it { should eq false }
end

126
test/resource/file.rb Normal file
View file

@ -0,0 +1,126 @@
describe file('/tmp') do
it { should exist }
end
describe file('/tmpest') do
it { should_not exist }
end
describe file('/tmp') do
its(:type) { should eq :directory }
it { should be_directory }
end
describe file('/proc/version') do
its(:type) { should eq :file }
it { should be_file }
it { should_not be_directory }
end
describe file('/dev/stdout') do
its(:type) { should eq :symlink }
it { should be_symlink }
it { should_not be_file }
it { should_not be_directory }
end
describe file('/dev/zero') do
its(:type) { should eq :character_device }
it { should be_character_device }
it { should_not be_file }
it { should_not be_directory }
end
# describe file('...') do
# its(:type) { should eq :block_device }
# it { should be_block_device }
# end
# describe file('...') do
# its(:type) { should eq :socket }
# it { should be_socket }
# end
# describe file('...') do
# its(:type) { should eq :pipe }
# it { should be_pipe }
# end
describe file('/dev') do
its(:mode) { should eq 00755 }
end
describe file('/dev') do
it { should be_mode 00755 }
end
describe file('/root') do
its(:owner) { should eq 'root' }
end
describe file('/dev') do
it { should be_owned_by 'root' }
end
describe file('/root') do
its(:group) { should eq 'root' }
end
describe file('/dev') do
it { should be_grouped_into 'root' }
end
describe file('/dev/kcore') do
its(:link_target) { should eq '/proc/kcore' }
end
describe file('/dev/kcore') do
it { should be_linked_to '/proc/kcore' }
end
describe file('/proc/cpuinfo') do
its(:content) { should match /^processor/ }
end
describe file('/').mtime.to_i do
it { should <= Time.now.to_i }
it { should >= Time.now.to_i - 1000}
end
describe file('/') do
its(:size) { should be > 64 }
its(:size) { should be < 10240 }
end
describe file('/proc/cpuinfo') do
its(:size) { should be 0 }
end
# @TODO selinux_label
describe file('/proc') do
it { should be_mounted }
end
describe file('/proc/cpuinfo') do
it { should_not be_mounted }
end
# @TODO immutable?
# @TODO product_version
# @TODO file_version
# @TODO version?
require 'digest'
cpuinfo = file('/proc/cpuinfo').content
md5sum = Digest::MD5.hexdigest(cpuinfo)
describe file('/proc/cpuinfo') do
its(:md5sum) { should eq md5sum }
end
sha256sum = Digest::SHA256.hexdigest(cpuinfo)
describe file('/proc/cpuinfo') do
its(:sha256sum) { should eq sha256sum }
end

66
test/unit/backend_test.rb Normal file
View file

@ -0,0 +1,66 @@
require 'helper'
describe 'Vulcano::Backend' do
it 'should have a populated registry' do
reg = Vulcano::Backend.registry
reg.must_be_kind_of Hash
reg.keys.must_include 'mock'
reg.keys.must_include 'specinfra'
end
describe 'target config helper' do
it 'configures resolves target' do
org = {
'target' => 'ssh://user:pass@host.com:123',
}
res = Vulcano::Backend.target_config(org)
res['backend'].must_equal 'ssh'
res['host'].must_equal 'host.com'
res['user'].must_equal 'user'
res['password'].must_equal 'pass'
res['port'].must_equal 123
res['target'].must_equal org['target']
org.keys.must_equal ['target']
end
it 'resolves a target while keeping existing fields' do
org = {
'target' => 'ssh://user:pass@host.com:123',
'backend' => rand,
'host' => rand,
'user' => rand,
'password' => rand,
'port' => rand,
'target' => rand,
}
res = Vulcano::Backend.target_config(org)
res.must_equal org
end
it 'keeps the configuration when incorrect target is supplied' do
org = {
'target' => 'wrong',
}
res = Vulcano::Backend.target_config(org)
res['backend'].must_be_nil
res['host'].must_be_nil
res['user'].must_be_nil
res['password'].must_be_nil
res['port'].must_be_nil
res['target'].must_equal org['target']
end
end
describe 'helper method for creating backends' do
it 'creates v1 backends by default' do
Vulcano.backend.must_equal Vulcano::Plugins::Backend
end
it 'creates v1 backends' do
Vulcano.backend(1).must_equal Vulcano::Plugins::Backend
end
end
end

View file

@ -0,0 +1,31 @@
require 'helper'
describe 'Vulcano::Plugins::Backend::FileCommon' do
let(:cls) { Vulcano::Plugins::Backend::FileCommon }
let(:backend) { cls.new }
it 'default type is :unkown' do
backend.type.must_equal :unknown
end
describe 'with non-empty content' do
let(:backend) {
Class.new(cls) do
def content; 'Hello World'; end
end.new
}
it 'must return raw content' do
backend.content.must_equal 'Hello World'
end
it 'must calculate the md5sum of content' do
backend.md5sum.must_equal 'b10a8db164e0754105b7a99be72e3fe5'
end
it 'must calculate the sha256sum of content' do
backend.sha256sum.must_equal 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'
end
end
end

View file

@ -0,0 +1,43 @@
require 'helper'
describe 'Vulcano::Plugins::Backend' do
let(:cls) { Vulcano::Plugins::Backend }
let(:child) { Class.new(cls) }
it 'provides a name method for registering' do
child.must_respond_to :name
end
describe 'when registering a plugin' do
let(:registry) { Vulcano::Backend.registry }
before do
child.name 'test'
end
after do
registry.delete('test')
end
it 'must have the backend registered' do
registry.keys.must_include 'test'
registry['test'].must_equal child
end
it 'must raise an error if file is not implemented' do
t = registry['test'].new
proc { t.run_command }.must_raise NotImplementedError
end
it 'must raise an error if run_command is not implemented' do
t = registry['test'].new
proc { t.file }.must_raise NotImplementedError
end
it 'must raise an error if os is not implemented' do
t = registry['test'].new
proc { t.os }.must_raise NotImplementedError
end
end
end

View file

@ -20,14 +20,13 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "bundler", "~> 1.5" spec.add_development_dependency "bundler", "~> 1.5"
spec.add_development_dependency "minitest", "~> 5.5" spec.add_development_dependency "minitest", "~> 5.5"
spec.add_development_dependency "rspec", "~> 3.2" spec.add_development_dependency "rspec", "~> 3.3"
spec.add_development_dependency "rake", "~> 10" spec.add_development_dependency "rake", "~> 10"
spec.add_development_dependency "pry", "~> 0.10" spec.add_development_dependency "pry", "~> 0.10"
spec.add_dependency 'thor', '~> 0.19' spec.add_dependency 'thor', '~> 0.19'
spec.add_dependency 'json', '~> 1.8' spec.add_dependency 'json', '~> 1.8'
spec.add_dependency 'rainbow', '~> 2' spec.add_dependency 'rainbow', '~> 2'
spec.add_dependency 'serverspec', '~> 2.18'
spec.add_dependency 'method_source', '~> 0.8' spec.add_dependency 'method_source', '~> 0.8'
spec.add_dependency 'rubyzip', '~> 1.1' spec.add_dependency 'rubyzip', '~> 1.1'
spec.add_dependency 'rspec', '~> 3.3' spec.add_dependency 'rspec', '~> 3.3'