diff --git a/CHANGELOG.md b/CHANGELOG.md index dede923d9..4aebbda26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ - Fixes test failures in xWindowsOptionalFeatureSet.Integration.Tests.ps1 due to accessing the windowsOptionalFeatureName variable before it is assigned. [issue #612](https://github.com/PowerShell/xPSDesiredStateConfiguration/issues/612) +- MSFT_xDSCWebService + - Fixes [issue + #536](https://github.com/PowerShell/xPSDesiredStateConfiguration/issues/536) + and starts the deprecation process for configuring a windows firewall + (exception) rule using xDSCWebService ## 8.6.0.0 diff --git a/DSCResources/MSFT_xDSCWebService/Firewall.psm1 b/DSCResources/MSFT_xDSCWebService/Firewall.psm1 new file mode 100644 index 000000000..2b29d21ea --- /dev/null +++ b/DSCResources/MSFT_xDSCWebService/Firewall.psm1 @@ -0,0 +1,95 @@ +# Name and description for the Firewall rules. Used in multiple locations +New-Variable -Name fireWallRuleDisplayName -Value 'Desired State Configuration - Pull Server Port:{0}' -Option ReadOnly -Scope Script -Force +New-Variable -Name netsh -Value "$env:windir\system32\netsh.exe" -Option ReadOnly -Scope Script -Force +<# + .SYNOPSIS + Create a firewall exception so that DSC clients are able to access the configured Pull Server + + .PARAMETER Port + The TCP port used to create the firewall exception +#> +function Add-PullServerFirewallConfiguration +{ + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt32] + $Port + ) + + Write-Verbose -Message 'Disable Inbound Firewall Notification' + $null = & $script:netsh advfirewall set currentprofile settings inboundusernotification disable + + # Remove all existing rules with that displayName + $null = & $script:netsh advfirewall firewall delete rule name=DSCPullServer_IIS_Port protocol=tcp localport=$Port + + Write-Verbose -Message "Add Firewall Rule for port $Port" + $null = & $script:netsh advfirewall firewall add rule name=DSCPullServer_IIS_Port dir=in action=allow protocol=TCP localport=$Port +} + +<# + .SYNOPSIS + Delete the Pull Server firewall exception + + .PARAMETER Port + The TCP port for which the firewall exception should be deleted +#> +function Remove-PullServerFirewallConfiguration +{ + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt32] + $Port + ) + + if (Test-PullServerFirewallConfiguration -Port $Port) + { + # remove all existing rules with that displayName + Write-Verbose -Message "Delete Firewall Rule for port $Port" + $null = & $script:netsh advfirewall firewall delete rule name=DSCPullServer_IIS_Port protocol=tcp localport=$Port + + # backwards compatibility with old code + if (Get-Command -Name Get-NetFirewallRule -CommandType Cmdlet -ErrorAction:SilentlyContinue) + { + # Remove all rules with that name + $ruleName = ($($FireWallRuleDisplayName) -f $port) + Get-NetFirewallRule | Where-Object -Property DisplayName -eq -Value "$ruleName" | Remove-NetFirewallRule + } + } + else + { + Write-Verbose -Message "No DSC PullServer firewall rule found with port $Port. No cleanup required" + } +} + +<# + .SYNOPSIS + Tests if a Pull Server firewall exception exists for a specific port + + .PARAMETER Port + The TCP port for which the firewall exception should be tested +#> +function Test-PullServerFirewallConfiguration +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt32] + $Port + ) + + # Remove all existing rules with that displayName + Write-Verbose -Message "Testing Firewall Rule for port $Port" + $result = & $script:netsh advfirewall firewall show rule name=DSCPullServer_IIS_Port | Select-String -Pattern "LocalPort:\s*$Port" + return -not [string]::IsNullOrWhiteSpace($result) +} + +Export-ModuleMember -Function '*-PullServerFirewallConfiguration' diff --git a/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.psm1 b/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.psm1 index 6173888fb..96ddfdd3b 100644 --- a/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.psm1 +++ b/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.psm1 @@ -1,6 +1,7 @@ # Import the helper functions -Import-Module -Name $PSScriptRoot\PSWSIISEndpoint.psm1 -Verbose:$false -Import-Module -Name $PSScriptRoot\UseSecurityBestPractices.psm1 -Verbose:$false +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'PSWSIISEndpoint.psm1') -Verbose:$false +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'UseSecurityBestPractices.psm1') -Verbose:$false +Import-Module -NAme (Join-Path -Path $PSScriptRoot -ChildPath 'Firewall.psm1') -Verbose:$false #region LocalizedData $script:culture = 'en-US' @@ -65,7 +66,11 @@ function Get-TargetResource # When this property is set to true, Pull Server will run on a 32 bit process on a 64 bit machine [Parameter()] [System.Boolean] - $Enable32BitAppOnWin64 = $false + $Enable32BitAppOnWin64 = $false, + + [Parameter()] + [System.Boolean] + $ConfigureFirewall = $true ) # If Certificate Subject is not specified then a value for CertificateThumbprint must be explicitly set instead. @@ -136,6 +141,8 @@ function Get-TargetResource { $acceptSelfSignedCertificates = $true } + + $ConfigureFirewall = Test-PullServerFirewallConfiguration -Port $iisPort } else { @@ -158,6 +165,7 @@ function Get-TargetResource UseSecurityBestPractices = $UseSecurityBestPractices DisableSecurityBestPractices = $DisableSecurityBestPractices Enable32BitAppOnWin64 = $Enable32BitAppOnWin64 + ConfigureFirewall = $ConfigureFirewall } if ($CertificateThumbPrint -eq 'AllowUnencryptedTraffic') @@ -204,6 +212,7 @@ function Set-TargetResource # Port number of the DSC Pull Server IIS Endpoint [Parameter()] + [ValidateRange(1, 65535)] [System.UInt32] $Port = 8080, @@ -291,7 +300,11 @@ function Set-TargetResource # When this property is set to true, Pull Server will run on a 32 bit process on a 64 bit machine [Parameter()] [System.Boolean] - $Enable32BitAppOnWin64 = $false + $Enable32BitAppOnWin64 = $false, + + [Parameter()] + [System.Boolean] + $ConfigureFirewall = $true ) # If Certificate Subject is not specified then a value for CertificateThumbprint must be explicitly set instead. @@ -313,6 +326,10 @@ function Set-TargetResource throw $LocalizedData.ThrowUseSecurityBestPractice } + if ($ConfigureFirewall) + { + Write-Warning -Message $LocalizedData.ConfigFirewallDeprecated + } # Initialize with default values $pathPullServer = "$pshome\modules\PSDesiredStateConfiguration\PullServer" @@ -354,16 +371,23 @@ function Set-TargetResource # ============ Absent block to remove existing site ========= if(($Ensure -eq "Absent")) { - $website = Get-Website -Name $EndpointName - if($null -ne $website) - { + if(Test-Path -LiteralPath "IIS:\Sites\$EndpointName") + { + # Get the port number for the Firewall rule + Write-Verbose -Message "Processing bindings for $EndpointName" + $portList = Get-WebBinding -Name $EndpointName | ForEach-Object -Process { + [System.Text.RegularExpressions.Regex]::Match($_.bindingInformation,':(\d+):').Groups[1].Value + } + # there is a web site, but there shouldn't be one Write-Verbose -Message "Removing web site $EndpointName" PSWSIISEndpoint\Remove-PSWSEndpoint -SiteName $EndpointName - } - # we are done here, all stuff below is for 'Present' - return + $portList | ForEach-Object -Process { Remove-PullServerFirewallConfiguration -Port $_ } + } + + # we are done here, all stuff below is for 'Present' + return } # =========================================================== @@ -382,10 +406,20 @@ function Set-TargetResource -language $language ` -dependentMUIFiles "$pathPullServer\$languagePath\Microsoft.Powershell.DesiredStateConfiguration.Service.Resources.dll" ` -certificateThumbPrint $certificateThumbPrint ` - -EnableFirewallException $true ` -Enable32BitAppOnWin64 $Enable32BitAppOnWin64 ` -Verbose + if ($ConfigureFirewall) + { + Write-Verbose -Message "Enabling firewall exception for port $port" + Add-PullServerFirewallConfiguration -Port $port + } + else + { + Write-Verbose -Message "Disabling firewall exception for port $port" + Remove-PullServerFirewallConfiguration -Port $port + } + Update-LocationTagInApplicationHostConfigForAuthentication -WebSite $EndpointName -Authentication "anonymous" Update-LocationTagInApplicationHostConfigForAuthentication -WebSite $EndpointName -Authentication "basic" Update-LocationTagInApplicationHostConfigForAuthentication -WebSite $EndpointName -Authentication "windows" @@ -565,7 +599,11 @@ function Test-TargetResource # When this property is set to true, Pull Server will run on a 32 bit process on a 64 bit machine [Parameter()] [System.Boolean] - $Enable32BitAppOnWin64 = $false + $Enable32BitAppOnWin64 = $false, + + [Parameter()] + [System.Boolean] + $ConfigureFirewall = $true ) # If Certificate Subject is not specified then a value for CertificateThumbprint must be explicitly set instead. @@ -616,6 +654,21 @@ function Test-TargetResource $actualCertificateHash = $website.bindings.Collection[0].certificateHash $websiteProtocol = $website.bindings.collection[0].Protocol + Write-Verbose -Message 'Checking firewall rule settings' + $ruleExists = Test-PullServerFirewallConfiguration -Port $Port + if ($ruleExists -and -not $ConfigureFirewall) + { + $desiredConfigurationMatch = $false + Write-Verbose -Message "Firewall rule exists for $Port and should not. Configuration does not match the desired state." + break + } + elseif (-not $ruleExists -and $ConfigureFirewall) + { + $desiredConfigurationMatch = $false + Write-Verbose -Message "Firewall rule does not exist for $Port and should. Configuration does not match the desired state." + break + } + switch ($PSCmdlet.ParameterSetName) { 'CertificateThumbprint' diff --git a/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.schema.mof b/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.schema.mof index dff562003..cc9c863f8 100644 --- a/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.schema.mof +++ b/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.schema.mof @@ -1,4 +1,4 @@ -[ClassVersion("1.0.0"), FriendlyName("xDSCWebService")] +[ClassVersion("1.0.0"), FriendlyName("xDSCWebService")] class MSFT_xDSCWebService : OMI_BaseResource { [Key] string EndpointName; @@ -13,7 +13,7 @@ class MSFT_xDSCWebService : OMI_BaseResource [write] string DatabasePath; [write] string ModulePath; [write] string ConfigurationPath; - [read] string DSCServerUrl; + [read] string DSCServerUrl; [write] string RegistrationKeyPath; [write] boolean AcceptSelfSignedCertificates; [write] boolean SqlProvider; @@ -21,4 +21,5 @@ class MSFT_xDSCWebService : OMI_BaseResource [required, Description("This property will ensure that the Pull Server is created with the most secure practices")] boolean UseSecurityBestPractices; [write,ValueMap{"SecureTLSProtocols"},Values{"SecureTLSProtocols"}] string DisableSecurityBestPractices []; [write, Description("When this property is set to true, Pull Server will run on a 32 bit process on a 64 bit machine")] boolean Enable32BitAppOnWin64; + [write, Description("Add firewall incoming exceptions for the configured PullServer Port. Default: true")] boolean ConfigureFirewall; }; diff --git a/DSCResources/MSFT_xDSCWebService/PSWSIISEndpoint.psm1 b/DSCResources/MSFT_xDSCWebService/PSWSIISEndpoint.psm1 index dd0967afd..8520018cc 100644 --- a/DSCResources/MSFT_xDSCWebService/PSWSIISEndpoint.psm1 +++ b/DSCResources/MSFT_xDSCWebService/PSWSIISEndpoint.psm1 @@ -1,9 +1,6 @@ # This module file contains a utility to perform PSWS IIS Endpoint setup # Module exports New-PSWSEndpoint function to perform the endpoint setup -# Name and description for the Firewall rules. Used in multiple locations -$FireWallRuleDisplayName = 'Desired State Configuration - Pull Server Port:{0}' - <# .SYNOPSIS Validate supplied configuration to setup the PSWS Endpoint Function @@ -440,49 +437,22 @@ function New-IISWebSite Write-Verbose -Message 'Add and Set Site Properties' if ($certificateThumbPrint -eq 'AllowUnencryptedTraffic') { - New-WebSite -Name $site -Id $siteID -Port $port -IPAddress "*" -PhysicalPath $path -ApplicationPool $appPool | Out-Null + $null = New-WebSite -Name $site -Id $siteID -Port $port -IPAddress "*" -PhysicalPath $path -ApplicationPool $appPool } else { - New-WebSite -Name $site -Id $siteID -Port $port -IPAddress "*" -PhysicalPath $path -ApplicationPool $appPool -Ssl | Out-Null + $null = New-WebSite -Name $site -Id $siteID -Port $port -IPAddress "*" -PhysicalPath $path -ApplicationPool $appPool -Ssl # Remove existing binding for $port Remove-Item IIS:\SSLBindings\0.0.0.0!$port -ErrorAction Ignore # Create a new binding using the supplied certificate - Get-Item CERT:\LocalMachine\MY\$certificateThumbPrint | New-Item IIS:\SSLBindings\0.0.0.0!$port | Out-Null + $null = Get-Item CERT:\LocalMachine\MY\$certificateThumbPrint | New-Item IIS:\SSLBindings\0.0.0.0!$port } Update-Site -siteName $site -siteAction Start } -<# - .SYNOPSIS - Allow Clients outsite the machine to access the setup endpoint on a - User Port. -#> -function Set-FirewallConfigurationToAllowPullServerAccess -{ - [CmdletBinding()] - param - ( - [Parameter()] - [System.String] - $firewallPort - ) - - $script:netsh = "$env:windir\system32\netsh.exe" - - Write-Verbose -Message 'Disable Inbound Firewall Notification' - & $script:netsh advfirewall set currentprofile settings inboundusernotification disable - - # remove all existing rules with that displayName - & $script:netsh advfirewall firewall delete rule name=DSCPullServer_IIS_Port protocol=tcp localport=$firewallPort | Out-Null - - Write-Verbose -Message "Add Firewall Rule for port $firewallPort" - & $script:netsh advfirewall firewall add rule name=DSCPullServer_IIS_Port dir=in action=allow protocol=TCP localport=$firewallPort -} - <# .SYNOPSIS Enable & Clear PSWS Operational/Analytic/Debug ETW Channels. @@ -490,19 +460,19 @@ function Set-FirewallConfigurationToAllowPullServerAccess function Enable-PSWSETW { # Disable Analytic Log - & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Analytic /e:false /q | Out-Null + $null = & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Analytic /e:false /q # Disable Debug Log - & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Debug /e:false /q | Out-Null + $null = & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Debug /e:false /q # Clear Operational Log - & $script:wevtutil cl Microsoft-Windows-ManagementOdataService/Operational | Out-Null + $null = & $script:wevtutil cl Microsoft-Windows-ManagementOdataService/Operational # Enable/Clear Analytic Log - & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Analytic /e:true /q | Out-Null + $null = & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Analytic /e:true /q # Enable/Clear Debug Log - & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Debug /e:true /q | Out-Null + $null = & $script:wevtutil sl Microsoft-Windows-ManagementOdataService/Debug /e:true /q } <# @@ -617,11 +587,6 @@ function New-PSWSEndpoint [System.Boolean] $removeSiteFiles = $false, - # Enable Firewall Exception for the supplied port - [Parameter()] - [System.Boolean] - $EnableFirewallException, - # Enable and Clear PSWS ETW [Parameter()] [System.Management.Automation.SwitchParameter] @@ -658,12 +623,6 @@ function New-PSWSEndpoint -removeSiteFiles $removeSiteFiles -certificateThumbPrint $certificateThumbPrint ` -enable32BitAppOnWin64 $Enable32BitAppOnWin64 - if ($EnableFirewallException -eq $true) - { - Write-Verbose -Message "Enabling firewall exception for port $port" - $null = Set-FirewallConfigurationToAllowPullServerAccess $port - } - if ($EnablePSWSETW) { Enable-PSWSETW @@ -689,49 +648,60 @@ function Remove-PSWSEndpoint ( # Unique Name of the IIS Site [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $siteName ) # Get the site to remove - $site = Get-Item -Path "IIS:\sites\$siteName" - # And the pool it is using - $pool = $site.applicationPool - - # Get the path so we can delete the files - $filePath = $site.PhysicalPath - # Get the port number for the Firewall rule - $bindings = (Get-WebBinding -Name $siteName).bindingInformation - $port = [System.Text.RegularExpressions.Regex]::Match($bindings,':(\d+):').Groups[1].Value - - # Remove the actual site. - Remove-Website -Name $siteName - <# - There may be running requests, wait a little - I had an issue where the files were still in use - when I tried to delete them - #> - Start-Sleep -Milliseconds 200 - - # Remove the files for the site - If (Test-Path -Path $filePath) + $site = Get-Website -Name $siteName + if ($site) { - Get-ChildItem -Path $filePath -Recurse | Remove-Item -Recurse - Remove-Item -Path $filePath - } + if ('Started' -eq $site.state) + { + Write-Verbose -Message "Stopping WebSite $($site.name)" + $website = Stop-Website -Name $site.name -Passthru + if ('Started' -eq $website.state) + { + Write-Error -Message "Unable to stop WebSite $($site.name)" -ErrorAction:Stop + } + } + + # And the pool it is using + $pool = $site.applicationPool - # Find out whether any other site is using this pool - $filter = "/system.applicationHost/sites/site/application[@applicationPool='" + $pool + "']" - $apps = (Get-WebConfigurationProperty -Filter $filter -PSPath 'machine/webroot/apphost' -name path).ItemXPath - if (-not $apps -or $apps.count -eq 1) + # Get the path so we can delete the files + $filePath = $site.PhysicalPath + + # Remove the actual site. + Remove-Website -Name $siteName + <# + There may be running requests, wait a little + I had an issue where the files were still in use + when I tried to delete them + #> + Start-Sleep -Milliseconds 2000 + + # Remove the files for the site + If (Test-Path -Path $filePath) + { + Get-ChildItem -Path $filePath -Recurse | Remove-Item -Recurse -Force + Remove-Item -Path $filePath -Force + } + + # Find out whether any other site is using this pool + $filter = "/system.applicationHost/sites/site/application[@applicationPool='" + $pool + "']" + $apps = (Get-WebConfigurationProperty -Filter $filter -PSPath 'machine/webroot/apphost' -name path).ItemXPath + if (-not $apps -or $apps.count -eq 1) + { + # If we are the only site in the pool, remove the pool as well. + Remove-WebAppPool -Name $pool + } + } + else { - # If we are the only site in the pool, remove the pool as well. - Remove-WebAppPool -Name $pool + Write-Verbose -Message "Website with name [$siteName] does not exist" } - - # Remove all rules with that name - $ruleName = ($($FireWallRuleDisplayName) -f $port) - Get-NetFirewallRule | Where-Object DisplayName -eq "$ruleName" | Remove-NetFirewallRule } <# diff --git a/DSCResources/MSFT_xDSCWebService/en-US/MSFT_xDSCWebService.psd1 b/DSCResources/MSFT_xDSCWebService/en-US/MSFT_xDSCWebService.psd1 index c55dd271d..ed91aeded 100644 --- a/DSCResources/MSFT_xDSCWebService/en-US/MSFT_xDSCWebService.psd1 +++ b/DSCResources/MSFT_xDSCWebService/en-US/MSFT_xDSCWebService.psd1 @@ -6,4 +6,5 @@ ConvertFrom-StringData -StringData @' FindCertificateBySubjectNotFound = Certificate not found with subject containing {0} and using template "{1}". IISInstallationPathNotFound = IIS installation path not found IISWebAdministrationAssemblyNotFound = IIS version of Microsoft.Web.Administration.dll not found + ConfigFirewallDeprecated = The support for configuring firewall rules is deprecated. Please set ConfigureFirewall to false and use the Firewall resource from NetworkingDSC module to configure required firewall rules. '@ diff --git a/Examples/Sample_xDscWebServiceRegistration.ps1 b/Examples/Sample_xDscWebServiceRegistration.ps1 index c33d4bea6..259cade4a 100644 --- a/Examples/Sample_xDscWebServiceRegistration.ps1 +++ b/Examples/Sample_xDscWebServiceRegistration.ps1 @@ -1,18 +1,39 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID c0a8626d-0f4f-469d-8f20-b79f860edc09 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/xPSDesiredStateConfiguration +.ICONURI +.EXTERNALMODULEDEPENDENCIES NetworkingDsc, xPSDesiredStateConfiguration +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +#> <# .SYNOPSIS + Configures a DSC Pull Server with enhanced security and a firewall rule + to allow extenal connections. + + .DESCRIPTION The Sample_xDscWebServiceRegistration configuration sets up a DSC pull server that is capable for client nodes to register with it and retrieve configuration documents with configuration names instead of configuration id. - Prerequisite: Install a certificate in 'CERT:\LocalMachine\MY\' store - For testing environments, you could use a self-signed - certificate. (New-SelfSignedCertificate cmdlet could - generate one for you). For production environments, you - will need a certificate signed by valid CA. Registration - only works over https protocols. So to use registration - feature, a secure pull server setup with certificate is - necessary. + Prerequisite: 1 - Install a certificate in 'CERT:\LocalMachine\MY\' store + For testing environments, you could use a self-signed + certificate. (New-SelfSignedCertificate cmdlet could + generate one for you). For production environments, you + will need a certificate signed by valid CA. Registration + only works over https protocols. So to use registration + feature, a secure pull server setup with certificate is + necessary. + 2 - To configure a Firewall Rule (Exception) to allow external + connections the [NetworkingDsc](https://github.com/PowerShell/NetworkingDsc) + DSC module is required. .PARAMETER NodeName The name of the node being configured as a DSC Pull Server. @@ -27,6 +48,9 @@ (randomness) to protect the registration of clients to the pull server. The example creates a new GUID for the registration key. + .PARAMETER Port + The TCP port on which the Pull Server will listen for connections + .EXAMPLE $thumbprint = (New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My).Thumbprint $registrationKey = [System.Guid]::NewGuid() @@ -41,17 +65,23 @@ Configuration Sample_xDscWebServiceRegistration [System.String[]] $NodeName = 'localhost', - [Parameter()] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $CertificateThumbPrint, - [Parameter()] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] - $RegistrationKey + $RegistrationKey, + + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt16] + $Port = 8080 ) + Import-DscResource -ModuleName NetworkingDsc Import-DSCResource -ModuleName xPSDesiredStateConfiguration Node $NodeName @@ -66,7 +96,7 @@ Configuration Sample_xDscWebServiceRegistration { Ensure = 'Present' EndpointName = 'PSDSCPullServer' - Port = 8080 + Port = $Port PhysicalPath = "$env:SystemDrive\inetpub\PSDSCPullServer" CertificateThumbPrint = $CertificateThumbPrint ModulePath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules" @@ -77,6 +107,7 @@ Configuration Sample_xDscWebServiceRegistration AcceptSelfSignedCertificates = $true Enable32BitAppOnWin64 = $false UseSecurityBestPractices = $true + ConfigureFirewall = $false } File RegistrationKeyFile @@ -86,6 +117,20 @@ Configuration Sample_xDscWebServiceRegistration DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt" Contents = $RegistrationKey } + + Firewall PSDSCPullServerRule + { + Ensure = 'Present' + Name = "DSC_PullServer_$Port" + DisplayName = "DSC PullServer $Port" + Group = 'DSC PullServer' + Enabled = $true + Action = 'Allow' + Direction = 'InBound' + LocalPort = $Port + Protocol = 'TCP' + DependsOn = '[xDscWebService]PSDSCPullServer' + } } } @@ -107,6 +152,9 @@ Configuration Sample_xDscWebServiceRegistration The HostName to use when configuring the Pull Server URL on the DSC client. + .PARAMETER Port + The port on which the PullServer is listening for connections + .EXAMPLE $registrationKey = [System.Guid]::NewGuid() @@ -123,14 +171,19 @@ Configuration Sample_MetaConfigurationToRegisterWithSecurePullServer $NodeName = 'localhost', [Parameter()] - [ValidateNotNullOrEmpty()] + [Parameter(Mandatory = $true)] [System.String] $RegistrationKey, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] - $ServerName = 'localhost' + $ServerName = 'localhost', + + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt16] + $Port = 8080 ) Node $NodeName @@ -142,14 +195,14 @@ Configuration Sample_MetaConfigurationToRegisterWithSecurePullServer ConfigurationRepositoryWeb CONTOSO-PullSrv { - ServerURL = "https://$ServerName`:8080/PSDSCPullServer.svc" + ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" RegistrationKey = $RegistrationKey ConfigurationNames = @('ClientConfig') } ReportServerWeb CONTOSO-PullSrv { - ServerURL = "https://$ServerName`:8080/PSDSCPullServer.svc" + ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" RegistrationKey = $RegistrationKey } } diff --git a/Examples/Sample_xDscWebServiceRegistrationWithSecurityBestPractices.ps1 b/Examples/Sample_xDscWebServiceRegistrationWithSecurityBestPractices.ps1 index 232e7d432..a082e71e3 100644 --- a/Examples/Sample_xDscWebServiceRegistrationWithSecurityBestPractices.ps1 +++ b/Examples/Sample_xDscWebServiceRegistrationWithSecurityBestPractices.ps1 @@ -1,5 +1,23 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 33346550-87c0-41d6-9446-1185236edc69 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/xPSDesiredStateConfiguration +.ICONURI +.EXTERNALMODULEDEPENDENCIES NetworkingDsc, xPSDesiredStateConfiguration +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +#> <# .SYNOPSIS + Configures a DSC Pull Server with enhanced security and a firewall rule + to allow extenal connections. + + .DESCRIPTION The Sample_xDscWebServiceRegistrationWithEnhancedSecurity configuration sets up a DSC pull server that is capable for client nodes to register with it and retrieve configuration documents with configuration names @@ -13,7 +31,11 @@ valid CA. Registration only works over https protocols. So to use registration feature, a secure pull server setup with certificate is necessary. - 2 - Install and Configure SQL Server + 2 - Install and Configure SQL Server, preferably using + [SqlServerDsc](https://github.com/PowerShell/SqlServerDsc) + 3 - To configure a Firewall Rule (Exception) to allow external + connections the [NetworkingDsc](https://github.com/PowerShell/NetworkingDsc) + DSC module is required. .PARAMETER NodeName The name of the node being configured as a DSC Pull Server. @@ -28,6 +50,9 @@ (randomness) to protect the registration of clients to the pull server. The example creates a new GUID for the registration key. + .PARAMETER Port + The TCP port on which the Pull Server will listen for connections + .EXAMPLE $thumbprint = (New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My).Thumbprint $registrationKey = [System.Guid]::NewGuid() @@ -42,17 +67,23 @@ Configuration Sample_xDscWebServiceRegistrationWithSecurityBestPractices [System.String[]] $NodeName = 'localhost', - [Parameter()] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $CertificateThumbPrint, - [Parameter()] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] - $RegistrationKey + $RegistrationKey, + + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt16] + $Port = 8080 ) + Import-DscResource -ModuleName NetworkingDsc Import-DSCResource -ModuleName xPSDesiredStateConfiguration # To explicitly import the resource WindowsFeature and File. Import-DscResource -ModuleName PSDesiredStateConfiguration @@ -69,7 +100,7 @@ Configuration Sample_xDscWebServiceRegistrationWithSecurityBestPractices { Ensure = 'Present' EndpointName = 'PSDSCPullServer' - Port = 8080 + Port = $Port PhysicalPath = "$env:SystemDrive\inetpub\PSDSCPullServer" CertificateThumbPrint = $CertificateThumbPrint ModulePath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules" @@ -79,6 +110,7 @@ Configuration Sample_xDscWebServiceRegistrationWithSecurityBestPractices RegistrationKeyPath = "$env:PROGRAMFILES\WindowsPowerShell\DscService" AcceptSelfSignedCertificates = $true UseSecurityBestPractices = $true + ConfigureFirewall = $false } File RegistrationKeyFile @@ -88,6 +120,20 @@ Configuration Sample_xDscWebServiceRegistrationWithSecurityBestPractices DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt" Contents = $RegistrationKey } + + Firewall PSDSCPullServerRule + { + Ensure = 'Present' + Name = "DSC_PullServer_$Port" + DisplayName = "DSC PullServer $Port" + Group = 'DSC PullServer' + Enabled = $true + Action = 'Allow' + Direction = 'InBound' + LocalPort = $Port + Protocol = 'TCP' + DependsOn = '[xDscWebService]PSDSCPullServer' + } } } diff --git a/Examples/Sample_xDscWebServiceRegistration_UseSQLProvider.ps1 b/Examples/Sample_xDscWebServiceRegistration_UseSQLProvider.ps1 index 2c9d26873..7df76c005 100644 --- a/Examples/Sample_xDscWebServiceRegistration_UseSQLProvider.ps1 +++ b/Examples/Sample_xDscWebServiceRegistration_UseSQLProvider.ps1 @@ -1,3 +1,17 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 33db3573-eeca-479e-9b91-b9578b6b0285 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/xPSDesiredStateConfiguration +.ICONURI +.EXTERNALMODULEDEPENDENCIES NetworkingDsc, xPSDesiredStateConfiguration +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +#> <# .SYNOPSIS The Sample_xDscWebServiceRegistration_UseSQLProvider configuration sets @@ -12,7 +26,11 @@ valid CA. Registration only works over https protocols. So to use registration feature, a secure pull server setup with certificate is necessary. - 2 - Install and Configure SQL Server + 2 - Install and Configure SQL Server, preferably using + [SqlServerDsc](https://github.com/PowerShell/SqlServerDsc) + 3 - To configure a Firewall Rule (Exception) to allow external + connections the [NetworkingDsc](https://github.com/PowerShell/NetworkingDsc) + DSC module is required. .PARAMETER NodeName The name of the node being configured as a DSC Pull Server. @@ -27,6 +45,9 @@ (randomness) to protect the registration of clients to the pull server. The example creates a new GUID for the registration key. + .PARAMETER Port + The TCP port on which the Pull Server will listen for connections + .EXAMPLE $thumbprint = (New-SelfSignedCertificate -Subject $env:COMPUTERNAME).Thumbprint $registrationKey = [System.Guid]::NewGuid() @@ -49,9 +70,15 @@ Configuration Sample_xDscWebServiceRegistration_UseSQLProvider [Parameter()] [ValidateNotNullOrEmpty()] [System.String] - $RegistrationKey + $RegistrationKey, + + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt16] + $Port = 8080 ) + Import-DscResource -ModuleName NetworkingDsc Import-DSCResource -ModuleName xPSDesiredStateConfiguration # To explicitly import the resource WindowsFeature and File. Import-DscResource -ModuleName PSDesiredStateConfiguration @@ -68,7 +95,7 @@ Configuration Sample_xDscWebServiceRegistration_UseSQLProvider { Ensure = 'Present' EndpointName = 'PSDSCPullServer' - Port = 8080 + Port = $Port PhysicalPath = "$env:SystemDrive\inetpub\PSDSCPullServer" CertificateThumbPrint = $CertificateThumbPrint ModulePath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules" @@ -80,6 +107,7 @@ Configuration Sample_xDscWebServiceRegistration_UseSQLProvider UseSecurityBestPractices = $true SqlProvider = $true SqlConnectionString = "Provider=SQLNCLI11;Data Source=(local)\SQLExpress;User ID=SA;Password=Password12!;Initial Catalog=master;" + ConfigureFirewall = $false } File RegistrationKeyFile @@ -89,6 +117,21 @@ Configuration Sample_xDscWebServiceRegistration_UseSQLProvider DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt" Contents = $RegistrationKey } + + Firewall PSDSCPullServerRule + { + Ensure = 'Present' + Name = "DSC_PullServer_$Port" + DisplayName = "DSC PullServer $Port" + Group = 'DSC PullServer' + Enabled = $true + Action = 'Allow' + Direction = 'InBound' + LocalPort = $Port + Protocol = 'TCP' + DependsOn = '[xDscWebService]PSDSCPullServer' + } + } } @@ -126,14 +169,19 @@ Configuration Sample_MetaConfigurationToRegisterWithSecurePullServer $NodeName = 'localhost', [Parameter()] - [ValidateNotNullOrEmpty()] + [Parameter(Mandatory = $true)] [System.String] $RegistrationKey, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] - $ServerName = 'localhost' + $ServerName = 'localhost', + + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt16] + $Port = 8080 ) Node $NodeName @@ -145,14 +193,14 @@ Configuration Sample_MetaConfigurationToRegisterWithSecurePullServer ConfigurationRepositoryWeb CONTOSO-PullSrv { - ServerURL = "https://$ServerName`:8080/PSDSCPullServer.svc" + ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" RegistrationKey = $RegistrationKey ConfigurationNames = @('ClientConfig') } ReportServerWeb CONTOSO-PullSrv { - ServerURL = "https://$ServerName`:8080/PSDSCPullServer.svc" + ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" RegistrationKey = $RegistrationKey } } diff --git a/Examples/Sample_xDscWebServiceRegistration_Win2k12and2k12R2.ps1 b/Examples/Sample_xDscWebServiceRegistration_Win2k12and2k12R2.ps1 index 2c8fbdf1a..de9f14db6 100644 --- a/Examples/Sample_xDscWebServiceRegistration_Win2k12and2k12R2.ps1 +++ b/Examples/Sample_xDscWebServiceRegistration_Win2k12and2k12R2.ps1 @@ -1,17 +1,24 @@ <# .SYNOPSIS + The Sample_xDscWebServiceRegistration_Win2k12and2k12R2 configuration + sets up a DSC pull server + + .DESCRIPTION The Sample_xDscWebServiceRegistration_Win2k12and2k12R2 configuration sets up a DSC pull server that is capable for client nodes to register with it. - Prerequisite: Install a certificate in 'CERT:\LocalMachine\MY\' store - For testing environments, you could use a self-signed - certificate. (New-SelfSignedCertificate cmdlet could - generate one for you). For production environments, you - will need a certificate signed by valid CA. Registration - only works over https protocols. So to use registration - feature, a secure pull server setup with certificate is - necessary. + Prerequisite: 1 - Install a certificate in 'CERT:\LocalMachine\MY\' store + For testing environments, you could use a self-signed + certificate. (New-SelfSignedCertificate cmdlet could + generate one for you). For production environments, you + will need a certificate signed by valid CA. Registration + only works over https protocols. So to use registration + feature, a secure pull server setup with certificate is + necessary. + 2 - To configure a Firewall Rule (Exception) to allow external + connections the [NetworkingDsc](https://github.com/PowerShell/NetworkingDsc) + DSC module is required. .PARAMETER NodeName The name of the node being configured as a DSC Pull Server. @@ -40,17 +47,23 @@ Configuration Sample_xDscWebServiceRegistration_Win2k12and2k12R2 [System.String[]] $NodeName = 'localhost', - [Parameter()] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $CertificateThumbPrint, - [Parameter()] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] - $RegistrationKey + $RegistrationKey, + + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt16] + $Port = 8080 ) + Import-DscResource -ModuleName NetworkingDsc Import-DSCResource -ModuleName xPSDesiredStateConfiguration # To explicitly import the resource WindowsFeature and File. Import-DscResource -ModuleName PSDesiredStateConfiguration @@ -67,7 +80,7 @@ Configuration Sample_xDscWebServiceRegistration_Win2k12and2k12R2 { Ensure = 'Present' EndpointName = 'PSDSCPullServer' - Port = 8080 + Port = $Port PhysicalPath = "$env:SystemDrive\inetpub\PSDSCPullServer" CertificateThumbPrint = $CertificateThumbPrint ModulePath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules" @@ -78,6 +91,7 @@ Configuration Sample_xDscWebServiceRegistration_Win2k12and2k12R2 AcceptSelfSignedCertificates = $true UseSecurityBestPractices = $true Enable32BitAppOnWin64 = $true + ConfigureFirewall = $false } File RegistrationKeyFile @@ -87,6 +101,20 @@ Configuration Sample_xDscWebServiceRegistration_Win2k12and2k12R2 DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt" Contents = $RegistrationKey } + + Firewall PSDSCPullServerRule + { + Ensure = 'Present' + Name = "DSC_PullServer_$Port" + DisplayName = "DSC PullServer $Port" + Group = 'DSC PullServer' + Enabled = $true + Action = 'Allow' + Direction = 'InBound' + LocalPort = $Port + Protocol = 'TCP' + DependsOn = '[xDscWebService]PSDSCPullServer' + } } } @@ -124,14 +152,19 @@ Configuration Sample_MetaConfigurationToRegisterWithSecurePullServer $NodeName = 'localhost', [Parameter()] - [ValidateNotNullOrEmpty()] + [Parameter(Mandatory = $true)] [System.String] $RegistrationKey, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] - $ServerName = 'localhost' + $ServerName = 'localhost', + + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt16] + $Port = 8080 ) Node $NodeName @@ -143,14 +176,14 @@ Configuration Sample_MetaConfigurationToRegisterWithSecurePullServer ConfigurationRepositoryWeb CONTOSO-PullSrv { - ServerURL = "https://$ServerName`:8080/PSDSCPullServer.svc" + ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" RegistrationKey = $RegistrationKey ConfigurationNames = @('ClientConfig') } ReportServerWeb CONTOSO-PullSrv { - ServerURL = "https://$ServerName`:8080/PSDSCPullServer.svc" + ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" RegistrationKey = $RegistrationKey } } diff --git a/README.md b/README.md index 742dad928..2f17865e0 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,26 @@ None best practice security settings. * **Enable32BitAppOnWin64**: When this property is set to true, Pull Server will run on a 32 bit process on a 64 bit machine. +* **ConfigureFirewall**: When this property is set to true, a Windows Firewall + rule will be created, which allows incoming HTTP traffic for the + selected **Port**. Default: **true** + +**Remark:** + +Configuring a Windows Firewall rule (exception) for a DSC Pull Server instance +by using the xDscWebService resource is **considered deprecated** and thus will +be removed in the future. + +DSC will issue a warning when the **ConfigureFirewall** property is set to +**true**. Currently the default value is **true** to maintain backwards +compatibility with existing configurations. At a later time the default value +will be set to **false** and in the last step the support to create a +firewall rule using xDscWebService will be removed. + +All users are requested to adjust existing configurations so that the +**ConfigureFirewall** is set to **false** and a required Windows Firewall rule +is created by using the **Firewall** resource from the +[NetworkingDsc](https://github.com/PowerShell/NetworkingDsc) module. ### xGroup diff --git a/Tests/Integration/MSFT_xDSCWebService.Integration.tests.ps1 b/Tests/Integration/MSFT_xDSCWebService.Integration.tests.ps1 index ecbd3eb17..59a9b929d 100644 --- a/Tests/Integration/MSFT_xDSCWebService.Integration.tests.ps1 +++ b/Tests/Integration/MSFT_xDSCWebService.Integration.tests.ps1 @@ -11,13 +11,19 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force +# Ensure that Powershell Module 'WebAdministration' is available +if (-not (Install-WindowsFeatureAndVerify -Name Web-Mgmt-Tools)) +{ + Write-Error -Message 'Failed to verify for required Windows Feature. Unable to continue ...' -ErrorAction:Stop +} +Import-Module -Name WebAdministration -ErrorAction:Stop -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -ErrorAction:Stop -Force $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` -DSCResourceName $script:dcsResourceName ` -TestType Integration -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'CommonTestHelper.psm1') +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'CommonTestHelper.psm1') -ErrorAction:Stop -Force if (Test-SkipContinuousIntegrationTask -Type 'Integration') { @@ -79,24 +85,106 @@ function Invoke-CommonResourceTesting .SYNOPSIS Performs common tests to ensure that the DSC pull server was properly installed. + + .PARAMETER WebsiteName + name of the Pull Server website + + .PARAMETER ResourceState + state of the resource (website) + + .PARAMETER WebsiteState + State of the website #> -function Test-DSCPullServerIsPresent +function Test-DSCPullServer { [CmdletBinding()] param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $WebsiteName, + + [Parameter(Mandatory = $true)] + [ValidateSet('Present', 'Absent')] + [System.String] + $ResourceState, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $WebsiteState ) It 'Should create a web.config file at the web site root' { Test-Path -Path (Join-Path -Path $ConfigurationData.AllNodes.PhysicalPath -ChildPath 'web.config') | Should -Be $true } - It 'Should create a firewall rule for the chosen port' { - (Get-NetFirewallRule | Where-Object -FilterScript { - $_.DisplayName -eq 'DSCPullServer_IIS_Port' - } | Measure-Object).Count | Should -Be 1 + switch ($ResourceState) + { + 'Present' { + It ("Should exist a WebSite called $WebsiteName") { + Get-WebSite -Name $WebsiteName | Should Not Be $null + } + + It ("WebSite $WebsiteName should be started") { + $website = Get-WebSite -Name $WebsiteName + $website | Should Not Be $null + $website.state | Should BeExactly $WebsiteState + } + } + + 'Absent' { + It ("Should not exist a WebSite called $WebsiteName") { + Get-WebSite -Name $WebsiteName | Should Be $null + } + } + } +} + +<# + .SYNOPSIS + Performs a test on defined firewall rules + + .PARAMETER RuleName + name of the firewall rule + + .PARAMETER State + state of the rule +#> +function Test-DSCPullServerFirewallRule +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $RuleName, + + [Parameter(Mandatory = $true)] + [ValidateSet('Present', 'Absent')] + [System.String] + $State + ) + + Write-Verbose -Message "Test-DSCPullServerFirewallRule $RuleName for state $State." + + $expectedRuleCount = 0 + if ('Present' -eq $State) + { + $expectedRuleCount = 1 + } + + It ("Should $(if ('Present' -eq $State) { '' } else { 'not ' })create a firewall rule $RuleName for the chosen port") { + $ruleCnt = (Get-NetFirewallRule | Where-Object -FilterScript { + $_.DisplayName -eq $RuleName #'DSCPullServer_IIS_Port' + } | Measure-Object).Count + Write-Verbose -Message "Found $ruleCnt firewall rules with name '$RuleName'" + $ruleCnt | Should -Be $expectedRuleCount } } + #endregion # Using try/finally to always cleanup. @@ -147,8 +235,26 @@ try Invoke-CommonResourceTesting -ConfigurationName $configurationName - Test-DSCPullServerIsPresent + Test-DSCPullServer -WebsiteName 'PSDSCPullServer' -ResourceState 'Present' -WebsiteState 'Started' + Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -State 'Present' + } + } + + Context 'No firewall configuration' { + $configurationName = 'MSFT_xDSCWebService_PullTestWithoutFirewall_Config' + + BeforeAll { + Invoke-CommonResourceTesting -ConfigurationName $ensureAbsentConfigurationName } + + AfterAll { + Invoke-CommonResourceTesting -ConfigurationName $ensureAbsentConfigurationName + } + + Invoke-CommonResourceTesting -ConfigurationName $configurationName + + Test-DSCPullServer -WebsiteName 'PSDSCPullServer' -ResourceState 'Present' -WebsiteState 'Started' + Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -State 'Absent' } } #endregion diff --git a/Tests/Integration/MSFT_xDSCWebService.config.ps1 b/Tests/Integration/MSFT_xDSCWebService.config.ps1 index 1d62ca3d4..5e9f78451 100644 --- a/Tests/Integration/MSFT_xDSCWebService.config.ps1 +++ b/Tests/Integration/MSFT_xDSCWebService.config.ps1 @@ -113,3 +113,32 @@ Configuration MSFT_xDSCWebService_PullTestWithoutSecurityBestPractices_Config } } } + +<# + .SYNOPSIS + Sets up a DSC pull server without firewall exceptions +#> +Configuration MSFT_xDSCWebService_PullTestWithoutFirewall_Config +{ + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + node $AllNodes.NodeName + { + xDSCWebService Integration_Test + { + Ensure = 'Present' + AcceptSelfSignedCertificates = $true + CertificateThumbPrint = $Node.CertificateThumbprint + ConfigurationPath = $Node.ConfigurationPath + Enable32BitAppOnWin64 = $false + EndpointName = $Node.EndpointName + ModulePath = $Node.ModulePath + Port = $Node.Port + PhysicalPath = $Node.PhysicalPath + RegistrationKeyPath = $Node.RegistrationKeyPath + State = 'Started' + UseSecurityBestPractices = $true + ConfigureFirewall = $false + } + } +} diff --git a/Tests/Unit/MSFT_xDSCWebService.Tests.ps1 b/Tests/Unit/MSFT_xDSCWebService.Tests.ps1 index 84bb0ac91..e99e0ebed 100644 --- a/Tests/Unit/MSFT_xDSCWebService.Tests.ps1 +++ b/Tests/Unit/MSFT_xDSCWebService.Tests.ps1 @@ -150,6 +150,8 @@ try function Get-Website {} function Get-WebBinding {} + function Stop-Website {} + $webConfigPath = 'TestDrive:\inetpub\PesterTestSite\Web.config' $null = New-Item -ItemType Directory -Path (Split-Path -Parent $webConfigPath) $null = New-Item -Path $webConfigPath -Value $webConfig @@ -179,6 +181,7 @@ try Mock -CommandName Get-WebConfigAppSetting -ParameterFilter {$AppSettingName -eq 'RegistrationKeyPath'} -MockWith {return $serviceData.RegistrationKeyPath} Mock -CommandName Get-WebConfigAppSetting -ParameterFilter {$AppSettingName -eq 'dbprovider'} -MockWith {return $serviceData.dbprovider} Mock -CommandName Get-WebConfigAppSetting -ParameterFilter {$AppSettingName -eq 'dbconnectionstr'} -MockWith {return $serviceData.dbconnectionstr} + Mock -CommandName Stop-Website -MockWith { Write-Verbose "MOCK:Stop-WebSite $Name" } #endregion Context -Name 'DSC Web Service is installed without certificate' -Fixture { @@ -460,6 +463,7 @@ try <# Create dummy functions so that Pester is able to mock them #> function Get-Website {} function Get-WebBinding {} + function Stop-Website {} #region Mocks Mock -CommandName Get-Command -ParameterFilter {$Name -eq '.\appcmd.exe'} -MockWith { @@ -489,26 +493,36 @@ try #endregion Context -Name 'DSC Service is not installed and Ensure is Absent' -Fixture { + #region Mocks + Mock -CommandName Test-Path -ParameterFilter { $LiteralPath -like "IIS:\Sites\*" } -MockWith { $false } + Mock -CommandName Remove-PSWSEndpoint + #endregion + It 'Should call expected mocks' { Set-TargetResource @testParameters -Ensure Absent + Assert-MockCalled -Exactly -Times 0 -CommandName Get-Website Assert-MockCalled -Exactly -Times 1 -CommandName Get-OSVersion - Assert-MockCalled -Exactly -Times 1 -CommandName Get-Website + Assert-MockCalled -Exactly -Times 1 -CommandName Test-Path + Assert-MockCalled -Exactly -Times 0 -CommandName Remove-PSWSEndpoint Assert-MockCalled -Exactly -Times 0 -CommandName Get-Command } } Context -Name 'DSC Service is installed and Ensure is Absent' -Fixture { #region Mocks - Mock -CommandName Get-Website -MockWith {'Website'} + Mock -CommandName Test-Path -ParameterFilter { $LiteralPath -like "IIS:\Sites\*" } -MockWith { $LiteralPath -eq "IIS:\Sites\$($testParameters.EndpointName)" } + Mock -CommandName Get-Website -MockWith { return $websiteDataHTTP } Mock -CommandName Remove-PSWSEndpoint #endregion It 'Should call expected mocks' { Set-TargetResource @testParameters -Ensure Absent - Assert-MockCalled -Exactly -Times 1 -CommandName Get-Website + Assert-MockCalled -Exactly -Times 0 -CommandName Get-Website + Assert-MockCalled -Exactly -Times 1 -CommandName Get-OSVersion Assert-MockCalled -Exactly -Times 0 -CommandName Get-Command + Assert-MockCalled -Exactly -Times 1 -CommandName Test-Path Assert-MockCalled -Exactly -Times 1 -CommandName Remove-PSWSEndpoint } } @@ -745,6 +759,7 @@ try function Get-Website {} function Get-WebBinding {} + function Stop-Website {} #region Mocks Mock -CommandName Get-Command -ParameterFilter {$Name -eq '.\appcmd.exe'} -MockWith { @@ -775,19 +790,23 @@ try Context -Name 'DSC Web Service is installed as HTTP' -Fixture { Mock -CommandName Get-Website -MockWith {$WebsiteDataHTTP} + Mock -CommandName Test-PullServerFirewallConfiguration -MockWith { $true } It 'Should return $false when Ensure is Absent' { Test-TargetResource @testParameters -Ensure Absent | Should -Be $false } + It 'Should return $false if Port doesn''t match' { Test-TargetResource @testParameters -Ensure Present -Port 8081 | Should -Be $false } + It 'Should return $false if Certificate Thumbprint is set' { $altTestParameters = $testParameters.Clone() $altTestParameters.CertificateThumbprint = $certificateData[0].Thumbprint Test-TargetResource @altTestParameters -Ensure Present | Should -Be $false } + It 'Should return $false if Physical Path doesn''t match' { Mock -CommandName Test-WebsitePath -MockWith {$true} -Verifiable @@ -804,6 +823,7 @@ try Assert-VerifiableMock } + It 'Should return $false when dbProvider is not set' { Mock -CommandName Get-WebConfigAppSetting -MockWith {''} -Verifiable @@ -945,6 +965,7 @@ try Context -Name 'DSC Web Service is installed as HTTPS' -Fixture { #region Mocks Mock -CommandName Get-Website -MockWith {$websiteDataHTTPS} + Mock -CommandName Test-PullServerFirewallConfiguration -MockWith { $true } #endregion It 'Should return $false if Certificate Thumbprint is set to AllowUnencryptedTraffic' { @@ -992,6 +1013,7 @@ try function Get-Website {} function Get-WebBinding {} + function Stop-Website {} $endpointPhysicalPath = 'TestDrive:\SitePath1' Mock -CommandName Get-ItemProperty -MockWith {$endpointPhysicalPath} @@ -1011,6 +1033,7 @@ try function Get-Website {} function Get-WebBinding {} + function Stop-Website {} $webConfigPath = 'TestDrive:\Web.config' $null = New-Item -Path $webConfigPath -Value $webConfig @@ -1061,6 +1084,7 @@ try function Get-Website {} function Get-WebBinding {} + function Stop-Website {} $webConfigPath = 'TestDrive:\Web.config' $null = New-Item -Path $webConfigPath -Value $webConfig @@ -1102,6 +1126,8 @@ try function Get-Website {} function Get-WebBinding {} + function Stop-Website {} + $webConfigPath = 'TestDrive:\Web.config' $null = New-Item -Path $webConfigPath -Value $webConfig @@ -1123,6 +1149,8 @@ try function Get-Website {} function Get-WebBinding {} + function Stop-Website {} + $webConfigPath = 'TestDrive:\Web.config' $null = New-Item -Path $webConfigPath -Value $webConfig @@ -1139,6 +1167,8 @@ try function Get-Website {} function Get-WebBinding {} + function Stop-Website {} + $appHostConfigSection = [System.Management.Automation.PSObject] @{OverrideMode = ''} $appHostConfig = [System.Management.Automation.PSObject] @{} $webAdminSrvMgr = [System.Management.Automation.PSObject] @{} @@ -1161,6 +1191,8 @@ try function Get-Website {} function Get-WebBinding {} + function Stop-Website {} + Mock -CommandName Get-ChildItem -MockWith {,@($certificateData)} It 'Should return the certificate thumbprint when the certificate is found' { Find-CertificateThumbprintWithSubjectAndTemplateName -Subject $certificateData[0].Subject -TemplateName 'WebServer' | Should -Be $certificateData[0].Thumbprint