2015-10-09 17:10:10 +00:00
# encoding: utf-8
# author: Christoph Hartmann
# author: Dominik Richter
# Usage:
# describe host('example.com') do
# it { should be_resolvable }
# it { should be_reachable }
2016-05-03 22:14:33 +00:00
# its('ipaddress') { should include '93.184.216.34' }
2015-10-09 17:10:10 +00:00
# end
#
# To verify a hostname with protocol and port
# describe host('example.com', port: 53, proto: 'udp') do
# it { should be_reachable }
# end
#
# We do not support the following serverspec syntax:
# describe host('example.com') do
# it { should be_reachable.with( :port => 22 ) }
# it { should be_reachable.with( :port => 22, :proto => 'tcp' ) }
# it { should be_reachable.with( :port => 53, :proto => 'udp' ) }
#
# it { should be_resolvable.by('hosts') }
# it { should be_resolvable.by('dns') }
# end
2016-03-08 18:06:55 +00:00
module Inspec::Resources
class Host < Inspec . resource ( 1 )
name 'host'
desc 'Use the host InSpec audit resource to test the name used to refer to a specific host and its availability, including the Internet protocols and ports over which that host name should be available.'
example "
describe host ( 'example.com' ) do
it { should be_reachable }
end
"
def initialize ( hostname , params = { } )
@hostname = hostname
@port = params [ :port ] || nil
@proto = params [ :proto ] || nil
@host_provider = nil
if inspec . os . linux?
@host_provider = LinuxHostProvider . new ( inspec )
elsif inspec . os . windows?
@host_provider = WindowsHostProvider . new ( inspec )
else
return skip_resource 'The `host` resource is not supported on your OS yet.'
end
2015-11-27 13:02:38 +00:00
end
2015-10-09 17:10:10 +00:00
2016-03-08 18:06:55 +00:00
# if we get the IP adress, the host is resolvable
def resolvable? ( type = nil )
warn " The `host` resource ignores #{ type } parameters. Continue to resolve host. " if ! type . nil?
resolve . nil? || resolve . empty? ? false : true
end
2015-10-09 17:10:10 +00:00
2016-03-08 18:06:55 +00:00
def reachable? ( port = nil , proto = nil , timeout = nil )
fail " Use `host` resource with host(' #{ @hostname } ', port: #{ port } , proto: ' #{ proto } ') parameters. " if ! port . nil? || ! proto . nil? || ! timeout . nil?
ping . nil? ? false : ping
end
2015-10-09 17:10:10 +00:00
2016-03-08 18:06:55 +00:00
# returns all A records of the IP adress, will return an array
def ipaddress
resolve . nil? || resolve . empty? ? nil : resolve
end
2015-10-09 17:10:10 +00:00
2016-03-08 18:06:55 +00:00
def to_s
" Host #{ @hostname } "
end
2015-10-09 17:10:10 +00:00
2016-03-08 18:06:55 +00:00
private
2015-10-09 17:10:10 +00:00
2016-03-08 18:06:55 +00:00
def ping
return @ping_cache if defined? ( @ping_cache )
@ping_cache = @host_provider . ping ( @hostname , @port , @proto ) if ! @host_provider . nil?
end
2015-10-09 17:10:10 +00:00
2016-03-08 18:06:55 +00:00
def resolve
return @ip_cache if defined? ( @ip_cache )
@ip_cache = @host_provider . resolve ( @hostname ) if ! @host_provider . nil?
end
2015-10-09 17:10:10 +00:00
end
2016-03-08 18:06:55 +00:00
class HostProvider
attr_reader :inspec
def initialize ( inspec )
@inspec = inspec
end
2015-10-09 17:10:10 +00:00
end
2016-03-08 18:06:55 +00:00
class LinuxHostProvider < HostProvider
# ping is difficult to achieve, since we are not sure
def ping ( hostname , _port = nil , _proto = nil )
# fall back to ping, but we can only test ICMP packages with ping
# therefore we have to skip the test, if we do not have everything on the node to run the test
ping = inspec . command ( " ping -w 1 -c 1 #{ hostname } " )
ping . exit_status . to_i != 0 ? false : true
end
2015-10-09 17:10:10 +00:00
2016-03-08 18:06:55 +00:00
def resolve ( hostname )
# TODO: we rely on getent hosts for now, but it prefers to return IPv6, only then IPv4
cmd = inspec . command ( " getent hosts #{ hostname } " )
return nil if cmd . exit_status . to_i != 0
2015-10-09 17:10:10 +00:00
2016-03-08 18:06:55 +00:00
# extract ip adress
resolve = / ^ \ s*(?<ip> \ S+) \ s+(.*) \ s*$ / . match ( cmd . stdout . chomp )
[ resolve [ 1 ] ] if resolve
end
2015-10-09 17:10:10 +00:00
end
2016-03-08 18:06:55 +00:00
# Windows
# TODO: UDP is not supported yey, we need a custom ps1 script to add udp support
# @see http://blogs.technet.com/b/josebda/archive/2015/04/18/windows-powershell-equivalents-for-common-networking-commands-ipconfig-ping-nslookup.aspx
# @see http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/19/creating-a-port-scanner-with-windows-powershell.aspx
class WindowsHostProvider < HostProvider
def ping ( hostname , port = nil , proto = nil )
# TODO: abort if we cannot run it via udp
return nil if proto == 'udp'
# ICMP: Test-NetConnection www.microsoft.com
# TCP and port: Test-NetConnection -ComputerName www.microsoft.com -RemotePort 80
request = " Test-NetConnection -ComputerName #{ hostname } "
request += " -RemotePort #{ port } " unless port . nil?
request += '| Select-Object -Property ComputerName, RemoteAddress, RemotePort, SourceAddress, PingSucceeded | ConvertTo-Json'
p request
request += '| Select-Object -Property ComputerName, PingSucceeded | ConvertTo-Json'
cmd = inspec . command ( request )
begin
ping = JSON . parse ( cmd . stdout )
rescue JSON :: ParserError = > _e
return nil
end
ping [ 'PingSucceeded' ]
2015-10-09 17:10:10 +00:00
end
2016-03-08 18:06:55 +00:00
def resolve ( hostname )
cmd = inspec . command ( " Resolve-DnsName – Type A #{ hostname } | ConvertTo-Json " )
begin
resolv = JSON . parse ( cmd . stdout )
rescue JSON :: ParserError = > _e
return nil
end
2015-10-09 17:10:10 +00:00
2016-03-08 18:06:55 +00:00
resolv = [ resolv ] unless resolv . is_a? ( Array )
resolv . map { | entry | entry [ 'IPAddress' ] }
2015-10-09 17:10:10 +00:00
end
end
end