mirror of
https://github.com/inspec/inspec
synced 2024-11-27 15:10:44 +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