diff --git a/.gitignore b/.gitignore index 9eedf918..75251011 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.swp .bundle .rvmrc +.versions.conf .config .yardoc .rspec @@ -21,3 +22,4 @@ test/version_tmp tmp Vagrantfile vendor/ +.DS_Store \ No newline at end of file diff --git a/Rakefile b/Rakefile index a44099c5..f7d77408 100644 --- a/Rakefile +++ b/Rakefile @@ -4,9 +4,9 @@ require 'rspec/core/rake_task' task :spec => 'spec:all' namespace :spec do - oses = %w( darwin debian gentoo redhat solaris solaris10 solaris11 smartos ) + oses = %w( darwin debian gentoo redhat aix solaris solaris10 solaris11 smartos windows freebsd) - task :all => [ oses.map {|os| "spec:#{os}" }, :helpers, :exec, :ssh ].flatten + task :all => [ oses.map {|os| "spec:#{os}" }, :helpers, :exec, :ssh, :cmd, :winrm, :powershell ].flatten oses.each do |os| RSpec::Core::RakeTask.new(os.to_sym) do |t| @@ -18,11 +18,9 @@ namespace :spec do t.pattern = "spec/helpers/*_spec.rb" end - RSpec::Core::RakeTask.new(:exec) do |t| - t.pattern = "spec/backend/exec/*_spec.rb" - end - - RSpec::Core::RakeTask.new(:ssh) do |t| - t.pattern = "spec/backend/ssh/*_spec.rb" + [:exec, :ssh, :cmd, :winrm, :powershell].each do |backend| + RSpec::Core::RakeTask.new(backend) do |t| + t.pattern = "spec/backend/#{backend.to_s}/*_spec.rb" + end end end diff --git a/WindowsSupport.md b/WindowsSupport.md new file mode 100644 index 00000000..3d2cdc40 --- /dev/null +++ b/WindowsSupport.md @@ -0,0 +1,88 @@ +## Windows support + +Serverspec is now providing a limited support for Microsoft Windows. + +If you want to test Windows based machines you need to set the target host's OS explicitly in your `spec/spec_helper.rb` + +For local testing (equivalent to the Exec option in Linux/Unix systems) simply do: + +```ruby +require 'serverspec' + +include Serverspec::Helper::Cmd +include Serverspec::Helper::Windows + +``` + +For remote testing you have to configure Windows Remote Management in order to communicate to the target host: + +```ruby +require 'serverspec' +require 'winrm' + +include Serverspec::Helper::WinRM +include Serverspec::Helper::Windows + +RSpec.configure do |c| + user = + pass = + endpoint = "http://:5985/wsman" + + c.winrm = ::WinRM::WinRMWebService.new(endpoint, :ssl, :user => user, :pass => pass, :basic_auth_only => true) + c.winrm.set_timeout 300 # 5 minutes max timeout for any operation +end +``` + +For different authentication mechanisms check the Microsoft WinRM documentation and verify the ones that are supported by [WinRb/WinRM](https://github.com/WinRb/WinRM) + + +###RSpec Examples for windows target hosts +```ruby +describe file('c:/windows') do + it { should be_directory } + it { should be_readable } + it { should_not be_writable.by('Everyone') } +end + +describe file('c:/temp/test.txt') do + it { should be_file } + it { should contain "some text" } +end + +describe package('Adobe AIR') do + it { should be_installed} +end + +describe service('DNS Client') do + it { should be_enabled } + it { should be_running } +end + +describe port(139) do + it { should be_listening } +end + +describe user('some.admin') do + it { should exist } + it { should belong_to_group('Administrators')} +end + +describe group('Guests') do + it { should exist } +end + +describe group('MYDOMAIN\Domain Users') do + it { should exist } +end + +describe windows_registry_key('HKEY_USERS\S-1-5-21-1319311448-2088773778-316617838-32407\Test MyKey') do + it { should exist } + it { should have_property('string value') } + it { should have_property('binary value', :type_binary) } + it { should have_property('dword value', :type_dword) } + it { should have_value('test default data') } + it { should have_property_value('multistring value', :type_multistring, "test\nmulti\nstring\ndata") } + it { should have_property_value('qword value', :type_qword, 'adff32') } + it { should have_property_value('binary value', :type_binary, 'dfa0f066') } +end +``` \ No newline at end of file diff --git a/lib/serverspec.rb b/lib/serverspec.rb index 6ebfc1cd..3896f3a6 100644 --- a/lib/serverspec.rb +++ b/lib/serverspec.rb @@ -11,11 +11,14 @@ require 'serverspec/commands/redhat' require 'serverspec/commands/debian' require 'serverspec/commands/gentoo' +require 'serverspec/commands/aix' require 'serverspec/commands/solaris' require 'serverspec/commands/solaris10' require 'serverspec/commands/solaris11' require 'serverspec/commands/smartos' require 'serverspec/commands/darwin' +require 'serverspec/commands/windows' +require 'serverspec/commands/freebsd' require 'serverspec/configuration' require 'rspec/core/formatters/base_formatter' @@ -34,15 +37,19 @@ def configuration c.include(Serverspec::Helper::RedHat, :os => :redhat) c.include(Serverspec::Helper::Debian, :os => :debian) c.include(Serverspec::Helper::Gentoo, :os => :gentoo) + c.include(Serverspec::Helper::AIX, :os => :aix) c.include(Serverspec::Helper::Solaris, :os => :solaris) c.include(Serverspec::Helper::Solaris10, :os => :solaris10) c.include(Serverspec::Helper::Solaris11, :os => :solaris11) c.include(Serverspec::Helper::SmartOS, :os => :smartos) c.include(Serverspec::Helper::Darwin, :os => :darwin) + c.include(Serverspec::Helper::Windows, :os => :windows) + c.include(Serverspec::Helper::FreeBSD, :os => :freebsd) c.add_setting :os, :default => nil c.add_setting :host, :default => nil c.add_setting :ssh, :default => nil c.add_setting :sudo_password, :default => nil + c.add_setting :winrm, :default => nil Serverspec.configuration.defaults.each { |k, v| c.add_setting k, :default => v } c.before :each do backend.set_example(example) diff --git a/lib/serverspec/backend.rb b/lib/serverspec/backend.rb index 21c38319..97f9c2c2 100644 --- a/lib/serverspec/backend.rb +++ b/lib/serverspec/backend.rb @@ -1,2 +1,7 @@ +require 'serverspec/backend/base' require 'serverspec/backend/ssh' require 'serverspec/backend/exec' +require 'serverspec/backend/powershell/script_helper' +require 'serverspec/backend/powershell/command' +require 'serverspec/backend/cmd' +require 'serverspec/backend/winrm' diff --git a/lib/serverspec/backend/base.rb b/lib/serverspec/backend/base.rb new file mode 100644 index 00000000..4f07a8a2 --- /dev/null +++ b/lib/serverspec/backend/base.rb @@ -0,0 +1,31 @@ +require 'singleton' + +module Serverspec + module Backend + class Base + include Singleton + + def set_commands(c) + @commands = c + end + + def set_example(e) + @example = e + end + + def commands + @commands + end + + def check_zero(cmd, *args) + ret = run_command(commands.send(cmd, *args)) + ret[:exit_status] == 0 + end + + # Default action is to call check_zero with args + def method_missing(meth, *args, &block) + check_zero(meth, *args) + end + end + end +end \ No newline at end of file diff --git a/lib/serverspec/backend/cmd.rb b/lib/serverspec/backend/cmd.rb new file mode 100644 index 00000000..e2137a60 --- /dev/null +++ b/lib/serverspec/backend/cmd.rb @@ -0,0 +1,35 @@ +require 'open3' + +module Serverspec + module Backend + class Cmd < Base + include PowerShell::ScriptHelper + + def run_command(cmd, opts={}) + script = create_script(cmd) + result = execute_script script + + if @example + @example.metadata[:command] = script + @example.metadata[:stdout] = result[:stdout] + result[:stderr] + end + { :stdout => result[:stdout], :stderr => result[:stderr], + :exit_status => result[:status], :exit_signal => nil } + end + + def execute_script script + ps_script = %Q{powershell -encodedCommand #{encode_script(script)}} + if Open3.respond_to? :capture3 + stdout, stderr, status = Open3.capture3(ps_script) + # powershell still exits with 0 even if there are syntax errors, although it spits the error out into stderr + # so we have to resort to return an error exit code if there is anything in the standard error + status = 1 if status == 0 and !stderr.empty? + { :stdout => stdout, :stderr => stderr, :status => status } + else + stdout = `#{ps_script} 2>&1` + { :stdout => stdout, :stderr => nil, :status => $? } + end + end + end + end +end diff --git a/lib/serverspec/backend/exec.rb b/lib/serverspec/backend/exec.rb index 177f7353..a91e15ed 100644 --- a/lib/serverspec/backend/exec.rb +++ b/lib/serverspec/backend/exec.rb @@ -2,20 +2,7 @@ module Serverspec module Backend - class Exec - include Singleton - - def set_commands(c) - @commands = c - end - - def set_example(e) - @example = e - end - - def commands - @commands - end + class Exec < Base def run_command(cmd, opts={}) cmd = build_command(cmd) @@ -52,16 +39,6 @@ def add_pre_command(cmd) cmd end - def check_zero(cmd, *args) - ret = run_command(commands.send(cmd, *args)) - ret[:exit_status] == 0 - end - - # Default action is to call check_zero with args - def method_missing(meth, *args, &block) - check_zero(meth, *args) - end - def check_running(process) ret = run_command(commands.check_running(process)) if ret[:exit_status] == 1 || ret[:stdout] =~ /stopped/ @@ -189,6 +166,8 @@ def check_os 'Debian' elsif run_command('ls /etc/gentoo-release')[:exit_status] == 0 'Gentoo' + elsif run_command('uname -s')[:stdout] =~ /AIX/i + 'AIX' elsif (os = run_command('uname -sr')[:stdout]) && os =~ /SunOS/i if os =~ /5.10/ 'Solaris10' @@ -201,6 +180,8 @@ def check_os end elsif run_command('uname -s')[:stdout] =~ /Darwin/i 'Darwin' + elsif run_command('uname -s')[:stdout] =~ /FreeBSD/i + 'FreeBSD' else 'Base' end diff --git a/lib/serverspec/backend/powershell/command.rb b/lib/serverspec/backend/powershell/command.rb new file mode 100644 index 00000000..44a48cbb --- /dev/null +++ b/lib/serverspec/backend/powershell/command.rb @@ -0,0 +1,36 @@ +module Serverspec + module Backend + module PowerShell + class Command + attr_reader :import_functions, :script + def initialize &block + @import_functions = [] + @script = "" + instance_eval &block if block_given? + end + + def using *functions + functions.each { |f| import_functions << f } + end + + def exec code + @script = code + end + + def convert_regexp(target) + case target + when Regexp + target.source + else + target.to_s.gsub '/', '' + end + end + + def get_identity id + raise "You must provide a specific Windows user/group" if id =~ /(owner|group|others)/ + identity = id || 'Everyone' + end + end + end + end +end diff --git a/lib/serverspec/backend/powershell/script_helper.rb b/lib/serverspec/backend/powershell/script_helper.rb new file mode 100644 index 00000000..9957b8d2 --- /dev/null +++ b/lib/serverspec/backend/powershell/script_helper.rb @@ -0,0 +1,69 @@ +require 'base64' + +module Serverspec + module Backend + module PowerShell + module ScriptHelper + def build_command(cmd) + path = Serverspec.configuration.path || RSpec.configuration.path + if path + cmd.strip! + cmd = +<<-EOF +$env:path = "#{path};$env:path" +#{cmd} +EOF + end + cmd + end + + def add_pre_command(cmd) + path = Serverspec.configuration.path || RSpec.configuration.path + if Serverspec.configuration.pre_command + cmd.strip! + cmd = +<<-EOF +if (#{Serverspec.configuration.pre_command}) +{ +#{cmd} +} +EOF + cmd = "$env:path = \"#{path};$env:path\"\n#{cmd}" if path + end + cmd + end + + def encode_script script + script_text = script.chars.to_a.join("\x00").chomp + script_text << "\x00" unless script_text[-1].eql? "\x00" + if script_text.respond_to?(:encode) + script_text = script_text.encode('ASCII-8BIT') + end + if Base64.respond_to?(:strict_encode64) + Base64.strict_encode64(script_text) + else + [ script_text ].pack("m").strip + end + end + + def create_script command + script = build_command(command.script) + script = add_pre_command(script) + ps_functions = command.import_functions.map { |f| File.read(File.join(File.dirname(__FILE__), 'support', f)) } + <<-EOF +$exitCode = 1 +try { + #{ps_functions.join("\n")} + $success = (#{script}) + if ($success -is [Boolean] -and $success) { $exitCode = 0 } +} catch { + Write-Output $_.Exception.Message +} +Write-Output "Exiting with code: $exitCode" +exit $exitCode + EOF + end + end + end + end +end diff --git a/lib/serverspec/backend/powershell/support/check_file_access_rules.ps1 b/lib/serverspec/backend/powershell/support/check_file_access_rules.ps1 new file mode 100644 index 00000000..15cbe196 --- /dev/null +++ b/lib/serverspec/backend/powershell/support/check_file_access_rules.ps1 @@ -0,0 +1,12 @@ +function CheckFileAccessRules +{ + param($path, $identity, $rules) + + $result = $false + $accessRules = (Get-Acl $path).access | Where-Object {$_.AccessControlType -eq 'Allow' -and $_.IdentityReference -eq $identity } + if ($accessRules) { + $match = $accessRules.FileSystemRights.ToString() -Split (', ') | ?{$rules -contains $_} + $result = $match -ne $null -or $match.length -gt 0 + } + $result +} diff --git a/lib/serverspec/backend/powershell/support/crop_text.ps1 b/lib/serverspec/backend/powershell/support/crop_text.ps1 new file mode 100644 index 00000000..59f1d232 --- /dev/null +++ b/lib/serverspec/backend/powershell/support/crop_text.ps1 @@ -0,0 +1,11 @@ +function CropText +{ + param($text, $fromPattern, $toPattern) + + $from, $to = ([regex]::matches($text, $fromPattern)), ([regex]::matches($text, $toPattern)) + if ($from.count -gt 0 -and $to.count -gt 0) { + $text.substring($from[0].index, $to[0].index + $to[0].length - $from[0].index) + } else { + "" + } +} diff --git a/lib/serverspec/backend/powershell/support/find_group.ps1 b/lib/serverspec/backend/powershell/support/find_group.ps1 new file mode 100644 index 00000000..c8134389 --- /dev/null +++ b/lib/serverspec/backend/powershell/support/find_group.ps1 @@ -0,0 +1,8 @@ +function FindGroup +{ + param($groupName, $domain) + if ($domain -eq $null) {$selectionCriteria = " and LocalAccount = true"} + else {$selectionCriteria = " and Domain = '$domain'"} + + Get-WmiObject Win32_Group -filter "Name = '$groupName' $selectionCriteria" +} \ No newline at end of file diff --git a/lib/serverspec/backend/powershell/support/find_installed_application.ps1 b/lib/serverspec/backend/powershell/support/find_installed_application.ps1 new file mode 100644 index 00000000..ee3ce752 --- /dev/null +++ b/lib/serverspec/backend/powershell/support/find_installed_application.ps1 @@ -0,0 +1,7 @@ +function FindInstalledApplication +{ + param($appName, $appVersion) + $selectionCriteria = "(Name like '$appName' or PackageName like '$appName') and InstallState = 5" + if ($appVersion -ne $null) { $selectionCriteria += " and version = '$appVersion'"} + Get-WmiObject Win32_Product -filter $selectionCriteria +} \ No newline at end of file diff --git a/lib/serverspec/backend/powershell/support/find_service.ps1 b/lib/serverspec/backend/powershell/support/find_service.ps1 new file mode 100644 index 00000000..e779770e --- /dev/null +++ b/lib/serverspec/backend/powershell/support/find_service.ps1 @@ -0,0 +1,5 @@ +function FindService +{ + param($name) + Get-WmiObject Win32_Service | Where-Object {$_.serviceName -eq $name -or $_.displayName -eq $name} +} \ No newline at end of file diff --git a/lib/serverspec/backend/powershell/support/find_user.ps1 b/lib/serverspec/backend/powershell/support/find_user.ps1 new file mode 100644 index 00000000..b60c11fe --- /dev/null +++ b/lib/serverspec/backend/powershell/support/find_user.ps1 @@ -0,0 +1,8 @@ +function FindUser +{ + param($userName, $domain) + if ($domain -eq $null) {$selectionCriteria = " and LocalAccount = true"} + else {$selectionCriteria = " and Domain = '$domain'"} + + Get-WmiObject Win32_UserAccount -filter "Name = '$userName' $selectionCriteria" +} diff --git a/lib/serverspec/backend/powershell/support/find_usergroup.ps1 b/lib/serverspec/backend/powershell/support/find_usergroup.ps1 new file mode 100644 index 00000000..968c2f28 --- /dev/null +++ b/lib/serverspec/backend/powershell/support/find_usergroup.ps1 @@ -0,0 +1,9 @@ +function FindUserGroup +{ + param($userName, $groupName, $userDomain, $groupDomain) + $user = FindUser -userName $userName -domain $userDomain + $group = FindGroup -groupName $groupName -domain $groupDomain + if ($user -and $group) { + Get-WmiObject Win32_GroupUser -filter ("GroupComponent = 'Win32_Group.Domain=`"" + $group.domain + "`",Name=`"" + $group.name + "`"' and PartComponent = 'Win32_UserAccount.Domain=`"" + $user.domain + "`",Name=`"" + $user.name + "`"'") + } +} \ No newline at end of file diff --git a/lib/serverspec/backend/powershell/support/is_port_listening.ps1 b/lib/serverspec/backend/powershell/support/is_port_listening.ps1 new file mode 100644 index 00000000..a76634f6 --- /dev/null +++ b/lib/serverspec/backend/powershell/support/is_port_listening.ps1 @@ -0,0 +1,13 @@ +function IsPortListening +{ + param($portNumber, $protocol) + $netstatOutput = netstat -an | Out-String + $networkIPs = (Get-WmiObject Win32_NetworkAdapterConfiguration | ? {$_.IPEnabled}) | %{ $_.IPAddress[0] } + foreach ($ipaddress in $networkIPs) + { + $matchExpression = ("$ipaddress" + ":" + $portNumber) + if ($protocol) { $matchExpression = ($protocol.toUpper() + "\s+$matchExpression") } + if ($netstatOutput -match $matchExpression) { return $true } + } + $false +} \ No newline at end of file diff --git a/lib/serverspec/backend/winrm.rb b/lib/serverspec/backend/winrm.rb new file mode 100644 index 00000000..1c1dff8a --- /dev/null +++ b/lib/serverspec/backend/winrm.rb @@ -0,0 +1,26 @@ +module Serverspec + module Backend + class WinRM < Base + include PowerShell::ScriptHelper + + def run_command(cmd, opts={}) + script = create_script(cmd) + winrm = RSpec.configuration.winrm + + result = winrm.powershell(script) + stdout, stderr = [:stdout, :stderr].map do |s| + result[:data].select {|item| item.key? s}.map {|item| item[s]}.join + end + result[:exitcode] = 1 if result[:exitcode] == 0 and !stderr.empty? + + if @example + @example.metadata[:command] = script + @example.metadata[:stdout] = stdout + stderr + end + + { :stdout => stdout, :stderr => stderr, + :exit_status => result[:exitcode], :exit_signal => nil } + end + end + end +end diff --git a/lib/serverspec/commands/aix.rb b/lib/serverspec/commands/aix.rb new file mode 100644 index 00000000..efbc3a3e --- /dev/null +++ b/lib/serverspec/commands/aix.rb @@ -0,0 +1,69 @@ +require 'shellwords' + +module Serverspec + module Commands + class AIX < Base + class NotImplementedError < Exception; end + + def check_access_by_user(file, user, access) + "su -s sh -c \"test -#{access} #{file}\" #{user}" + end + + def check_enabled(service,level=nil) + "lssrc -s #{escape(service)} | grep active" + end + + def check_running(service) + "ps -ef | grep -v grep | grep #{escape(service)}" + end + + def check_installed(package,version) + + if version + "lslpp -L #{escape(package)} | awk '{print $2}' | grep -w -- #{version}" + else + "lslpp -L #{escape(package)}" + end + end + + def check_listening(port) + regexp = "*.#{port} " + "netstat -an -f inet | awk '{print $4}' | grep -- #{regexp}" + #"netstat -an -f inet | awk '{print $4}' | grep -- #{escape(regexp)}" + end + + def check_belonging_group(user, group) + "lsuser -a groups #{escape(user)} | awk -F'=' '{print $2}'| sed -e 's/,/ /g' |grep -w -- #{escape(group)}" + end + + def check_gid(group, gid) + regexp = "^#{group}" + "cat etc/group | grep -w -- #{escape(regexp)} | cut -f 3 -d ':' | grep -w -- #{escape(gid)}" + end + + def check_login_shell(user, path_to_shell) + "lsuser -a shell #{escape(user)} |awk -F'=' '{print $2}' | grep -w -- #{escape(path_to_shell)}" + end + + def check_home_directory(user, path_to_home) + "lsuser -a home #{escape(user)} | awk -F'=' '{print $2}' | grep -w -- #{escape(path_to_home)}" + end + + def check_mode(file, mode) + false unless sprintf("%o",File.stat(file).mode).slice!(3,3) == mode + end + + def check_owner(file, owner) + regexp = "^#{owner}$" + "ls -al #{escape(file)} | awk '{print $3}' | grep -- #{escape(regexp)}" + end + + def check_grouped(file, group) + regexp = "^#{group}$" + "ls -al #{escape(file)} | awk '{print $4}' | grep -- #{escape(regexp)}" + end + + + end + end +end diff --git a/lib/serverspec/commands/freebsd.rb b/lib/serverspec/commands/freebsd.rb new file mode 100644 index 00000000..ec3cb412 --- /dev/null +++ b/lib/serverspec/commands/freebsd.rb @@ -0,0 +1,18 @@ +module Serverspec + module Commands + class FreeBSD < Base + def check_enabled(service, level=3) + "service -e | grep -- #{escape(service)}" + end + + def check_installed(package, version=nil) + "pkg_version -X -s #{escape(package)}" + end + + def check_listening(port) + regexp = ":#{port} " + "sockstat -46l -p #{port} | grep -- #{escape(regexp)}" + end + end + end +end diff --git a/lib/serverspec/commands/windows.rb b/lib/serverspec/commands/windows.rb new file mode 100644 index 00000000..6229b88f --- /dev/null +++ b/lib/serverspec/commands/windows.rb @@ -0,0 +1,211 @@ +module Serverspec + module Commands + class Windows + class NotSupportedError < Exception; end + REGISTRY_KEY_TYPES = { + :type_string => 'String', + :type_binary => 'Binary', + :type_dword => 'DWord', + :type_qword => 'QWord', + :type_multistring => 'MultiString', + :type_expandstring => 'ExpandString' + } + + def method_missing method, *args + raise NotSupportedError.new "#{method} currently not supported in Windows os" if method.to_s =~ /^check_.+/ + super(method, *args) + end + + def check_file(file) + cmd = item_has_attribute file, 'Archive' + Backend::PowerShell::Command.new do + exec cmd + end + end + + def check_file_hidden(file) + cmd = item_has_attribute file, 'Hidden' + Backend::PowerShell::Command.new do + exec cmd + end + end + + def check_file_readonly(file) + cmd = item_has_attribute file, 'ReadOnly' + Backend::PowerShell::Command.new do + exec cmd + end + end + + def check_file_system(file) + cmd = item_has_attribute file, 'System' + Backend::PowerShell::Command.new do + exec cmd + end + end + + def check_directory(dir) + cmd = item_has_attribute dir, 'Directory' + Backend::PowerShell::Command.new do + exec cmd + end + end + + def check_file_contain(file, pattern) + Backend::PowerShell::Command.new do + exec "[Io.File]::ReadAllText('#{file}') -match '#{convert_regexp(pattern)}'" + end + end + + def check_file_contain_within file, pattern, from=nil, to=nil + from ||= '^' + to ||= '$' + Backend::PowerShell::Command.new do + using 'crop_text.ps1' + exec %Q[(CropText -text ([Io.File]::ReadAllText('#{file}')) -fromPattern '#{convert_regexp(from)}' -toPattern '#{convert_regexp(to)}') -match '#{pattern}'] + end + end + + def check_access_by_user(file, user, access) + case access + when 'r' + check_readable(file, user) + when 'w' + check_writable(file, user) + when 'x' + check_executable(file, user) + end + end + + def check_readable(file, by_whom) + Backend::PowerShell::Command.new do + using 'check_file_access_rules.ps1' + exec "CheckFileAccessRules -path '#{file}' -identity '#{get_identity by_whom}' -rules @('FullControl', 'Modify', 'ReadAndExecute', 'Read', 'ListDirectory')" + end + end + + def check_writable(file, by_whom) + Backend::PowerShell::Command.new do + using 'check_file_access_rules.ps1' + exec "CheckFileAccessRules -path '#{file}' -identity '#{get_identity by_whom}' -rules @('FullControl', 'Modify', 'Write')" + end + end + + def check_executable(file, by_whom) + Backend::PowerShell::Command.new do + using 'check_file_access_rules.ps1' + exec "CheckFileAccessRules -path '#{file}' -identity '#{get_identity by_whom}' -rules @('FullControl', 'Modify', 'ReadAndExecute', 'ExecuteFile')" + end + end + + def check_installed(package, version=nil) + version_selection = version.nil? ? "" : "-appVersion '#{version}'" + Backend::PowerShell::Command.new do + using 'find_installed_application.ps1' + exec "(FindInstalledApplication -appName '#{package}' #{version_selection}) -ne $null" + end + end + + def check_enabled(service, level=nil) + Backend::PowerShell::Command.new do + using 'find_service.ps1' + exec "(FindService -name '#{service}').StartMode -eq 'Auto'" + end + end + + def check_running(service) + Backend::PowerShell::Command.new do + using 'find_service.ps1' + exec "(FindService -name '#{service}').State -eq 'Running'" + end + end + + def check_process(process) + Backend::PowerShell::Command.new do + exec "(Get-Process '#{process}') -ne $null" + end + end + + def check_listening(port) + Backend::PowerShell::Command.new do + using 'is_port_listening.ps1' + exec "IsPortListening -portNumber #{port}" + end + end + + def check_listening_with_protocol(port, protocol) + Backend::PowerShell::Command.new do + using 'is_port_listening.ps1' + exec "IsPortListening -portNumber #{port} -protocol '#{protocol}'" + end + end + + def check_user(user) + user_id, domain = windows_account user + Backend::PowerShell::Command.new do + using 'find_user.ps1' + exec "(FindUser -userName '#{user_id}'#{domain.nil? ? "" : " -domain '#{domain}'"}) -ne $null" + end + end + + def check_group(group) + group_id, domain = windows_account group + Backend::PowerShell::Command.new do + using 'find_group.ps1' + exec "(FindGroup -groupName '#{group_id}'#{domain.nil? ? "" : " -domain '#{domain}'"}) -ne $null" + end + end + + def check_belonging_group(user, group) + user_id, user_domain = windows_account user + group_id, group_domain = windows_account group + Backend::PowerShell::Command.new do + using 'find_user.ps1' + using 'find_group.ps1' + using 'find_usergroup.ps1' + exec "(FindUserGroup -userName '#{user_id}'#{user_domain.nil? ? "" : " -userDomain '#{user_domain}'"} -groupName '#{group_id}'#{group_domain.nil? ? "" : " -groupDomain '#{group_domain}'"}) -ne $null" + end + end + + def check_registry_key(key_name, key_property = {}) + if key_property.empty? + cmd = "(Get-Item 'Registry::#{key_name}') -ne $null" + else + if key_property.key? :value + value = convert_key_property_value key_property + cmd = "(Compare-Object (Get-Item 'Registry::#{key_name}').GetValue('#{key_property[:name]}') #{value}) -eq $null" + else + cmd = "(Get-Item 'Registry::#{key_name}').GetValueKind('#{key_property[:name]}') -eq '#{REGISTRY_KEY_TYPES[key_property[:type]]}'" + end + end + Backend::PowerShell::Command.new { exec cmd } + end + + private + + def item_has_attribute item, attribute + "((Get-Item -Path '#{item}' -Force).attributes.ToString() -Split ', ') -contains '#{attribute}'" + end + + def convert_key_property_value property + case property[:type] + when :type_binary + byte_array = [property[:value]].pack('H*').bytes.to_a + "([byte[]] #{byte_array.join(',')})" + when:type_dword, :type_qword + property[:value].hex + else + string_array = property[:value].split("\n").map {|s| "'#{s}'"}.reduce {|acc, s| "#{acc},#{s}"} + "@(#{string_array})" + end + end + + def windows_account account + match = /((.+)\\)?(.+)/.match account + domain = match[2] + name = match[3] + [name, domain] + end + end + end +end diff --git a/lib/serverspec/helper.rb b/lib/serverspec/helper.rb index 2e8439c2..c94ae796 100644 --- a/lib/serverspec/helper.rb +++ b/lib/serverspec/helper.rb @@ -3,17 +3,22 @@ # Backend helpers require 'serverspec/helper/ssh' require 'serverspec/helper/exec' +require 'serverspec/helper/cmd' +require 'serverspec/helper/winrm' require 'serverspec/helper/puppet' # Command helpers require 'serverspec/helper/redhat' require 'serverspec/helper/debian' require 'serverspec/helper/gentoo' +require 'serverspec/helper/aix' require 'serverspec/helper/solaris' require 'serverspec/helper/solaris10' require 'serverspec/helper/solaris11' require 'serverspec/helper/smartos' require 'serverspec/helper/darwin' +require 'serverspec/helper/windows' +require 'serverspec/helper/freebsd' require 'serverspec/helper/detect_os' # Attributes helper diff --git a/lib/serverspec/helper/aix.rb b/lib/serverspec/helper/aix.rb new file mode 100644 index 00000000..71ecb163 --- /dev/null +++ b/lib/serverspec/helper/aix.rb @@ -0,0 +1,9 @@ +module Serverspec + module Helper + module AIX + def commands + Serverspec::Commands::AIX.new + end + end + end +end diff --git a/lib/serverspec/helper/cmd.rb b/lib/serverspec/helper/cmd.rb new file mode 100644 index 00000000..a4bb9dc0 --- /dev/null +++ b/lib/serverspec/helper/cmd.rb @@ -0,0 +1,15 @@ +module Serverspec + module Helper + module Cmd + def backend(commands_object=nil) + if ! respond_to?(:commands) + commands_object = Serverspec::Commands::Windows.new + end + instance = Serverspec::Backend::Cmd.instance + instance.set_commands(commands_object || commands) + instance + end + end + end +end + diff --git a/lib/serverspec/helper/freebsd.rb b/lib/serverspec/helper/freebsd.rb new file mode 100644 index 00000000..b4d4c2f8 --- /dev/null +++ b/lib/serverspec/helper/freebsd.rb @@ -0,0 +1,9 @@ +module Serverspec + module Helper + module FreeBSD + def commands + Serverspec::Commands::FreeBSD.new + end + end + end +end diff --git a/lib/serverspec/helper/type.rb b/lib/serverspec/helper/type.rb index 3c6a1577..0173fc23 100644 --- a/lib/serverspec/helper/type.rb +++ b/lib/serverspec/helper/type.rb @@ -4,7 +4,7 @@ module Type types = %w( base yumrepo service package port file cron command linux_kernel_parameter iptables host routing_table default_gateway selinux user group zfs ipnat ipfilter kernel_module interface php_config - mail_alias + mail_alias windows_registry_key ) types.each {|type| require "serverspec/type/#{type}" } diff --git a/lib/serverspec/helper/windows.rb b/lib/serverspec/helper/windows.rb new file mode 100644 index 00000000..5f0d7cef --- /dev/null +++ b/lib/serverspec/helper/windows.rb @@ -0,0 +1,9 @@ +module Serverspec + module Helper + module Windows + def commands + Serverspec::Commands::Windows.new + end + end + end +end diff --git a/lib/serverspec/helper/winrm.rb b/lib/serverspec/helper/winrm.rb new file mode 100644 index 00000000..d0780d89 --- /dev/null +++ b/lib/serverspec/helper/winrm.rb @@ -0,0 +1,15 @@ +module Serverspec + module Helper + module WinRM + def backend(commands_object=nil) + if ! respond_to?(:commands) + commands_object = Serverspec::Commands::Windows.new + end + instance = Serverspec::Backend::WinRM.instance + instance.set_commands(commands_object || commands) + instance + end + end + end +end + diff --git a/lib/serverspec/setup.rb b/lib/serverspec/setup.rb index a17c6dd5..491d1020 100644 --- a/lib/serverspec/setup.rb +++ b/lib/serverspec/setup.rb @@ -1,21 +1,18 @@ require 'fileutils' +require 'erb' module Serverspec class Setup def self.run - prompt = <<-EOF -Select a backend type: - 1) SSH - 2) Exec (local) + ask_os_type -Select number: -EOF - print prompt.chop - num = gets.to_i - 1 - puts + if @os_type == 'UN*X' + ask_unix_backend + else + ask_windows_backend + end - @backend_type = [ 'Ssh', 'Exec' ][num] if @backend_type == 'Ssh' print "Vagrant instance y/n: " @vagrant = gets.chomp @@ -37,14 +34,63 @@ def self.run else @hostname = 'localhost' end + [ 'spec', "spec/#{@hostname}" ].each { |dir| safe_mkdir(dir) } safe_create_spec safe_create_spec_helper safe_create_rakefile end - def self.safe_create_spec + def self.ask_os_type + prompt = <<-EOF +Select OS type: + + 1) UN*X + 2) Windows + +Select number: +EOF + + print prompt.chop + num = gets.to_i - 1 + puts + + @os_type = [ 'UN*X', 'Windows' ][num] || 'UN*X' + end + + def self.ask_unix_backend + prompt = <<-EOF +Select a backend type: + + 1) SSH + 2) Exec (local) + +Select number: +EOF + print prompt.chop + num = gets.to_i - 1 + puts + + @backend_type = [ 'Ssh', 'Exec' ][num] || 'Exec' + end + + def self.ask_windows_backend + prompt = <<-EOF +Select a backend type: + + 1) WinRM + 2) Cmd (local) +Select number: +EOF + print prompt.chop + num = gets.to_i - 1 + puts + + @backend_type = [ 'WinRM', 'Cmd' ][num] || 'Exec' + end + + def self.safe_create_spec content = <<-EOF require 'spec_helper' @@ -92,77 +138,8 @@ def self.safe_mkdir(dir) end def self.safe_create_spec_helper - content = <<-EOF -require 'serverspec' -require 'pathname' -### include requirements ### - -### include backend helper ### -include Serverspec::Helper::DetectOS - -RSpec.configure do |c| - if ENV['ASK_SUDO_PASSWORD'] - require 'highline/import' - c.sudo_password = ask("Enter sudo password: ") { |q| q.echo = false } - else - c.sudo_password = ENV['SUDO_PASSWORD'] - end - ### include backend conf ### -end -EOF - - if not @backend_type.nil? - content.gsub!(/### include backend helper ###/, "include Serverspec::Helper::#{@backend_type}") - case @backend_type - when 'Ssh' - content.gsub!(/### include requirements ###/, "require 'net/ssh'") - content.gsub!(/### include backend conf ###/, "c.before :all do - block = self.class.metadata[:example_group_block] - if RUBY_VERSION.start_with?('1.8') - file = block.to_s.match(/.*@(.*):[0-9]+>/)[1] - else - file = block.source_location.first - end - host = File.basename(Pathname.new(file).dirname) - if c.host != host - c.ssh.close if c.ssh - c.host = host - options = Net::SSH::Config.for(c.host) - user = options[:user] || Etc.getlogin - ### include vagrant conf ### - c.ssh = Net::SSH.start(c.host, user, options) - end - end") - if @vagrant - content.gsub!(/### include vagrant conf ###/," - vagrant_up = `vagrant up #{@hostname}` - config = `vagrant ssh-config #{@hostname}` - if config != '' - config.each_line do |line| - if match = /HostName (.*)/.match(line) - c.host = match[1] - elsif match = /User (.*)/.match(line) - user = match[1] - elsif match = /IdentityFile (.*)/.match(line) - options[:keys] = [match[1].gsub(/\"/,'')] - elsif match = /Port (.*)/.match(line) - options[:port] = match[1] - end - end - end - ") - else - content.gsub!(/### include vagrant conf ###/,'') - end - when 'Exec' - content.gsub!(/### include backend conf ###/, "c.before :all do - end") - when 'Puppet' - content.gsub!(/### include requirements ###/, "require 'puppet'\nrequire 'serverspec/backend/puppet' -") - end - end - + requirements = [] + content = ERB.new(spec_helper_template, nil, '-').result(binding) if File.exists? 'spec/spec_helper.rb' old_content = File.read('spec/spec_helper.rb') if old_content != content @@ -234,5 +211,83 @@ def self.auto_vagrant_configuration end end + def self.spec_helper_template + template = <<-EOF +require 'serverspec' +<% if @os_type == 'UN*X' -%> +require 'pathname' +<% end -%> +<% if @backend_type == 'Ssh' -%> +require 'net/ssh' +<% end -%> +<% if @backend_type == 'WinRM' -%> +require 'winrm' +<% end -%> + +include Serverspec::Helper::<%= @backend_type %> +<% if @os_type == 'UN*X' -%> +include Serverspec::Helper::DetectOS +<% else -%> +include Serverspec::Helper::Windows +<% end -%> + +<% if @os_type == 'UN*X' -%> +RSpec.configure do |c| + if ENV['ASK_SUDO_PASSWORD'] + require 'highline/import' + c.sudo_password = ask("Enter sudo password: ") { |q| q.echo = false } + else + c.sudo_password = ENV['SUDO_PASSWORD'] + end + <%- if @backend_type == 'Ssh' -%> + c.before :all do + block = self.class.metadata[:example_group_block] + if RUBY_VERSION.start_with?('1.8') + file = block.to_s.match(/.*@(.*):[0-9]+>/)[1] + else + file = block.source_location.first + end + host = File.basename(Pathname.new(file).dirname) + if c.host != host + c.ssh.close if c.ssh + c.host = host + options = Net::SSH::Config.for(c.host) + user = options[:user] || Etc.getlogin + <%- if @vagrant -%> + vagrant_up = `vagrant up #{@hostname}` + config = `vagrant ssh-config #{@hostname}` + if config != '' + config.each_line do |line| + if match = /HostName (.*)/.match(line) + c.host = match[1] + elsif match = /User (.*)/.match(line) + user = match[1] + elsif match = /IdentityFile (.*)/.match(line) + options[:keys] = [match[1].gsub(/\"/,'')] + elsif match = /Port (.*)/.match(line) + options[:port] = match[1] + end + end + end + <%- end -%> + c.ssh = Net::SSH.start(c.host, user, options) + end + end + <%- end -%> +end +<% end -%> +<% if @backend_type == 'WinRM'-%> +RSpec.configure do |c| + user = + pass = + endpoint = "http://:5985/wsman" + + c.winrm = ::WinRM::WinRMWebService.new(endpoint, :ssl, :user => user, :pass => pass, :basic_auth_only => true) + c.winrm.set_timeout 300 # 5 minutes max timeout for any operation +end +<% end -%> +EOF + template + end end end diff --git a/lib/serverspec/type/windows_registry_key.rb b/lib/serverspec/type/windows_registry_key.rb new file mode 100644 index 00000000..8987cb48 --- /dev/null +++ b/lib/serverspec/type/windows_registry_key.rb @@ -0,0 +1,21 @@ +module Serverspec + module Type + class WindowsRegistryKey < Base + def exists? + backend.check_registry_key(@name) + end + + def has_property?(property_name, property_type = :type_string) + backend.check_registry_key(@name, {:name => property_name, :type => property_type}) + end + + def has_value?(value) + backend.check_registry_key(@name, {:name => '', :type => :type_string, :value => value}) + end + + def has_property_value?(property_name, property_type, value) + backend.check_registry_key(@name, {:name => property_name, :type => property_type, :value => value}) + end + end + end +end diff --git a/lib/serverspec/version.rb b/lib/serverspec/version.rb index e1f523ae..d489485b 100644 --- a/lib/serverspec/version.rb +++ b/lib/serverspec/version.rb @@ -1,3 +1,3 @@ module Serverspec - VERSION = "0.8.1" + VERSION = "0.9.4" end diff --git a/spec/aix/command_spec.rb b/spec/aix/command_spec.rb new file mode 100644 index 00000000..89ab4fed --- /dev/null +++ b/spec/aix/command_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe command('cat /etc/resolv.conf') do + let(:stdout) { "nameserver 127.0.0.1\r\n" } + it { should return_stdout("nameserver 127.0.0.1") } + its(:command) { should eq 'cat /etc/resolv.conf' } +end + +describe 'complete matching of stdout' do + context command('cat /etc/resolv.conf') do + let(:stdout) { "foocontent-should-be-includedbar\r\n" } + it { should_not return_stdout('content-should-be-included') } + end +end + +describe 'regexp matching of stdout' do + context command('cat /etc/resolv.conf') do + let(:stdout) { "nameserver 127.0.0.1\r\n" } + it { should return_stdout(/127\.0\.0\.1/) } + end +end + +describe command('cat /etc/resolv.conf') do + let(:stdout) { "No such file or directory\r\n" } + it { should return_stderr("No such file or directory") } + its(:command) { should eq 'cat /etc/resolv.conf' } +end + +describe 'complete matching of stderr' do + context command('cat /etc/resolv.conf') do + let(:stdout) { "No such file or directory\r\n" } + it { should_not return_stdout('file') } + end +end + +describe 'regexp matching of stderr' do + context command('cat /etc/resolv.conf') do + let(:stdout) { "No such file or directory\r\n" } + it { should return_stderr(/file/) } + end +end + +describe command('cat /etc/resolv.conf') do + it { should return_exit_status 0 } + its(:command) { should eq 'cat /etc/resolv.conf' } +end diff --git a/spec/aix/cron_spec.rb b/spec/aix/cron_spec.rb new file mode 100644 index 00000000..5fc868f0 --- /dev/null +++ b/spec/aix/cron_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe cron do + it { should have_entry '* * * * * /usr/local/bin/batch.sh' } + its(:command) { should eq 'crontab -l | grep -- \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ /usr/local/bin/batch.sh' } +end + +describe cron do + it { should_not have_entry 'invalid entry' } +end + +describe cron do + it { should have_entry('* * * * * /usr/local/bin/batch.sh').with_user('root') } + its(:command) { should eq 'crontab -u root -l | grep -- \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ /usr/local/bin/batch.sh' } +end + +describe cron do + it { should_not have_entry('* * * * * /usr/local/bin/batch.sh').with_user('invalid-user') } +end diff --git a/spec/aix/default_gateway_spec.rb b/spec/aix/default_gateway_spec.rb new file mode 100644 index 00000000..0f512594 --- /dev/null +++ b/spec/aix/default_gateway_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe default_gateway do + let(:stdout) { "default via 192.168.1.1 dev eth1 \r\n" } + + its(:ipaddress) { should eq '192.168.1.1' } + its(:command) { should eq "ip route | grep -E '^default |^default '" } + + its(:interface) { should eq 'eth1' } + its(:command) { should eq "ip route | grep -E '^default |^default '" } + + its(:ipaddress) { should_not eq '192.168.1.2' } + its(:interface) { should_not eq 'eth0' } +end diff --git a/spec/aix/file_spec.rb b/spec/aix/file_spec.rb new file mode 100644 index 00000000..94e955b2 --- /dev/null +++ b/spec/aix/file_spec.rb @@ -0,0 +1,390 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe file('/etc/ssh/sshd_config') do + it { should be_file } + its(:command) { should eq "test -f /etc/ssh/sshd_config" } +end + +describe file('/etc/invalid_file') do + it { should_not be_file } +end + +describe file('/etc/ssh') do + it { should be_directory } + its(:command) { should eq "test -d /etc/ssh" } +end + +describe file('/etc/invalid_directory') do + it { should_not be_directory } +end + +describe file('/var/run/unicorn.sock') do + it { should be_socket } + its(:command) { should eq "test -S /var/run/unicorn.sock" } +end + +describe file('/etc/invalid_socket') do + it { should_not be_socket } +end + +describe file('/etc/ssh/sshd_config') do + it { should contain 'This is the sshd server system-wide configuration file' } + its(:command) { should eq "grep -q -- This\\ is\\ the\\ sshd\\ server\\ system-wide\\ configuration\\ file /etc/ssh/sshd_config || grep -qF -- This\\ is\\ the\\ sshd\\ server\\ system-wide\\ configuration\\ file /etc/ssh/sshd_config" } +end + +describe file('/etc/ssh/sshd_config') do + it { should contain /^This is the sshd server system-wide configuration file/ } + its(:command) { should eq "grep -q -- \\^This\\ is\\ the\\ sshd\\ server\\ system-wide\\ configuration\\ file /etc/ssh/sshd_config || grep -qF -- \\^This\\ is\\ the\\ sshd\\ server\\ system-wide\\ configuration\\ file /etc/ssh/sshd_config" } +end + +describe file('/etc/ssh/sshd_config') do + it { should_not contain 'This is invalid text!!' } +end + +describe file('Gemfile') do + it { should contain('rspec').from(/^group :test do/).to(/^end/) } + its(:command) { should eq "sed -n /\\^group\\ :test\\ do/,/\\^end/p Gemfile | grep -q -- rspec - || sed -n /\\^group\\ :test\\ do/,/\\^end/p Gemfile | grep -qF -- rspec -" } +end + +describe file('/etc/ssh/sshd_config') do + it { should_not contain('This is invalid text!!').from(/^group :test do/).to(/^end/) } +end + +describe file('Gemfile') do + it { should contain('rspec').after(/^group :test do/) } + its(:command) { should eq "sed -n /\\^group\\ :test\\ do/,\\$p Gemfile | grep -q -- rspec - || sed -n /\\^group\\ :test\\ do/,\\$p Gemfile | grep -qF -- rspec -" } +end + +describe file('/etc/ssh/sshd_config') do + it { should_not contain('This is invalid text!!').after(/^group :test do/) } +end + +describe file('Gemfile') do + it { should contain('rspec').before(/^end/) } + its(:command) { should eq "sed -n 1,/\\^end/p Gemfile | grep -q -- rspec - || sed -n 1,/\\^end/p Gemfile | grep -qF -- rspec -" } +end + +describe file('/etc/ssh/sshd_config') do + it { should_not contain('This is invalid text!!').before(/^end/) } +end + +describe file('/etc/passwd') do + it { should be_mode 644 } +end + +describe file('/etc/passwd') do + it { should be_owned_by 'root' } + its(:command) { should eq "ls -al /etc/passwd | awk '{print $3}' | grep -- \\^root\\$" } +end + +describe file('/etc/passwd') do + it { should_not be_owned_by 'invalid-owner' } +end + +describe file('/etc/passwd') do + it { should be_grouped_into 'root' } + its(:command) { should eq "ls -al /etc/passwd | awk '{print $4}' | grep -- \\^root\\$" } +end + +describe file('/etc/passwd') do + it { should_not be_grouped_into 'invalid-group' } +end + +describe file('/etc/pam.d/system-auth') do + it { should be_linked_to '/etc/pam.d/system-auth-ac' } + its(:command) { should eq "stat -c %N /etc/pam.d/system-auth | grep -- /etc/pam.d/system-auth-ac" } +end + +describe file('dummy-link') do + it { should_not be_linked_to '/invalid/target' } +end + +describe file('/dev') do + let(:stdout) { "755\r\n" } + it { should be_readable } + its(:command) { should eq "stat -c %a /dev" } +end + +describe file('/dev') do + let(:stdout) { "333\r\n" } + it { should_not be_readable } +end + +describe file('/dev') do + let(:stdout) { "400\r\n" } + it { should be_readable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "044\r\n" } + it { should_not be_readable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "040\r\n" } + it { should be_readable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "404\r\n" } + it { should_not be_readable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "044\r\n" } + it { should be_readable.by('others') } +end + +describe file('/dev') do + let(:stdout) { "443\r\n" } + it { should_not be_readable.by('others') } +end + +describe file('/tmp') do + it { should be_readable.by_user('mail') } + its(:command) { should eq "su -s sh -c \"test -r /tmp\" mail" } +end + +describe file('/tmp') do + it { should_not be_readable.by_user('invalid-user') } +end + +describe file('/dev') do + let(:stdout) { "755\r\n" } + it { should be_writable } + its(:command) { should eq "stat -c %a /dev" } +end + +describe file('/dev') do + let(:stdout) { "555\r\n" } + it { should_not be_writable } +end + +describe file('/dev') do + let(:stdout) { "200\r\n" } + it { should be_writable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "555\r\n" } + it { should_not be_writable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "030\r\n" } + it { should be_writable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "555\r\n" } + it { should_not be_writable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "666\r\n" } + it { should be_writable.by('others') } +end + +describe file('/dev') do + let(:stdout) { "555\r\n" } + it { should_not be_writable.by('others') } +end + +describe file('/tmp') do + it { should be_writable.by_user('mail') } + its(:command) { should eq "su -s sh -c \"test -w /tmp\" mail" } +end + +describe file('/tmp') do + it { should_not be_writable.by_user('invalid-user') } +end + +describe file('/dev') do + let(:stdout) { "755\r\n" } + it { should be_executable } + its(:command) { should eq "stat -c %a /dev" } +end + +describe file('/dev') do + let(:stdout) { "666\r\n" } + it { should_not be_executable } +end + +describe file('/dev') do + let(:stdout) { "100\r\n" } + it { should be_executable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "666\r\n" } + it { should_not be_executable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "070\r\n" } + it { should be_executable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "666\r\n" } + it { should_not be_executable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "001\r\n" } + it { should be_executable.by('others') } +end + +describe file('/dev') do + let(:stdout) { "666\r\n" } + it { should_not be_executable.by('others') } +end + +describe file('/tmp') do + it { should be_executable.by_user('mail') } + its(:command) { should eq "su -s sh -c \"test -x /tmp\" mail" } +end + +describe file('/tmp') do + it { should_not be_executable.by_user('invalid-user') } +end + +describe file('/') do + it { should be_mounted } + its(:command) { should eq "mount | grep -w -- on\\ /" } +end + +describe file('/etc/invalid-mount') do + it { should_not be_mounted } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should be_mounted.with( :type => 'ext4' ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should be_mounted.with( :type => 'ext4', :options => { :rw => true } ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should be_mounted.with( :type => 'ext4', :options => { :mode => 620 } ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should be_mounted.with( :type => 'ext4', :device => '/dev/mapper/VolGroup-lv_root' ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'xfs' ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'ext4', :options => { :rw => false } ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'ext4', :options => { :mode => 600 } ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'xfs', :device => '/dev/mapper/VolGroup-lv_root' ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'ext4', :device => '/dev/mapper/VolGroup-lv_r00t' ) } +end + +describe file('/etc/invalid-mount') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'ext4' ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it do + should be_mounted.only_with( + :device => '/dev/mapper/VolGroup-lv_root', + :type => 'ext4', + :options => { + :rw => true, + :mode => 620, + } + ) + end +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it do + should_not be_mounted.only_with( + :device => '/dev/mapper/VolGroup-lv_root', + :type => 'ext4', + :options => { + :rw => true, + :mode => 620, + :bind => true, + } + ) + end +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it do + should_not be_mounted.only_with( + :device => '/dev/mapper/VolGroup-lv_root', + :type => 'ext4', + :options => { + :rw => true, + } + ) + end +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it do + should_not be_mounted.only_with( + :device => '/dev/mapper/VolGroup-lv_roooooooooot', + :type => 'ext4', + :options => { + :rw => true, + :mode => 620, + } + ) + end +end + +describe file('/etc/invalid-mount') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.only_with( :type => 'ext4' ) } +end + +describe file('/etc/services') do + it { should match_md5checksum '35435ea447c19f0ea5ef971837ab9ced' } + its(:command) { should eq "md5sum /etc/services | grep -iw -- \\^35435ea447c19f0ea5ef971837ab9ced" } +end + +describe file('invalid-file') do + it { should_not match_md5checksum 'INVALIDMD5CHECKSUM' } +end + +describe file('/etc/services') do + it { should match_sha256checksum '0c3feee1353a8459f8c7d84885e6bc602ef853751ffdbce3e3b6dfa1d345fc7a' } + its(:command) { should eq "sha256sum /etc/services | grep -iw -- \\^0c3feee1353a8459f8c7d84885e6bc602ef853751ffdbce3e3b6dfa1d345fc7a" } +end + +describe file('invalid-file') do + it { should_not match_sha256checksum 'INVALIDSHA256CHECKSUM' } +end diff --git a/spec/aix/group_spec.rb b/spec/aix/group_spec.rb new file mode 100644 index 00000000..c05c38f9 --- /dev/null +++ b/spec/aix/group_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe group('root') do + it { should exist } + its(:command) { should eq "getent group | grep -wq -- root" } +end + +describe group('invalid-group') do + it { should_not exist } +end + +describe group('root') do + it { should have_gid 0 } + its(:command) { should eq "cat etc/group | grep -w -- \\^root | cut -f 3 -d ':' | grep -w -- 0" } +end + +describe group('root') do + it { should_not have_gid 'invalid-gid' } +end diff --git a/spec/aix/host_spec.rb b/spec/aix/host_spec.rb new file mode 100644 index 00000000..c16fb6da --- /dev/null +++ b/spec/aix/host_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe host('127.0.0.1') do + it { should be_resolvable } + its(:command) { should eq "getent hosts 127.0.0.1" } +end + +describe host('invalid-name') do + it { should_not be_resolvable } +end + +describe host('127.0.0.1') do + it { should be_resolvable.by('hosts') } + its(:command) { should eq "grep -w -- 127.0.0.1 /etc/hosts" } +end + +describe host('invalid-name') do + it { should_not be_resolvable.by('hosts') } +end + +describe host('127.0.0.1') do + it { should be_resolvable.by('dns') } + its(:command) { should eq "nslookup -timeout=1 127.0.0.1" } +end + +describe host('invalid-name') do + it { should_not be_resolvable.by('dns') } +end + +describe host('127.0.0.1') do + it { should be_reachable } + its(:command) { should eq "ping -n 127.0.0.1 -w 5 -c 2" } +end + +describe host('invalid-host') do + it { should_not be_reachable } +end + +describe host('127.0.0.1') do + it { should be_reachable.with(:proto => "icmp", :timeout=> 1) } + its(:command) { should eq "ping -n 127.0.0.1 -w 1 -c 2" } +end + +describe host('127.0.0.1') do + it { should be_reachable.with(:proto => "tcp", :port => 22, :timeout=> 1) } + its(:command) { should eq "nc -vvvvzt 127.0.0.1 22 -w 1" } +end + +describe host('127.0.0.1') do + it { should be_reachable.with(:proto => "udp", :port => 53, :timeout=> 1) } + its(:command) { should eq "nc -vvvvzu 127.0.0.1 53 -w 1" } +end + +describe host('invalid-host') do + it { should_not be_reachable.with(:proto => "udp", :port => 53, :timeout=> 1) } +end diff --git a/spec/aix/linux_kernel_parameter_spec.rb b/spec/aix/linux_kernel_parameter_spec.rb new file mode 100644 index 00000000..7e164d7a --- /dev/null +++ b/spec/aix/linux_kernel_parameter_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe linux_kernel_parameter('net.ipv4.tcp_syncookies') do + let(:stdout) { "1\n" } + its(:value) { should eq 1 } + its(:command) { should eq "/sbin/sysctl -q -n net.ipv4.tcp_syncookies" } +end + +describe linux_kernel_parameter('net.ipv4.tcp_syncookies') do + let(:stdout) { "1\n" } + its(:value) { should_not eq 2 } +end + +describe linux_kernel_parameter('kernel.osrelease') do + let(:stdout) { "2.6.32-131.0.15.el6.x86_64\n" } + its(:value) { should eq "2.6.32-131.0.15.el6.x86_64" } + its(:command) { should eq "/sbin/sysctl -q -n kernel.osrelease" } +end + +describe linux_kernel_parameter('kernel.osrelease') do + let(:stdout) { "2.6.32-131.0.15.el6.x86_64\n" } + its(:value) { should_not eq "2.6.32-131.0.15.el6.i386" } +end + +describe linux_kernel_parameter('net.ipv4.tcp_wmem') do + let(:stdout) { "4096 16384 4194304\n" } + its(:value) { should match /16384/ } + its(:command) { should eq "/sbin/sysctl -q -n net.ipv4.tcp_wmem" } +end + +describe linux_kernel_parameter('net.ipv4.tcp_wmem') do + let(:stdout) { "4096 16384 4194304\n" } + its(:value) { should_not match /123456/ } +end diff --git a/spec/aix/package_spec.rb b/spec/aix/package_spec.rb new file mode 100644 index 00000000..58973945 --- /dev/null +++ b/spec/aix/package_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe package('httpd') do + it { should be_installed } + its(:command) { should eq "lslpp -L httpd" } +end + +describe package('invalid-package') do + it { should_not be_installed } +end + +package('invalid-package') do + it { should_not be_installed.by('rpm') } +end + +describe package('httpd') do + it { should be_installed.with_version('2.2.15-28.el6') } + its(:command) { should eq "lslpp -L httpd | awk '{print $2}' | grep -w -- 2.2.15-28.el6" } +end + +describe package('httpd') do + it { should_not be_installed.with_version('invalid-version') } +end + +describe package('jekyll') do + it { should be_installed.by('gem') } + its(:command) { should eq "gem list --local | grep -w -- \\^jekyll" } +end + +describe package('invalid-gem') do + it { should_not be_installed.by('gem') } +end + +describe package('jekyll') do + it { should be_installed.by('gem').with_version('1.1.1') } + its(:command) { should eq "gem list --local | grep -w -- \\^jekyll | grep -w -- 1.1.1" } +end + +describe package('jekyll') do + it { should_not be_installed.by('gem').with_version('invalid-version') } +end + +describe package('bower') do + it { should be_installed.by('npm') } + its(:command) { should eq "npm ls bower -g" } +end + +describe package('invalid-npm-package') do + it { should_not be_installed.by('npm') } +end + +describe package('bower') do + it { should be_installed.by('npm').with_version('0.9.2') } + its(:command) { should eq "npm ls bower -g | grep -w -- 0.9.2" } +end + +describe package('bower') do + it { should_not be_installed.by('npm').with_version('invalid-version') } +end + + +describe package('mongo') do + it { should be_installed.by('pecl') } + its(:command) { should eq "pecl list | grep -w -- \\^mongo" } +end + +describe package('invalid-pecl') do + it { should_not be_installed.by('pecl') } +end + +describe package('mongo') do + it { should be_installed.by('pecl').with_version('1.4.1') } + its(:command) { should eq "pecl list | grep -w -- \\^mongo | grep -w -- 1.4.1" } +end + +describe package('mongo') do + it { should_not be_installed.by('pecl').with_version('invalid-version') } +end + +describe package('supervisor') do + it { should be_installed.by('pip').with_version('3.0') } + its(:command) { should eq "pip list | grep -w -- \\^supervisor | grep -w -- 3.0" } +end + +describe package('invalid-pip') do + it { should_not be_installed.by('pip').with_version('invalid-version') } +end diff --git a/spec/aix/php_config_spec.rb b/spec/aix/php_config_spec.rb new file mode 100644 index 00000000..7e6dcbc4 --- /dev/null +++ b/spec/aix/php_config_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe php_config('default_mimetype') do + let(:stdout) { 'text/html' } + its(:value) { should eq 'text/html' } + its(:command) { should eq "php -r 'echo get_cfg_var( \"default_mimetype\" );'" } +end + +describe php_config('default_mimetype') do + let(:stdout) { 'text/html' } + its(:value) { should_not eq 'text/plain' } +end + +describe php_config('session.cache_expire') do + let(:stdout) { '180' } + its(:value) { should eq 180 } + its(:command) { should eq "php -r 'echo get_cfg_var( \"session.cache_expire\" );'" } +end + +describe php_config('session.cache_expire') do + let(:stdout) { '180' } + its(:value) { should_not eq 360 } +end + +describe php_config('mbstring.http_output_conv_mimetypes') do + let(:stdout) { 'application' } + its(:value) { should match /application/ } + its(:command) { should eq "php -r 'echo get_cfg_var( \"mbstring.http_output_conv_mimetypes\" );'" } +end + +describe php_config('mbstring.http_output_conv_mimetypes') do + let(:stdout) { 'application' } + its(:value) { should_not match /html/ } +end diff --git a/spec/aix/port_spec.rb b/spec/aix/port_spec.rb new file mode 100644 index 00000000..55bccc5e --- /dev/null +++ b/spec/aix/port_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe port(80) do + it { should be_listening } + its(:command) { should eq "netstat -an -f inet | awk '{print $4}' | grep -- *.80 " } +end + +describe port('invalid') do + it { should_not be_listening } +end + +describe port(80) do + it { should be_listening.with("tcp") } + its(:command) { should eq 'netstat -tunl | grep -- \\^tcp\\ .\\*:80\\ ' } +end + +describe port(123) do + it { should be_listening.with("udp") } + its(:command) { should eq 'netstat -tunl | grep -- \\^udp\\ .\\*:123\\ ' } +end + +describe port(80) do + it { + expect { + should be_listening.with('not implemented') + }.to raise_error(ArgumentError, %r/\A`be_listening` matcher doesn\'t support/) + } +end diff --git a/spec/aix/routing_table_spec.rb b/spec/aix/routing_table_spec.rb new file mode 100644 index 00000000..6c2713df --- /dev/null +++ b/spec/aix/routing_table_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe routing_table do + let(:stdout) { "192.168.100.0/24 dev eth1 proto kernel scope link src 192.168.100.10 \r\ndefault via 192.168.100.1 dev eth0 \r\n" } + it { should have_entry( :destination => '192.168.100.0/24' ) } + its(:command) { should eq "ip route | grep -E '^192.168.100.0/24 |^default '" } +end + +describe routing_table do + let(:stdout) { "192.168.100.0/24 dev eth1 proto kernel scope link src 192.168.100.10 \r\ndefault via 192.168.100.1 dev eth0 \r\n" } + it { should_not have_entry( :destination => '192.168.100.100/24' ) } + its(:command) { should eq "ip route | grep -E '^192.168.100.100/24 |^default '" } +end + +describe routing_table do + let(:stdout) { "192.168.100.0/24 dev eth1 proto kernel scope link src 192.168.100.10 \r\ndefault via 192.168.100.1 dev eth0 \r\n" } + it do + should have_entry( + :destination => '192.168.100.0/24', + :gateway => '192.168.100.1' + ) + end + + it do + should have_entry( + :destination => '192.168.100.0/24', + :gateway => '192.168.100.1', + :interface => 'eth1' + ) + end + + it do + should_not have_entry( + :gateway => '192.168.100.1', + :interface => 'eth1' + ) + end + + it do + should_not have_entry( + :destination => '192.168.100.0/32', + :gateway => '192.168.100.1', + :interface => 'eth1' + ) + end +end + +describe routing_table do + let(:stdout) { "192.168.200.0/24 via 192.168.200.1 dev eth0 \r\ndefault via 192.168.100.1 dev eth0 \r\n" } + it { should have_entry( :destination => '192.168.200.0/24' ) } + it { should_not have_entry( :destination => '192.168.200.200/24' ) } + + it do + should have_entry( + :destination => '192.168.200.0/24', + :gateway => '192.168.200.1' + ) + end + + it do + should have_entry( + :destination => '192.168.200.0/24', + :gateway => '192.168.200.1', + :interface => 'eth0' + ) + end + + it do + should_not have_entry( + :gateway => '192.168.200.1', + :interface => 'eth0' + ) + end + + it do + should_not have_entry( + :destination => '192.168.200.0/32', + :gateway => '192.168.200.1', + :interface => 'eth0' + ) + end +end + +describe routing_table do + let(:stdout) { "default via 10.0.2.2 dev eth0 \r\n" } + it { should have_entry( :destination => 'default' ) } + it { should_not have_entry( :destination => 'defaulth' ) } + + it do + should have_entry( + :destination => 'default', + :gateway => '10.0.2.2' + ) + end + + it do + should have_entry( + :destination => 'default', + :gateway => '10.0.2.2', + :interface => 'eth0' + ) + end + + it do + should_not have_entry( + :gateway => '10.0.2.2', + :interface => 'eth0' + ) + end + + it do + should_not have_entry( + :destination => 'default', + :gateway => '10.0.2.1', + :interface => 'eth0' + ) + end +end diff --git a/spec/aix/service_spec.rb b/spec/aix/service_spec.rb new file mode 100644 index 00000000..384cd9cd --- /dev/null +++ b/spec/aix/service_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe service('sshd') do + it { should be_enabled } + its(:command) { should eq "lssrc -s sshd | grep active" } +end + +describe service('invalid-service') do + it { should_not be_enabled } +end + +describe service('sshd') do + it { should be_enabled.with_level(4) } + its(:command) { should eq "lssrc -s sshd | grep active" } +end + +describe service('invalid-service') do + it { should_not be_enabled.with_level(4) } +end + +describe service('sshd') do + it { should be_running } + its(:command) { should eq "ps -ef | grep -v grep | grep sshd" } +end + +describe service('invalid-daemon') do + it { should_not be_running } +end + +describe service('sshd') do + let(:stdout) { "sshd is stopped\r\n" } + it { should be_running } +end + +describe service('sshd') do + it { should be_running.under('supervisor') } + its(:command) { should eq "supervisorctl status sshd | grep RUNNING" } +end + +describe service('invalid-daemon') do + it { should_not be_running.under('supervisor') } +end + +describe service('sshd') do + it { should be_running.under('upstart') } + its(:command) { should eq "initctl status sshd | grep running" } +end + +describe service('invalid-daemon') do + it { should_not be_running.under('upstart') } +end + +describe service('sshd') do + it { + expect { + should be_running.under('not implemented') + }.to raise_error(ArgumentError, %r/\A`be_running` matcher doesn\'t support/) + } +end + +describe service('sshd') do + let(:stdout) { "Process 'sshd'\r\n status running\r\n monitoring status monitored" } + it { should be_monitored_by('monit') } + its(:command) { should eq "monit status" } +end + +describe service('sshd') do + let(:stdout) { "Process 'sshd'\r\n status not monitored\r\n monitoring status not monitored" } + it { should_not be_monitored_by('monit') } +end + +describe service('invalid-daemon') do + it { should_not be_monitored_by('monit') } +end + +describe service('unicorn') do + it { should be_monitored_by('god') } + its(:command) { should eq "god status unicorn" } +end + +describe service('invalid-daemon') do + it { should_not be_monitored_by('god') } +end + +describe service('sshd') do + it { + expect { + should be_monitored_by('not implemented') + }.to raise_error(ArgumentError, %r/\A`be_monitored_by` matcher doesn\'t support/) + } +end diff --git a/spec/aix/user_spec.rb b/spec/aix/user_spec.rb new file mode 100644 index 00000000..2e1c3565 --- /dev/null +++ b/spec/aix/user_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +include Serverspec::Helper::AIX + +describe user('root') do + it { should exist } + its(:command) { should eq "id root" } +end + +describe user('invalid-user') do + it { should_not exist } +end + +describe user('root') do + it { should belong_to_group 'root' } + its(:command) { should eq "lsuser -a groups root | awk -F'=' '{print $2}'| sed -e 's/,/ /g' |grep -w -- root" } +end + +describe user('root') do + it { should_not belong_to_group 'invalid-group' } +end + +describe user('root') do + it { should have_uid 0 } + its(:command) { should eq "id root | grep -- \\^uid\\=0\\(" } +end + +describe user('root') do + it { should_not have_uid 'invalid-uid' } +end + +describe user('root') do + it { should have_login_shell '/bin/bash' } + its(:command) { should eq "lsuser -a shell root |awk -F'=' '{print $2}' | grep -w -- /bin/bash" } +end + +describe user('root') do + it { should_not have_login_shell 'invalid-login-shell' } +end + +describe user('root') do + it { should have_home_directory '/root' } + its(:command) { should eq "lsuser -a home root | awk -F'=' '{print $2}' | grep -w -- /root" } +end + +describe user('root') do + it { should_not have_home_directory 'invalid-home-directory' } +end + +describe user('root') do + it { should have_authorized_key 'ssh-rsa ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH foo@bar.local' } + its(:command) { should eq "grep -w -- ssh-rsa\\ ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH ~root/.ssh/authorized_keys" } +end + +describe user('root') do + it { should_not have_authorized_key 'invalid-key' } +end diff --git a/spec/backend/cmd/configuration_spec.rb b/spec/backend/cmd/configuration_spec.rb new file mode 100644 index 00000000..75b10ac7 --- /dev/null +++ b/spec/backend/cmd/configuration_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' +require 'support/powershell_command_runner' + +include Serverspec::Helper::Cmd +include Serverspec::Helper::Windows + +describe "Cmd" do + it_behaves_like "a powershell command runner" +end diff --git a/spec/backend/powershell/script_helper_spec.rb b/spec/backend/powershell/script_helper_spec.rb new file mode 100644 index 00000000..411ef754 --- /dev/null +++ b/spec/backend/powershell/script_helper_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +include Serverspec::Backend::PowerShell::ScriptHelper + +describe 'build command with path' do + before :each do + RSpec.configure do |c| + c.path = 'c:/test/path/bin' + end + end + + it "should prefix the command with the path instruction" do + cmd = build_command('run_script -f param') + cmd.should eq <<-eof +$env:path = "c:/test/path/bin;$env:path" +run_script -f param +eof + end + + after :each do + RSpec.configure do |c| + c.path = nil + end + end +end + +describe 'add pre-command' do + before :each do + Serverspec.configuration.pre_command = 'test_pre_command' + end + + it "should add the test for pre_command before the command" do + cmd = add_pre_command('run_script -f param') + cmd.should eq <<-eof +if (test_pre_command) +{ +run_script -f param +} +eof + end + + context "with path" do + before :each do + RSpec.configure do |c| + c.path = 'c:/test/path/bin' + end + end + + it "should add the path instruction and the test for pre_command before the command" do + cmd = add_pre_command('run_script -f param') + cmd.should eq <<-eof +$env:path = "c:/test/path/bin;$env:path" +if (test_pre_command) +{ +run_script -f param +} +eof + end + + after :each do + RSpec.configure do |c| + c.path = nil + end + end + end + + after :each do + Serverspec.configuration.pre_command = nil + end +end + +describe "script encoding" do + it "should encode the given script" do + script = encode_script("test_powershell_script") + script.should == "dABlAHMAdABfAHAAbwB3AGUAcgBzAGgAZQBsAGwAXwBzAGMAcgBpAHAAdAA=" + end +end diff --git a/spec/backend/winrm/configuration_spec.rb b/spec/backend/winrm/configuration_spec.rb new file mode 100644 index 00000000..71bbfd12 --- /dev/null +++ b/spec/backend/winrm/configuration_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' +require 'support/powershell_command_runner' + +include Serverspec::Helper::WinRM +include Serverspec::Helper::Windows + +describe "WinRM" do + it_behaves_like "a powershell command runner" +end diff --git a/spec/freebsd/command_spec.rb b/spec/freebsd/command_spec.rb new file mode 100644 index 00000000..bece1602 --- /dev/null +++ b/spec/freebsd/command_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe command('cat /etc/resolv.conf') do + let(:stdout) { "nameserver 127.0.0.1\r\n" } + it { should return_stdout("nameserver 127.0.0.1") } + its(:command) { should eq 'cat /etc/resolv.conf' } +end + +describe 'complete matching of stdout' do + context command('cat /etc/resolv.conf') do + let(:stdout) { "foocontent-should-be-includedbar\r\n" } + it { should_not return_stdout('content-should-be-included') } + end +end + +describe 'regexp matching of stdout' do + context command('cat /etc/resolv.conf') do + let(:stdout) { "nameserver 127.0.0.1\r\n" } + it { should return_stdout(/127\.0\.0\.1/) } + end +end + +describe command('cat /etc/resolv.conf') do + let(:stdout) { "No such file or directory\r\n" } + it { should return_stderr("No such file or directory") } + its(:command) { should eq 'cat /etc/resolv.conf' } +end + +describe 'complete matching of stderr' do + context command('cat /etc/resolv.conf') do + let(:stdout) { "No such file or directory\r\n" } + it { should_not return_stdout('file') } + end +end + +describe 'regexp matching of stderr' do + context command('cat /etc/resolv.conf') do + let(:stdout) { "No such file or directory\r\n" } + it { should return_stderr(/file/) } + end +end + +describe command('cat /etc/resolv.conf') do + it { should return_exit_status 0 } + its(:command) { should eq 'cat /etc/resolv.conf' } +end diff --git a/spec/freebsd/cron_spec.rb b/spec/freebsd/cron_spec.rb new file mode 100644 index 00000000..f88435a6 --- /dev/null +++ b/spec/freebsd/cron_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe cron do + it { should have_entry '* * * * * /usr/local/bin/batch.sh' } + its(:command) { should eq 'crontab -l | grep -- \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ /usr/local/bin/batch.sh' } +end + +describe cron do + it { should_not have_entry 'invalid entry' } +end + +describe cron do + it { should have_entry('* * * * * /usr/local/bin/batch.sh').with_user('root') } + its(:command) { should eq 'crontab -u root -l | grep -- \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ \\\\\\*\\ /usr/local/bin/batch.sh' } +end + +describe cron do + it { should_not have_entry('* * * * * /usr/local/bin/batch.sh').with_user('invalid-user') } +end diff --git a/spec/freebsd/default_gateway_spec.rb b/spec/freebsd/default_gateway_spec.rb new file mode 100644 index 00000000..e24c474f --- /dev/null +++ b/spec/freebsd/default_gateway_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe default_gateway do + let(:stdout) { "default via 192.168.1.1 dev eth1 \r\n" } + + its(:ipaddress) { should eq '192.168.1.1' } + its(:command) { should eq "ip route | grep -E '^default |^default '" } + + its(:interface) { should eq 'eth1' } + its(:command) { should eq "ip route | grep -E '^default |^default '" } + + its(:ipaddress) { should_not eq '192.168.1.2' } + its(:interface) { should_not eq 'eth0' } +end diff --git a/spec/freebsd/file_spec.rb b/spec/freebsd/file_spec.rb new file mode 100644 index 00000000..2ca9f78d --- /dev/null +++ b/spec/freebsd/file_spec.rb @@ -0,0 +1,367 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe file('/etc/ssh/sshd_config') do + it { should be_file } + its(:command) { should eq "test -f /etc/ssh/sshd_config" } +end + +describe file('/etc/invalid_file') do + it { should_not be_file } +end + +describe file('/etc/ssh') do + it { should be_directory } + its(:command) { should eq "test -d /etc/ssh" } +end + +describe file('/etc/invalid_directory') do + it { should_not be_directory } +end + +describe file('/var/run/unicorn.sock') do + it { should be_socket } + its(:command) { should eq "test -S /var/run/unicorn.sock" } +end + +describe file('/etc/invalid_socket') do + it { should_not be_socket } +end + +describe file('/etc/ssh/sshd_config') do + it { should contain 'This is the sshd server system-wide configuration file' } + its(:command) { should eq "grep -q -- This\\ is\\ the\\ sshd\\ server\\ system-wide\\ configuration\\ file /etc/ssh/sshd_config || grep -qF -- This\\ is\\ the\\ sshd\\ server\\ system-wide\\ configuration\\ file /etc/ssh/sshd_config" } +end + +describe file('/etc/ssh/sshd_config') do + it { should contain /^This is the sshd server system-wide configuration file/ } + its(:command) { should eq "grep -q -- \\^This\\ is\\ the\\ sshd\\ server\\ system-wide\\ configuration\\ file /etc/ssh/sshd_config || grep -qF -- \\^This\\ is\\ the\\ sshd\\ server\\ system-wide\\ configuration\\ file /etc/ssh/sshd_config" } +end + +describe file('/etc/ssh/sshd_config') do + it { should_not contain 'This is invalid text!!' } +end + +describe file('Gemfile') do + it { should contain('rspec').from(/^group :test do/).to(/^end/) } + its(:command) { should eq "sed -n /\\^group\\ :test\\ do/,/\\^end/p Gemfile | grep -q -- rspec - || sed -n /\\^group\\ :test\\ do/,/\\^end/p Gemfile | grep -qF -- rspec -" } +end + +describe file('/etc/ssh/sshd_config') do + it { should_not contain('This is invalid text!!').from(/^group :test do/).to(/^end/) } +end + +describe file('Gemfile') do + it { should contain('rspec').after(/^group :test do/) } + its(:command) { should eq "sed -n /\\^group\\ :test\\ do/,\\$p Gemfile | grep -q -- rspec - || sed -n /\\^group\\ :test\\ do/,\\$p Gemfile | grep -qF -- rspec -" } +end + +describe file('/etc/ssh/sshd_config') do + it { should_not contain('This is invalid text!!').after(/^group :test do/) } +end + +describe file('Gemfile') do + it { should contain('rspec').before(/^end/) } + its(:command) { should eq "sed -n 1,/\\^end/p Gemfile | grep -q -- rspec - || sed -n 1,/\\^end/p Gemfile | grep -qF -- rspec -" } +end + +describe file('/etc/ssh/sshd_config') do + it { should_not contain('This is invalid text!!').before(/^end/) } +end + +describe file('/etc/passwd') do + it { should be_mode 644 } + its(:command) { should eq "stat -c %a /etc/passwd | grep -- \\^644\\$" } +end + +describe file('/etc/passwd') do + it { should_not be_mode 'invalid' } +end + +describe file('/etc/passwd') do + it { should be_owned_by 'root' } + its(:command) { should eq "stat -c %U /etc/passwd | grep -- \\^root\\$" } +end + +describe file('/etc/passwd') do + it { should_not be_owned_by 'invalid-owner' } +end + +describe file('/etc/passwd') do + it { should be_grouped_into 'root' } + its(:command) { should eq "stat -c %G /etc/passwd | grep -- \\^root\\$" } +end + +describe file('/etc/passwd') do + it { should_not be_grouped_into 'invalid-group' } +end + +describe file('/etc/pam.d/system-auth') do + it { should be_linked_to '/etc/pam.d/system-auth-ac' } + its(:command) { should eq "stat -c %N /etc/pam.d/system-auth | grep -- /etc/pam.d/system-auth-ac" } +end + +describe file('dummy-link') do + it { should_not be_linked_to '/invalid/target' } +end + +describe file('/dev') do + let(:stdout) { "755\r\n" } + it { should be_readable } + its(:command) { should eq "stat -c %a /dev" } +end + +describe file('/dev') do + let(:stdout) { "333\r\n" } + it { should_not be_readable } +end + +describe file('/dev') do + let(:stdout) { "400\r\n" } + it { should be_readable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "044\r\n" } + it { should_not be_readable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "040\r\n" } + it { should be_readable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "404\r\n" } + it { should_not be_readable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "044\r\n" } + it { should be_readable.by('others') } +end + +describe file('/dev') do + let(:stdout) { "443\r\n" } + it { should_not be_readable.by('others') } +end + +describe file('/dev') do + let(:stdout) { "755\r\n" } + it { should be_writable } + its(:command) { should eq "stat -c %a /dev" } +end + +describe file('/dev') do + let(:stdout) { "555\r\n" } + it { should_not be_writable } +end + +describe file('/dev') do + let(:stdout) { "200\r\n" } + it { should be_writable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "555\r\n" } + it { should_not be_writable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "030\r\n" } + it { should be_writable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "555\r\n" } + it { should_not be_writable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "666\r\n" } + it { should be_writable.by('others') } +end + +describe file('/dev') do + let(:stdout) { "555\r\n" } + it { should_not be_writable.by('others') } +end + +describe file('/dev') do + let(:stdout) { "755\r\n" } + it { should be_executable } +end + +describe file('/dev') do + let(:stdout) { "666\r\n" } + it { should_not be_executable } +end + +describe file('/dev') do + let(:stdout) { "100\r\n" } + it { should be_executable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "666\r\n" } + it { should_not be_executable.by('owner') } +end + +describe file('/dev') do + let(:stdout) { "070\r\n" } + it { should be_executable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "666\r\n" } + it { should_not be_executable.by('group') } +end + +describe file('/dev') do + let(:stdout) { "001\r\n" } + it { should be_executable.by('others') } +end + +describe file('/dev') do + let(:stdout) { "666\r\n" } + it { should_not be_executable.by('others') } +end + +describe file('/') do + it { should be_mounted } + its(:command) { should eq "mount | grep -w -- on\\ /" } +end + +describe file('/etc/invalid-mount') do + it { should_not be_mounted } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should be_mounted.with( :type => 'ext4' ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should be_mounted.with( :type => 'ext4', :options => { :rw => true } ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should be_mounted.with( :type => 'ext4', :options => { :mode => 620 } ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should be_mounted.with( :type => 'ext4', :device => '/dev/mapper/VolGroup-lv_root' ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'xfs' ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'ext4', :options => { :rw => false } ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'ext4', :options => { :mode => 600 } ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'xfs', :device => '/dev/mapper/VolGroup-lv_root' ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'ext4', :device => '/dev/mapper/VolGroup-lv_r00t' ) } +end + +describe file('/etc/invalid-mount') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.with( :type => 'ext4' ) } +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it do + should be_mounted.only_with( + :device => '/dev/mapper/VolGroup-lv_root', + :type => 'ext4', + :options => { + :rw => true, + :mode => 620, + } + ) + end +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it do + should_not be_mounted.only_with( + :device => '/dev/mapper/VolGroup-lv_root', + :type => 'ext4', + :options => { + :rw => true, + :mode => 620, + :bind => true, + } + ) + end +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it do + should_not be_mounted.only_with( + :device => '/dev/mapper/VolGroup-lv_root', + :type => 'ext4', + :options => { + :rw => true, + } + ) + end +end + +describe file('/') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it do + should_not be_mounted.only_with( + :device => '/dev/mapper/VolGroup-lv_roooooooooot', + :type => 'ext4', + :options => { + :rw => true, + :mode => 620, + } + ) + end +end + +describe file('/etc/invalid-mount') do + let(:stdout) { "/dev/mapper/VolGroup-lv_root on / type ext4 (rw,mode=620)\r\n" } + it { should_not be_mounted.only_with( :type => 'ext4' ) } +end + +describe file('/etc/services') do + it { should match_md5checksum '35435ea447c19f0ea5ef971837ab9ced' } + its(:command) { should eq "md5sum /etc/services | grep -iw -- \\^35435ea447c19f0ea5ef971837ab9ced" } +end + +describe file('invalid-file') do + it { should_not match_md5checksum 'INVALIDMD5CHECKSUM' } +end + +describe file('/etc/services') do + it { should match_sha256checksum '0c3feee1353a8459f8c7d84885e6bc602ef853751ffdbce3e3b6dfa1d345fc7a' } + its(:command) { should eq "sha256sum /etc/services | grep -iw -- \\^0c3feee1353a8459f8c7d84885e6bc602ef853751ffdbce3e3b6dfa1d345fc7a" } +end + +describe file('invalid-file') do + it { should_not match_sha256checksum 'INVALIDSHA256CHECKSUM' } +end diff --git a/spec/freebsd/group_spec.rb b/spec/freebsd/group_spec.rb new file mode 100644 index 00000000..6f0010de --- /dev/null +++ b/spec/freebsd/group_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe group('root') do + it { should exist } + its(:command) { should eq "getent group | grep -wq -- root" } +end + +describe group('invalid-group') do + it { should_not exist } +end + +describe group('root') do + it { should have_gid 0 } + its(:command) { should eq "getent group | grep -w -- \\^root | cut -f 3 -d ':' | grep -w -- 0" } +end + +describe group('root') do + it { should_not have_gid 'invalid-gid' } +end diff --git a/spec/freebsd/host_spec.rb b/spec/freebsd/host_spec.rb new file mode 100644 index 00000000..febde258 --- /dev/null +++ b/spec/freebsd/host_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe host('127.0.0.1') do + it { should be_resolvable } + its(:command) { should eq "getent hosts 127.0.0.1" } +end + +describe host('invalid-name') do + it { should_not be_resolvable } +end + +describe host('127.0.0.1') do + it { should be_resolvable.by('hosts') } + its(:command) { should eq "grep -w -- 127.0.0.1 /etc/hosts" } +end + +describe host('invalid-name') do + it { should_not be_resolvable.by('hosts') } +end + +describe host('127.0.0.1') do + it { should be_resolvable.by('dns') } + its(:command) { should eq "nslookup -timeout=1 127.0.0.1" } +end + +describe host('invalid-name') do + it { should_not be_resolvable.by('dns') } +end + +describe host('127.0.0.1') do + it { should be_reachable } + its(:command) { should eq "ping -n 127.0.0.1 -w 5 -c 2" } +end + +describe host('invalid-host') do + it { should_not be_reachable } +end + +describe host('127.0.0.1') do + it { should be_reachable.with(:proto => "icmp", :timeout=> 1) } + its(:command) { should eq "ping -n 127.0.0.1 -w 1 -c 2" } +end + +describe host('127.0.0.1') do + it { should be_reachable.with(:proto => "tcp", :port => 22, :timeout=> 1) } + its(:command) { should eq "nc -vvvvzt 127.0.0.1 22 -w 1" } +end + +describe host('127.0.0.1') do + it { should be_reachable.with(:proto => "udp", :port => 53, :timeout=> 1) } + its(:command) { should eq "nc -vvvvzu 127.0.0.1 53 -w 1" } +end + +describe host('invalid-host') do + it { should_not be_reachable.with(:proto => "udp", :port => 53, :timeout=> 1) } +end diff --git a/spec/freebsd/package_spec.rb b/spec/freebsd/package_spec.rb new file mode 100644 index 00000000..164d9382 --- /dev/null +++ b/spec/freebsd/package_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe package('httpd') do + it { should be_installed } + its(:command) { should eq "pkg_version -X -s httpd" } +end + +describe package('invalid-package') do + it { should_not be_installed } +end + +describe package('httpd') do + it { should be_installed.with_version('2.2.15-28.el6') } + its(:command) { should eq "pkg_version -X -s httpd"} +end + +describe package('jekyll') do + it { should be_installed.by('gem') } + its(:command) { should eq "gem list --local | grep -w -- \\^jekyll" } +end + +describe package('invalid-gem') do + it { should_not be_installed.by('gem') } +end + +describe package('jekyll') do + it { should be_installed.by('gem').with_version('1.1.1') } + its(:command) { should eq "gem list --local | grep -w -- \\^jekyll | grep -w -- 1.1.1" } +end + +describe package('jekyll') do + it { should_not be_installed.by('gem').with_version('invalid-version') } +end + +describe package('bower') do + it { should be_installed.by('npm') } + its(:command) { should eq "npm ls bower -g" } +end + +describe package('invalid-npm-package') do + it { should_not be_installed.by('npm') } +end + +describe package('bower') do + it { should be_installed.by('npm').with_version('0.9.2') } + its(:command) { should eq "npm ls bower -g | grep -w -- 0.9.2" } +end + +describe package('bower') do + it { should_not be_installed.by('npm').with_version('invalid-version') } +end + + +describe package('mongo') do + it { should be_installed.by('pecl') } + its(:command) { should eq "pecl list | grep -w -- \\^mongo" } +end + +describe package('invalid-pecl') do + it { should_not be_installed.by('pecl') } +end + +describe package('mongo') do + it { should be_installed.by('pecl').with_version('1.4.1') } + its(:command) { should eq "pecl list | grep -w -- \\^mongo | grep -w -- 1.4.1" } +end + +describe package('mongo') do + it { should_not be_installed.by('pecl').with_version('invalid-version') } +end + +describe package('supervisor') do + it { should be_installed.by('pip').with_version('3.0') } + its(:command) { should eq "pip list | grep -w -- \\^supervisor | grep -w -- 3.0" } +end + +describe package('invalid-pip') do + it { should_not be_installed.by('pip').with_version('invalid-version') } +end diff --git a/spec/freebsd/php_config_spec.rb b/spec/freebsd/php_config_spec.rb new file mode 100644 index 00000000..382ded0b --- /dev/null +++ b/spec/freebsd/php_config_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe php_config('default_mimetype') do + let(:stdout) { 'text/html' } + its(:value) { should eq 'text/html' } + its(:command) { should eq "php -r 'echo get_cfg_var( \"default_mimetype\" );'" } +end + +describe php_config('default_mimetype') do + let(:stdout) { 'text/html' } + its(:value) { should_not eq 'text/plain' } +end + +describe php_config('session.cache_expire') do + let(:stdout) { '180' } + its(:value) { should eq 180 } + its(:command) { should eq "php -r 'echo get_cfg_var( \"session.cache_expire\" );'" } +end + +describe php_config('session.cache_expire') do + let(:stdout) { '180' } + its(:value) { should_not eq 360 } +end + +describe php_config('mbstring.http_output_conv_mimetypes') do + let(:stdout) { 'application' } + its(:value) { should match /application/ } + its(:command) { should eq "php -r 'echo get_cfg_var( \"mbstring.http_output_conv_mimetypes\" );'" } +end + +describe php_config('mbstring.http_output_conv_mimetypes') do + let(:stdout) { 'application' } + its(:value) { should_not match /html/ } +end diff --git a/spec/freebsd/port_spec.rb b/spec/freebsd/port_spec.rb new file mode 100644 index 00000000..343c2e60 --- /dev/null +++ b/spec/freebsd/port_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe port(80) do + it { should be_listening } + its(:command) { should eq 'sockstat -46l -p 80 | grep -- :80\\ ' } +end + +describe port('invalid') do + it { should_not be_listening } +end + +describe port(80) do + it { should be_listening.with("tcp") } + its(:command) { should eq 'netstat -tunl | grep -- \\^tcp\\ .\\*:80\\ ' } +end + +describe port(123) do + it { should be_listening.with("udp") } + its(:command) { should eq 'netstat -tunl | grep -- \\^udp\\ .\\*:123\\ ' } +end + +describe port(80) do + it { + expect { + should be_listening.with('not implemented') + }.to raise_error(ArgumentError, %r/\A`be_listening` matcher doesn\'t support/) + } +end diff --git a/spec/freebsd/routing_table_spec.rb b/spec/freebsd/routing_table_spec.rb new file mode 100644 index 00000000..40c6e0f7 --- /dev/null +++ b/spec/freebsd/routing_table_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe routing_table do + let(:stdout) { "192.168.100.0/24 dev eth1 proto kernel scope link src 192.168.100.10 \r\ndefault via 192.168.100.1 dev eth0 \r\n" } + it { should have_entry( :destination => '192.168.100.0/24' ) } + its(:command) { should eq "ip route | grep -E '^192.168.100.0/24 |^default '" } +end + +describe routing_table do + let(:stdout) { "192.168.100.0/24 dev eth1 proto kernel scope link src 192.168.100.10 \r\ndefault via 192.168.100.1 dev eth0 \r\n" } + it { should_not have_entry( :destination => '192.168.100.100/24' ) } + its(:command) { should eq "ip route | grep -E '^192.168.100.100/24 |^default '" } +end + +describe routing_table do + let(:stdout) { "192.168.100.0/24 dev eth1 proto kernel scope link src 192.168.100.10 \r\ndefault via 192.168.100.1 dev eth0 \r\n" } + it do + should have_entry( + :destination => '192.168.100.0/24', + :gateway => '192.168.100.1' + ) + end + + it do + should have_entry( + :destination => '192.168.100.0/24', + :gateway => '192.168.100.1', + :interface => 'eth1' + ) + end + + it do + should_not have_entry( + :gateway => '192.168.100.1', + :interface => 'eth1' + ) + end + + it do + should_not have_entry( + :destination => '192.168.100.0/32', + :gateway => '192.168.100.1', + :interface => 'eth1' + ) + end +end + +describe routing_table do + let(:stdout) { "192.168.200.0/24 via 192.168.200.1 dev eth0 \r\ndefault via 192.168.100.1 dev eth0 \r\n" } + it { should have_entry( :destination => '192.168.200.0/24' ) } + it { should_not have_entry( :destination => '192.168.200.200/24' ) } + + it do + should have_entry( + :destination => '192.168.200.0/24', + :gateway => '192.168.200.1' + ) + end + + it do + should have_entry( + :destination => '192.168.200.0/24', + :gateway => '192.168.200.1', + :interface => 'eth0' + ) + end + + it do + should_not have_entry( + :gateway => '192.168.200.1', + :interface => 'eth0' + ) + end + + it do + should_not have_entry( + :destination => '192.168.200.0/32', + :gateway => '192.168.200.1', + :interface => 'eth0' + ) + end +end + +describe routing_table do + let(:stdout) { "default via 10.0.2.2 dev eth0 \r\n" } + it { should have_entry( :destination => 'default' ) } + it { should_not have_entry( :destination => 'defaulth' ) } + + it do + should have_entry( + :destination => 'default', + :gateway => '10.0.2.2' + ) + end + + it do + should have_entry( + :destination => 'default', + :gateway => '10.0.2.2', + :interface => 'eth0' + ) + end + + it do + should_not have_entry( + :gateway => '10.0.2.2', + :interface => 'eth0' + ) + end + + it do + should_not have_entry( + :destination => 'default', + :gateway => '10.0.2.1', + :interface => 'eth0' + ) + end +end diff --git a/spec/freebsd/service_spec.rb b/spec/freebsd/service_spec.rb new file mode 100644 index 00000000..9f3a3a19 --- /dev/null +++ b/spec/freebsd/service_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + +describe service('sshd') do + it { should be_enabled } + its(:command) { should eq "service -e | grep -- sshd" } +end + +describe service('invalid-service') do + it { should_not be_enabled } +end + +describe service('sshd') do + it { should be_enabled.with_level(4) } + its(:command) { should eq "service -e | grep -- sshd" } +end + +describe service('invalid-service') do + it { should_not be_enabled.with_level(4) } +end + +describe service('sshd') do + it { should be_running } + its(:command) { should eq "service sshd status" } +end + +describe service('invalid-daemon') do + it { should_not be_running } +end + +describe service('sshd') do + let(:stdout) { "sshd is stopped\r\n" } + it { should be_running } +end + +describe service('sshd') do + it { should be_running.under('supervisor') } + its(:command) { should eq "supervisorctl status sshd | grep RUNNING" } +end + +describe service('invalid-daemon') do + it { should_not be_running.under('supervisor') } +end + +describe service('sshd') do + it { should be_running.under('upstart') } + its(:command) { should eq "initctl status sshd | grep running" } +end + +describe service('invalid-daemon') do + it { should_not be_running.under('upstart') } +end + +describe service('sshd') do + it { + expect { + should be_running.under('not implemented') + }.to raise_error(ArgumentError, %r/\A`be_running` matcher doesn\'t support/) + } +end + +describe service('sshd') do + let(:stdout) { "Process 'sshd'\r\n status running\r\n monitoring status monitored" } + it { should be_monitored_by('monit') } + its(:command) { should eq "monit status" } +end + +describe service('sshd') do + let(:stdout) { "Process 'sshd'\r\n status not monitored\r\n monitoring status not monitored" } + it { should_not be_monitored_by('monit') } +end + +describe service('invalid-daemon') do + it { should_not be_monitored_by('monit') } +end + +describe service('unicorn') do + it { should be_monitored_by('god') } + its(:command) { should eq "god status unicorn" } +end + +describe service('invalid-daemon') do + it { should_not be_monitored_by('god') } +end + +describe service('sshd') do + it { + expect { + should be_monitored_by('not implemented') + }.to raise_error(ArgumentError, %r/\A`be_monitored_by` matcher doesn\'t support/) + } +end diff --git a/spec/freebsd/user_spec.rb b/spec/freebsd/user_spec.rb new file mode 100644 index 00000000..a4b1214f --- /dev/null +++ b/spec/freebsd/user_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +include Serverspec::Helper::FreeBSD + + +describe user('root') do + it { should exist } + its(:command) { should eq "id root" } +end + +describe user('invalid-user') do + it { should_not exist } +end + +describe user('root') do + it { should belong_to_group 'root' } + its(:command) { should eq "id root | awk '{print $3}' | grep -- root" } +end + +describe user('root') do + it { should_not belong_to_group 'invalid-group' } +end + +describe user('root') do + it { should have_uid 0 } + its(:command) { should eq "id root | grep -- \\^uid\\=0\\(" } +end + +describe user('root') do + it { should_not have_uid 'invalid-uid' } +end + +describe user('root') do + it { should have_login_shell '/bin/bash' } + its(:command) { should eq "getent passwd root | cut -f 7 -d ':' | grep -w -- /bin/bash" } +end + +describe user('root') do + it { should_not have_login_shell 'invalid-login-shell' } +end + +describe user('root') do + it { should have_home_directory '/root' } + its(:command) { should eq "getent passwd root | cut -f 6 -d ':' | grep -w -- /root" } +end + +describe user('root') do + it { should_not have_home_directory 'invalid-home-directory' } +end + +describe user('root') do + it { should have_authorized_key 'ssh-rsa ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH foo@bar.local' } + its(:command) { should eq "grep -w -- ssh-rsa\\ ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH ~root/.ssh/authorized_keys" } +end + +describe user('root') do + it { should_not have_authorized_key 'invalid-key' } +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 126271ba..28436355 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,10 +11,8 @@ module Serverspec module Backend - class Exec - def run_command(cmd) - cmd = build_command(cmd) - cmd = add_pre_command(cmd) + module TestCommandRunner + def do_run cmd if @example @example.metadata[:subject].set_command(cmd) end @@ -36,29 +34,23 @@ def run_command(cmd) end end end - - class Ssh - def run_command(cmd) - cmd = build_command(cmd) - cmd = add_pre_command(cmd) - if @example - @example.metadata[:subject].set_command(cmd) + [Exec, Ssh].each do |clz| + clz.class_eval do + include TestCommandRunner + def run_command(cmd) + cmd = build_command(cmd) + cmd = add_pre_command(cmd) + do_run cmd end - - if cmd =~ /invalid/ - { - :stdout => ::Serverspec.configuration.stdout, - :stderr => ::Serverspec.configuration.stderr, - :exit_status => 1, - :exit_signal => nil - } - else - { - :stdout => ::Serverspec.configuration.stdout, - :stderr => ::Serverspec.configuration.stderr, - :exit_status => 0, - :exit_signal => nil - } + end + end + [Cmd, WinRM].each do |clz| + clz.class_eval do + include TestCommandRunner + def run_command(cmd) + cmd = build_command(cmd.script) + cmd = add_pre_command(cmd) + do_run cmd end end end diff --git a/spec/support/powershell_command_runner.rb b/spec/support/powershell_command_runner.rb new file mode 100644 index 00000000..165b0813 --- /dev/null +++ b/spec/support/powershell_command_runner.rb @@ -0,0 +1,52 @@ +shared_examples "a powershell command runner" do + describe 'configurations are not set' do + context file('/some/file') do + it { should be_file } + its(:command) { should == "((Get-Item -Path '/some/file' -Force).attributes.ToString() -Split ', ') -contains 'Archive'" } + end + end + + describe 'path is set' do + let(:path) { 'c:/path/bin' } + context file('/some/file') do + it { should be_file } + its(:command) { + should == <<-eof +$env:path = "c:/path/bin;$env:path" +((Get-Item -Path '/some/file' -Force).attributes.ToString() -Split ', ') -contains 'Archive' +eof + } + end + end + + describe 'pre_command is set' do + let(:pre_command) { 'some_other_command' } + context file('/some/file') do + it { should be_file } + its(:command) { should == <<-eof +if (some_other_command) +{ +((Get-Item -Path '/some/file' -Force).attributes.ToString() -Split ', ') -contains 'Archive' +} +eof + } + end + end + + describe 'path and pre_command are set' do + let(:path) { 'c:/path/bin' } + let(:pre_command) { 'some_other_command' } + context file('/some/file') do + it { should be_file } + its(:command) { should == <<-eof +$env:path = "c:/path/bin;$env:path" +if (some_other_command) +{ +$env:path = "c:/path/bin;$env:path" +((Get-Item -Path '/some/file' -Force).attributes.ToString() -Split ', ') -contains 'Archive' +} +eof + } + end + end +end \ No newline at end of file diff --git a/spec/windows/file_spec.rb b/spec/windows/file_spec.rb new file mode 100644 index 00000000..acee9799 --- /dev/null +++ b/spec/windows/file_spec.rb @@ -0,0 +1,161 @@ +require 'spec_helper' + +include Serverspec::Helper::Cmd +include Serverspec::Helper::Windows + +describe file('/some/valid/file') do + it { should be_file } + its(:command) { should == "((Get-Item -Path '/some/valid/file' -Force).attributes.ToString() -Split ', ') -contains 'Archive'" } +end + +describe file('/some/invalid/file') do + it { should_not be_file } +end + +describe file('/some/valid/folder') do + it { should be_directory } + its(:command) { should == "((Get-Item -Path '/some/valid/folder' -Force).attributes.ToString() -Split ', ') -contains 'Directory'" } +end + +describe file('/some/invalid/folder') do + it { should_not be_directory } +end + +describe file('/some/file') do + it { should contain 'search text' } + its(:command) { should == "[Io.File]::ReadAllText('/some/file') -match 'search text'" } +end + +describe file('/some/file') do + it { should contain /^search text/ } + its(:command) { should == "[Io.File]::ReadAllText('/some/file') -match '^search text'" } +end + +describe file('/some/file') do + it { should_not contain 'This is invalid text!!' } +end + +describe file('Gemfile') do + it { should contain('rspec').from(/^group :test do/).to(/^end/) } + its(:command) { should == "(CropText -text ([Io.File]::ReadAllText('Gemfile')) -fromPattern '^group :test do' -toPattern '^end') -match 'rspec'" } +end + +describe file('/some/file') do + it { should_not contain('This is invalid text!!').from(/^group :test do/).to(/^end/) } +end + +describe file('Gemfile') do + it { should contain('rspec').after(/^group :test do/) } + its(:command) { should == "(CropText -text ([Io.File]::ReadAllText('Gemfile')) -fromPattern '^group :test do' -toPattern '$') -match 'rspec'" } +end + +describe file('Gemfile') do + it { should_not contain('This is invalid text!!').after(/^group :test do/) } +end + +describe file('Gemfile') do + it { should contain('rspec').before(/end/) } + its(:command) { should == "(CropText -text ([Io.File]::ReadAllText('Gemfile')) -fromPattern '^' -toPattern 'end') -match 'rspec'" } +end + +describe file('Gemfile') do + it { should_not contain('This is invalid text!!').before(/^end/) } +end + +describe file('/some/file') do + it { should be_readable } + its(:command) { should eq "CheckFileAccessRules -path '/some/file' -identity 'Everyone' -rules @('FullControl', 'Modify', 'ReadAndExecute', 'Read', 'ListDirectory')" } +end + +describe file('/some/invalid/file') do + it { should_not be_readable } +end + +describe file('/some/file') do + it "should raise error if trying to check access by 'owner' or 'group' or 'others'" do + ['owner', 'group', 'others'].each do |access| + expect { should be_readable.by(access) }.to raise_error + end + end +end + +describe file('/some/file') do + it { should be_readable.by('test.identity') } + its(:command) { should eq "CheckFileAccessRules -path '/some/file' -identity 'test.identity' -rules @('FullControl', 'Modify', 'ReadAndExecute', 'Read', 'ListDirectory')" } +end + +describe file('/some/file') do + it { should be_readable.by_user('test.identity') } + its(:command) { should eq "CheckFileAccessRules -path '/some/file' -identity 'test.identity' -rules @('FullControl', 'Modify', 'ReadAndExecute', 'Read', 'ListDirectory')" } +end + +describe file('/some/file') do + it { should be_writable } + its(:command) { should eq "CheckFileAccessRules -path '/some/file' -identity 'Everyone' -rules @('FullControl', 'Modify', 'Write')" } +end + +describe file('/some/invalid/file') do + it { should_not be_writable } +end + +describe file('/some/file') do + it "should raise error if trying to check access by 'owner' or 'group' or 'others'" do + ['owner', 'group', 'others'].each do |access| + expect { should be_writable.by(access) }.to raise_error + end + end +end + +describe file('/some/file') do + it { should be_writable.by('test.identity') } + its(:command) { should eq "CheckFileAccessRules -path '/some/file' -identity 'test.identity' -rules @('FullControl', 'Modify', 'Write')" } +end + +describe file('/some/file') do + it { should be_writable.by_user('test.identity') } + its(:command) { should eq "CheckFileAccessRules -path '/some/file' -identity 'test.identity' -rules @('FullControl', 'Modify', 'Write')" } +end + +describe file('/some/file') do + it { should be_executable } + its(:command) { should eq "CheckFileAccessRules -path '/some/file' -identity 'Everyone' -rules @('FullControl', 'Modify', 'ReadAndExecute', 'ExecuteFile')" } +end + +describe file('/some/invalid/file') do + it { should_not be_executable } +end + +describe file('/some/file') do + it "should raise error if trying to check access by 'owner' or 'group' or 'others'" do + ['owner', 'group', 'others'].each do |access| + expect { should be_executable.by(access) }.to raise_error + end + end +end + +describe file('/some/file') do + it { should be_executable.by('test.identity') } + its(:command) { should eq "CheckFileAccessRules -path '/some/file' -identity 'test.identity' -rules @('FullControl', 'Modify', 'ReadAndExecute', 'ExecuteFile')" } +end + +describe file('/some/file') do + it { should be_executable.by_user('test.identity') } + its(:command) { should eq "CheckFileAccessRules -path '/some/file' -identity 'test.identity' -rules @('FullControl', 'Modify', 'ReadAndExecute', 'ExecuteFile')" } +end + +describe file('/some/test/file') do + it "should raise error if command is not supported" do + { + :be_socket => [], + :be_mode => 644, + :be_owned_by => 'root', + :be_grouped_into => 'root', + :be_linked_to => '/some/other/file', + :be_mounted => [], + :match_md5checksum => '35435ea447c19f0ea5ef971837ab9ced', + :match_sha256checksum => '0c3feee1353a8459f8c7d84885e6bc602ef853751ffdbce3e3b6dfa1d345fc7a' + }.each do |method, args| + expect { should self.send(method, *args) }.to raise_error Serverspec::Commands::Windows::NotSupportedError + end + end +end diff --git a/spec/windows/group_spec.rb b/spec/windows/group_spec.rb new file mode 100644 index 00000000..35c706ca --- /dev/null +++ b/spec/windows/group_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +include Serverspec::Helper::Cmd +include Serverspec::Helper::Windows + +describe group('test.group') do + it { should exist } + its(:command) { should eq "(FindGroup -groupName 'test.group') -ne $null" } +end + +describe group('test.domain\test.group') do + it { should exist } + its(:command) { should eq "(FindGroup -groupName 'test.group' -domain 'test.domain') -ne $null" } +end + +describe group('invalid-group') do + it { should_not exist } +end + +describe group('test.group') do + it "should raise error if command is not supported" do + { + :have_gid => [nil], + }.each do |method, args| + expect { should self.send(method, *args) }.to raise_error Serverspec::Commands::Windows::NotSupportedError + end + end +end + diff --git a/spec/windows/port_spec.rb b/spec/windows/port_spec.rb new file mode 100644 index 00000000..558d2f8b --- /dev/null +++ b/spec/windows/port_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +include Serverspec::Helper::Cmd +include Serverspec::Helper::Windows + +describe port(80) do + it { should be_listening } + its(:command) { should eq 'IsPortListening -portNumber 80' } +end + +describe port('invalid') do + it { should_not be_listening } +end + +describe port(80) do + it { should be_listening.with("tcp") } + its(:command) { should eq "IsPortListening -portNumber 80 -protocol 'tcp'" } +end + +describe port(123) do + it { should be_listening.with("udp") } + its(:command) { should eq "IsPortListening -portNumber 123 -protocol 'udp'" } +end + +describe port(80) do + it { + expect { + should be_listening.with('not implemented') + }.to raise_error(ArgumentError, %r/\A`be_listening` matcher doesn\'t support/) + } +end diff --git a/spec/windows/user_spec.rb b/spec/windows/user_spec.rb new file mode 100644 index 00000000..f454e0cf --- /dev/null +++ b/spec/windows/user_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +include Serverspec::Helper::Cmd +include Serverspec::Helper::Windows + +describe user('test.user') do + it { should exist } + its(:command) { should eq "(FindUser -userName 'test.user') -ne $null" } +end + +describe user('test.domain\test.user') do + it { should exist } + its(:command) { should eq "(FindUser -userName 'test.user' -domain 'test.domain') -ne $null" } +end + +describe user('invalid-user') do + it { should_not exist } +end + +describe user('test.user') do + it { should belong_to_group 'test.group' } + its(:command) { should eq "(FindUserGroup -userName 'test.user' -groupName 'test.group') -ne $null" } +end + +describe user('test.user.domain\test.user') do + it { should belong_to_group 'test.group.domain\test.group' } + its(:command) { should eq "(FindUserGroup -userName 'test.user' -userDomain 'test.user.domain' -groupName 'test.group' -groupDomain 'test.group.domain') -ne $null" } +end + +describe user('test.user') do + it { should_not belong_to_group 'invalid-group' } +end + +describe user('test.user') do + it "should raise error if command is not supported" do + { + :have_uid => [nil], + :have_login_shell => [nil], + :have_authorized_key => [nil], + }.each do |method, args| + expect { should self.send(method, *args) }.to raise_error Serverspec::Commands::Windows::NotSupportedError + end + end +end