Framework/Core/SVT/Services/DBForPostgreSQL.ps1
#using namespace Microsoft.Azure.Commands.AppService.Models Set-StrictMode -Version Latest class DBForPostgreSQL: AzSVTBase { hidden [PSObject] $ResourceObject; hidden [PSObject] $PostgreSQLFirewallRules; DBForPostgreSQL([string] $subscriptionId, [SVTResource] $svtResource): Base($subscriptionId, $svtResource) { $this.GetResourceObject(); } hidden [PSObject] GetResourceObject() { if (-not $this.ResourceObject) { $this.ResourceObject = Get-AzResource -ResourceId $this.ResourceId if(-not $this.ResourceObject) { throw ([SuppressedException]::new(("Resource '{0}' not found under Resource Group '{1}'" -f ($this.ResourceContext.ResourceName), ($this.ResourceContext.ResourceGroupName)), [SuppressedExceptionType]::InvalidOperation)) } } return $this.ResourceObject; } hidden [ControlResult] CheckPostgreSQLSSLConnection([ControlResult] $controlResult) { #Fetching ssl Object $ssl_option = $this.ResourceObject.properties.sslEnforcement #checking ssl is enabled or disabled if($ssl_option -eq 'Enabled') { $controlResult.AddMessage([VerificationResult]::Passed, "SSL enforcement is enabled"); } else { $controlResult.AddMessage([VerificationResult]::Failed, "SSL enforcement is disabled"); } #return return $controlResult } [PSObject] GetFirewallRules() { if ($null -eq $this.PostgreSQLFirewallRules) { # List firewall rules for Azure Database for PostgreSQL $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $uri=[system.string]::Format($ResourceAppIdURI+"/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.DBforPostgreSQL/servers/{2}/firewallRules?api-version=2017-12-01",$this.SubscriptionContext.SubscriptionId,$this.ResourceContext.ResourceGroupName,$this.ResourceContext.ResourceName) try { $this.PostgreSQLFirewallRules = [WebRequestHelper]::InvokeGetWebRequest($uri); } catch { $this.PostgreSQLFirewallRules = 'error' } } return $this.PostgreSQLFirewallRules } hidden [ControlResult] CheckPostgreSQLFirewallIpRange([ControlResult] $controlResult) { $firewallRules = $this.GetFirewallRules() if ($firewallRules -eq 'error') { $controlResult.AddMessage([VerificationResult]::Manual, "Unable to get firewall rules for - [$($this.ResourceContext.ResourceName)]"); } else { if([Helpers]::CheckMember($firewallRules,"id")) { $firewallRulesForAzure = $firewallRules | Where-Object { $_.name -ne "AllowAllWindowsAzureIps" } if(($firewallRulesForAzure | Measure-Object ).Count -eq 0) { $controlResult.AddMessage([VerificationResult]::Passed, "No custom firewall rules found."); return $controlResult } $controlResult.AddMessage([MessageData]::new("Current firewall settings for - ["+ $this.ResourceContext.ResourceName +"]", $firewallRulesForAzure)); $anyToAnyRule = $firewallRulesForAzure | Where-Object { $_.properties.StartIpAddress -eq $this.ControlSettings.IPRangeStartIP -and $_.properties.EndIpAddress -eq $this.ControlSettings.IPRangeEndIP} if (($anyToAnyRule | Measure-Object).Count -gt 0) { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("Firewall rule covering all IPs (Start IP address: $($this.ControlSettings.IPRangeStartIP) To End IP Address: $($this.ControlSettings.IPRangeEndIP)) is defined.")); } else { $controlResult.VerificationResult = [VerificationResult]::Verify } $controlResult.SetStateData("Firewall IP addresses", $firewallRules); } else { $controlResult.AddMessage([VerificationResult]::Passed, "No custom firewall rules found."); } } return $controlResult; } hidden [ControlResult] CheckPostgreSQLFirewallAccessAzureService([ControlResult] $controlResult) { $firewallRules = $this.GetFirewallRules() if ($firewallRules -eq 'error') { $controlResult.AddMessage([VerificationResult]::Manual, "Unable to get firewall rules for - [$($this.ResourceContext.ResourceName)]"); } else { if([Helpers]::CheckMember($firewallRules, "id")) { $firewallRulesForAzure = $firewallRules | Where-Object { $_.name -eq "AllowAllWindowsAzureIps" } if(($firewallRulesForAzure | Measure-Object ).Count -gt 0) { $controlResult.AddMessage([VerificationResult]::Verify, [MessageData]::new("'Allow access to Azure services' is turned ON.")); } else { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("'Allow access to Azure services' is turned OFF.")); } } else { $controlResult.AddMessage([VerificationResult]::Passed, "No custom firewall rules found."); } } return $controlResult } # This is a verify control. As the backup is by default enabled, the customer must verify backup settings from a BC-DR standpoint. hidden [ControlResult] CheckPostgreSQLBCDRStatus([ControlResult] $controlResult) { $backupSettings = @{ "backupRetentionDays" = $this.ResourceObject.properties.storageProfile.backupRetentionDays; "geoRedundantBackup" = $this.ResourceObject.properties.storageProfile.geoRedundantBackup } $controlResult.AddMessage([VerificationResult]::Verify, "Verify back up settings for PostgreSQL server.",$backupSettings); $controlResult.SetStateData("Backup setting:", $backupSettings); return $controlResult; } hidden [ControlResult] CheckPostgreSQLATPSetting([ControlResult] $controlResult) { $securityAlertPolicies = "" # Get advanced threat protection settings for Azure Database of PostgreSQL $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $uri=[system.string]::Format($ResourceAppIdURI+"/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.DBforPostgreSQL/servers/{2}/securityAlertPolicies/Default?api-version=2017-12-01",$this.SubscriptionContext.SubscriptionId,$this.ResourceContext.ResourceGroupName,$this.ResourceContext.ResourceName) try { $securityAlertPolicies = [WebRequestHelper]::InvokeGetWebRequest($uri); } catch { $controlResult.AddMessage([VerificationResult]::Manual, "Unable to get Advanced Threat Protection setting for - [$($this.ResourceContext.ResourceName)]"); } if([Helpers]::CheckMember($securityAlertPolicies,"properties.state") -and [Helpers]::CheckMember($securityAlertPolicies.properties,"emailAccountAdmins", $false)) { if(($securityAlertPolicies.properties.state -eq 'Enabled') -and ($securityAlertPolicies.properties.emailAccountAdmins -eq 'true')) { $controlResult.AddMessage([VerificationResult]::Passed, "Advanced Threat Protection is enabled."); } else { $result = @{ 'securityAlertPolicies' = @{'State' = $securityAlertPolicies.properties.state; 'emailAccountAdmins' = $securityAlertPolicies.properties.emailAccountAdmins }} $controlResult.AddMessage([VerificationResult]::Failed, "Advanced Threat Protection is disabled.", $result); $controlResult.SetStateData("Advanced Threat Protection setting:", $result); } } else { $controlResult.AddMessage([VerificationResult]::Manual, "Unable to get Advanced Threat Protection setting for - [$($this.ResourceContext.ResourceName)]"); } return $controlResult; } hidden [ControlResult] CheckPostgreSQLVnetRules([ControlResult] $controlResult) { $virtualNetworkRules = '' # Get virtual network rules for Azure Database of PostgreSQL $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $uri=[system.string]::Format($ResourceAppIdURI+"/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.DBforPostgreSQL/servers/{2}/virtualNetworkRules?api-version=2017-12-01",$this.SubscriptionContext.SubscriptionId,$this.ResourceContext.ResourceGroupName,$this.ResourceContext.ResourceName) try { $virtualNetworkRules = [WebRequestHelper]::InvokeGetWebRequest($uri); } catch { $controlResult.AddMessage([VerificationResult]::Manual, "Unable to fetch details of functions."); } if ([Helpers]::CheckMember($virtualNetworkRules,"id")) { $vnetRules = $virtualNetworkRules | ForEach-Object { @{ 'name'="$($_.name)"; 'id'="$($_.id)"; 'virtualNetworkSubnetId'="$($_.properties.virtualNetworkSubnetId)" } } $controlResult.AddMessage([VerificationResult]::Passed, "The enabled virtual network rules are:",$vnetRules); } else { $controlResult.AddMessage([VerificationResult]::Verify, "There are no virtual network rules enabled for '$($this.ResourceContext.ResourceName)' server. Consider using virtual network rules for improved isolation."); } return $controlResult } # This function checks for a specific category of log. # We have created this custom function since log category based filter is not available in the default 'CheckDiagnosticsSettings' function. hidden [ControlResult] CheckPostgreSQLDiagnosticsSettings([ControlResult] $controlResult) { $diagnostics = $Null try { $diagnostics = Get-AzDiagnosticSetting -ResourceId $this.ResourceId -ErrorAction Stop -WarningAction SilentlyContinue } catch { if([Helpers]::CheckMember($_.Exception, "Response") -and ($_.Exception).Response.StatusCode -eq [System.Net.HttpStatusCode]::NotFound) { $controlResult.AddMessage([VerificationResult]::Failed, "Diagnostics setting is disabled for resource - [$($this.ResourceContext.ResourceName)]."); return $controlResult } else { $this.PublishException($_); } } if($Null -ne $diagnostics -and ($diagnostics.Logs | Measure-Object).Count -ne 0) { $nonCompliantLogs = $diagnostics.Logs | Where-Object {$_.Category -eq 'PostgreSQLLogs'} | Where-Object { -not ($_.Enabled -and ($_.RetentionPolicy.Days -eq $this.ControlSettings.Diagnostics_RetentionPeriod_Forever -or $_.RetentionPolicy.Days -ge $this.ControlSettings.Diagnostics_RetentionPeriod_Min))}; $selectedDiagnosticsProps = $diagnostics | Select-Object -Property @{ Name = "Logs"; Expression = {$_.Logs | Where-Object {$_.Category -eq 'PostgreSQLLogs'}}}, StorageAccountId, EventHubName, Name; if(($nonCompliantLogs | Measure-Object).Count -eq 0) { $controlResult.AddMessage([VerificationResult]::Passed, "Diagnostics settings are correctly configured for resource - [$($this.ResourceContext.ResourceName)]", $selectedDiagnosticsProps); } else { $failStateDiagnostics = $nonCompliantLogs | Select-Object -Property @{ Name = "Logs"; Expression = {$_.Logs | Where-Object {$_.Category -eq 'PostgreSQLLogs'}}}, StorageAccountId, EventHubName, Name; $controlResult.SetStateData("Non compliant resources are:", $failStateDiagnostics); $controlResult.AddMessage([VerificationResult]::Failed, "Diagnostics settings are either disabled OR not retaining logs for at least $($this.ControlSettings.Diagnostics_RetentionPeriod_Min) days for resource - [$($this.ResourceContext.ResourceName)]", $selectedDiagnosticsProps); } } else { $controlResult.AddMessage([VerificationResult]::Failed, "Diagnostics setting is disabled for resource - [$($this.ResourceContext.ResourceName)]."); } return $controlResult } hidden [ControlResult] CheckPostgreSQLConnectionThrottlingServerParameter([ControlResult] $controlResult) { $status = ''; $status = $this.CheckServerParameters("connection_throttling") if ($status -eq "ON") { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("Connection throttling for $($this.ResourceContext.ResourceName) is turned ON.")); } elseif ($status -eq "OFF") { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("Connection throttling for $($this.ResourceContext.ResourceName) is turned OFF.")); } else { $controlResult.AddMessage([VerificationResult]::Manual, [MessageData]::new("Unable to validate control. Please verify from portal, 'connection-throttling' is ON or OFF.")); } return $controlResult } hidden [string] CheckServerParameters([string] $parameterName) { $status = ''; # Get postgreSQL server parameter $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl() $uri = [system.string]::Format($ResourceAppIdURI + "subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.DBforPostgreSQL/servers/{2}/configurations/{3}?api-version=2017-12-01", $this.SubscriptionContext.SubscriptionId, $this.ResourceContext.ResourceGroupName, $this.ResourceContext.ResourceName, $parameterName) $response = $null; try { $response = [WebRequestHelper]::InvokeGetWebRequest($uri); } catch { $response = $null; } if (($null -ne $response) -and (($response | Measure-Object).Count -gt 0)) { if (([Helpers]::CheckMember($response[0], "properties.value")) -and ($response.properties.value -eq "ON")) { $status = 'ON' } else { $status = 'OFF' } } else { $status = 'error' } return $status } hidden [ControlResult] CheckPostgreSQLLoggingParameters([ControlResult] $controlResult) { $statusLogConnections = ''; $statusLogDisconnections = ''; $statusLogConnections = $this.CheckServerParameters("log_connections") $statusLogDisconnections = $this.CheckServerParameters("log_disconnections") $message = "'log_connections' for $($this.ResourceContext.ResourceName) is turned " + $statusLogConnections + "." $message += "`n'log_disconnections' for $($this.ResourceContext.ResourceName) is turned " + $statusLogDisconnections + "." if (($statusLogConnections -eq "ON") -and ($statusLogDisconnections -eq "ON")) { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new($message)); } elseif (($statusLogConnections -eq "OFF") -or ($statusLogDisconnections -eq "OFF")) { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new($message)); $controlResult.SetStateData("Log server parameters", @{ 'log_connections' = $statusLogConnections; "log_disconnections" = $statusLogDisconnections}); } else { $controlResult.AddMessage([VerificationResult]::Manual, [MessageData]::new("Unable to validate control.Please verify from portal values for log_connections and log_disconnections.")); } return $controlResult } } |