Merge pull request #6045 from inspec/ss/enhance-host-resource

CFINSPEC-90: Enhance `host` resource
This commit is contained in:
Vasundhara Jagdale 2022-05-17 06:01:15 +00:00 committed by GitHub
commit 0978c8c274
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 144 additions and 44 deletions

View file

@ -11,21 +11,21 @@ platform = "os"
parent = "inspec/resources/os"
+++
Use the `host` Chef 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.
Use the `host` Chef InSpec audit resource to test the specific host name and its availability. This test includes the internet protocols and ports on which the respective host name must be available.
## Availability
### Installation
This resource is distributed along with Chef InSpec itself. You can use it automatically.
The Chef InSpec distributes this resource.
### Version
This resource first became available in v1.0.0 of InSpec.
This resource is available from InSpec version 1.0.
## Syntax
A `host` resource block declares a host name, and then (depending on what is to be tested) a port and/or a protocol:
A `host` resource block declares a host name, a port, and a protocol.
describe host('example.com', port: 80, protocol: 'tcp') do
it { should be_reachable }
@ -33,58 +33,98 @@ A `host` resource block declares a host name, and then (depending on what is to
its('ipaddress') { should include '12.34.56.78' }
end
where
- `host()` must specify a host name and may specify a port number and/or a protocol
- `'example.com'` is the host name
- `port:` is the port number
- `protocol:` is the Internet protocol: TCP (`protocol: 'tcp'`), UDP (`protocol: 'udp'` or ICMP (`protocol: 'icmp'`))
> where
>
> - `host()` must specify a host name. The port number and protocol are optional values.
> - `example.com` is the host name.
> - `port` is the port number.
> - `protocol` is the internet protocol, TCP (`protocol: 'tcp'`), UDP (`protocol: 'udp'`), and ICMP (`protocol: 'icmp'`)
## Properties
### ipaddress
The `ipaddress` property returns the ipaddress of the host.
The `ipaddress` property returns the IP addresses of the host.
its('ipaddress') { should include '93.184.216.34' }
### ipv4_address
The `ipv4_address` property returns the IPv4 address of the host.
its('ipv4_address') { should include '93.184.216.34' }
### ipv6_address
The `ipv6_address` property returns the IPv6 addresses of the host.
its('ipv6_address') { should include '2404:6800:4009:82a::200e' }
### connection
The `connection` property returns the connection string.
its('connection') { should match /connection refused/ }
### `protocol`
### protocol
The `protocol` property returns the protocol that given host is using.
The `protocol` property returns the protocol the specified host uses.
its('protocol') { should eq 'tcp' }
its('protocol') { should eq 'TCP' }
### `socket`property returns the socket for the given host.
### socket property returns the socket value of the specified host
its('socket') { should match /STATUS_OK/ }
## Matchers
This Chef InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [matchers page](/inspec/matchers/).
### be_reachable
The `be_reachable` matcher tests if the host name is available.
it { should be_reachable }
### be_resolvable
The `be_resolvable` matcher tests for host name resolution. For example, "resolvable to an IP address".
it { should be_resolvable }
## Examples
### Verify that host name is resolvable to specific IP address.
### Verify host name is resolvable to a specific IP address
describe host('example.com') do
its('ipaddress') { should include '93.184.216.34' }
end
### Verify host name is resolvable to a specific IPv4 address
describe host('example.com') do
its('ipv4_address') { should include '93.184.216.34' }
end
### Verify host name is resolvable to a specific IPv6 address
describe host('example.com') do
its('ipv6_address') { should include '2404:6800:4009:82a::200e' }
end
### Verify a specific IP address can be resolved
describe host('example.com') do
it { should be_resolvable }
its('ipaddress') { should include '93.184.216.34' }
end
### Verify host name is reachable over a specific protocol and port number
describe host('example.com', port: 80, protocol: 'tcp') do
it { should be_reachable }
end
### Verify that a specific IP address can be resolved
describe host('example.com') do
it { should be_resolvable }
its('ipaddress') { should include '93.184.216.34' }
end
### Review the connection setup and socket contents when checking reachability
describe host('example.com', port: 12345, protocol: 'tcp') do
@ -92,19 +132,3 @@ The `protocol` property returns the protocol that given host is using.
its('connection') { should_not match /connection refused/ }
its('socket') { should match /STATUS_OK/ }
end
## Matchers
This Chef InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [matchers page](/inspec/matchers/).
### be_reachable
The `be_reachable` matcher tests if the host name is available:
it { should be_reachable }
### be_resolvable
The `be_resolvable` matcher tests for host name resolution, i.e. "resolvable to an IP address":
it { should be_resolvable }

View file

@ -113,6 +113,16 @@ module Inspec::Resources
resolve.nil? || resolve.empty? ? nil : resolve
end
# returns an array of the ipv4 addresses
def ipv4_address
ipaddress.select { |ip| ip.match(Resolv::IPv4::Regex) }
end
# returns an array of the ipv6 addresses
def ipv6_address
ipaddress.select { |ip| ip.match(Resolv::IPv6::Regex) }
end
def to_s
resource_name = "Host #{hostname}"
resource_name += " port #{port} proto #{protocol}" if port
@ -296,15 +306,44 @@ module Inspec::Resources
end
def resolve(hostname)
addresses = []
# -Type A is the DNS query for IPv4 server Address.
cmd = inspec.command("Resolve-DnsName Type A #{hostname} | ConvertTo-Json")
begin
resolv = JSON.parse(cmd.stdout)
resolve_ipv4 = JSON.parse(cmd.stdout)
rescue JSON::ParserError => _e
return nil
end
resolv = [resolv] unless resolv.is_a?(Array)
resolv.map { |entry| entry["IPAddress"] }
resolve_ipv4 = resolve_ipv4.inject(:merge) if resolve_ipv4.is_a?(Array)
# Append the ipv4 addresses
resolve_ipv4.each_value do |ip|
matched = ip.to_s.chomp.match(Resolv::IPv4::Regex)
next if matched.nil? || addresses.include?(matched.to_s)
addresses << matched.to_s
end
# -Type AAAA is the DNS query for IPv6 server Address.
cmd = inspec.command("Resolve-DnsName Type AAAA #{hostname} | ConvertTo-Json")
begin
resolve_ipv6 = JSON.parse(cmd.stdout)
rescue JSON::ParserError => _e
return nil
end
resolve_ipv6 = resolve_ipv6.inject(:merge) if resolve_ipv6.is_a?(Array)
# Append the ipv6 addresses
resolve_ipv6.each_value do |ip|
matched = ip.to_s.chomp.match(Resolv::IPv6::Regex)
next if matched.nil? || addresses.include?(matched.to_s)
addresses << matched.to_s
end
addresses
end
end
end

12
test/fixtures/cmd/Resolve-DnsName-ipv6 vendored Normal file
View file

@ -0,0 +1,12 @@
{
"IP6Address": "2404:6800:4009:827::200e",
"Name": "microsoft.com",
"Type": 28,
"CharacterSet": 1,
"Section": 1,
"DataLength": 16,
"TTL": 176,
"Address": "2404:6800:4009:827::200e",
"IPAddress": "2404:6800:4009:827::200e",
"QueryType": 28
}

View file

@ -369,6 +369,7 @@ class MockLoader
"Get-NetAdapterBinding -ComponentID ms_bridge | Get-NetAdapter | Select-Object -Property Name, InterfaceDescription | ConvertTo-Json" => cmd.call("get-netadapter-binding-bridge"),
# host for Windows
"Resolve-DnsName Type A microsoft.com | ConvertTo-Json" => cmd.call("Resolve-DnsName"),
"Resolve-DnsName Type AAAA microsoft.com | ConvertTo-Json" => cmd.call("Resolve-DnsName-ipv6"),
"Test-NetConnection -ComputerName microsoft.com -WarningAction SilentlyContinue| Select-Object -Property ComputerName, TcpTestSucceeded, PingSucceeded | ConvertTo-Json" => cmd.call("Test-NetConnection"),
# host for Linux
"getent ahosts example.com" => cmd.call("getent-ahosts-example.com"),

View file

@ -32,7 +32,7 @@ describe "Inspec::Resources::Host" do
resource = MockLoader.new(:windows).load_resource("host", "microsoft.com")
_(resource.resolvable?).must_equal true
_(resource.reachable?).must_equal false
_(resource.ipaddress).must_equal ["134.170.185.46", "134.170.188.221"]
_(resource.ipaddress).must_equal ["134.170.188.221", "2404:6800:4009:827::200e"]
_(resource.to_s).must_equal "Host microsoft.com"
end
@ -96,7 +96,7 @@ describe "Inspec::Resources::Host" do
resource = MockLoader.new(:windows).load_resource("host", "microsoft.com", port: 1234, protocol: "tcp")
_(resource.resolvable?).must_equal true
_(resource.reachable?).must_equal true
_(resource.ipaddress).must_equal ["134.170.185.46", "134.170.188.221"]
_(resource.ipaddress).must_equal ["134.170.188.221", "2404:6800:4009:827::200e"]
_(resource.to_s).must_equal "Host microsoft.com port 1234 proto tcp"
end
@ -355,5 +355,29 @@ describe Inspec::Resources::UnixHostProvider do
ncat_command.expects(:exist?).returns(false)
_(provider.netcat_check_command("foo", 1234, "tcp")).must_be_nil
end
it "checks ipv4_address and ipv6_address properties on ubuntu" do
resource = MockLoader.new(:ubuntu).load_resource("host", "example.com")
_(resource.ipv4_address).must_equal ["12.34.56.78"]
_(resource.ipv4_address).must_include "12.34.56.78"
_(resource.ipv6_address).must_equal ["2606:2800:220:1:248:1893:25c8:1946"]
_(resource.ipv6_address).must_include "2606:2800:220:1:248:1893:25c8:1946"
end
it "checks ipv4_address and ipv6_address properties on windows" do
resource = MockLoader.new(:windows).load_resource("host", "microsoft.com")
_(resource.ipv4_address).must_equal ["134.170.188.221"]
_(resource.ipv4_address).must_include "134.170.188.221"
_(resource.ipv6_address).must_equal ["2404:6800:4009:827::200e"]
_(resource.ipv6_address).must_include "2404:6800:4009:827::200e"
end
it "checks ipv4_address and ipv6_address properties on darwin" do
resource = MockLoader.new(:macos10_10).load_resource("host", "example.com")
_(resource.ipv4_address).must_equal ["12.34.56.78"]
_(resource.ipv4_address).must_include "12.34.56.78"
_(resource.ipv6_address).must_equal ["2606:2800:220:1:248:1893:25c8:1946"]
_(resource.ipv6_address).must_include "2606:2800:220:1:248:1893:25c8:1946"
end
end
end