Framework/Core/SVT/Services/VirtualMachine.ps1
using namespace Microsoft.Azure.Commands.Network.Models using namespace Microsoft.Azure.Commands.Compute.Models Set-StrictMode -Version Latest class VirtualMachine: SVTBase { hidden [PSVirtualMachine] $ResourceObject; hidden [PSNetworkInterface[]] $VMNICs = $null; hidden [PSObject] $ASCSettings = $null; VirtualMachine([string] $subscriptionId, [string] $resourceGroupName, [string] $resourceName): Base($subscriptionId, $resourceGroupName, $resourceName) { $this.GetResourceObject(); } VirtualMachine([string] $subscriptionId, [SVTResource] $svtResource): Base($subscriptionId, $svtResource) { $this.GetResourceObject(); } hidden [PSVirtualMachine] GetResourceObject() { if (-not $this.ResourceObject) { $this.ResourceObject = Get-AzureRmVM -ResourceGroupName $this.ResourceContext.ResourceGroupName -Name $this.ResourceContext.ResourceName -WarningAction SilentlyContinue if(-not $this.ResourceObject) { throw ("Resource '{0}' not found under Resource Group '{1}'" -f ($this.ResourceContext.ResourceName), ($this.ResourceContext.ResourceGroupName)) } } #compute ASC object for VM $this.ASCSettings = $this.GetASCSettings(); return $this.ResourceObject; } hidden [PSObject] GetASCSettings() { try { $result = $null; [SecurityCenterHelper]::RegisterResourceProvider(); $uri = [System.String]::Format("{0}subscriptions/{1}/providers/microsoft.Security/securityStatuses?api-version=2015-06-01-preview", [WebRequestHelper]::AzureManagementUri, $this.SubscriptionContext.SubscriptionId) try { $result = [WebRequestHelper]::InvokeGetWebRequest($uri) } catch { return $null; } if(($result | Measure-Object).Count -gt 0) { $vmSecurityState = $result | Where-Object { $_.name -eq $this.ResourceContext.ResourceName -and $_.properties.type -eq 'VirtualMachine' } | Select-Object -First 1; if($vmSecurityState) { $vmSecurityStateProperties = @{ SecurityState = $vmSecurityState.properties.securityState; DetailedStatus = @{ Monitered = $vmSecurityState.properties.baselineScannerData.securityState; SystemUpdates = $vmSecurityState.properties.patchScannerData.securityState; EndpointProtection = $vmSecurityState.properties.antimalwareScannerData.securityState; Vulnerabilities = $vmSecurityState.properties.vulnerabilityAssessmentScannerStatus.securityState; DiskEncryption = $vmSecurityState.properties.encryptionDataState.securityState; }; }; return $vmSecurityStateProperties; } } } catch { return $null; } return $null; } hidden [PSNetworkInterface[]] GetVMNICObjects() { if (-not $this.VMNICs) { $this.VMNICs = @(); if($this.ResourceObject.NetworkProfile -and $this.ResourceObject.NetworkProfile.NetworkInterfaces) { $this.ResourceObject.NetworkProfile.NetworkInterfaces | ForEach-Object { $currentNic = Get-AzureRmResource -ResourceId $_.Id -ErrorAction SilentlyContinue if($currentNic) { $nicResource = Get-AzureRmNetworkInterface -Name $currentNic.ResourceName ` -ResourceGroupName $currentNic.ResourceGroupName ` -ExpandResource NetworkSecurityGroup ` -ErrorAction SilentlyContinue if($nicResource) { $this.VMNICs += $nicResource; } } } } } return $this.VMNICs; } hidden [bool] IsLinuxVM() { return ($this.ResourceObject.OSProfile -and $this.ResourceObject.OSProfile.LinuxConfiguration); } hidden [ControlResult] CheckOSVersion([ControlResult] $controlResult) { $vmOSDetails = $this.ResourceObject.StorageProfile.ImageReference if($this.ResourceObject.StorageProfile -and $this.ResourceObject.StorageProfile.ImageReference) { $message = ""; $verificationResult = [VerificationResult]::Failed; if($vmOSDetails.Version -eq "latest") { $verificationResult = [VerificationResult]::Passed $message = "Virtual Machine is running latest OS version" } else { $message = "Virtual Machine is not running latest OS version. Please upgrade the OS in order to comply." } $controlResult.AddMessage($verificationResult, $message, $vmOSDetails); } else { $controlResult.AddMessage([MessageData]::new("We are not able to fetch the required data for the resource", [MessageType]::Error)); } return $controlResult; } hidden [ControlResult] CheckOSAutoUpdateStatus([ControlResult] $controlResult) { #TCP is not applicable for Linux. if($this.ResourceObject.OSProfile -and $this.ResourceObject.OSProfile.WindowsConfiguration) { $message = ""; $verificationResult = [VerificationResult]::Failed; if($this.ResourceObject.OSProfile.WindowsConfiguration.EnableAutomaticUpdates -eq $true) { $verificationResult = [VerificationResult]::Passed; $message = "Automatic OS updates are enabled on Windows Virtual Machine"; } else { $message = "Automatic OS updates are disabled on Windows Virtual Machine. Please enable OS automatic updates in order to comply."; } $controlResult.AddMessage($verificationResult, $message, $this.ResourceObject.OSProfile.WindowsConfiguration); } elseif($this.IsLinuxVM()) { $controlResult.AddMessage([VerificationResult]::Manual, "The control is not applicable on Linux Virtual Machine. It's good practice to periodically update OS of Virtual Machine.", $this.ResourceObject.OSProfile.LinuxConfiguration); } else { $controlResult.AddMessage([MessageData]::new("We are not able to fetch the required data for the resource", [MessageType]::Error)); } return $controlResult; } hidden [ControlResult] CheckAntimalwareStatus([ControlResult] $controlResult) { $verificationResult = [VerificationResult]::Failed; $ascAntimalwareStatus = $false; if($null -ne $this.ASCSettings -and $this.ASCSettings.DetailedStatus.EndpointProtection -eq 'Healthy') { $ascAntimalwareStatus = $true; } #TCP is not applicable for Linux. if($this.ResourceObject.Extensions) { $antimalwareExt = $this.ResourceObject.Extensions | Where-Object { $_.VirtualMachineExtensionType -eq 'IaaSAntimalware'} | Select-Object -First 1 if($antimalwareExt -and $antimalwareExt.Settings) { #create Antimalware data object $antimalwareData = @{ AntimalwareEnabled = $antimalwareExt.Settings.AntimalwareEnabled.Value; RealtimeProtectionEnabled = $false ASCAntimalwareStatus = $ascAntimalwareStatus ScheduledScanSettings = @{ IsEnabled = $false } }; if($null -ne $antimalwareExt.Settings.RealtimeProtectionEnabled) { $antimalwareData.RealtimeProtectionEnabled = $antimalwareExt.Settings.RealtimeProtectionEnabled.Value; } if($null -ne $antimalwareExt.Settings.ScheduledScanSettings) { $antimalwareData.ScheduledScanSettings = @{ IsEnabled = $antimalwareExt.Settings.ScheduledScanSettings.isEnabled.Value ScanType = $antimalwareExt.Settings.ScheduledScanSettings.scanType.Value Day = $antimalwareExt.Settings.ScheduledScanSettings.day.Value Time = $antimalwareExt.Settings.ScheduledScanSettings.time.Value }; } #$Exclusions = $antimalwareExt.Settings | where{ $_.Path -eq 'Exclusions'} #-and $scheduleScanSettings.isEnabled $message = ""; if(($antimalwareData.AntimalwareEnabled -eq $true) -and ($antimalwareData.RealtimeProtectionEnabled -eq $true) -and ($antimalwareData.ScheduledScanSettings.IsEnabled -eq $true) -and ($antimalwareData.ASCAntimalwareStatus -eq $true)) { $verificationResult = [VerificationResult]::Passed; $message = "Antimalware extension is installed and is configured correctly"; } else { $verificationResult = [VerificationResult]::Failed $message = "Antimalware extension is installed but is not configured correctly"; } $controlResult.AddMessage($verificationResult, $message, $antimalwareData); $controlResult.SetStateData("Antimalware extension configurations", $antimalwareData); } else { $controlResult.AddMessage([VerificationResult]::Failed, "Antimalware extension is not installed"); } } elseif($ascAntimalwareStatus) { $verificationResult = [VerificationResult]::Passed; $controlResult.AddMessage("Antimalware is configured correctly for the VM. Validated the status through ASC (No specifc antimalware extension found)."); } else { $verificationResult = [VerificationResult]::Failed; $controlResult.AddMessage("Antimalware is not configured correctly for the VM. Validated the status through ASC (No specifc antimalware extension found)."); } return $controlResult; } hidden [ControlResult] CheckNSGConfig([ControlResult] $controlResult) { $controlResult.VerificationResult = [VerificationResult]::Failed; $this.GetVMNICObjects() | ForEach-Object { #Check NSGs applied at NIC level if($_.NetworkSecurityGroup) { if($_.NetworkSecurityGroup.SecurityRules.Count -gt 0) { $controlResult.AddMessage("Validate NSG security rules applied to NIC - [$($_.Name)], Total - $($_.NetworkSecurityGroup.SecurityRules.Count)", $_.NetworkSecurityGroup.SecurityRules); } if($_.NetworkSecurityGroup.DefaultSecurityRules.Count -gt 0) { $controlResult.AddMessage("Validate NSG default security rules applied to NIC - [$($_.Name)], Total - $($_.NetworkSecurityGroup.DefaultSecurityRules.Count)", $_.NetworkSecurityGroup.DefaultSecurityRules); } $controlResult.VerificationResult = [VerificationResult]::Verify; $controlResult.SetStateData("NSG security rules", $_.NetworkSecurityGroup.SecurityRules); } #check NSGs applied at subnet level if($_.IpConfigurations) { $_.IpConfigurations | ForEach-Object { $subnetId = $_.Subnet.Id; $subnetName = $subnetId.Substring($subnetId.LastIndexOf("/") + 1); #vnet id = trim '/subnets/' from subnet id $vnetResource = Get-AzureRmResource -ResourceId $subnetId.Substring(0, $subnetId.IndexOf("/subnets/")) if($vnetResource) { $vnetObject = Get-AzureRmVirtualNetwork -Name $vnetResource.Name -ResourceGroupName $vnetResource.ResourceGroupName if($vnetObject) { $subnetConfig = Get-AzureRmVirtualNetworkSubnetConfig -Name $subnetName -VirtualNetwork $vnetObject if($subnetConfig -and $subnetConfig.NetworkSecurityGroup -and $subnetConfig.NetworkSecurityGroup.Id) { $nsgResource = Get-AzureRmResource -ResourceId $subnetConfig.NetworkSecurityGroup.Id if($nsgResource) { $nsgObject = Get-AzureRmNetworkSecurityGroup -Name $nsgResource.Name -ResourceGroupName $nsgResource.ResourceGroupName if($nsgObject) { if($nsgObject.SecurityRules.Count -gt 0) { $controlResult.AddMessage("Validate NSG security rules applied to Subnet - [$subnetName] in Virtual Network - [$($vnetResource.Name)]. Total - $($nsgObject.SecurityRules.Count)", $nsgObject.SecurityRules); } if($nsgObject.DefaultSecurityRules.Count -gt 0) { $controlResult.AddMessage("Validate NSG default security rules applied to Subnet - [$subnetName] in Virtual Network - [$($vnetResource.Name)]. Total - $($nsgObject.DefaultSecurityRules.Count)", $nsgObject.DefaultSecurityRules); } $controlResult.VerificationResult = [VerificationResult]::Verify; $controlResult.SetStateData("NSG security rules", $nsgObject.SecurityRules); } } } } } } } } if($controlResult.VerificationResult -ne [VerificationResult]::Verify) { $controlResult.AddMessage("No NSG is configured on Virtual Machine") } return $controlResult; } hidden [ControlResult] CheckPublicIP([ControlResult] $controlResult) { $publicIps = @(); $this.GetVMNICObjects() | ForEach-Object { $_.IpConfigurations | Where-Object { $_.PublicIpAddress } | ForEach-Object { $ipResource = Get-AzureRmResource -ResourceId $_.PublicIpAddress.Id if($ipResource) { $publicIpObject = Get-AzureRmPublicIpAddress -Name $ipResource.Name -ResourceGroupName $ipResource.ResourceGroupName if($publicIpObject) { $_.PublicIpAddress = $publicIpObject; $publicIps += $publicIpObject; } } } } if($publicIps.Count -gt 0) { $controlResult.AddMessage([VerificationResult]::Verify, "Validate Public IP(s) associated with Virtual Machine. Total - $($publicIps.Count)", $publicIps); $controlResult.SetStateData("Public IP(s) associated with Virtual Machine", $publicIps); } else { $controlResult.AddMessage([VerificationResult]::Passed, "No Public IP is associated with Virtual Machine"); } return $controlResult; } hidden [ControlResult] CheckDiskEncryption([ControlResult] $controlResult) { $verificationResult = [VerificationResult]::Failed; $ascDiskEncryptionStatus = $false; if($null -ne $this.ASCSettings -and $this.ASCSettings.DetailedStatus.DiskEncryption -eq 'Healthy') { $ascDiskEncryptionStatus = $true; } #TCP is not applicable for Linux. if($this.ResourceObject.OSProfile -and $this.ResourceObject.OSProfile.WindowsConfiguration) { $diskEncryptionStatus = Get-AzureRmVMDiskEncryptionStatus -ResourceGroupName $this.ResourceContext.ResourceGroupName -VMName $this.ResourceContext.ResourceName $message = ""; $diskEncryptionStatusData = @{ VMDiskEncryptionStatus = $diskEncryptionStatus ASCDiskEncryptionStatus = $ascDiskEncryptionStatus } #Need to convert the string values to Enum [Microsoft.Azure.Commands.Compute.Models.EncryptionStatus] #Enum type is not resolving here if(($diskEncryptionStatus.OsVolumeEncrypted -eq "NotEncrypted") -or ($diskEncryptionStatus.DataVolumesEncrypted -eq "NotEncrypted") -or -not $ascDiskEncryptionStatus) { $message = "All Virtual Machine disks (OS and Data disks) are not encrypted"; } else { $verificationResult = [VerificationResult]::Passed; $message = "All Virtual Machine disks (OS and Data disks) are encrypted"; } $controlResult.AddMessage($verificationResult, $message, $diskEncryptionStatusData); $controlResult.SetStateData("Virtual Machine disks encryption status", $diskEncryptionStatusData); } elseif($this.IsLinuxVM()) { $controlResult.AddMessage([VerificationResult]::Manual, "The control is not applicable on Linux Virtual Machine."); } elseif($ascDiskEncryptionStatus) { $verificationResult = [VerificationResult]::Passed; $message = "All Virtual Machine disks (OS and Data disks) are encrypted. Validated the status through ASC."; $controlResult.AddMessage($message); } else { $verificationResult = [VerificationResult]::Failed; $message = "All Virtual Machine disks (OS and Data disks) are not encrypted. Validated the status through ASC."; $controlResult.AddMessage($message); } return $controlResult; } hidden [ControlResult] CheckASCStatus([ControlResult] $controlResult) { $isManual = $false; if($this.ASCSettings) { if($this.ASCSettings.SecurityState -ne 'Healthy') { $controlResult.VerificationResult = [VerificationResult]::Failed } else { $controlResult.VerificationResult = [VerificationResult]::Passed } $controlResult.AddMessage("Security Center status for Virtual Machine [$($this.ResourceContext.ResourceName)] is: [$($this.ASCSettings.SecurityState)]", $this.ASCSettings); $controlResult.SetStateData("Security Center status for Virtual Machine", $this.ASCSettings); } else { $isManual = $true; } if($isManual) { $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check Security Center status right now. Please validate manually."); } return $controlResult; } hidden [ControlResult] CheckASCVulnerabilities([ControlResult] $controlResult) { $ascVMVulnerabilitiesStatusHealthy = $false; if($null -ne $this.ASCSettings -and $this.ASCSettings.DetailedStatus.Vulnerabilities -eq 'Healthy') { $ascVMVulnerabilitiesStatusHealthy = $true; } if($ascVMVulnerabilitiesStatusHealthy) { $controlResult.VerificationResult = [VerificationResult]::Passed } else { $controlResult.VerificationResult = [VerificationResult]::Failed } $controlResult.AddMessage("Security Center VM Vulnerability status for Virtual Machine [$($this.ResourceContext.ResourceName)]", $ascVMVulnerabilitiesStatusHealthy); return $controlResult; } hidden [ControlResult] CheckASCVMMissingPatchingStatus([ControlResult] $controlResult) { $isManual = $false; $result = $null [Helpers]::RegisterResourceProviderIfNotRegistered([SecurityCenterHelper]::ProviderNamespace); $uri = [System.String]::Format("{0}{1}/providers/microsoft.Security/dataCollectionResults/patch?api-version=2015-06-01-preview", [WebRequestHelper]::AzureManagementUri, $this.ResourceContext.ResourceId); try { $result = [WebRequestHelper]::InvokeGetWebRequest($uri) } catch { $isManual = $true; } if(($result | Measure-Object).Count -gt 0) { $vmobject = $result[0]; if($null -ne $vmobject -and (Get-Member -InputObject $vmobject -Name properties) -and $null -ne $vmobject.properties ` -and (Get-Member -InputObject $vmobject.properties -Name missingPatches) ` -and ($vmobject.properties.missingPatches | Measure-Object).Count -gt 0) { $controlResult.VerificationResult = [VerificationResult]::Failed $controlResult.AddMessage("Details of Security Center OS missing patch status for Virtual Machine [$($this.ResourceContext.ResourceName)]:", $vmobject.properties.missingPatches); } else { $controlResult.VerificationResult = [VerificationResult]::Passed } } else { $isManual = $true; } if($isManual) { $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check Security Center status right now. Please validate manually."); } return $controlResult; } hidden [ControlResult] CheckASCVMSecurityBaselineStatus([ControlResult] $controlResult) { $isManual = $false; $result = $null $baselineIds = @(); $baselineIds += $this.ControlSettings.VirtualMachine.Windows_OS_Baseline_Ids [Helpers]::RegisterResourceProviderIfNotRegistered([SecurityCenterHelper]::ProviderNamespace); $uri = [System.String]::Format("{0}{1}/providers/microsoft.Security/dataCollectionResults/baseline?api-version=2015-06-01-preview", [WebRequestHelper]::AzureManagementUri, $this.ResourceContext.ResourceId); try { $result = [WebRequestHelper]::InvokeGetWebRequest($uri) } catch { $isManual = $true; } if(($result | Measure-Object).Count -gt 0) { $vmobject = $result[0]; if($null -ne $vmobject -and (Get-Member -InputObject $vmobject -Name properties) -and $null -ne $vmobject.properties ` -and (Get-Member -InputObject $vmobject.properties -Name failedBaselineRules) ` -and ($vmobject.properties.failedBaselineRules | Measure-Object).Count -gt 0) { $missingBaselines = @() if($baselineIds.Count -gt 0) { $foundMissingBaseline = $false; foreach($failedBaseline in $vmobject.properties.failedBaselineRules) { if($null -ne $failedBaseline.baselineRuleResults -and $failedBaseline.baselineRuleResults.evalResult -eq "FAIL") { foreach($baselineId in $baselineIds) { if($baselineId.Trim() -eq $failedBaseline.baselineRuleData.cceid) { $foundMissingBaseline = $true; $missingBaselines += $failedBaseline; } } } } if($foundMissingBaseline) { $controlResult.VerificationResult = [VerificationResult]::Failed $controlResult.AddMessage("Details of Security Center baseline status for Virtual Machine [$($this.ResourceContext.ResourceName)]:", $missingBaselines); } } else { $controlResult.VerificationResult = [VerificationResult]::Verify $controlResult.AddMessage("Details of Security Center baseline status for Virtual Machine [$($this.ResourceContext.ResourceName)]:", $vmobject.properties.failedBaselineRules); } } else { $controlResult.VerificationResult = [VerificationResult]::Passed } } else { $isManual = $true; } if($isManual) { $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check Security Center status right now. Please validate manually."); } return $controlResult; } hidden [ControlResult] CheckVMDiagnostics([ControlResult] $controlResult) { if($this.ResourceObject.Extensions) { $diagnosticExtensionType = if($this.IsLinuxVM()) { "LinuxDiagnostic" } else { "IaaSDiagnostics" } $diagExtension = $this.ResourceObject.Extensions | Where-Object { $_.VirtualMachineExtensionType -eq $diagnosticExtensionType } | Select-Object -First 1 if($diagExtension) { $controlResult.AddMessage([VerificationResult]::Passed, "'$diagnosticExtensionType' extension is installed on Virtual Machine"); } else { $controlResult.AddMessage([VerificationResult]::Failed, "'$diagnosticExtensionType' extension is not installed on Virtual Machine"); } } else { $controlResult.AddMessage([MessageData]::new("We are not able to fetch the required data for the resource", [MessageType]::Error)); } return $controlResult; } hidden [ControlResult] CheckOpenPorts([ControlResult] $controlResult) { $isManual = $false $controlResult.AddMessage("Checking for Virtual Machine management ports RDP - $($this.ControlSettings.VirtualMachine.RDP_Port) and WinRM - $($this.ControlSettings.VirtualMachine.WinRM_Port)"); $vulnerableNSGsWithRules = @(); $this.GetVMNICObjects() | ForEach-Object { $effectiveNSG = $null; try { $effectiveNSG = Get-AzureRmEffectiveNetworkSecurityGroup -NetworkInterfaceName $_.Name -ResourceGroupName $_.ResourceGroupName -WarningAction SilentlyContinue -ErrorAction Stop } catch { $isManual = $true $statusCode = ($_.Exception).InnerException.Response.StatusCode; if($statusCode -eq [System.Net.HttpStatusCode]::BadRequest -or $statusCode -eq [System.Net.HttpStatusCode]::Forbidden) { $controlResult.AddMessage(($_.Exception).InnerException.Message); } else { throw $_ } } if($effectiveNSG) { $vulnerableRules = @(); $vulnerableRules += $effectiveNSG.EffectiveSecurityRules | Where-Object { ($_.direction -eq "Inbound") -and ($_.access -eq "Allow") -and ($_.destinationPortRange -like "*$($this.ControlSettings.VirtualMachine.RDP_Port)*" -or $_.destinationPortRange -like "*$($this.ControlSettings.VirtualMachine.WinRM_Port)*")} if($vulnerableRules.Count -ne 0) { $vulnerableNSGsWithRules += @{ Association = $effectiveNSG.Association; NetworkSecurityGroup = $effectiveNSG.NetworkSecurityGroup; VulnerableRules = $vulnerableRules; NicName = $_.Name }; } } } if($isManual) { $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check the NSG rules for some NICs. Please validate manually."); if($vulnerableNSGsWithRules.Count -ne 0) { $controlResult.AddMessage([VerificationResult]::Manual, "Management ports are open on Virtual Machine. Please verify and remove the NSG rules in order to comply.", $vulnerableNSGsWithRules); } } else { if($vulnerableNSGsWithRules.Count -eq 0) { $controlResult.AddMessage([VerificationResult]::Passed, "No management ports are open on Virtual Machine"); } else { $controlResult.AddMessage([VerificationResult]::Failed, "Management ports are open on Virtual Machine. Please verify and remove the NSG rules in order to comply.", $vulnerableNSGsWithRules); $controlResult.SetStateData("Management ports list on Virtual Machine", $vulnerableNSGsWithRules); } } return $controlResult; } } |