Framework/Core/SVT/Services/SQLDatabase.ps1

using namespace Microsoft.Azure.Commands.Sql.Auditing.Model
using namespace Microsoft.Azure.Commands.Sql.ServerUpgrade.Model
using namespace Microsoft.Azure.Commands.Sql.TransparentDataEncryption.Model
using namespace Microsoft.Azure.Commands.Sql.ThreatDetection.Model

Set-StrictMode -Version Latest 
class SQLDatabase: SVTBase
{       
    hidden [PSObject] $ResourceObject;

    SQLDatabase([string] $subscriptionId, [string] $resourceGroupName, [string] $resourceName): 
        Base($subscriptionId, $resourceGroupName, $resourceName) 
    { 
        $this.GetResourceObject();
    }
    
    SQLDatabase([string] $subscriptionId, [SVTResource] $svtResource): 
        Base($subscriptionId, $svtResource) 
    { 
        $this.GetResourceObject();
    }

    hidden [PSObject] GetResourceObject()
    {
        if (-not $this.ResourceObject) {
            $this.ResourceObject =   Get-AzureRmResource -ResourceName $this.ResourceContext.ResourceName -ResourceGroupName $this.ResourceContext.ResourceGroupName 
                                                         
            if(-not $this.ResourceObject)
            {
                throw ("Resource '{0}' not found under Resource Group '{1}'" -f ($this.ResourceContext.ResourceName), ($this.ResourceContext.ResourceGroupName))
            }
        }
        return $this.ResourceObject;
    }

    hidden [ControlResult] CheckSqlServerVersionUpgrade([ControlResult] $controlResult)
    {
        $upgradeStatus = Get-AzureRmSqlServerUpgrade -ResourceGroupName  $this.ResourceContext.ResourceGroupName -ServerName $this.ResourceContext.ResourceName -ErrorAction Stop

        $controlResult.AddMessage([MessageData]::new("Current status of SQL Database server upgrade -",
                                                     $upgradeStatus));

        if ($upgradeStatus.Status -eq [ServerUpgradeStatus]::Completed)   
        {
            $controlResult.VerificationResult = [VerificationResult]::Passed
        }                 
        else
        {
            $controlResult.VerificationResult = [VerificationResult]::Failed
        }

        return $controlResult;
    }

    
    hidden [ControlResult] CheckSqlServerAuditing([ControlResult] $controlResult)
    {
        #Facing issue while refrencing AuditEventType class.
        #--------------Enforced Events-------------------------------------------------
        $enforcedEvents = ([AuditEventType]::Login_Failure, `
                [AuditEventType]::Login_Success, `
                [AuditEventType]::ParameterizedSQL_Failure, `
                [AuditEventType]::PlainSQL_Failure,`
                [AuditEventType]::StoredProcedure_Failure,`
                [AuditEventType]::TransactionManagement_Failure`
                ) 
        #---------------------------------------------------------------------------
        
        $controlResult.AddMessage([MessageData]::new("Following event types must be enabled under SQL Server Auditing:", 
                                    ($enforcedEvents | Select-Object -ExcludeProperty * )));

        $serverAudit = Get-AzureRmSqlServerAuditingPolicy -ResourceGroupName $this.ResourceContext.ResourceGroupName -ServerName $this.ResourceContext.ResourceName -ErrorAction Stop
        $controlResult.AddMessage([MessageData]::new("Current audit status for server ["+ $this.ResourceContext.ResourceName +"]", 
                                    $serverAudit))
        $excudedTypesCount = ((($enforcedEvents) |
                            Select-Object @{ Name = "Status"; Expression = { $serverAudit.EventType.Contains($_) } }).Status |
                            Where-Object { $_ -eq $false } | Measure-Object ).Count 
        
        if($excudedTypesCount -gt 0) {
            $controlResult.AddMessage([MessageData]::new("All the required audit event types are not enabled for SQL Server - ["+ $this.ResourceContext.ResourceName +"]"));
        }

        $isCompliant =  ((($serverAudit.AuditState -eq [AuditStateType]::Enabled)) `
                           -and ($excudedTypesCount -eq 0) -and ($serverAudit.RetentionInDays -eq $this.ControlSettings.SqlServer.AuditRetentionPeriod_Days))
        
        if ($isCompliant){
                $controlResult.VerificationResult = [VerificationResult]::Passed
            }                 
            else{
                $controlResult.VerificationResult = [VerificationResult]::Failed
            }

        return $controlResult;
    }

   hidden [ControlResult[]] CheckSqlDatabaseAuditing([ControlResult] $controlResult)
   {
        [ControlResult[]] $resultControlResultList = @()
        #--------------Enforced Events--------------------------------------------------
        $enforcedEvents =  ([AuditEventType]::Login_Failure, `
                            [AuditEventType]::Login_Success, `
                            [AuditEventType]::ParameterizedSQL_Failure, `
                            [AuditEventType]::PlainSQL_Failure,`
                            [AuditEventType]::StoredProcedure_Failure,`
                            [AuditEventType]::TransactionManagement_Failure)
        #-------------------------------------------------------------------------------

        $sqlDataBaseDtls = Get-AzureRmSqlDatabase -ResourceGroupName $this.ResourceContext.ResourceGroupName -ServerName $this.ResourceContext.ResourceName -ErrorAction Stop | 
            Where-Object { $_.DatabaseName -ne "master" } 

        if(($sqlDataBaseDtls | Measure-Object ).Count -eq 0)
        {
            $controlResult.AddMessage([MessageData]::new("No database found on SQL Server - ["+ $this.ResourceContext.ResourceName +"]"));
            #Passing the status as there is no database found on the SQL Server
            $controlResult.VerificationResult = [VerificationResult]::Passed; 
            $resultControlResultList += $controlResult
        }
        else
        {
            $sqlDataBaseDtls | 
            ForEach-Object {
                $dbName = $_.DatabaseName;
                [ControlResult] $childControlResult = $this.CreateControlResult($dbName);    
                        
                $childControlResult.AddMessage([MessageData]::new("Following event types must be enabled under SQL Server Database Auditing - ["+ $dbName +"]", 
                                                                ($enforcedEvents | Select-Object -ExcludeProperty *)));      

                $dbAuditPolicy = Get-AzureRmSqlDatabaseAuditingPolicy `
                                    -ResourceGroupName $this.ResourceContext.ResourceGroupName `
                                    -ServerName $this.ResourceContext.ResourceName `
                                    -DatabaseName $dbName `
                                    -ErrorAction Stop
                        
                $childControlResult.AddMessage([MessageData]::new("Current audit status for database '$dbName'", 
                                            ($dbAuditPolicy)));

                if(Get-Member -InputObject $dbAuditPolicy -Name 'UseServerDefault' -MemberType Properties)
                {
                   $isCompliant = $dbAuditPolicy.UseServerDefault -eq [UseServerDefaultOptions]::Enabled
                }
                else
                {
                  $isCompliant = $false  
                }
                if(-not $isCompliant) 
                {
                    $excudedTypesCount = ((($enforcedEvents) |
                            Select-Object @{ Name = "Status"; Expression = { $dbAuditPolicy.EventType.Contains($_) } }).Status |
                            Where-Object { $_ -eq $false } | Measure-Object ).Count 

                    if($excudedTypesCount -gt 0)
                    {
                        $childControlResult.AddMessage([MessageData]::new("All the required audit event types are not enabled for SQL Database '$dbName'"));
                    }
                            
                    if(($dbAuditPolicy.AuditState -eq [AuditStateType]::Enabled) `
                        -and ($excudedTypesCount -eq 0) -and ($dbAuditPolicy.RetentionInDays -eq $this.ControlSettings.SqlServer.AuditRetentionPeriod_Days)) {
                                $isCompliant = $True
                    }
                    else{
                        $isCompliant = $false                
                    }
                }                          
                #$propClone = $telemetryProperties.Clone() #
                #$propClone.Add($Global:TeleKeys::NestedResourceName, $dbName)

                if($isCompliant){
                    $childControlResult.VerificationResult = [VerificationResult]::Passed; 
                }
                else
                {
                    $childControlResult.AddMessage([VerificationResult]::Failed, "All required Audit settings are not configured correctly for SQL Database '$dbName'");
                }  
                $resultControlResultList += $childControlResult
            }
        }
         
        return $resultControlResultList; 
    }
       
    hidden [ControlResult[]] CheckSqlDatabaseTDE([ControlResult] $controlResult)
        {
            [ControlResult[]] $resultControlResultList = @()

            $sqlDataBaseDtls = Get-AzureRmSqlDatabase -ResourceGroupName $this.ResourceContext.ResourceGroupName -ServerName $this.ResourceContext.ResourceName -ErrorAction Stop | 
                Where-Object { $_.DatabaseName -ne "master" } 
               
            if(($sqlDataBaseDtls | Measure-Object ).Count -eq 0)
            {
                $controlResult.AddMessage([MessageData]::new("No database found on SQL Server - ["+ $this.ResourceContext.ResourceName +"]"));
                #Since there is no database found we are passing this control
                $controlResult.VerificationResult = [VerificationResult]::Passed; 
                $resultControlResultList += $controlResult
            }
            else
            {
                $sqlDataBaseDtls | ForEach-Object {
                        $dbName = $_.DatabaseName;
                        [ControlResult] $childControlResult = [ControlResult]@{
                            ChildResourceName = $dbName;
                        };      
                        $tdeStatus = Get-AzureRmSqlDatabaseTransparentDataEncryption `
                                        -ResourceGroupName $this.ResourceContext.ResourceGroupName `
                                        -ServerName $this.ResourceContext.ResourceName `
                                        -DatabaseName $dbName `
                                        -ErrorAction Stop

                        $childControlResult.AddMessage([MessageData]::new("Current TDE Status for - ["+ $dbName +"]", 
                                                        ($tdeStatus )));      
                                
                        if($tdeStatus.State -eq [TransparentDataEncryptionStateType]::Enabled){
                                $childControlResult.VerificationResult = [VerificationResult]::Passed; 
                        }
                        else{
                                $childControlResult.VerificationResult = [VerificationResult]::Failed;
                        }
                  $resultControlResultList += $childControlResult
               } #End of ForEach-Object
            }
            return  $resultControlResultList;
         }

      
       hidden [ControlResult] CheckSqlServerADAdmin([ControlResult] $controlResult)
        {
            $adAdmin = Get-AzureRmSqlServerActiveDirectoryAdministrator -ResourceGroup $this.ResourceContext.ResourceGroupName -Server $this.ResourceContext.ResourceName -ErrorAction Stop
              
            $controlResult.AddMessage([MessageData]::new("Current status of Active Directory Admin for ["+ $this.ResourceContext.ResourceName +"] is"));

            if(($adAdmin | Measure-Object).Count -gt 0){
                 $controlResult.VerificationResult = [VerificationResult]::Passed
                 $controlResult.AddMessage([MessageData]::new("Active Directory admins are assigned on SQL Server - ["+ $this.ResourceContext.ResourceName +"]", 
                                                             ($adAdmin)));
            }
            else{
                $controlResult.VerificationResult = [VerificationResult]::Failed
                $controlResult.AddMessage([MessageData]::new("No Active Directory admin is assigned on SQL Server - ["+ $this.ResourceContext.ResourceName +"]"));
            }
            return $controlResult
        }

       
        hidden [ControlResult] CheckSqlServerThreatDetection([ControlResult] $controlResult)
        { 
            $isCompliant = $false
            $serverThreat = Get-AzureRmSqlServerThreatDetectionPolicy `
                                -ResourceGroupName $this.ResourceContext.ResourceGroupName `
                                -ServerName $this.ResourceContext.ResourceName `
                                -ErrorAction Stop

            $controlResult.AddMessage([MessageData]::new("Current threat detection status for server ["+ $this.ResourceContext.ResourceName +"] is",
                                                        ($serverThreat)));

            $excludedTypeCount = ($serverThreat.ExcludedDetectionTypes | Measure-Object ).Count

            if($excludedTypeCount -gt 0){
                $controlResult.AddMessage([MessageData]::new("All the required audit event types are not enabled for SQL Server - ["+ $this.ResourceContext.ResourceName +"]"));
            }

            $isCompliant =  (($serverThreat.ThreatDetectionState -eq [ThreatDetectionStateType]::Enabled) `
                                -and ($excludedTypeCount -eq 0) `
                                -and (($serverThreat.EmailAdmins  -eq $True) -or ($null -ne $serverThreat.NotificationRecipientsEmails)))
            if ($isCompliant) {
                $controlResult.VerificationResult = [VerificationResult]::Passed
            }                 
            else{
                $controlResult.VerificationResult = [VerificationResult]::Failed
            }
             
          return $controlResult
        }

        hidden [ControlResult[]] CheckSqlDatabaseThreatDetection([ControlResult] $controlResult)
        {
            [ControlResult[]] $resultControlResultList = @()

            $sqlDataBaseDtls = Get-AzureRmSqlDatabase -ResourceGroupName $this.ResourceContext.ResourceGroupName -ServerName $this.ResourceContext.ResourceName -ErrorAction Stop | 
                Where-Object { $_.DatabaseName -ne "master" } 
               
            if(($sqlDataBaseDtls | Measure-Object ).Count -eq 0)
            {
                $controlResult.AddMessage([MessageData]::new("No database found on SQL Server - ["+ $this.ResourceContext.ResourceName +"]"));
                #Since there is no database found we are passing this control
                $controlResult.VerificationResult = [VerificationResult]::Passed; 
                $resultControlResultList += $controlResult
            }
            else
            {
                $sqlDataBaseDtls | ForEach-Object {
                    $dbName = $_.DatabaseName;
                    [ControlResult] $childControlResult = [ControlResult]@{
                        ChildResourceName = $dbName;
                    };      
 
                    #$telemetryTimerPerDb = [System.Diagnostics.Stopwatch]::StartNew()
                    $dbThreat = Get-AzureRmSqlDatabaseThreatDetectionPolicy `
                                    -ResourceGroupName $this.ResourceContext.ResourceGroupName `
                                    -ServerName $this.ResourceContext.ResourceName `
                                    -DatabaseName $dbName `
                                    -ErrorAction Stop

                    $childControlResult.AddMessage([MessageData]::new("Current threat detection state for database - ["+ $dbName +"]", 
                                                                      ($dbThreat)));      
                            
                    $dbAuditPolicy = Get-AzureRmSqlDatabaseAuditingPolicy `
                                    -ResourceGroupName $this.ResourceContext.ResourceGroupName `
                                    -ServerName $this.ResourceContext.ResourceName `
                                    -DatabaseName $dbName `
                                    -ErrorAction Stop

                    $childControlResult.AddMessage([MessageData]::new("Current audit status for database - ["+ $dbName +"]", 
                                                                      ($dbAuditPolicy ))); 
                    
                    if(Get-Member -InputObject $dbAuditPolicy -Name 'UseServerDefault' -MemberType Properties)
                    {
                       $isCompliant = $dbAuditPolicy.UseServerDefault -eq [UseServerDefaultOptions]::Enabled
                    }
                    else
                    {
                      $isCompliant = $false  
                    }


                    if(-not $isCompliant) {
                        if(($dbThreat.ThreatDetectionState -eq [ThreatDetectionStateType]::Enabled) `
                            -and ($dbThreat.ExcludedDetectionTypes.Count -eq 0) `
                            -and (($dbThreat.EmailAdmins -eq $True) -or ($dbThreat.NotificationRecipientsEmails.Length -gt 0)))
                        {
                            $isCompliant = $True
                        }
                        else{
                            $isCompliant = $false                
                        }
                    }

                if($isCompliant) {
                     $childControlResult.VerificationResult = [VerificationResult]::Passed; 
                }
                else {
                     $childControlResult.VerificationResult = [VerificationResult]::Failed;
                }
               
                 $resultControlResultList += $childControlResult
               }
            }
           
            return  $resultControlResultList;
        }
    
       hidden [ControlResult] CheckSqlDatabaseFirewallEnabled([ControlResult] $controlResult)
        {
          $firewallDtls = Get-AzureRmSqlServerFirewallRule -ResourceGroupName $this.ResourceContext.ResourceGroupName  -ServerName $this.ResourceContext.ResourceName
          if(($firewallDtls | Measure-Object ).Count -gt 0){
               $controlResult.VerificationResult = [VerificationResult]::Passed
            }
            else{
               $controlResult.VerificationResult = [VerificationResult]::Failed 
            }
          return $controlResult
        }

        hidden [ControlResult] CheckSqlDatabaseFirewallIPAddressRange([ControlResult] $controlResult)
        {
          #As current function will check firewall ip address renages, if firewall is enabled. When it enabled, it allows any traffic from services within your Azure subscription to pass through.
          #Default record will be their with Start IP address as 0.0.0.0 and End Ip address as 0.0.0.0
            $firewallDtls = Get-AzureRmSqlServerFirewallRule -ResourceGroupName $this.ResourceContext.ResourceGroupName  -ServerName $this.ResourceContext.ResourceName
            if(($firewallDtls | Measure-Object ).Count -gt 0)
            {
                $controlResult.AddMessage([MessageData]::new("Current firewall settings for - ["+ $this.ResourceContext.ResourceName +"]", 
                                                             $firewallDtls));

                $anyToAnyRule =  $firewallDtls | Where-Object { $_.StartIpAddress -eq $this.ControlSettings.IPRangeStartIP -and $_.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:", $firewallDtls);
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Failed, "Control can not be validated: Firewall is not enabled");
            }
            return $controlResult
        }
}