Framework/Core/SVT/Services/KeyVault.ps1

using namespace Microsoft.Azure.Commands.KeyVault.Models
Set-StrictMode -Version Latest 
class KeyVault: SVTBase
{       
    hidden [PSVault] $ResourceObject;

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

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

    hidden [PSVault] GetResourceObject()
    {
        if (-not $this.ResourceObject) 
        {
            $this.ResourceObject = Get-AzureRmKeyVault -VaultName $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] CheckAdvancedAccessPolicies([ControlResult] $controlResult)
    {
        $accessPolicies = @{};
        $accessPolicies.Add("Enable access to Azure Virtual Machines for deployment", $this.ResourceObject.EnabledForDeployment);
        $accessPolicies.Add("Enable access to Azure Resource Manager for template deployment", $this.ResourceObject.EnabledForTemplateDeployment);
        $accessPolicies.Add("Enable access to Azure Disk Encryption for volume encryption", $this.ResourceObject.EnabledForDiskEncryption);
        
        if($this.ResourceObject.EnabledForDeployment -and $this.ResourceObject.EnabledForDiskEncryption -and $this.ResourceObject.EnabledForTemplateDeployment)
        {          
            $controlResult.AddMessage([VerificationResult]::Failed,
                                      [MessageData]::new("All Advanced Access Policies are enabled - ["+ $this.ResourceContext.ResourceName +"]"  , 
                                                         $accessPolicies));
        }
        elseif($this.ResourceObject.EnabledForDeployment -or $this.ResourceObject.EnabledForDiskEncryption -or $this.ResourceObject.EnabledForTemplateDeployment)
        {
            $controlResult.AddMessage([VerificationResult]::Verify,
                                     [MessageData]::new("Validate Advanced Access Policies - ["+ $this.ResourceContext.ResourceName +"]" , 
                                                         $accessPolicies));
        }
        else
        {       
            $controlResult.AddMessage([VerificationResult]::Passed, 
                                      [MessageData]::new("All Advanced Access Policies are disabled - ["+ $this.ResourceContext.ResourceName +"]", 
                                                         $accessPolicies));
        }  
        return $controlResult;
    }

    hidden [ControlResult] CheckAccessPolicies([ControlResult] $controlResult)
    {
        $accessPolicies = $this.ResourceObject.AccessPolicies
        $allPermisionUsers = $accessPolicies | Where-Object { $_.PermissionsToKeys -eq "All" -and $_.PermissionsToSecrets -eq "All" -and $_.PermissionsToCertificates -eq "All" } 
        
        if(($allPermisionUsers | Measure-Object).count -gt 0)
        {
            $controlResult.VerificationResult = [VerificationResult]::Failed; 
            $controlResult.AddMessage([MessageData]::new("Applications/Users having full control to Key/Secret/Certificate - ["+ $this.ResourceContext.ResourceName +"]" , 
                                                         $allPermisionUsers));
        }
        else
        {
            $controlResult.VerificationResult = [VerificationResult]::Verify; 
        }

        $controlResult.AddMessage([MessageData]::new("Validate access policies and their assigned permissions to Key/Secret/Certificate - ["+ $this.ResourceContext.ResourceName +"]" , 
                                                         $accessPolicies));
        return $controlResult;
    }

    hidden [ControlResult] CheckKeyHSMProtected([ControlResult] $controlResult)
    {
        try
        {
            $allKeys = Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -ErrorAction Stop
            
            if(($allKeys | Measure-Object).Count -gt 0) 
            {
                $nonHsmKeys = @();
                $allKeys | ForEach-Object {
                    Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -IncludeVersions |
                    ForEach-Object {
                        $nonHsmKeys += Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -Version $_.Version | 
                                            Where-Object { $_.Attributes.KeyType -ne $this.ControlSettings.KeyVault.KeyType };
                    }
                }
                
                if(($nonHsmKeys | Measure-Object).Count -eq 0)
                { 
                    $controlResult.AddMessage( [VerificationResult]::Passed,
                        [MessageData]::new("All Keys, including previous versions, are protected by HSM for Key Vault - ["+ $this.ResourceContext.ResourceName +"]"));   
                }
                else 
                {
                    $controlResult.AddMessage([VerificationResult]::Failed,
                        [MessageData]::new("Following Keys, including previous versions, are not protected by HSM. Delete the key versions in order to comply."  , 
                                ($nonHsmKeys | Select-Object Name, Version -ExpandProperty Attributes )));
                }
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Passed,
                    [MessageData]::new("No Keys found in - ["+ $this.ResourceContext.ResourceName +"]"));
            }
        }
        catch
        {
            if ($_.Exception.GetType().FullName -eq "Microsoft.Azure.KeyVault.Models.KeyVaultErrorException")
            {
                $controlResult.AddMessage([MessageData]::new("Access denied: Read access is required on Key Vault Keys."));
            }
            else
            {
                throw $_
            }
        }

        return $controlResult;
    }
    
    hidden [ControlResult] CheckKeyMinimumOperations([ControlResult] $controlResult)
    {
        try
        {
            $allKeys = Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -ErrorAction Stop
            
            if(($allKeys| Measure-Object).Count -gt 0)    
            {
                $keyDetails = @();
                $allKeys | ForEach-Object {
                    Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -IncludeVersions |
                    ForEach-Object {
                        $key = Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -Version $_.Version #| Where-Object {$null -eq $_.Attributes.Expires }
                        $keyDetails += $key #rename variable
                    }
                }
                $controlResult.AddMessage([VerificationResult]::Verify,
                                            [MessageData]::new("Verify the operations permitted using Key on - ["+ $this.ResourceContext.ResourceName +"]",
                                            ($keyDetails | Select-Object Name, Version, @{Label="Key Operations"; Expression={[system.string]::Join(", ",$_.Key.KeyOps)}} ) ) );   

            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Passed,
                                            [MessageData]::new("No Keys found in - ["+ $this.ResourceContext.ResourceName +"]"));   
            }
        }
        catch 
        {
            if ($_.Exception.GetType().FullName -eq "Microsoft.Azure.KeyVault.Models.KeyVaultErrorException")
            {
            $controlResult.AddMessage([MessageData]::new("Access denied: Read access is required on Key Vault Keys."));
            }
            else
            {
                throw $_
            }
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckAppAuthenticationCertificate([ControlResult] $controlResult)
    {
        try{
              $outputList = @();
              $appList = $this.GetAzureRmKeyVaultApplications()
              $appList |
                ForEach-Object {
                    $credentials = Get-AzureRmADAppCredential -ApplicationId $_.ApplicationId
                    $compliance =  if (($credentials| Where-Object { $_.Type -eq $this.ControlSettings.KeyVault.ADAppCredentialTypePwd } | Measure-Object).Count -eq 0 ) { "Yes" } else { "No" } ;
                    $output = New-Object System.Object
                    $output | Add-Member -type NoteProperty -name AzureADAppName -Value $_.DisplayName
                    $output | Add-Member -type NoteProperty -name ApplicationId -Value $_.ApplicationId
                    $output | Add-Member -type NoteProperty -name CertificateCredentialCount -Value ($credentials | Where-Object { $_.Type -eq $this.ControlSettings.KeyVault.ADAppCredentialTypeCrt } | Measure-Object ).Count
                    $output | Add-Member -type NoteProperty -name PasswordCredentialCount -Value ($credentials | Where-Object { $_.Type -eq $this.ControlSettings.KeyVault.ADAppCredentialTypePwd } | Measure-Object).Count
                    $output | Add-Member -type NoteProperty -name Compliance -Value $compliance
                    $outputList += $output;
                }
         
                if(($outputList| Measure-Object).Count -gt 0)
                {
                    $controlResult.AddMessage([MessageData]::new("Compliance details of Azure Active Directory applications:",
                                                                  $outputList));
          
                    if (($outputList | Where-Object { ($_.Compliance -eq "No") } | Measure-Object ).Count -gt 0)
                    {
                        $controlResult.AddMessage([VerificationResult]::Failed ,
                                                  [MessageData]::new("Remove the password credentials from Azure AD Applications which are non-compliant.") );
                    }
                    else
                    {
                        $controlResult.VerificationResult = [VerificationResult]::Passed
                    }
                }
                else
                {
                    $controlResult.AddMessage([VerificationResult]::Passed ,
                                                  [MessageData]::new("No Azure AD Applications have access to Key Vault.") );
                }
        }
        catch
        {
             if ($_.Exception.GetType().FullName -eq "Microsoft.Azure.KeyVault.Models.KeyVaultErrorException")
             {
                $controlResult.AddMessage([MessageData]::new("Access denied: Read access is required on Key Vault Keys."));
             }
             else
             {
                 throw $_
             }    
        }
        return $controlResult;
    }

   
    hidden [ControlResult] CheckAppsSharingKayVault([ControlResult] $controlResult)
    {
        $appList = $this.GetAzureRmKeyVaultApplications() 
      
        if( ($appList | Measure-Object ).Count -gt 1)
        {
            $controlResult.AddMessage([VerificationResult]::Verify,
                                        [MessageData]::new("Validate that Azure AD Applications requires access to Key Vault. Total:" + ($appList | Measure-Object ).Count , 
                                                            $appList)); 
        }
        elseif( ($appList | Measure-Object ).Count -eq 1)
        {
           $controlResult.AddMessage([VerificationResult]::Passed,
                                     "Only 1 Azure AD Application has access to Key Vault.", $appList); 
        } 
        else
        {
           $controlResult.AddMessage([VerificationResult]::Passed,
                                     "No Azure AD Applications have access to Key Vault."); 
        } 
      
        return $controlResult;
    }

    hidden [ControlResult] CheckKeyExpirationDate([ControlResult] $controlResult)
    {
        $IsKeysCompliant = $True
        $IsAccessDenied = $False
        try
        {
            $allKeys = Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -ErrorAction Stop
            
            if(($allKeys| Measure-Object).Count -gt 0) 
            {
                $keysWithoutExpiry = @();
                $keysWithExpiry = @();
                $longDurationActiveKeys = @();
                
                $allKeys | 
                ForEach-Object {
                        Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -IncludeVersions |
                        ForEach-Object {
                            $key = Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -Version $_.Version #| Where-Object { $_.Attributes.Expires -eq $null }
                            if($null -eq $key.Attributes.Expires)
                            {
                                $keysWithoutExpiry += $key
                            }
                            else
                            {
                                $keysWithExpiry += $key
                            }
                        }
                    }

                if(($keysWithoutExpiry | Measure-Object ).Count -gt 0)
                {
                    $IsKeysCompliant = $False
                    $controlResult.AddMessage([MessageData]::new("Following Keys, including previous versions, does not have expiry date. Set the expiry date in order to comply.", 
                                                            ($keysWithoutExpiry | Select-Object -Property Name, Version -ExpandProperty Attributes) ));
                }
             
                if(($keysWithExpiry | Measure-Object ).Count -gt 0) 
                { 
                    $controlResult.AddMessage([MessageData]::new("Keys, including previous versions, have expiry date for Key Vault - ["+$this.ResourceContext.ResourceName+"]", 
                                                 ($keysWithExpiry | Select-Object -Property Name, Version -ExpandProperty Attributes ) ));
                    $keysWithExpiry | 
                    ForEach-Object {
                        $startDate = [DateTime] $_.Attributes.Created
                        if($null -ne $_.Attributes.NotBefore)
                        {
                            $startDate = [DateTime] $_.Attributes.NotBefore;
                        }

                        if ((((([DateTime] $_.Attributes.Expires) - $startDate).TotalDays)/($this.ControlSettings.KeyVault.KeyRotationDuration_Days)) -gt 1)
                        {
                            $longDurationActiveKeys += $_ 
                        }               
                    }

                    if (($longDurationActiveKeys| Measure-Object ).Count -gt 0)
                    {
                        $IsKeysCompliant = $False
                        $controlResult.AddMessage([MessageData]::new("Following Keys, including previous versions, are active for more than $($this.ControlSettings.KeyVault.KeyRotationDuration_Days) Days. Please delete/rotate the keys in order to comply.", 
                                                    ($longDurationActiveKeys | Select-Object -Property Name, Version -ExpandProperty Attributes ) ));
                    }
                }                     
            }
            else
            {
                $controlResult.AddMessage( [MessageData]::new("No Keys found in - ["+ $this.ResourceContext.ResourceName +"]")); 
            }
        }
        catch
        {
            $IsKeysCompliant = $False
            $IsAccessDenied = $True
             if ($_.Exception.GetType().FullName -eq "Microsoft.Azure.KeyVault.Models.KeyVaultErrorException")
             {
               $controlResult.AddMessage([MessageData]::new("Access denied: Read access is required on Key Vault Keys."));
             }
             else
             {
                 throw $_
             }    
        }
  
        $IsSecretCompliant = $True
        try
        {
            $allSecrets = Get-AzureKeyVaultSecret -VaultName $this.ResourceContext.ResourceName -ErrorAction Stop
            
            if(($allSecrets| Measure-Object).Count -gt 0) 
            {     
                $secretsWithoutExpiry = @();
                $secretsWithExpiry = @();
                $longDurationActiveSecrets = @();
  
                $allSecrets | 
                ForEach-Object {
                    Get-AzureKeyVaultSecret -VaultName $this.ResourceContext.ResourceName -Name $_.Name -IncludeVersions |
                    ForEach-Object {
                        $secret = Get-AzureKeyVaultSecret -VaultName $this.ResourceContext.ResourceName -Name $_.Name -Version $_.Version #| Where-Object { $_.Attributes.Expires -eq $null }
                        if($null -eq $secret.Attributes.Expires)
                        {
                            $secretsWithoutExpiry += $secret
                        }
                        else
                        {
                            $secretsWithExpiry += $secret
                        }
                    }
                }

                if(($secretsWithoutExpiry| Measure-Object ).Count -gt 0)
                {
                    $IsSecretCompliant = $False
                    $controlResult.AddMessage([MessageData]::new("Following Secrets, including previous versions, does not have expiry date. Set the expiry date in order to comply.", 
                                                      ($secretsWithoutExpiry | Select-Object Name, Version -ExpandProperty Attributes  ) ));
                }

                if(($secretsWithExpiry| Measure-Object ).Count -gt 0)
                {                       
                    $controlResult.AddMessage([MessageData]::new("Secrets, including previous versions, have expiry date for Key Vault - ["+ $this.ResourceContext.ResourceName +"]", 
                                             ( $secretsWithExpiry | Select-Object Name, Version -ExpandProperty Attributes  ) ));
                    $secretsWithExpiry | 
                    ForEach-Object {
                        $startDate = [DateTime] $_.Attributes.Created
                        if($null -ne $_.Attributes.NotBefore)
                        {
                            $startDate = [DateTime] $_.Attributes.NotBefore;
                        }

                        if ((((([DateTime] $_.Attributes.Expires) - $startDate).TotalDays)/($this.ControlSettings.KeyVault.SecretRotationDuration_Days)) -gt 1)
                        {
                            $longDurationActiveSecrets += $_ 
                        }  
                    }

                    if (($longDurationActiveSecrets| Measure-Object ).Count -gt 0)
                    {
                         $IsSecretCompliant = $False                   
                         $controlResult.AddMessage([MessageData]::new("Following Secrets, including previous versions, are active for more than $($this.ControlSettings.KeyVault.SecretRotationDuration_Days) Days. Please delete/renew the secrets in order to comply.", 
                                                  ( $longDurationActiveSecrets | Select-Object Name, Version -ExpandProperty Attributes   ) ));
                    }
                }
            }
            else
            {
                $controlResult.AddMessage( [MessageData]::new("No Secrets found in - ["+ $this.ResourceContext.ResourceName +"]")); 
            }
    }
    catch
        {
             $IsSecretCompliant = $False
             $IsAccessDenied = $True
             if ($_.Exception.GetType().FullName -eq "Microsoft.Azure.KeyVault.Models.KeyVaultErrorException")
             {
                $controlResult.AddMessage([MessageData]::new("Access denied: Read access is required on Key Vault Secrets."));
             }
             else
             {
                 throw $_
             }    
        }
 
        #Got exception becuase of acess denied, Keeping status Verify.
        if(-not $IsAccessDenied)
        {
            if($IsKeysCompliant -and $IsSecretCompliant)
            {
                $controlResult.VerificationResult = [VerificationResult]::Passed
            }
            else
            {
                $controlResult.VerificationResult = [VerificationResult]::Failed
            }
        }
        return $controlResult;
   }

    hidden [PSObject] GetAzureRmKeyVaultApplications ()  
     {
        $applicationList = @();
        $this.ResourceObject.AccessPolicies  | 
        ForEach-Object { 
            $svcPrincipal= Get-AzureRmADServicePrincipal -ObjectId $_.ObjectId
            if($svcPrincipal){
                $application = Get-AzureRmADApplication -ApplicationId $svcPrincipal.ApplicationId
                if($application){
                    $applicationList += $application
                }
            }
        }
        return $applicationList;
}
}