mirror of
https://github.com/inspec/inspec
synced 2024-11-23 21:23:29 +00:00
Add new windows_firewall and windows_firewall_rule resources
Signed-off-by: Thomas Heinen <theinen@tecracer.de>
This commit is contained in:
parent
08bc5edfaf
commit
3e824b5203
4 changed files with 478 additions and 0 deletions
90
docs/resources/windows_firewall.md.erb
Normal file
90
docs/resources/windows_firewall.md.erb
Normal file
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
title: About the windows_firewall Resource
|
||||
platform: windows
|
||||
---
|
||||
|
||||
# windows_firewall
|
||||
|
||||
Use the `windows_firewall` Chef InSpec audit resource to test if a firewall profile is correctly configured on a Windows system.
|
||||
|
||||
<br>
|
||||
|
||||
## Availability
|
||||
|
||||
### Installation
|
||||
|
||||
This resource is distributed along with Chef InSpec itself. You can use it automatically.
|
||||
|
||||
## Syntax
|
||||
|
||||
A `windows_firewall` resource block specifies which profile to validate:
|
||||
|
||||
describe windows_firewall('name') do
|
||||
it { should be_enabled }
|
||||
end
|
||||
|
||||
where
|
||||
|
||||
* `('name')` must specify the name of a firewall profile, such as `'Public'`, `'Private'` or `'Domain'`
|
||||
* `be_enabled` is a valid matcher for this resource
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following example shows how to use this Chef InSpec audit resource.
|
||||
|
||||
### Test if the firewall has the appropriate amount of rules and default Accept
|
||||
|
||||
describe windows_firewall('Public') do
|
||||
it { should be_enabled }
|
||||
it { should have_default_inbound_allowed }
|
||||
its('num_rules') { should eq 219 }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
The resource compiles the following list of firewall profile properties:
|
||||
|
||||
* `description`
|
||||
* `default_inbound_action`
|
||||
* `default_outbound_action`
|
||||
* `allow_inbound_rules`
|
||||
* `allow_local_firewall_rules`
|
||||
* `allow_local_ipsec_rules`
|
||||
* `allow_user_apps`
|
||||
* `allow_user_ports`
|
||||
* `allow_unicast_response_to_multicast`
|
||||
* `notify_on_listen`
|
||||
* `enable_stealth_mode_for_ipsec`
|
||||
* `log_max_size_kilobytes`
|
||||
* `log_allowed`
|
||||
* `log_blocked`
|
||||
* `log_ignored`
|
||||
* `num_rules`
|
||||
|
||||
Each of these properties can be used in two distinct ways:
|
||||
|
||||
its('default_inbound_action') { should cmp 'Allow' }
|
||||
|
||||
or via matcher:
|
||||
|
||||
it { should have_default_inbound_action 'Allow' }
|
||||
|
||||
Shortcuts are defined for:
|
||||
|
||||
* `have_default_inbound_allow?`
|
||||
* `have_default_outbound_allow?`
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers, please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
|
||||
|
||||
### be_enabled
|
||||
|
||||
The `be_enabled` matcher tests if the Profile is enabled:
|
||||
|
||||
it { should be_enabled }
|
||||
|
141
docs/resources/windows_firewall_rule.md.erb
Normal file
141
docs/resources/windows_firewall_rule.md.erb
Normal file
|
@ -0,0 +1,141 @@
|
|||
---
|
||||
title: About the windows_firewall_rule Resource
|
||||
platform: windows
|
||||
---
|
||||
|
||||
# windows_firewall_rule
|
||||
|
||||
Use the `windows_firewall_rule` Chef InSpec audit resource to test if a firewall rule is correctly configured on a Windows system.
|
||||
|
||||
<br>
|
||||
|
||||
## Availability
|
||||
|
||||
### Installation
|
||||
|
||||
This resource is distributed along with Chef InSpec itself. You can use it automatically.
|
||||
|
||||
## Syntax
|
||||
|
||||
A `windows_firewall_rule` resource block specifies which rule to validate:
|
||||
|
||||
describe windows_firewall_rule('name') do
|
||||
it { should be_enabled }
|
||||
end
|
||||
|
||||
where
|
||||
|
||||
* `('name')` must specify the name of a firewall rule, which is not the firewall rule's display name
|
||||
* `be_enabled` is a valid matcher for this resource
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following example shows how to use this Chef InSpec audit resource.
|
||||
|
||||
### Test if the firewall contains a rule for outbound HTTPS
|
||||
|
||||
describe windows_firewall_rule('HTTPS Out') do
|
||||
it { should be_enabled }
|
||||
it { should be_allowed }
|
||||
it { should be_outbound }
|
||||
it { should be_tcp }
|
||||
|
||||
its('remote_port') { should eq 443 }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
The resource compiles the following list of firewall rule properties:
|
||||
|
||||
* `description`
|
||||
* `displayname`
|
||||
* `group`
|
||||
* `local_address`
|
||||
* `local_port`
|
||||
* `remote_address`
|
||||
* `remote_port`
|
||||
* `direction`
|
||||
* `protocol`
|
||||
* `icmp_type`
|
||||
* `action`
|
||||
* `profile`
|
||||
* `program`
|
||||
* `service`
|
||||
* `interface_type`
|
||||
|
||||
Each of these properties can be used in two distinct ways:
|
||||
|
||||
its('remote_address') { should cmp '192.0.2.42' }
|
||||
|
||||
or via matcher:
|
||||
|
||||
it { should have_remote_address '192.0.2.42' }
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers, please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
|
||||
|
||||
### exist
|
||||
|
||||
The `be_enabled` matcher tests if the rule does exist:
|
||||
|
||||
it { should exist }
|
||||
|
||||
### be_enabled
|
||||
|
||||
The `be_enabled` matcher tests if the rule is enabled:
|
||||
|
||||
it { should be_enabled }
|
||||
|
||||
### be_allowed
|
||||
|
||||
The `be_allowed` matcher tests if the rule is allowing traffic:
|
||||
|
||||
it { should be_allowed }
|
||||
|
||||
### be_inbound
|
||||
|
||||
The `be_inbound` matcher tests if the rule is an inbound rule:
|
||||
|
||||
it { should be_inbound }
|
||||
|
||||
### be_outbound
|
||||
|
||||
The `be_outbound` matcher tests if the rule is an outbound rule:
|
||||
|
||||
it { should be_outbound }
|
||||
|
||||
### be_tcp
|
||||
|
||||
The `be_tcp` matcher tests if the rule is for the TCP protocol:
|
||||
|
||||
it { should be_tcp }
|
||||
|
||||
### be_ucp
|
||||
|
||||
The `be_ucp` matcher tests if the rule is for the DCP protocol:
|
||||
|
||||
it { should be_dcp }
|
||||
|
||||
### be_icmp
|
||||
|
||||
The `be_icmp` matcher tests if the rule is for any ICMP protocol:
|
||||
|
||||
it { should be_icmp }
|
||||
|
||||
### be_icmpv4
|
||||
|
||||
The `be_icmpv4` matcher tests if the rule is for the ICMPv4 protocol:
|
||||
|
||||
it { should be_icmpv4 }
|
||||
|
||||
### be_icmpv6
|
||||
|
||||
The `be_icmpv6` matcher tests if the rule is for any ICMPv6 protocol:
|
||||
|
||||
it { should be_icmpv6 }
|
||||
|
110
lib/inspec/resources/windows_firewall.rb
Normal file
110
lib/inspec/resources/windows_firewall.rb
Normal file
|
@ -0,0 +1,110 @@
|
|||
module Inspec::Resources
|
||||
class WindowsFirewall < Inspec.resource(1)
|
||||
name "windows_firewall"
|
||||
supports platform: "windows"
|
||||
desc "Check properties of the Windows Firewall for a specific profile."
|
||||
example <<~EXAMPLE
|
||||
describe windows_firewall("Public") do
|
||||
it { should be_enabled }
|
||||
its("default_inbound_action") { should_not cmp "NotConfigured" }
|
||||
its("num_rules") { should be 19 }
|
||||
end
|
||||
EXAMPLE
|
||||
|
||||
def initialize(profile = "Public")
|
||||
@profile = profile
|
||||
@state = {}
|
||||
|
||||
load_profile_cmd = load_firewall_profile(profile)
|
||||
cmd = inspec.powershell(load_profile_cmd)
|
||||
|
||||
@state = JSON.load(cmd.stdout) unless cmd.stdout.empty?
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Windows Firewall (Profile #{@profile})"
|
||||
end
|
||||
|
||||
def exist?
|
||||
!@state.empty?
|
||||
end
|
||||
|
||||
def enabled?
|
||||
@state["enabled"]
|
||||
end
|
||||
|
||||
def default_inbound_allowed?
|
||||
@state["default_inbound_action"] == "Allow"
|
||||
end
|
||||
|
||||
def default_outbound_allowed?
|
||||
@state["default_outbound_action"] == "Allow"
|
||||
end
|
||||
|
||||
# Access to return values from Powershell via `its("PROPERTY")` and `have_PROPERTY "VALUE"`
|
||||
def method_missing(method_name, *arguments, &_block)
|
||||
property = normalize_for_have_access(method_name)
|
||||
|
||||
if method_name.to_s.start_with? "has_"
|
||||
expected_value = arguments.first
|
||||
respond_to_have(property, expected_value)
|
||||
else
|
||||
access_property(property)
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to_missing?(method_name, _include_private = false)
|
||||
property = normalize_for_have_access(method_name)
|
||||
|
||||
@state.key? property
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def normalize_for_have_access(property)
|
||||
property.to_s
|
||||
.delete_prefix("has_")
|
||||
.delete_suffix("?")
|
||||
end
|
||||
|
||||
def access_property(property)
|
||||
@state[property]
|
||||
end
|
||||
|
||||
def respond_to_have(property, value)
|
||||
@state[property] == value
|
||||
end
|
||||
|
||||
def load_firewall_profile(profile_name)
|
||||
<<-EOH
|
||||
Remove-TypeData System.Array # workaround for PS bug here: https://bit.ly/2SRMQ8M
|
||||
$profile = Get-NetFirewallProfile -Name "#{profile_name}"
|
||||
$count = @($profile | Get-NetFirewallRule).Count
|
||||
([PSCustomObject]@{
|
||||
profile_name = $profile.Name
|
||||
profile = $profile.Profile.ToString()
|
||||
description = $profile.Description
|
||||
enabled = [bool]::Parse($profile.Enabled.ToString())
|
||||
default_inbound_action = $profile.DefaultInboundAction.ToString()
|
||||
default_outbound_action = $profile.DefaultOutboundAction.ToString()
|
||||
|
||||
allow_inbound_rules = $profile.AllowInboundRules.ToString()
|
||||
allow_local_firewall_rules = $profile.AllowLocalFirewallRules.ToString()
|
||||
allow_local_ipsec_rules = $profile.AllowLocalIPsecRules.ToString()
|
||||
allow_user_apps = $profile.AllowUserApps.ToString()
|
||||
allow_user_ports = $profile.AllowUserPorts.ToString()
|
||||
allow_unicast_response_to_multicast = $profile.AllowUnicastResponseToMulticast.ToString()
|
||||
|
||||
notify_on_listen = $profile.NotifyOnListen.ToString()
|
||||
enable_stealth_mode_for_ipsec = $profile.EnableStealthModeForIPsec.ToString()
|
||||
log_max_size_kilobytes = $profile.LogMaxSizeKilobytes
|
||||
log_allowed = $profile.LogAllowed.ToString()
|
||||
log_blocked = $profile.LogBlocked.ToString()
|
||||
log_ignored = $profile.LogIgnored.ToString()
|
||||
|
||||
num_rules = $count
|
||||
}) | ConvertTo-Json
|
||||
EOH
|
||||
end
|
||||
end
|
||||
end
|
137
lib/inspec/resources/windows_firewall_rule.rb
Normal file
137
lib/inspec/resources/windows_firewall_rule.rb
Normal file
|
@ -0,0 +1,137 @@
|
|||
module Inspec::Resources
|
||||
class WindowsFirewallRule < Inspec.resource(1)
|
||||
name "windows_firewall_rule"
|
||||
supports platform: "windows"
|
||||
desc "Check properties of a Windows Firewall rule."
|
||||
example <<~EXAMPLE
|
||||
describe windows_firewall_rule("Name") do
|
||||
it { should exist }
|
||||
it { should be_enabled }
|
||||
|
||||
it { should be_outbound}
|
||||
it { should be_tcp }
|
||||
it { should have_remote_port 80 }
|
||||
end
|
||||
EXAMPLE
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
@state = {}
|
||||
|
||||
query = load_firewall_state(name)
|
||||
cmd = inspec.powershell(query)
|
||||
@state = JSON.load(cmd.stdout) unless cmd.stdout.empty?
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Windows Firewall Rule #{@name}"
|
||||
end
|
||||
|
||||
def exist?
|
||||
!@state.empty?
|
||||
end
|
||||
|
||||
def enabled?
|
||||
@state["enabled"]
|
||||
end
|
||||
|
||||
def allowed?
|
||||
@state["action"] == "Allow"
|
||||
end
|
||||
|
||||
def inbound?
|
||||
@state["direction"] == "Inbound"
|
||||
end
|
||||
|
||||
def outbound?
|
||||
! inbound?
|
||||
end
|
||||
|
||||
def tcp?
|
||||
@state["protocol"] == "TCP"
|
||||
end
|
||||
|
||||
def udp?
|
||||
@state["protocol"] == "UDP"
|
||||
end
|
||||
|
||||
def icmp?
|
||||
@state["protocol"].start_with? "ICMP"
|
||||
end
|
||||
|
||||
def icmpv4?
|
||||
@state["protocol"] == "ICMPv4"
|
||||
end
|
||||
|
||||
def icmpv6?
|
||||
@state["protocol"] == "ICMPv6"
|
||||
end
|
||||
|
||||
# Access to return values from Powershell via `its("PROPERTY")` and `have_PROPERTY? "VALUE"`
|
||||
def method_missing(method_name, *arguments, &_block)
|
||||
property = normalize_for_have_access(method_name)
|
||||
|
||||
if method_name.to_s.start_with? "has_"
|
||||
expected_value = arguments.first
|
||||
respond_to_have(property, expected_value)
|
||||
else
|
||||
access_property(property)
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to_missing?(method_name, _include_private = false)
|
||||
property = normalize_for_have_access(method_name)
|
||||
|
||||
@state.key? property
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def normalize_for_have_access(property)
|
||||
property.to_s
|
||||
.delete_prefix("has_")
|
||||
.delete_suffix("?")
|
||||
end
|
||||
|
||||
def access_property(property)
|
||||
@state[property]
|
||||
end
|
||||
|
||||
def respond_to_have(property, value)
|
||||
@state[property] == value
|
||||
end
|
||||
|
||||
# Taken from Chef, but changed `firewall_action` to `action` for consistency
|
||||
# @see https://github.com/chef/chef/blob/master/lib/chef/resource/windows_firewall_rule.rb
|
||||
def load_firewall_state(rule_name)
|
||||
<<-EOH
|
||||
Remove-TypeData System.Array # workaround for PS bug here: https://bit.ly/2SRMQ8M
|
||||
$rule = Get-NetFirewallRule -Name "#{rule_name}"
|
||||
$addressFilter = $rule | Get-NetFirewallAddressFilter
|
||||
$portFilter = $rule | Get-NetFirewallPortFilter
|
||||
$applicationFilter = $rule | Get-NetFirewallApplicationFilter
|
||||
$serviceFilter = $rule | Get-NetFirewallServiceFilter
|
||||
$interfaceTypeFilter = $rule | Get-NetFirewallInterfaceTypeFilter
|
||||
([PSCustomObject]@{
|
||||
rule_name = $rule.Name
|
||||
description = $rule.Description
|
||||
displayname = $rule.DisplayName
|
||||
group = $rule.Group
|
||||
local_address = $addressFilter.LocalAddress
|
||||
local_port = $portFilter.LocalPort
|
||||
remote_address = $addressFilter.RemoteAddress
|
||||
remote_port = $portFilter.RemotePort
|
||||
direction = $rule.Direction.ToString()
|
||||
protocol = $portFilter.Protocol
|
||||
icmp_type = $portFilter.IcmpType
|
||||
action = $rule.Action.ToString()
|
||||
profile = $rule.Profile.ToString()
|
||||
program = $applicationFilter.Program
|
||||
service = $serviceFilter.Service
|
||||
interface_type = $interfaceTypeFilter.InterfaceType.ToString()
|
||||
enabled = [bool]::Parse($rule.Enabled.ToString())
|
||||
}) | ConvertTo-Json
|
||||
EOH
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue