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 }
|
|
|
|
|
# its(:ipaddress) { should include '93.184.216.34' }
|
|
|
|
|
# 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
|
|
|
|
|
|
2015-10-26 03:04:18 +00:00
|
|
|
|
class Host < Inspec.resource(1)
|
2015-10-09 17:10:10 +00:00
|
|
|
|
name 'host'
|
|
|
|
|
|
|
|
|
|
def initialize(hostname, params = {})
|
|
|
|
|
@hostname = hostname
|
2015-10-16 21:50:54 +00:00
|
|
|
|
@port = params[:port] || nil
|
|
|
|
|
@proto = params[:proto] || nil
|
2015-10-09 17:10:10 +00:00
|
|
|
|
|
|
|
|
|
@host_provider = nil
|
2015-10-26 03:04:18 +00:00
|
|
|
|
if inspec.os.linux?
|
|
|
|
|
@host_provider = LinuxHostProvider.new(inspec)
|
|
|
|
|
elsif inspec.os.windows?
|
|
|
|
|
@host_provider = WindowsHostProvider.new(inspec)
|
2015-10-09 17:10:10 +00:00
|
|
|
|
else
|
|
|
|
|
return skip_resource 'The `host` resource is not supported on your OS yet.'
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
# returns all A records of the IP adress, will return an array
|
|
|
|
|
def ipaddress
|
|
|
|
|
resolve.nil? || resolve.empty? ? nil : resolve
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def to_s
|
2015-10-12 11:01:58 +00:00
|
|
|
|
"Host #{@hostname}"
|
2015-10-09 17:10:10 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
|
|
def ping
|
|
|
|
|
return @ping_cache if defined?(@ping_cache)
|
|
|
|
|
@ping_cache = @host_provider.ping(@hostname, @port, @proto) if !@host_provider.nil?
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def resolve
|
|
|
|
|
return @ip_cache if defined?(@ip_cache)
|
|
|
|
|
@ip_cache = @host_provider.resolve(@hostname) if !@host_provider.nil?
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
class HostProvider
|
2015-10-26 03:04:18 +00:00
|
|
|
|
attr_reader :inspec
|
|
|
|
|
def initialize(inspec)
|
|
|
|
|
@inspec = inspec
|
2015-10-09 17:10:10 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
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
|
2015-10-26 03:04:18 +00:00
|
|
|
|
ping = inspec.command("ping -w 1 -c 1 #{hostname}")
|
2015-10-09 17:10:10 +00:00
|
|
|
|
ping.exit_status.to_i != 0 ? false : true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def resolve(hostname)
|
|
|
|
|
# TODO: we rely on getent hosts for now, but it prefers to return IPv6, only then IPv4
|
2015-10-26 03:04:18 +00:00
|
|
|
|
cmd = inspec.command("getent hosts #{hostname}")
|
2015-10-09 17:10:10 +00:00
|
|
|
|
return nil if cmd.exit_status.to_i != 0
|
|
|
|
|
|
|
|
|
|
# extract ip adress
|
|
|
|
|
resolve = /^\s*(?<ip>\S+)\s+(.*)\s*$/.match(cmd.stdout.chomp)
|
|
|
|
|
[resolve[1]] if resolve
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# 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
|
2015-10-09 17:21:12 +00:00
|
|
|
|
request += '| Select-Object -Property ComputerName, PingSucceeded | ConvertTo-Json'
|
2015-10-26 03:04:18 +00:00
|
|
|
|
cmd = inspec.command(request)
|
2015-10-09 17:10:10 +00:00
|
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
ping = JSON.parse(cmd.stdout)
|
|
|
|
|
rescue JSON::ParserError => _e
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
ping['PingSucceeded']
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def resolve(hostname)
|
2015-10-26 03:04:18 +00:00
|
|
|
|
cmd = inspec.command("Resolve-DnsName –Type A #{hostname} | ConvertTo-Json")
|
2015-10-09 17:10:10 +00:00
|
|
|
|
begin
|
|
|
|
|
resolv = JSON.parse(cmd.stdout)
|
|
|
|
|
rescue JSON::ParserError => _e
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
resolv = [resolv] unless resolv.is_a?(Array)
|
|
|
|
|
resolv.map { |entry| entry['IPAddress'] }
|
|
|
|
|
end
|
|
|
|
|
end
|