mirror of
https://github.com/inspec/inspec
synced 2024-11-22 20:53:11 +00:00
support address matchers on interface resource
Adds missing functionality to `interface`. Fixes #1830 ``` describe interface("eth0") do its(ipv4_addresses) { should include 1.2.3.4 } end ``` And so on... see diff/docs for additional matchers. Signed-off-by: Matt Kulka <mkulka@parchment.com>
This commit is contained in:
parent
7887d25251
commit
633cea6673
7 changed files with 173 additions and 3 deletions
|
@ -44,16 +44,46 @@ An `interface` resource block declares network interface properties to be tested
|
|||
|
||||
### name
|
||||
|
||||
The `name` matcher tests if the named network interface exists:
|
||||
The `name` property tests if the named network interface exists:
|
||||
|
||||
its('name') { should eq eth0 }
|
||||
|
||||
### speed
|
||||
|
||||
The `speed` matcher tests the speed of the network interface, in MB/sec:
|
||||
The `speed` property tests the speed of the network interface, in MB/sec:
|
||||
|
||||
its('speed') { should eq 1000 }
|
||||
|
||||
### ipv4_addresses
|
||||
|
||||
The `ipv4_addresses` property tests if the specified address exists on the named network interface:
|
||||
|
||||
its('ipv4_addresses') { should include '127.0.0.1' }
|
||||
|
||||
### ipv4_addresses_netmask
|
||||
|
||||
The `ipv4_addresses_netmask` property tests if the specified address and netmask exists on the named network interface:
|
||||
|
||||
its('ipv4_addresses_netmask') { should include '127.0.0.1/255.0.0.0' }
|
||||
|
||||
### ipv6_addresses
|
||||
|
||||
The `ipv6_addresses` property tests if the specified address exists on the named network interface:
|
||||
|
||||
its('ipv6_addresses') { should include '::1' }
|
||||
|
||||
### ipv4_cidrs
|
||||
|
||||
The `ipv4_cidrs` property tests if the specified address and netmask combination exists on the named network interface:
|
||||
|
||||
its('ipv4_cidrs') { should include '127.0.0.1/8' }
|
||||
|
||||
### ipv6_cidrs
|
||||
|
||||
The `ipv6_cidrs` property tests if the specified address and netmask combination exists on the named network interface:
|
||||
|
||||
its('ipv6_cidrs') { should include '::1/128' }
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
@ -66,3 +96,14 @@ The `be_up` matcher tests if the network interface is available:
|
|||
|
||||
it { should be_up }
|
||||
|
||||
### have_an_ipv4_address
|
||||
|
||||
The `have_an_ipv4_address` matcher tests if the network interface has any IPv4 addresses assigned:
|
||||
|
||||
it { should have_an_ipv4_address }
|
||||
|
||||
### have_an_ipv6_address
|
||||
|
||||
The `have_an_ipv6_address` matcher tests if the network interface has any IPv6 addresses assigned:
|
||||
|
||||
it { should have_an_ipv6_address }
|
||||
|
|
|
@ -13,6 +13,8 @@ module Inspec::Resources
|
|||
it { should exist }
|
||||
it { should be_up }
|
||||
its('speed') { should eq 1000 }
|
||||
its('ipv4_addresses') { should include '127.0.0.1' }
|
||||
its('ipv6_cidrs') { should include '::1/128' }
|
||||
end
|
||||
"
|
||||
def initialize(iface)
|
||||
|
@ -41,6 +43,42 @@ module Inspec::Resources
|
|||
interface_info.nil? ? nil : interface_info[:speed]
|
||||
end
|
||||
|
||||
def ipv4_address?
|
||||
!ipv4_addresses.nil? && !ipv4_addresses.empty?
|
||||
end
|
||||
|
||||
def ipv6_address?
|
||||
!ipv6_addresses.nil? && !ipv6_addresses.empty?
|
||||
end
|
||||
|
||||
def ipv4_addresses
|
||||
ipv4_cidrs.map { |i| i.split('/')[0] }
|
||||
end
|
||||
|
||||
def ipv6_addresses
|
||||
ipv6_cidrs.map { |i| i.split('/')[0] }
|
||||
end
|
||||
|
||||
def ipv4_addresses_netmask
|
||||
ipv4_cidrs.map { |i| i.split('/') }.map do |addr, netlen|
|
||||
binmask = "#{'1' * netlen.to_i}#{'0' * (32 - netlen.to_i)}".to_i(2)
|
||||
netmask = []
|
||||
(1..4).each do |_byte|
|
||||
netmask.unshift(binmask & 255)
|
||||
binmask = binmask >> 8
|
||||
end
|
||||
"#{addr}/#{netmask.join('.')}"
|
||||
end
|
||||
end
|
||||
|
||||
def ipv4_cidrs
|
||||
interface_info.nil? ? [] : interface_info[:ipv4_addresses]
|
||||
end
|
||||
|
||||
def ipv6_cidrs
|
||||
interface_info.nil? ? [] : interface_info[:ipv6_addresses]
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Interface #{@iface}"
|
||||
end
|
||||
|
@ -87,28 +125,54 @@ module Inspec::Resources
|
|||
speed = convert_to_i(speed)
|
||||
end
|
||||
|
||||
family_addresses = addresses(iface)
|
||||
{
|
||||
name: iface,
|
||||
up: state,
|
||||
speed: speed,
|
||||
ipv4_addresses: family_addresses['inet'],
|
||||
ipv6_addresses: family_addresses['inet6'],
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def addresses(iface)
|
||||
addrs_by_family = { 'inet6' => [], 'inet' => [] }
|
||||
[4, 6].each do |v|
|
||||
cmd = inspec.command("/sbin/ip -br -#{v} address show dev #{iface}")
|
||||
next unless cmd.exit_status.to_i == 0
|
||||
family = v == 6 ? 'inet6' : 'inet'
|
||||
|
||||
cmd.stdout.each_line do |line|
|
||||
_dev, _state, *addrs = line.split(/\s+/)
|
||||
addrs_by_family[family] = addrs
|
||||
end
|
||||
end
|
||||
addrs_by_family
|
||||
end
|
||||
end
|
||||
|
||||
class WindowsInterface < InterfaceInfo
|
||||
def interface_info(iface)
|
||||
# gather all network interfaces
|
||||
cmd = inspec.command('Get-NetAdapter | Select-Object -Property Name, InterfaceDescription, Status, State, MacAddress, LinkSpeed, ReceiveLinkSpeed, TransmitLinkSpeed, Virtual | ConvertTo-Json')
|
||||
cmd = inspec.command('Get-NetAdapter | Select-Object -Property Name, InterfaceDescription, Status, State, ' \
|
||||
'MacAddress, LinkSpeed, ReceiveLinkSpeed, TransmitLinkSpeed, Virtual | ConvertTo-Json')
|
||||
|
||||
addr_cmd = inspec.command('Get-NetIPAddress | Select-Object -Property IPv6Address, IPv4Address, InterfaceAlias,' \
|
||||
' PrefixLength | ConvertTo-Json')
|
||||
|
||||
# filter network interface
|
||||
begin
|
||||
net_adapter = JSON.parse(cmd.stdout)
|
||||
addresses = JSON.parse(addr_cmd.stdout)
|
||||
rescue JSON::ParserError => _e
|
||||
return nil
|
||||
end
|
||||
|
||||
# ensure we have an array of groups
|
||||
net_adapter = [net_adapter] if !net_adapter.is_a?(Array)
|
||||
addresses = [addresses] if !addresses.is_a?(Array)
|
||||
|
||||
# select the requested interface
|
||||
adapters = net_adapter.each_with_object([]) do |adapter, adapter_collection|
|
||||
|
@ -117,6 +181,8 @@ module Inspec::Resources
|
|||
name: adapter['Name'],
|
||||
up: adapter['State'] == 2,
|
||||
speed: adapter['ReceiveLinkSpeed'] / 1000,
|
||||
ipv4_addresses: addresses_for_proto(addresses, adapter['Name'], 'IPv4'),
|
||||
ipv6_addresses: addresses_for_proto(addresses, adapter['Name'], 'IPv6'),
|
||||
}
|
||||
adapter_collection.push(info) if info[:name].casecmp(iface) == 0
|
||||
end
|
||||
|
@ -125,5 +191,13 @@ module Inspec::Resources
|
|||
warn "[Possible Error] detected multiple network interfaces with the name #{iface}" if adapters.size > 1
|
||||
adapters[0]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def addresses_for_proto(all_addresses, iface, proto)
|
||||
all_addresses.select { |i| i['InterfaceAlias'] == iface }
|
||||
.map { |i| "#{i["#{proto}Address"]}/#{i['PrefixLength']}" unless i["#{proto}Address"].nil? }
|
||||
.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -343,7 +343,10 @@ class MockLoader
|
|||
# network interface
|
||||
'fddd70e8b8510f5fcc0413cfdc41598c55d6922bb2a0a4075e2118633a0bf422' => cmd.call('find-net-interface'),
|
||||
'c33821dece09c8b334e03a5bb9daefdf622007f73af4932605e758506584ec3f' => empty.call,
|
||||
'/sbin/ip -br -4 address show dev eth0' => cmd.call('interface-addresses-4'),
|
||||
'/sbin/ip -br -6 address show dev eth0' => cmd.call('interface-addresses-6'),
|
||||
'Get-NetAdapter | Select-Object -Property Name, InterfaceDescription, Status, State, MacAddress, LinkSpeed, ReceiveLinkSpeed, TransmitLinkSpeed, Virtual | ConvertTo-Json' => cmd.call('Get-NetAdapter'),
|
||||
'Get-NetIPAddress | Select-Object -Property IPv6Address, IPv4Address, InterfaceAlias, PrefixLength | ConvertTo-Json' => cmd.call('Get-NetIPAddress'),
|
||||
# bridge on linux
|
||||
'ls -1 /sys/class/net/br0/brif/' => cmd.call('ls-sys-class-net-br'),
|
||||
# bridge on Windows
|
||||
|
|
14
test/unit/mock/cmd/Get-NetIPAddress
Normal file
14
test/unit/mock/cmd/Get-NetIPAddress
Normal file
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"IPv6Address": "::1",
|
||||
"IPv4Address": null,
|
||||
"InterfaceAlias": "vEthernet (Intel(R) PRO 1000 MT Network Connection - Virtual Switch)",
|
||||
"PrefixLength": 128
|
||||
},
|
||||
{
|
||||
"IPv6Address": null,
|
||||
"IPv4Address": "127.0.0.1",
|
||||
"InterfaceAlias": "vEthernet (Intel(R) PRO 1000 MT Network Connection - Virtual Switch)",
|
||||
"PrefixLength": 8
|
||||
}
|
||||
]
|
1
test/unit/mock/cmd/interface-addresses-4
Normal file
1
test/unit/mock/cmd/interface-addresses-4
Normal file
|
@ -0,0 +1 @@
|
|||
eth0 UNKNOWN 127.0.0.1/8
|
1
test/unit/mock/cmd/interface-addresses-6
Normal file
1
test/unit/mock/cmd/interface-addresses-6
Normal file
|
@ -0,0 +1 @@
|
|||
eth0 UNKNOWN ::1/128
|
|
@ -13,6 +13,13 @@ describe 'Inspec::Resources::Interface' do
|
|||
_(resource.exists?).must_equal true
|
||||
_(resource.up?).must_equal true
|
||||
_(resource.speed).must_equal 10000
|
||||
_(resource.ipv4_cidrs).must_include '127.0.0.1/8'
|
||||
_(resource.ipv4_addresses).must_include '127.0.0.1'
|
||||
_(resource.ipv4_addresses_netmask).must_include '127.0.0.1/255.0.0.0'
|
||||
_(resource.ipv6_cidrs).must_include '::1/128'
|
||||
_(resource.ipv6_addresses).must_include '::1'
|
||||
_(resource.ipv4_address?).must_equal true
|
||||
_(resource.ipv6_address?).must_equal true
|
||||
end
|
||||
|
||||
it 'verify invalid interface on ubuntu' do
|
||||
|
@ -20,13 +27,28 @@ describe 'Inspec::Resources::Interface' do
|
|||
_(resource.exists?).must_equal false
|
||||
_(resource.up?).must_equal false
|
||||
_(resource.speed).must_be_nil
|
||||
_(resource.ipv4_cidrs).must_be_empty
|
||||
_(resource.ipv4_addresses).must_be_empty
|
||||
_(resource.ipv4_addresses_netmask).must_be_empty
|
||||
_(resource.ipv6_cidrs).must_be_empty
|
||||
_(resource.ipv6_addresses).must_be_empty
|
||||
_(resource.ipv4_address?).must_equal false
|
||||
_(resource.ipv6_address?).must_equal false
|
||||
end
|
||||
|
||||
# windows
|
||||
it 'verify interface on windows' do
|
||||
resource = MockLoader.new(:windows).load_resource('interface', 'ethernet0')
|
||||
_(resource.exists?).must_equal true
|
||||
_(resource.up?).must_equal false
|
||||
_(resource.speed).must_equal 0
|
||||
_(resource.ipv4_address?).must_equal false
|
||||
_(resource.ipv6_address?).must_equal false
|
||||
_(resource.ipv4_addresses).must_be_empty
|
||||
_(resource.ipv4_addresses_netmask).must_be_empty
|
||||
_(resource.ipv6_addresses).must_be_empty
|
||||
_(resource.ipv4_cidrs).must_be_empty
|
||||
_(resource.ipv6_cidrs).must_be_empty
|
||||
end
|
||||
|
||||
it 'verify interface on windows' do
|
||||
|
@ -34,6 +56,13 @@ describe 'Inspec::Resources::Interface' do
|
|||
_(resource.exists?).must_equal true
|
||||
_(resource.up?).must_equal true
|
||||
_(resource.speed).must_equal 10000000
|
||||
_(resource.ipv4_cidrs).must_include '127.0.0.1/8'
|
||||
_(resource.ipv4_addresses).must_include '127.0.0.1'
|
||||
_(resource.ipv4_addresses_netmask).must_include '127.0.0.1/255.0.0.0'
|
||||
_(resource.ipv6_cidrs).must_include '::1/128'
|
||||
_(resource.ipv6_addresses).must_include '::1'
|
||||
_(resource.ipv4_address?).must_equal true
|
||||
_(resource.ipv6_address?).must_equal true
|
||||
end
|
||||
|
||||
it 'verify invalid interface on windows' do
|
||||
|
@ -41,6 +70,13 @@ describe 'Inspec::Resources::Interface' do
|
|||
_(resource.exists?).must_equal false
|
||||
_(resource.up?).must_equal false
|
||||
_(resource.speed).must_be_nil
|
||||
_(resource.ipv4_address?).must_equal false
|
||||
_(resource.ipv6_address?).must_equal false
|
||||
_(resource.ipv4_addresses).must_be_empty
|
||||
_(resource.ipv4_addresses_netmask).must_be_empty
|
||||
_(resource.ipv6_addresses).must_be_empty
|
||||
_(resource.ipv4_cidrs).must_be_empty
|
||||
_(resource.ipv6_cidrs).must_be_empty
|
||||
end
|
||||
|
||||
# undefined
|
||||
|
|
Loading…
Reference in a new issue