mirror of
https://github.com/inspec/inspec
synced 2024-11-27 15:10:44 +00:00
577688a3a0
Many of the resources are named as a top-level class with a fairly generic class name, such as "OS". This causes an issue specifically with kitchen-google which depends on a gem which depends on the "os" gem which itself defines an OS class with a different superclass. This prevents users from using TK, Google Compute, and Inspec without this fix. Some mocked commands had their digest changed as well due to the new indentation, specifically in the User and RegistryKey classes. I strongly recommend viewing this diff with `git diff --ignore-space-change` to see the *real* changes. :)
151 lines
5 KiB
Ruby
151 lines
5 KiB
Ruby
# 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
|
||
|
||
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
|
||
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
|
||
"Host #{@hostname}"
|
||
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
|
||
attr_reader :inspec
|
||
def initialize(inspec)
|
||
@inspec = inspec
|
||
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
|
||
ping = inspec.command("ping -w 1 -c 1 #{hostname}")
|
||
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
|
||
cmd = inspec.command("getent hosts #{hostname}")
|
||
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
|
||
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']
|
||
end
|
||
|
||
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
|
||
|
||
resolv = [resolv] unless resolv.is_a?(Array)
|
||
resolv.map { |entry| entry['IPAddress'] }
|
||
end
|
||
end
|
||
end
|