# # ------------- # CONSTANTS # ------------- # $ISCSI_TARGET_SERVICE_NAME = "wintarget" $ISCSI_TARGET_FEATURE_NAME = "FS-iSCSITarget-Server" $CLUSTER_SERVICE_NAME = "clussvc" $FIREWALL_RULE_ALLOW = 1 $FIREWALL_RULE_DIRECTION_INCOMING = 1 $FIREWALL_RULE_PROTOCOL_TCP = 6 # # ---------------- # HELPER FUNCTIONS # ---------------- # function MatchPortInArray($Port, $Ports) # # FUN DESCRIPTION: # Check if a given port is matched in any of them in a port array # If array contains "*", that means it can match any given port # # PARAMETERS: # $Port [string] - given port # $Ports [string] - port array # # RETURN VALUES: # $true or $false # { $matched = $false if ($Ports -ne "*") { $portsArray = $Ports -split "," foreach($eachPort in $portsArray) { if ($Port -eq $eachPort) { $matched = $true break } } } else { $matched = $true } $matched } function MatchIPAddressInArray($IP, $IPs) # # FUNCTION DESCRIPTION: # Check if a given IP is matched in any of them in a IP array # If array contains "*", that means it can match any given IP # # PARAMETERS: # $IP [string] - given IP # $IPs [string] - IP array # # RETURN VALUES: # $true or $false # { $matched = $false if ($IPs -ne "*") { $ipsArray = @() $ipFWStringArray = $IPs -split "," foreach ($ipFWString in $ipFWStringArray) { # # If it contains mask after IP address, need to check "/", else firewall team uses "-" for range # if ( $ipFWString.indexof("/") -gt 0 ) { $ipsArray += $ipFWString.substring(0, $ipFWString.indexof("/")) } else { if ( $ipFWString.indexof("-") -gt 0 ) { $ipsArray += $ipFWString.substring(0, $ipFWString.indexof("-")) } else { $ipsArray += $ipFWString } } } foreach ($eachIP in $ipsArray) { if ($IP -eq $eachIP) { $matched = $true break } } } else { $matched = $true } $matched } function IsFirewallOpenForRule($IPEndpoint, $Rule) # # FUNCTION DESCRIPTION: # Check if a given port is matched in any of them in a port array # If array contains "*", that means it can match any given port # # PARAMETERS: # $Port [string] - given port # $Ports [string] - port array # # RETURN VALUES: # $true or $false # { $firewallOpen = $false $firewallOpen = (MatchIPAddressInArray $IPEndpoint.Address $Rule.LocalAddresses) if ($firewallOpen) { $firewallOpen = (MatchPortInArray $IPEndpoint.Port $Rule.LocalPorts) } $firewallOpen } function GetRelevantFirewallRules # # FUNCTION DESCRIPTION: # Check if all portals have firewall opened for them # # PARAMETERS: # None. # # RETURN VALUES: # A Portal with firewall not opened, if none, then $null # { $relevantlRules = @() $svchostExeFullPath = (Get-ChildItem Env:systemroot).value + "\system32\svchost.exe" $allFirewallRules = (New-Object -ComObject HNetCfg.FwPolicy2).rules foreach($rule in $allFirewallRules) { # # Rule is enabled and it is an allow rule # Direction is incoming and TCP is the protocol # ServiceName is "WinTarget"(case insensitive) or any service # ServiceName and applicationName is not set # if( ( ($rule.Enabled -and $rule.Action -eq $FIREWALL_RULE_ALLOW) -and ($rule.Direction -eq $FIREWALL_RULE_DIRECTION_INCOMING -and $rule.Protocol -eq $FIREWALL_RULE_PROTOCOL_TCP) ) -and ( ($rule.ServiceName -eq $ISCSI_TARGET_SERVICE_NAME -or $rule.ServiceName -eq "*") -or ($rule.ServiceName -eq $null -and ($rule.ApplicationName -eq $null -or $rule.ApplicationName -eq $svchostExeFullPath)) ) ) { $relevantlRules += $rule } } $relevantlRules } function CheckPortalsFirewall # # FUNCTION DESCRIPTION: # Check if all portals have firewall opened for them # # PARAMETERS: # None. # # RETURN VALUES: # A Portal with firewall not opened, if none, then $null # { $fwNotOpenedPortal = $null # # Find all relevant firewall rules, that is enabled, incoming, TCP protocol etc # $relevantlRules = @() $relevantlRules += (GetRelevantFirewallRules) # # Find all enabled iSCSI portals on this local machine # $portals = (get-iscsiTargetServerSetting).portals | Where-Object {$_.Enabled} # # Check if we can find any firewall exception rule for this portal(IP, port) # foreach($portal in $portals) { $firewallOpen = $false foreach($rule in $relevantlRules) { $firewallOpen = (IsFirewallOpenForRule $portal.IPEndPoint $rule) if ($firewallOpen) { break } } if (!$firewallOpen) { $fwNotOpenedPortal = $portal break } } $fwNotOpenedPortal } # # Function Description: # Find out nodes in cluster are currently down # # Return Value: # A string of node names that are down function CheckUnreachableNodesInCluster { $unreachableNodes = "" $nodes = get-clusternode foreach($node in $nodes) { if ($node.State -eq "Down") { $unreachableNodes += $node.Name + "`n" } } return $unreachableNodes } # # Function Description: # Find out nodes in cluster don't have service installed or running # # Return Value: # A string of node names which don't have service installed or running function CheckServiceInstalationInCluster { $noServiceNodes = "" $nodes = get-clusternode # # Set exception handling is silent (no warnings spew) and continue (which is default) # in case where remote WMI call failed due to networking or machine down # $ErrorActionPreference = 'SilentlyContinue' foreach($node in $nodes) { $iSCSITargetService = gwmi win32_service -computername $node.Name | where { $_.Name -eq $ISCSI_TARGET_SERVICE_NAME} if(!$?) { $noServiceNodes += $node.Name + "`n" } else { if($iSCSITargetService -eq $null -or $iSCSITargetService.State -ne "Running") { $noServiceNodes += $node.Name + "`n" } } } return $noServiceNodes } # # Function Description: # Check whether the current node is in cluster # # Arguments: # none # # Return Value: # $true - if it is in cluster # $false - if it is not in cluster # function IsHostInCluster { $inCluster = $false $nodes = get-clusternode $computerName = [System.Net.Dns]::GetHostName() foreach($node in $nodes) { if($node.Name -eq $computername) { $inCluster = $true break } } $inCluster } # # ------------------------ # RULE VIOLATION GENERATOR # ------------------------ # function RuleViolation # # FUNCTION DESCRIPTION: # Reports a BPA rule violation # NOTE: This function uses global variables defined in MAIN section # # PARAMETERS: # [varargs] List of violation descriptors # # RETURN VALUES: # None. # { $violation = $xdoc.createElement("violation",$tns) $id = $xdoc.createElement("ID",$tns) $id.InnerText = $args[0] $violation.appendChild($id) $xdoc.DocumentElement.appendChild($violation) if ($args.length -gt 1) { $args[1..($args.length-1)] | ` foreach ` { $context = $xdoc.createElement("context",$tns) $context.InnerText = $_ $violation.appendChild($context) } } } # # ------------------------ # RULE VIOLATION DETECTORS # ------------------------ # # These use the limits defined in the Global Limits section # $detectors = { #------------------------------ # Prerequisite Rules #------------------------------ # # A flag to indicate that we can import-module IscsiTarget PS module to proceed # $iscsiTargetAvailable = $false # # Rule FeatureInstalled - iSCSI target service should be installed # import-module ServerManager $feature = $null $feature = get-WindowsFeature | where { $_.Name -eq $ISCSI_TARGET_FEATURE_NAME} if ($feature -eq $null -or $feature.Installed -eq $false) { RuleViolation FeatureInstalled } # # Rule ServiceRunning - iSCSI target service should be running # $service = $null $service = gwmi win32_service -filter "Name = '$ISCSI_TARGET_SERVICE_NAME'" if ($service -eq $null -or $service.State -ne "Running") { RuleViolation ServiceRunning } # # Make sure iscsi target available # if (($feature -ne $null -and $feature.Installed) -and ($service -ne $null -and $service.State -eq "Running")) { $iscsiTargetAvailable = $true import-module IscsiTarget } #------------------------------ # Non-Prerequisite Rules #------------------------------ if ($iscsiTargetAvailable) { # # Rule ServiceAutoStart - Service should start automatically # if ($service.startmode -ne "Auto") { RuleViolation ServiceAutoStart } # # Rule RestrictedTargetAccess - No target allow any IQN initiator to connect # $violatedTarget = $null $targets=Get-IscsiServerTarget foreach($target in $targets) { foreach($initID in $target.InitiatorIds) { if($initID.method -eq "iqn" -and $initID.value -eq "*") { $violatedTarget = $target break } } if ($violatedTarget -ne $null) { break } } if ($violatedTarget -ne $null) { RuleViolation RestrictedTargetAccess $violatedTarget.TargetName } # # Rule UnusedTargets - No target should have empty lun mappings # $violatedTarget = $null $targets=Get-IscsiServerTarget foreach($target in $targets) { if($target.LunMappings.length -eq 0) { $violatedTarget = $target break } } if ($violatedTarget -ne $null) { RuleViolation UnusedTargets $violatedTarget.TargetName } # # Rule FirewallOpened - All active portals should have firewall opened # $fwNotOpenedPortal = $null $fwNotOpenedPortal = (CheckPortalsFirewall) if ($fwNotOpenedPortal -ne $null) { RuleViolation FirewallOpened $fwNotOpenedPortal.IPEndPoint.Address $fwNotOpenedPortal.IPEndPoint.Port } # # Failover Clustering checks # $clusvc = gwmi win32_service -filter "Name = '$CLUSTER_SERVICE_NAME'" if($clusvc -ne $null -and $clusvc.State -eq "Running") { import-module failoverclusters $hostInCluser = (IsHostInCluster) if ($hostInCluser) { # # Rule UnReachableNodesInCluster - All cluster joined nodes should be reachable # $unreachableNodes = "" $unreachableNodes = (CheckUnreachableNodesInCluster) if ($unreachableNodes.length -ne 0) { RuleViolation UnReachableNodesInCluster $unreachableNodes } # # Rule ServiceInstallationInCluster - All cluster joined target servers should have iSCSI target installed # if ($unreachableNodes.length -eq 0) { $noServiceNodes = "" $noServiceNodes = (CheckServiceInstalationInCluster) if ($noServiceNodes.length -ne 0) { RuleViolation ServiceInstallationInCluster $noServiceNodes } } }# if host is in a cluster }# if $clussvc is running on the local machine }# if $iscsiTargetAvailable = $true } #end Violation Detectors # #------------------------------ # MAIN #------------------------------ # # # Custom namespace # $tns="http://schemas.microsoft.com/mbca/models/ISCSITarget/2010/12" # # Create Rule violation XML document # $xdoc = [xml]"" # # Run violation detectors # .($detectors) | out-null # # Return the xml document # $xdoc