Framework/Core/SVT/Services/KeyVault.ps1
using namespace Microsoft.Azure.Commands.KeyVault.Models Set-StrictMode -Version Latest class KeyVault: SVTBase { hidden [PSVault] $ResourceObject; hidden [PSObject[]] $AllEnabledKeys = $null; hidden [PSObject[]] $AllEnabledSecrets = $null; 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 ([SuppressedException]::new(("Resource '{0}' not found under Resource Group '{1}'" -f ($this.ResourceContext.ResourceName), ($this.ResourceContext.ResourceGroupName)), [SuppressedExceptionType]::InvalidOperation)) } } 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); $controlResult.SetStateData("Key Vault advanced access policies", $accessPolicies); 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 $controlResult.VerificationResult = [VerificationResult]::Verify; $controlResult.SetStateData("Access policies and their assigned permissions to Key/Secret/Certificate", $accessPolicies); $controlResult.AddMessage([MessageData]::new("Validate access policies and their assigned permissions to Key/Secret/Certificate - ["+ $this.ResourceContext.ResourceName +"]" , $accessPolicies)); return $controlResult; } hidden [PSObject[]] FetchAllEnabledKeysWithVersions([ControlResult] $controlResult) { if($this.AllEnabledKeys -eq $null) { try { $keysResult = @(); $keysResult += Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -ErrorAction Stop | Where-Object { $_.Enabled -eq $true }; $this.AllEnabledKeys = @(); if($keysResult.Count -gt 0) { $keysResult | ForEach-Object { Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -IncludeVersions | Where-Object { $_.Enabled -eq $true } | ForEach-Object { $this.AllEnabledKeys += Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -Version $_.Version ; } } } } catch { # null indicates exception $this.AllEnabledKeys = $null; 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 $this.AllEnabledKeys; } hidden [PSObject[]] FetchAllEnabledSecretsWithVersions([ControlResult] $controlResult) { if($this.AllEnabledSecrets -eq $null) { try { $secretsResult = @(); $secretsResult += Get-AzureKeyVaultSecret -VaultName $this.ResourceContext.ResourceName -ErrorAction Stop | Where-Object { $_.Enabled -eq $true }; $this.AllEnabledSecrets = @(); if($secretsResult.Count -gt 0) { $secretsResult | ForEach-Object { Get-AzureKeyVaultSecret -VaultName $this.ResourceContext.ResourceName -Name $_.Name -IncludeVersions | Where-Object { $_.Enabled -eq $true } | ForEach-Object { $this.AllEnabledSecrets += Get-AzureKeyVaultSecret -VaultName $this.ResourceContext.ResourceName -Name $_.Name -Version $_.Version ; } } } } catch { # null indicates exception $this.AllEnabledSecrets = $null; 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 $_ } } } return $this.AllEnabledSecrets; } hidden [ControlResult] CheckKeyHSMProtected([ControlResult] $controlResult) { $enabledKeys = $this.FetchAllEnabledKeysWithVersions($controlResult); if($null -ne $enabledKeys) { if($enabledKeys.Count -ne 0) { $nonHsmKeys = @(); $nonHsmKeys += $enabledKeys | Where-Object { $_.Attributes.KeyType -ne $this.ControlSettings.KeyVault.KeyType }; if($nonHsmKeys.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." , ($nonHsmKeys | Select-Object Name, Version -ExpandProperty Attributes ))); } } else { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("No Keys (enabled) found in - ["+ $this.ResourceContext.ResourceName +"]")); } } return $controlResult; } hidden [ControlResult] CheckKeyMinimumOperations([ControlResult] $controlResult) { $enabledKeys = $this.FetchAllEnabledKeysWithVersions($controlResult); if($null -ne $enabledKeys) { if($enabledKeys.Count -ne 0) { $keyOperations = $enabledKeys | Select-Object Name, Version, @{Label="Key Operations"; Expression={[system.string]::Join(", ",$_.Key.KeyOps)}} $controlResult.SetStateData("Key Vault key operations", $keyOperations); $controlResult.AddMessage([VerificationResult]::Verify, [MessageData]::new("Verify the operations permitted using Key on - ["+ $this.ResourceContext.ResourceName +"]", ($keyOperations )) ); } else { $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("No Keys (enabled) found in - ["+ $this.ResourceContext.ResourceName +"]")); } } 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() $controlResult.SetStateData("Key Vault sharing app list", $appList); 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 [void] ProcessKeySecretExpiryDate([PSObject[]] $enabledResources, [ControlResult] $controlResult, [string] $resourceType, [bool] $isAccessDenied, [int] $rotationDurationDays) { if($enabledResources.Count -ne 0) { $utcNow = [DateTime]::UtcNow; $utcNow30 = $utcNow.AddDays(30); $withoutExpiry = @(); $withoutExpiry += $enabledResources | Where-Object { $null -eq $_.Attributes.Expires }; if($withoutExpiry.Count -gt 0) { # result = Failed $this.SetVerificationResultForExpiryDate($controlResult, [VerificationResult]::Failed, $isAccessDenied) $controlResult.AddMessage([MessageData]::new("Following $resourceType, including previous versions, does not have expiry date.", ($withoutExpiry | Select-Object -Property Name, Version -ExpandProperty Attributes) )); } $longActiveResources = @(); # VerificationResult = Failed $needToDisableResources = @(); # VerificationResult = Failed $needToRotateResources = @(); # VerificationResult = Verify $enabledResources | ForEach-Object { $expiryDate = $null if($null -ne $_.Attributes.Expires) { $expiryDate = [DateTime] $_.Attributes.Expires } #check if expiry is future or null if(($null -eq $expiryDate) -or ($utcNow30 -le $expiryDate)) { $createdDate = [DateTime] $_.Attributes.Created # check if resource is created/active for more than 180 days ago if(($utcNow - $createdDate).TotalDays -ge $rotationDurationDays) { $longActiveResources += $_; $this.SetVerificationResultForExpiryDate($controlResult, [VerificationResult]::Failed, $isAccessDenied); } else { $this.SetVerificationResultForExpiryDate($controlResult, [VerificationResult]::Passed, $isAccessDenied); } } # resource already expired but still enabled elseif($expiryDate -lt $utcNow) { $needToDisableResources += $_; $this.SetVerificationResultForExpiryDate($controlResult, [VerificationResult]::Failed, $isAccessDenied); } # resource is about to expire within next 30 days elseif(($utcNow -le $expiryDate) -and ($expiryDate -le $utcNow30)) { $needToRotateResources += $_; $this.SetVerificationResultForExpiryDate($controlResult, [VerificationResult]::Verify, $isAccessDenied); } }; # Display summary messages if ($longActiveResources.Count -gt 0) { $controlResult.AddMessage([MessageData]::new("Following $resourceType, including previous versions, are more than $rotationDurationDays days old.", ($longActiveResources | Select-Object -Property Name, Version -ExpandProperty Attributes ) )); } if ($needToDisableResources.Count -gt 0) { $controlResult.AddMessage([MessageData]::new("Following $resourceType, including previous versions, are expired but 'Enabled'.", ($needToDisableResources | Select-Object -Property Name, Version -ExpandProperty Attributes ) )); } if ($needToRotateResources.Count -gt 0) { $controlResult.AddMessage([MessageData]::new("Following $resourceType, including previous versions, are about to expire within next 30 days. Please rotate the $resourceType.", ($needToRotateResources | Select-Object -Property Name, Version -ExpandProperty Attributes ) )); } } else { $this.SetVerificationResultForExpiryDate($controlResult, [VerificationResult]::Passed, $isAccessDenied); $controlResult.AddMessage([MessageData]::new("No $resourceType (enabled) found in - ["+ $this.ResourceContext.ResourceName +"]")); } } hidden [void] SetVerificationResultForExpiryDate([ControlResult] $controlResult, [VerificationResult] $newResult, [bool] $isAccessDenied) { if($isAccessDenied) { $controlResult.VerificationResult = [VerificationResult]::Manual; return; } $stateMachine = @( [VerificationResult]::Manual, [VerificationResult]::Passed, [VerificationResult]::Verify, [VerificationResult]::Failed ); $existingIndex = [array]::indexof($stateMachine, $controlResult.VerificationResult); $newIndex = [array]::indexof($stateMachine, $newResult); if($existingIndex -le $newIndex) { $controlResult.VerificationResult = $newResult; } } hidden [ControlResult] CheckKeyExpirationDate([ControlResult] $controlResult) { $isKeysCompliant = $True $isAccessDenied = $False $enabledKeys = $this.FetchAllEnabledKeysWithVersions($controlResult); if($null -ne $enabledKeys) { $this.ProcessKeySecretExpiryDate($enabledKeys, $controlResult, "Keys", $isAccessDenied, $this.ControlSettings.KeyVault.KeyRotationDuration_Days); } else { $isAccessDenied = $True $this.SetVerificationResultForExpiryDate($controlResult, [VerificationResult]::Manual, $isAccessDenied); } $enabledSecrets = $this.FetchAllEnabledSecretsWithVersions($controlResult); if($null -ne $enabledSecrets) { $this.ProcessKeySecretExpiryDate($enabledSecrets, $controlResult, "Secrets", $isAccessDenied, $this.ControlSettings.KeyVault.SecretRotationDuration_Days); } else { $isAccessDenied = $True $this.SetVerificationResultForExpiryDate($controlResult, [VerificationResult]::Manual, $isAccessDenied); } 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; } hidden [ControlResult] CheckKeyVaultSoftDelete([ControlResult] $controlResult) { $isSoftDeleteEnable=$this.ResourceObject.EnableSoftDelete; if($isSoftDeleteEnable -eq $true){ $controlResult.AddMessage([VerificationResult]::Passed, [MessageData]::new("Soft delete is enabled for this Key Vault")); } else { $controlResult.AddMessage([VerificationResult]::Failed, [MessageData]::new("Soft delete is disabled for this Key Vault")); } return $controlResult; } } |