Merge pull request #4979 from tecracer-theinen/theinen/windows_firewall

Add new windows_firewall and windows_firewall_rule resources
This commit is contained in:
Nick Schwaderer 2020-08-10 12:52:16 +01:00 committed by GitHub
commit ff1e558088
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 478 additions and 0 deletions

View 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 }

View 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 }

View 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

View 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