Framework/Core/SVT/Services/AppService.ps1
#using namespace Microsoft.Azure.Commands.AppService.Models Set-StrictMode -Version Latest class AppService: SVTBase { hidden [PSObject] $ResourceObject; hidden [PSObject] $WebAppDetails; hidden [PSObject] $AuthenticationSettings; hidden [bool] $IsReaderRole; AppService([string] $subscriptionId, [string] $resourceGroupName, [string] $resourceName): Base($subscriptionId, $resourceGroupName, $resourceName) { $this.GetResourceObject(); } AppService([string] $subscriptionId, [SVTResource] $svtResource): Base($subscriptionId, $svtResource) { $this.GetResourceObject(); } hidden [PSObject] GetResourceObject() { if (-not $this.ResourceObject) { # Get App Service details $this.ResourceObject = Get-AzureRmResource -ResourceName $this.ResourceContext.ResourceName ` -ResourceType $this.ResourceContext.ResourceType ` -ResourceGroupName $this.ResourceContext.ResourceGroupName if(-not $this.ResourceObject) { throw ("Resource '$($this.ResourceContext.ResourceName)' not found under Resource Group '$($this.ResourceContext.ResourceGroupName)'") } # Get web sites details $this.WebAppDetails = Get-AzureRmWebApp -Name $this.ResourceContext.ResourceName ` -ResourceGroupName $this.ResourceContext.ResourceGroupName try { $this.AuthenticationSettings = Invoke-AzureRmResourceAction -ResourceType "Microsoft.Web/sites/config/authsettings" ` -ResourceGroupName $this.ResourceContext.ResourceGroupName ` -ResourceName $this.ResourceContext.ResourceName ` -Action list ` -ApiVersion $this.ControlSettings.AppService.AADAuthAPIVersion ` -Force ` -ErrorAction Stop $this.IsReaderRole = $false; } catch { if(($_.Exception | Get-Member -Name "HttpStatus" ) -and $_.Exception.HttpStatus -eq "Forbidden") { $this.IsReaderRole = $true; } } } return $this.ResourceObject; } [ControlItem[]] ApplyServiceFilters([ControlItem[]] $controls) { $serviceFilterTag = "AppService"; if([Helpers]::CheckMember($this.ResourceObject, "Kind")) { if($this.ResourceObject.Kind -eq "functionapp") { $serviceFilterTag = "FunctionApp"; } } $result = @(); $result += $controls | Where-Object { $_.Tags -contains $serviceFilterTag }; return $result; } hidden [ControlResult] CheckAppServiceCustomDomainWithSSLConfig([ControlResult] $controlResult) { # Get custom domain URLs $customHostNames = $this.ResourceObject.Properties.HostNames | Where-Object { -not $_.EndsWith(".azurewebsites.net") }; # Combine custom domain name and SSL configuration TCP if(($customHostNames | Measure-Object).Count -gt 0) { $controlResult.AddMessage([MessageData]::new("Custom domains are configured for resource " + $this.ResourceContext.ResourceName), $customHostNames); $SSLStateNotEnabled = $this.ResourceObject.Properties.hostNameSslStates | Where-Object { (($customHostNames | Measure-Object) -contains $_.name) -and ($_.sslState -eq 'Disabled')} | Select-Object -Property Name if($null -eq $SSLStateNotEnabled) { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("SSL configuration for resource " + $this.ResourceContext.ResourceName + " is enabled for all custom domains", $this.ResourceObject.Properties.hostNameSslStates)); } else { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("SSL configuration for resource " + $this.ResourceContext.ResourceName + " is not enabled for all custom domains", $this.ResourceObject.Properties.hostNameSslStates)); } } else { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("Custom domains are not configured for resource " + $this.ResourceContext.ResourceName, $this.ResourceObject.Properties.HostNames)); } return $controlResult; } hidden [ControlResult] CheckAppServiceADAuthentication([ControlResult] $controlResult) { try { if($this.IsReaderRole) { $controlResult.AddMessage([VerificationResult]::Manual, [MessageData]::new("Control can not be validated due to insufficient access permission on resource")); } else { $aadSettings = New-Object PSObject Add-Member -InputObject $aadSettings -MemberType NoteProperty -Name "Enabled" -Value $this.AuthenticationSettings.Properties.enabled Add-Member -InputObject $aadSettings -MemberType NoteProperty -Name "ClientId" -Value $this.AuthenticationSettings.Properties.ClientId $AADEnabled = $false if($this.AuthenticationSettings.Properties.enabled -eq $True) { if($null -ne $this.AuthenticationSettings.Properties.ClientId) { $AADEnabled = $True; } } if($AADEnabled) { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("AAD Authentication for resource " + $this.ResourceContext.ResourceName + " is enabled", $aadSettings)); } else { $controlResult.AddMessage([VerificationResult]::Verify, [MessageData]::new("Verify that AAD Authentication for resource " + $this.ResourceContext.ResourceName + " is enabled", $aadSettings)); $controlResult.SetStateData("App Service AAD settings", $aadSettings); } } } catch { throw; } return $controlResult; } hidden [ControlResult] CheckAppServiceRemoteDebuggingConfiguration([ControlResult] $controlResult) { if($this.WebAppDetails.SiteConfig.RemoteDebuggingEnabled) { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("Remote debugging for resource " + $this.ResourceContext.ResourceName + " is turned on", ($this.WebAppDetails.SiteConfig | Select-Object RemoteDebuggingEnabled))); } else { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("Remote debugging for resource " + $this.ResourceContext.ResourceName + " is turned off", ($this.WebAppDetails.SiteConfig | Select-Object RemoteDebuggingEnabled))); } return $controlResult; } hidden [ControlResult] CheckAppServiceWebSocketsConfiguration([ControlResult] $controlResult) { if($this.WebAppDetails.SiteConfig.WebSocketsEnabled) { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("Web sockets for resource " + $this.ResourceContext.ResourceName + " is enabled", ($this.WebAppDetails.SiteConfig | Select-Object WebSocketsEnabled))); } else { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("Web sockets for resource " + $this.ResourceContext.ResourceName + " is not enabled", ($this.WebAppDetails.SiteConfig | Select-Object WebSocketsEnabled))); } return $controlResult; } hidden [ControlResult] CheckAppServiceAlwaysOnConfiguration([ControlResult] $controlResult) { if($this.WebAppDetails.SiteConfig.AlwaysOn) { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("Always On feature for resource " + $this.ResourceContext.ResourceName + " is enabled", ($this.WebAppDetails.SiteConfig | Select-Object AlwaysOn))); } else { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("Always On feature for resource " + $this.ResourceContext.ResourceName + " is not enabled", ($this.WebAppDetails.SiteConfig | Select-Object AlwaysOn))); } return $controlResult; } hidden [ControlResult] CheckAppService64BitPlatformConfiguration([ControlResult] $controlResult) { if($this.WebAppDetails.SiteConfig.Use32BitWorkerProcess) { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("32-bit platform is used for resource " + $this.ResourceContext.ResourceName, ($this.WebAppDetails.SiteConfig | Select-Object Use32BitWorkerProcess))); } else { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("64-bit platform is used for resource " + $this.ResourceContext.ResourceName, ($this.WebAppDetails.SiteConfig | Select-Object Use32BitWorkerProcess))); } return $controlResult; } hidden [ControlResult] CheckAppServiceDotNetFrameworkVersion([ControlResult] $controlResult) { $dotNetFrameworkVersion = $this.WebAppDetails.SiteConfig.NetFrameworkVersion $splitVersionNumber = $this.ControlSettings.AppService.LatestDotNetFrameworkVersionNumber.split(".") # Compare App Service Net Framework version with latest Net Framework version from configuration $isCompliant = $dotNetFrameworkVersion.CompareTo($splitVersionNumber[0] + "." + $splitVersionNumber[1]) -eq 0 if($isCompliant) { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("Latest .Net Framework version is used for resource " + $this.ResourceContext.ResourceName, ($this.WebAppDetails.SiteConfig | Select-Object NetFrameworkVersion))); } else { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("Latest .Net Framework version is not used for resource " + $this.ResourceContext.ResourceName, ($this.WebAppDetails.SiteConfig | Select-Object NetFrameworkVersion))); } return $controlResult; } hidden [ControlResult] CheckAppServiceInstanceCount([ControlResult] $controlResult) { # Get number of instances $sku = (Get-AzureRmResource -ResourceId $this.ResourceObject.Properties.ServerFarmId).Sku if($sku.Capacity -ge $this.ControlSettings.AppService.Minimum_Instance_Count) { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("SKU for resource " + $this.ResourceContext.ResourceName + " is :", $sku)); } else { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("SKU for resource " + $this.ResourceContext.ResourceName + " is :", $sku)); } return $controlResult; } hidden [ControlResult] CheckAppServiceBackupConfiguration([ControlResult] $controlResult) { try { if($this.IsReaderRole) { $controlResult.AddMessage([VerificationResult]::Manual, [MessageData]::new("Control can not be validated due to insufficient access permission on resource")); } else { $backupConfiguration = Get-AzureRmWebAppBackupConfiguration ` -ResourceGroupName $this.ResourceContext.ResourceGroupName ` -Name $this.ResourceContext.ResourceName ` -ErrorAction Stop $isCompliant = $False If ($null -ne $backupConfiguration) { # Backup must be enabled and retention period days must be more than 365 and backup start date is less than current time (backup has been already started) # and at least one backup is available If($backupConfiguration.Enabled -eq $True -and ` ($backupConfiguration.RetentionPeriodInDays -eq $this.ControlSettings.AppService.Backup_RetentionPeriod_Forever -or $backupConfiguration.RetentionPeriodInDays -ge $this.ControlSettings.AppService.Backup_RetentionPeriod_Min) -and` $backupConfiguration.StartTime -le $(Get-Date) -and $backupConfiguration.KeepAtLeastOneBackup -eq $True) { $isCompliant = $True } } if(-not $isCompliant) { $controlResult.VerificationResult = [VerificationResult]::Failed; if($null -ne $backupConfiguration) { $controlResult.AddMessage([MessageData]::new("Configured backup for resource " + $this.ResourceContext.ResourceName + " is not as per the security guidelines. Please make sure that the configured backup is inline with below settings:-")); $controlResult.AddMessage([MessageData]::new("Enabled=True, StorageAccountEncryption=Enabled, RetentionPeriodInDays=0 or RetentionPeriodInDays>=365, BackupStartTime<=CurrentTime, KeepAtLeastOneBackup=True", $backupConfiguration)); } else { $controlResult.AddMessage([MessageData]::new("Backup for resource " + $this.ResourceContext.ResourceName + " is not configured", $backupConfiguration)); } } else { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("Backup for resource " + $this.ResourceContext.ResourceName + " is enabled", $backupConfiguration)); } } } catch { if(($_.Exception).Response.StatusCode.value__ -eq 404) { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("Backup for resource " + $this.ResourceContext.ResourceName + " is not configured")); } else { throw $_ } } return $controlResult; } hidden [ControlResult] CheckAppServiceDiagnosticLogsConfiguration([ControlResult] $controlResult) { $diagnosticLogsConfig = New-Object PSObject Add-Member -InputObject $diagnosticLogsConfig -MemberType NoteProperty -Name "HttpLoggingEnabled" -Value $this.WebAppDetails.SiteConfig.HttpLoggingEnabled Add-Member -InputObject $diagnosticLogsConfig -MemberType NoteProperty -Name "DetailedErrorLoggingEnabled" -Value $this.WebAppDetails.SiteConfig.DetailedErrorLoggingEnabled Add-Member -InputObject $diagnosticLogsConfig -MemberType NoteProperty -Name "RequestTracingEnabled" -Value $this.WebAppDetails.SiteConfig.RequestTracingEnabled $isCompliant = $diagnosticLogsConfig.HttpLoggingEnabled -eq $true -and ` $diagnosticLogsConfig.DetailedErrorLoggingEnabled -eq $true -and ` $diagnosticLogsConfig.RequestTracingEnabled -eq $true if($isCompliant) { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("Diagnostics logs for resource " + $this.ResourceContext.ResourceName + " are enabled", $diagnosticLogsConfig)); } else { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("All configurations of diagnostics logs for resource " + $this.ResourceContext.ResourceName + " are not enabled", $diagnosticLogsConfig)); } return $controlResult; } hidden [ControlResult] CheckAppServiceHttpCertificateSSL([ControlResult] $controlResult) { $hostNames = $this.ResourceObject.Properties.HostNames | Where-Object { -not $_.EndsWith(".scm.azurewebsites.net") }; $isPass = 0 if(($hostNames | Measure-Object).Count -gt 0) { $hostNames | ForEach-Object{ try { $request = [System.Net.WebRequest]::Create('http://'+$_) $response = $request.GetResponse() $responceURL = $response.ResponseUri.OriginalString $response.Close() $scheme = ([System.Uri]$responceURL).Scheme if($scheme -eq "https") { $controlResult.AddMessage([MessageData]::new("HTTP requests are not allowed for resource " + $this.ResourceContext.ResourceName, $responceURL)); } else { $isPass = If ($isPass -lt 2) { 2 } Else { $isPass } $controlResult.AddMessage([MessageData]::new("HTTP requests are allowed for resource " + $this.ResourceContext.ResourceName, $responceURL)); } } catch [System.Management.Automation.MethodInvocationException] { $isPass = If ($isPass -lt 1) { 1 } Else { $isPass } $controlResult.AddMessage([MessageData]::new("Default Host URL is not reachable or self-signed certificate is used", $request.Address.OriginalString)); } } } if($isPass -eq 0) { $controlResult.VerificationResult = [VerificationResult]::Passed } ElseIf ($isPass -eq 1) { $controlResult.VerificationResult = [VerificationResult]::Verify $controlResult.SetStateData("Host urls", $hostNames); } else { $controlResult.VerificationResult = [VerificationResult]::Failed $controlResult.SetStateData("Host urls", $hostNames); } return $controlResult; } hidden [ControlResult] CheckAppServiceLoadCertAppSettings([ControlResult] $controlResult) { if($this.IsReaderRole) { $controlResult.AddMessage([VerificationResult]::Manual, [MessageData]::new("Control can not be validated due to insufficient access permission on resource")); } else { $appSettings = $this.WebAppDetails.SiteConfig.AppSettings $appSettingParameterList = $null if(($appSettings| Measure-Object).Count -gt 0) { $appSettingParameterList = $appSettings | Where-Object { $_.Name -eq $this.ControlSettings.AppService.LoadCertAppSettings -and $_.Value -eq "*"} } if($null -ne $appSettingParameterList) { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("'WEBSITE_LOAD_CERTIFICATES' parameter defined equal to '*' is found in App Settings for resource " + $this.ResourceContext.ResourceName)); } else { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("'WEBSITE_LOAD_CERTIFICATES' parameter defined equal to '*' is not found in App Settings for resource " + $this.ResourceContext.ResourceName)); } } return $controlResult; } } |