Src/Private/Get-AbrVbrSecInfraHard.ps1
function Get-AbrVbrSecInfraHard { <# .SYNOPSIS Used by As Built Report to returns security infrastructure hardening recomendations from Veeam Backup & Replication. .DESCRIPTION Documents the configuration of Veeam VBR in Word/HTML/Text formats using PScribo. .NOTES Version: 0.6.0 Author: Jonathan Colon Twitter: @jcolonfzenpr Github: rebelinux Credits: Iain Brighton (@iainbrighton) - PScribo module .LINK https://github.com/AsBuiltReport/AsBuiltReport.Veeam.VBR #> [CmdletBinding()] param ( ) begin { Write-PscriboMessage "Discovering Veeam VBR security infrastructure hardening recomendations from $System." } process { try { $Servers = Get-VBRServer $BackupServer = Get-VBRServer -Type Local Section -Style Heading3 "Backup & Replication Server ($($BackupServer.Name.ToString().ToUpper().Split(".")[0]))" { if (($BackupServer).count -gt 0) { $PssSession = New-PSSession $BackupServer.Name -Credential $Credential -Authentication $Options.PSDefaultAuthentication $Software = @() $SoftwareX64 = Invoke-Command -Session $PssSession -ScriptBlock {Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object -Property DisplayName,Publisher,InstallDate | Sort-Object -Property DisplayName} $SoftwareX86 = Invoke-Command -Session $PssSession -ScriptBlock {Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object -Property DisplayName,Publisher,InstallDate | Sort-Object -Property DisplayName} Remove-PSSession -Session $PssSession If ($SoftwareX64) { $Software += $SoftwareX64 } If ($SoftwareX86) { $Software += $SoftwareX86 } try { $Unused = if ( $Software ) { $OutObj = @() foreach ($APP in ($Software | Where-Object {($_.Publisher -notlike "Microsoft*" -and $_.DisplayName -notlike "VMware*" -and $_.DisplayName -notlike "Microsoft*" -and $_.DisplayName -notlike "*Veeam*") -and ($Null -ne $_.Publisher -or $Null -ne $_.DisplayName)})) { try { $inObj = [ordered] @{ 'Name' = $APP.DisplayName 'Publisher' = ConvertTo-EmptyToFiller $APP.Publisher } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } if ($OutObj) { $TableParams = @{ Name = "Non-essential software programs - $($BackupServer.Name.ToString().ToUpper().Split(".")[0])" List = $false ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Name' | Table @TableParams } } if ($Unused) { Section -Style Heading4 'Remove Unused Components' { Paragraph "Remove all non-essential software programs and utilities from the deployed Veeam components. While these programs may offer useful features to the administrator, if they provide 'back-door' access to the system, they must be removed during the hardening process. Think about additional software like web browsers, java, adobe reader and such. All parts which do not belong to the operating system or to active Veeam components, remove it. It will make maintaining an up-to-date patch level much easier." BlankLine $Unused Paragraph 'Reference: https://bp.veeam.com/vbr/Security/infrastructure_hardening.html#remove-unused-components' -Bold } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { $Console = if ( $Software ) { $OutObj = @() foreach ($APP in ($Software | Where-Object {($_.DisplayName -like "Veeam Explorer*" -or $_.DisplayName -like "Veeam Backup & Replication Console") -and ($Null -ne $_.Publisher -or $Null -ne $_.DisplayName)})) { try { $inObj = [ordered] @{ 'Name' = $APP.DisplayName 'Publisher' = ConvertTo-EmptyToFiller $APP.Publisher } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "Backup & Replication Console - $($BackupServer.Name.ToString().ToUpper().Split(".")[0])" List = $false ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Name' | Table @TableParams } if ($Console) { Section -Style Heading4 'Remove Backup & Replication Console' { Paragraph "Remove the Veeam Backup & Replication Console from the Veeam Backup & Replication server. The console is installed locally on the backup server by default." BlankLine $Console Paragraph 'Reference: https://bp.veeam.com/vbr/Security/infrastructure_hardening.html#how-to-remove-the-veeam-backup--replication-console' -Bold } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { $PssSession = New-PSSession $BackupServer.Name -Credential $Credential -Authentication $Options.PSDefaultAuthentication $Available = Invoke-Command -Session $PssSession -ScriptBlock {Get-Service "W32Time" | Select-Object DisplayName, Name, Status} $Services = Invoke-Command -Session $PssSession -ScriptBlock {Get-Service VeeamNFSSvc} Remove-PSSession -Session $PssSession $vPowerNFS = if ( $Services ) { $OutObj = @() foreach ($Service in $Services) { try { $inObj = [ordered] @{ 'Display Name' = $Service.DisplayName 'Short Name' = $Service.Name 'Status' = $Service.Status } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "vPower NFS Services Status - $($BackupServer.Name.ToString().ToUpper().Split(".")[0])" List = $false ColumnWidths = 34, 33, 33 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Display Name' | Table @TableParams } if ($vPowerNFS) { Section -Style Heading4 'Switch off the vPower NFS Service' { Paragraph "Stop the Veeam vPower NFS Service if you do not plan on using the following Veeam features: SureBackup, Instant Recovery, or Other-OS File Level Recovery (FLR) operations." BlankLine $vPowerNFS Paragraph 'Reference: https://bp.veeam.com/vbr/Security/infrastructure_hardening.html#remove-unused-components' -Bold } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } } try { $OutObj = @() Write-PscriboMessage "Collecting Enterprise Manager information from $($BackupServer.Name)." $EMInfo = [Veeam.Backup.Core.SBackupOptions]::GetEnterpriseServerInfo() $EMObj = if ($EMInfo.IsConnected -eq $true) { Section -Style Heading4 "Enterprise Manager Server ($($EMInfo.ServerName.ToString().ToUpper().Split(".")[0]))" { $inObj = [ordered] @{ 'Server Name' = Switch ($EMInfo.ServerName) { $Null {'Not Connected'} default {$EMInfo.ServerName} } 'Server URL' = Switch ($EMInfo.URL) { $Null {'Not Connected'} default {$EMInfo.URL} } 'Skip License Push' = ConvertTo-TextYN $EMInfo.SkipLicensePush 'Is Connected' = ConvertTo-TextYN $EMInfo.IsConnected } $OutObj = [pscustomobject]$inobj $TableParams = @{ Name = "Enterprise Manager - $($BackupServer.Name.Split(".")[0])" List = $true ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Table @TableParams } } if ($EMObj) { Section -Style Heading3 'Enterprise Manager' { Paragraph "When Enterprise Manager is not in use de-install it and remove it from your environment." $EMObj } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } Section -Style Heading3 'Console Access' { Paragraph "The Veeam Backup & Replication console is a client-side component that provides access to the backup server. The console lets several backup operators and admins log in to Veeam Backup & Replication simultaneous and perform all kind of data protection and disaster recovery operations as if you work on the backup server." BlankLine Paragraph "Install the Veeam Backup & Replication console on a central management server that is, positioned in a DMZ and protected with 2-factor authentication. Do NOT install the console on the local desktops of backup & recovery admins." } try { $PssSession = New-PSSession $BackupServer.Name -Credential $Credential -Authentication $Options.PSDefaultAuthentication $secEditPath = Invoke-Command -Session $PssSession -ScriptBlock {[System.Environment]::ExpandEnvironmentVariables("%SystemRoot%\system32\secedit.exe")} $tempFile = Invoke-Command -Session $PssSession -ScriptBlock {[System.IO.Path]::GetTempFileName()} $exportArguments = '/export /cfg "{0}" /quiet' -f $tempFile $importArguments = '/configure /db secedit.sdb /cfg "{0}" /quiet' -f $tempFile Invoke-Command -Session $PssSession -ScriptBlock {Start-Process -FilePath $using:secEditPath -ArgumentList $using:exportArguments -Wait} $policyConfig = Invoke-Command -Session $PssSession -ScriptBlock {Get-Content -Path $using:tempFile} Remove-PSSession -Session $PssSession $Regex = [Regex]::new("(?<=System Access)(.*)(?=Event Audit)") $Match = $Regex.Match($policyConfig) $policyConfigs = [RegEx]::Matches($Match.Value.Split(']['),"\w+ = \w+").value $policyConfigHash = @{} foreach ($policyConfig in $policyConfigs) { $policyConfigSplitted = $policyConfig.split() $policyConfigHash[$policyConfigSplitted[0]] = $policyConfigSplitted[2] } $PasswordPolicyConfiObj = if ($policyConfigHash) { Section -Style Heading4 "Password Management Policy" { Paragraph "Use a clever Password management policy, which works for your organization. Enforcing the use of strong passwords across your infrastructure is a valuable control. It's more challenging for attackers to guess passwords/crack hashes to gain unauthorized access to critical systems." BlankLine Paragraph "Selecting passwords of 10 characters with a mixture of upper and lowercase letters, numbers and special characters is a good start for user accounts." BlankLine Paragraph "For Admin accounts adding 2-factor authentication is also a must to secure the infrastructure." BlankLine Paragraph "And for service accounts use 25+ characters combined with a password tool for easier management. An Admin can copy and paste the password when needed, increasing security of the service accounts." BlankLine $OutObj = @() $inObj = [ordered] @{ 'Password Must Meet Complexity Requirements' = Switch ($policyConfigHash.PasswordComplexity) { 1 {'Yes'} 0 {'No'} default {'Unknown'} } 'Max Password Age' = $policyConfigHash.MaximumPasswordAge 'Min Password Age' = $policyConfigHash.MinimumPasswordAge 'Min Password Length' = $policyConfigHash.MinimumPasswordLength 'Enforce Password History' = $policyConfigHash.PasswordHistorySize 'Store Password using Reversible Encryption' = Switch ($policyConfigHash.ClearTextPassword) { 1 {'Yes'} 0 {'No'} default {'Unknown'} } } $OutObj = [pscustomobject]$inobj $TableParams = @{ Name = "Password Management Policy - $($BackupServer.Name.Split(".")[0])" List = $true ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Table @TableParams Paragraph 'Reference: https://bp.veeam.com/vbr/Security/infrastructure_hardening.html#password-management-policy' -Bold } } $LockpolicyConfiObj = if ($policyConfigHash) { Section -Style Heading4 "Lockout Policy" { Paragraph "Use a Lockout policy that complements a clever password management policy. Accounts will be locked after a small number of incorrect attempts. This can stop password guessing attacks dead in the water. But be careful that this can also lock everyone out of the backup & replication system for a period! For service accounts, sometimes it is better just to raise alarms fast. Instead of locking the accounts. This way you gain visibility into suspicious behavior towards your data/infrastructure." BlankLine $OutObj = @() $inObj = [ordered] @{ 'Account Lockout Thresholds' = $policyConfigHash.LockoutBadCount 'Account Lockout Duration Age' = Switch ([string]::IsNullOrEmpty($policyConfigHash.LockoutDuration)) { $true {'-'} $false {$policyConfigHash.LockoutDuration} default {'Unknown'} } 'Reset account lockout counter after' = Switch ([string]::IsNullOrEmpty($policyConfigHash.ResetLockoutCount)) { $true {'-'} $false {$policyConfigHash.ResetLockoutCount} default {'Unknown'} } } $OutObj = [pscustomobject]$inobj $TableParams = @{ Name = "Lockout Policy - $($BackupServer.Name.Split(".")[0])" List = $true ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Table @TableParams Paragraph 'Reference: https://bp.veeam.com/vbr/Security/infrastructure_hardening.html#lockout-policy' -Bold } } if ($PasswordPolicyConfiObj -or $LockpolicyConfiObj) { Section -Style Heading3 'Roles and Users' { Paragraph "Deploy an Access Control policy, managing access to management components is crucial for a good protection. Use the principle of least privilege. Provide the minimal privilege needed for some operation to occur. An attacker who gained high-privilege access to backup infrastructure servers can get credentials of user accounts and compromise other systems in your environment. Make sure that all accounts have a specific role and that they are added to that specific group." Blankline Paragraph "Containment to keep the attackers from moving around too easily. Some standard measures and policies are:" Blankline Paragraph '* Do not use user accounts for admin access, reducing incidents and accidents.' Paragraph '* Give every Veeam admin his own admin account or add their admin account to the appropriate security group within Veeam, for traceability and easy adding and removal.' Paragraph '* Only give out access to what is needed for the job.' Paragraph '* Limit users who can log in using Remote Desktop and/or Veeam Backup Console.' Paragraph '* Add 2-factor authentication to highly valuable assets.' Paragraph '* Monitor your accounts for suspicious activity.' Blankline Paragraph "A role assigned to the user defines the user activity scope: what operations in Veeam Backup & Replication the user can perform." BlankLine try { $OutObj = @() try { $RoleAssignments = Get-VBRUserRoleAssignment foreach ($RoleAssignment in $RoleAssignments) { Write-PscriboMessage "Discovered $($RoleAssignment.Name) Server." $inObj = [ordered] @{ 'Name' = $RoleAssignment.Name 'Type' = $RoleAssignment.Type 'Role' = $RoleAssignment.Role } $OutObj += [pscustomobject]$inobj } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } $TableParams = @{ Name = "Roles and Users - $VeeamBackupServer" List = $false ColumnWidths = 45, 15, 40 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Name' | Table @TableParams Paragraph 'Reference: https://bp.veeam.com/vbr/Security/infrastructure_hardening.html#roles-and-users' -Bold } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } $PasswordPolicyConfiObj $LockpolicyConfiObj } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { $VCInventObjs = Get-VBRServer | Where-Object {$_.Type -eq 'VC'} $vSphereCredObj = if ($VCInventObjs) { Section -Style Heading4 "VMware vSphere Credentials" { Paragraph 'If VMware vCenter Server is added to the backup infrastructure, an account with reduced permissions can be used. Use the minimum permissions for your use-case. See Required Permissions document:' BlankLine Paragraph '* https://helpcenter.veeam.com/docs/backup/permissions/installation.html?ver=110' BlankLine Paragraph 'For example, Hot-Add backup requires the delete disk permission. You can also consider elevating permissions for restores.' try { Section -Style Heading5 'vCenter Server' { $OutObj = @() foreach ($InventObj in $VCInventObjs) { try { $inObj = [ordered] @{ 'Name' = $InventObj.Name 'Credential' = ($InventObj).GetSoapCreds().User } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "vCenter Servers - $VeeamBackupServer" List = $false ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Name' | Table @TableParams } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } $ESXiInventObjs = Get-VBRServer | Where-Object {$_.Type -eq 'Esxi' -and $_.IsStandaloneEsx() -eq 'True'} if ($ESXiInventObjs) { try { Section -Style Heading5 'Standalone ESXi Server' { $OutObj = @() foreach ($InventObj in $ESXiInventObjs) { try { $inObj = [ordered] @{ 'Name' = $InventObj.Name 'Credential' = ($InventObj).GetSoapCreds().User } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "ESXi Servers - $VeeamBackupServer" List = $false ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Name' | Table @TableParams } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } } } if ($vSphereCredObj -or $EsxiCredObj) { Section -Style Heading3 'Required Permissions' { Paragraph "Use the principle of least privilege. Provide the minimal required permissions needed for the accounts to run. The accounts used for installing and using Veeam Backup & Replication must have the following permissions:" Blankline Paragraph "* https://helpcenter.veeam.com/docs/backup/vsphere/required_permissions.html?ver=110" Blankline Paragraph "Backup proxies must be considered the target for compromise. During backup, proxies obtain from the backup server credentials required to access virtual infrastructure servers. A person having administrator privileges on a backup proxy can intercept the credentials and use them to access the virtual infrastructure." $vSphereCredObj Paragraph "Reference: https://helpcenter.veeam.com/docs/backup/permissions/installation.html?ver=110" -Bold } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { $PssSession = New-PSSession $BackupServer.Name -Credential $Credential -Authentication $Options.PSDefaultAuthentication $Updates = Invoke-Command -Session $PssSession -ScriptBlock {(New-Object -ComObject Microsoft.Update.Session).CreateupdateSearcher().Search("IsHidden=0 and IsInstalled=0").Updates | Select-Object Title,KBArticleIDs} Remove-PSSession -Session $PssSession $UpdatesObj = if ($Updates) { Section -Style Heading4 "Ensure timely guest OS updates on backup infrastructure servers" { Paragraph 'Install the latest updates and patches on backup infrastructure servers to minimize the risk of exploiting guest OS vulnerabilities by attackers.' try { Section -Style Heading5 "Backup & Replication Server ($($BackupServer.Name.ToString().ToUpper().Split(".")[0]))" { try { $Software = @() $OutObj = @() foreach ($Update in $Updates) { try { $inObj = [ordered] @{ 'KB Article' = "KB$($Update.KBArticleIDs)" 'Name' = $Update.Title } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "Missing Windows Updates - $($BackupServer.Name.ToString().ToUpper().Split(".")[0])" List = $false ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Name' | Table @TableParams } catch { Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Backup & Replication Server - Installed Software Update Table)" } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { $ViBackupProxies = Get-VBRViProxy | Where-Object {$_.Host.Type -eq "Windows"} $HvBackupProxies = Get-VBRHvProxy | Where-Object {$_.Host.Type -eq "Windows"} $BackupProxies = @() $BackupProxies += $ViBackupProxies $BackupProxies += $HvBackupProxies if ($BackupProxies | Where-Object {$_.Name -ne $BackupServer.Name}) { Section -Style Heading5 "Backup Proxy Servers" { foreach ($BackupProxy in $BackupProxies) { if (($BackupProxie.Host.id.Guid -notin $BackupServer.id.Guid)) { try { $PssSession = New-PSSession $BackupProxy.Name -Credential $Credential -Authentication $Options.PSDefaultAuthentication $Updates = Invoke-Command -Session $PssSession -ScriptBlock {(New-Object -ComObject Microsoft.Update.Session).CreateupdateSearcher().Search("IsHidden=0 and IsInstalled=0").Updates | Select-Object Title,KBArticleIDs} Remove-PSSession -Session $PssSession if ($Updates) { $Software = @() $OutObj = @() foreach ($Update in $Updates) { try { $inObj = [ordered] @{ 'KB Article' = "KB$($Update.KBArticleIDs)" 'Name' = $Update.Title } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "Missing Windows Updates - $($BackupProxy.Name.ToString().ToUpper().Split(".")[0])" List = $false ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } if ($OutObj) { Section -Style Heading6 "$($BackupProxy.Name.ToString().ToUpper().Split(".")[0])" { $OutObj | Sort-Object -Property 'Name' | Table @TableParams } } } } catch { Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Backup Proxy Servers- Installed Software Update Table)" } } } } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { $BackupRepos = Get-VBRBackupRepository | Where-Object {$_.Type -eq "WinLocal"} if ($BackupRepos.Host.Name | Where-Object {$_ -ne $BackupServer.Name}) { $BRObj = foreach ($BackupRepo in $BackupRepos) { if ((($BackupRepo.id.Guid -notin $BackupServer.id.Guid) -and ($BackupRepo.id.Guid -notin $BackupProxies.id.Guid))) { try { $PssSession = New-PSSession $BackupRepo.Host.Name -Credential $Credential -Authentication $Options.PSDefaultAuthentication $Updates = Invoke-Command -Session $PssSession -ScriptBlock {(New-Object -ComObject Microsoft.Update.Session).CreateupdateSearcher().Search("IsHidden=0 and IsInstalled=0").Updates | Select-Object Title,KBArticleIDs} Remove-PSSession -Session $PssSession if ($Updates) { $Software = @() $OutObj = @() foreach ($Update in $Updates) { try { $inObj = [ordered] @{ 'KB Article' = SWitch ($Update.KBArticleIDs -match '^\d+$') { $false {'Unknown'} default {"KB$($Update.KBArticleIDs)"} } 'Name' = $Update.Title } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "Missing Windows Updates - $($BackupRepo.Host.Name.ToString().ToUpper().Split(".")[0])" List = $false ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } if ($OutObj) { Section -Style Heading6 "$($BackupRepo.Host.Name.ToString().ToUpper().Split(".")[0])" { $OutObj | Sort-Object -Property 'Name' | Table @TableParams } } } } catch { Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Installed Software Update Table)" } } } if ($BRObj) { Section -Style Heading5 "Backup Repository Servers" { $BRObj } } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { $WANAccels = Get-VBRWANAccelerator if ($WANAccels) { $WANObj = foreach ($WANAccel in $WANAccels) { if (($WANAccel.HostId.Guid -notin $BackupServer.id.Guid) -and ($WANAccel.HostId.Guid -notin $BackupRepos.Host.id.Guid) -and ($WANAccel.HostId.Guid -notin $BackupProxies.Host.id.Guid)) { try { $PssSession = New-PSSession ($Servers | Where-Object {$_.id -eq ($WANAccel).HostId.Guid}).Info.DnsName -Credential $Credential -Authentication $Options.PSDefaultAuthentication $Updates = Invoke-Command -Session $PssSession -ScriptBlock {(New-Object -ComObject Microsoft.Update.Session).CreateupdateSearcher().Search("IsHidden=0 and IsInstalled=0").Updates | Select-Object Title,KBArticleIDs} Remove-PSSession -Session $PssSession if ($Updates) { $Software = @() $OutObj = @() foreach ($Update in $Updates) { try { $inObj = [ordered] @{ 'KB Article' = SWitch ($Update.KBArticleIDs -match '^\d+$') { $false {'Unknown'} default {"KB$($Update.KBArticleIDs)"} } 'Name' = $Update.Title } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "Missing Windows Updates - $($WANAccel.Name.ToString().ToUpper().Split(".")[0])" List = $false ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } if ($OutObj) { Section -Style Heading6 "$($WANAccel.Name.ToString().ToUpper().Split(".")[0])" { $OutObj | Sort-Object -Property 'Name' | Table @TableParams } } } } catch { Write-PscriboMessage -IsWarning "$($_.Exception.Message) (WAN Accelerators Servers- Installed Software Update Table)" } } } if ($WANObj) { Section -Style Heading5 "WAN Accelerators Servers" { $WANObj } } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { $TapeServers = Get-VBRTapeServer if ($TapeServers) { $TapeObj = foreach ($TapeServer in $TapeServers) { if (($TapeServer.ServerId.Guid -notin $BackupServer.id.Guid) -and ($TapeServer.ServerId.Guid -notin $BackupRepos.Host.id.Guid) -and ($TapeServer.ServerId.Guid -notin $BackupProxies.Host.id.Guid) -and ($TapeServer.ServerId.Guid -notin $WANAccels.HostId.Guid)) { try { $PssSession = New-PSSession ($Servers | Where-Object {$_.id -eq ($TapeServer).ServerId.Guid}).Info.DnsName -Credential $Credential -Authentication $Options.PSDefaultAuthentication $Updates = Invoke-Command -Session $PssSession -ScriptBlock {(New-Object -ComObject Microsoft.Update.Session).CreateupdateSearcher().Search("IsHidden=0 and IsInstalled=0").Updates | Select-Object Title,KBArticleIDs} Remove-PSSession -Session $PssSession if ($Updates) { $Software = @() $OutObj = @() foreach ($Update in $Updates) { try { $inObj = [ordered] @{ 'KB Article' = SWitch ($Update.KBArticleIDs -match '^\d+$') { $false {'Unknown'} default {"KB$($Update.KBArticleIDs)"} } 'Name' = $Update.Title } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "Missing Windows Updates - $($TapeServer.Name.ToString().ToUpper().Split(".")[0])" List = $false ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } if ($OutObj) { Section -Style Heading6 "$($TapeServer.Name.ToString().ToUpper().Split(".")[0])" { $OutObj | Sort-Object -Property 'Name' | Table @TableParams } } } } catch { Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Tape Servers- Installed Software Update Table)" } } } if ($TapeObj) { Section -Style Heading5 "Tape Servers" { $TapeObj } } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } } if ($UpdatesObj) { Section -Style Heading3 'Patching and Updates' { Paragraph "Patch operating systems, software, and firmware on Veeam components. Most hacks succeed because there is already vulnerable software in use which is not up-to-date with current patch levels. So make sure all software and hardware where Veeam components are running are up-to-date. One of the most possible causes of a credential theft are missing guest OS updates and use of outdated authentication protocols." Paragraph 'Reference: https://bp.veeam.com/vbr/Security/infrastructure_hardening.html#patching-and-updates' -Bold $UpdatesObj } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { $Bkjobs = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.TypetoString -notlike '*Agent*' -and $_.TypetoString -notlike '*File*'} $ABkjobs = Get-VBRComputerBackupJob | Sort-Object -Property Name $FSjobs = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.TypeToString -like 'File Backup'} | Sort-Object -Property Name $BKJobsEncObj = if ($BKJobs) { Section -Style Heading4 "Backup Jobs Encryption Status" { Paragraph 'Data security is an important part of the backup strategy. You must protect your information from unauthorized access, especially if you back up sensitive VM data to off-site locations or archive it to tape. To keep your data safe, you can use data encryption.' try { Section -Style Heading5 'Backup Jobs' { $OutObj = @() foreach ($BKJob in $BKJobs) { try { $inObj = [ordered] @{ 'Name' = $BKJob.Name 'Storage Encryption' = ConvertTo-TextYN $Bkjob.Options.BackupStorageOptions.StorageEncryptionEnabled 'Encryption Key' = Switch ($Bkjob.Options.BackupStorageOptions.StorageEncryptionEnabled) { $false {'None'} default {(Get-VBREncryptionKey | Where-Object { $_.id -eq $Bkjob.Info.PwdKeyId }).Description} } } $OutObj += [pscustomobject]$inobj if ($HealthCheck.Security.BestPractice) { $OutObj | Where-Object { $_.'Storage Encryption' -like 'No'} | Set-Style -Style Warning -Property 'Storage Encryption' } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "Backup Jobs - $VeeamBackupServer" List = $false ColumnWidths = 34, 33, 33 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Name' | Table @TableParams } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { if ($ABkjobs) { Section -Style Heading5 'Agent Backup Jobs' { $OutObj = @() foreach ($ABkjob in $ABkjobs) { try { $inObj = [ordered] @{ 'Name' = $ABkjob.Name 'Enabled Backup File Encryption' = ConvertTo-TextYN $ABkjob.StorageOptions.EncryptionEnabled 'Encryption Key' = Switch ($ABkjob.StorageOptions.EncryptionEnabled) { $false {'None'} default {(Get-VBREncryptionKey | Where-Object { $_.id -eq $ABkjob.StorageOptions.EncryptionKey.Id }).Description} } } $OutObj += [pscustomobject]$inobj if ($HealthCheck.Security.BestPractice) { $OutObj | Where-Object { $_.'Enabled Backup File Encryption' -like 'No'} | Set-Style -Style Warning -Property 'Enabled Backup File Encryption' } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "Agent Backup Jobs - $VeeamBackupServer" List = $false ColumnWidths = 34, 33, 33 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Name' | Table @TableParams } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { if ($FSjobs) { Section -Style Heading4 'File Share Backup Jobs' { $OutObj = @() foreach ($FSjob in $FSjobs) { try { $inObj = [ordered] @{ 'Name' = $FSjob.Name 'Enabled Backup File Encryption' = ConvertTo-TextYN $FSjob.Options.BackupStorageOptions.StorageEncryptionEnabled 'Encryption Key' = Switch ($FSjob.Options.BackupStorageOptions.StorageEncryptionEnabled) { $false {'None'} default {(Get-VBREncryptionKey | Where-Object { $_.id -eq $FSjob.Info.PwdKeyId }).Description} } } $OutObj += [pscustomobject]$inobj if ($HealthCheck.Security.BestPractice) { $OutObj | Where-Object { $_.'Enabled Backup File Encryption' -like 'No'} | Set-Style -Style Warning -Property 'Enabled Backup File Encryption' } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } $TableParams = @{ Name = "File Share Backup Jobs - $VeeamBackupServer" List = $false ColumnWidths = 34, 33, 33 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Name' | Table @TableParams } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } } try { $TrafficRules = Get-VBRNetworkTrafficRule $EncryptNetworkTraffic = if (($TrafficRules).count -gt 0) { Section -Style Heading4 'Encrypt Network Traffic' { Paragraph "By default, Veeam Backup & Replication encrypts network traffic traveling between public networks. To ensure secure communication of sensitive data within the boundaries of the same network, you can also encrypt backup traffic in private networks." BlankLine $OutObj = @() try { foreach ($TrafficRule in $TrafficRules) { $inObj = [ordered] @{ 'Name' = $TrafficRule.Name 'Source IP Start' = $TrafficRule.SourceIPStart 'Source IP End' = ConvertTo-EmptyToFiller $TrafficRule.SourceIPEnd 'Target IP Start' = $TrafficRule.TargetIPStart 'Target IP End' = ConvertTo-EmptyToFiller $TrafficRule.TargetIPEnd 'Encryption Enabled' = ConvertTo-TextYN $TrafficRule.EncryptionEnabled } $OutObj += [pscustomobject]$inobj if ($HealthCheck.Security.BestPractice) { $OutObj | Where-Object { $_.'Encryption Enabled' -like 'No'} | Set-Style -Style Warning -Property 'Encryption Enabled' } } $TableParams = @{ Name = "Encrypt Network Traffic - $VeeamBackupServer" List = $false ColumnWidths = 20, 17, 17, 17, 17, 12 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Table @TableParams } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } if ($BKJobsEncObj) { Section -Style Heading3 'Encryption' { Paragraph "Backup and replica data is a highly potential source of vulnerability. To secure data stored in backups and replicas, follow these guidelines:" BlankLine Paragraph "* Ensure physical security of target servers. Check that only authorized personnel have access to the room where your target servers (backup repositories and hosts) reside." Paragraph "* Restrict user access to backups and replicas. Check that only authorized users have permissions to access backups and replicas on target servers." Paragraph "* Encrypt data in backups. Use Veeam Backup & Replication inbuilt encryption to protect data in backups. To guarantee security of data in backups, follow Encryption Best Practices." BlankLine Paragraph "Reference: https://bp.veeam.com/vbr/Security/infrastructure_hardening.html#encryption" -Bold $BKJobsEncObj $EncryptNetworkTraffic } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } try { $BackupSettings = Get-VBRConfigurationBackupJob $BKPConf = if (($BackupSettings).count -gt 0) { Section -Style Heading4 'Encrypt Data in Configuration Backups' { Paragraph 'Enable data encryption for configuration backup to secure sensitive data stored in the configuration database.' BlankLine Paragraph "Reference: https://helpcenter.veeam.com/docs/backup/vsphere/config_backup_encrypted.html?ver=110" -Bold BlankLine $OutObj = @() try { if ($BackupSettings.ScheduleOptions.Type -like "Daily") { $ScheduleOptions = "Type: $($BackupSettings.ScheduleOptions.DailyOptions.Type)`r`nPeriod: $($BackupSettings.ScheduleOptions.DailyOptions.Period)`r`nDay Of Week: $($BackupSettings.ScheduleOptions.DailyOptions.DayOfWeek)" } elseif ($BackupSettings.ScheduleOptions.Type -like "Monthly") { $ScheduleOptions = "Period: $($BackupSettings.ScheduleOptions.MonthlyOptions.Period)`r`nDay Number In Month: $($BackupSettings.ScheduleOptions.MonthlyOptions.DayNumberInMonth)`r`nDay of Week: $($BackupSettings.ScheduleOptions.MonthlyOptions.DayOfWeek)`r`nDay of Month: $($BackupSettings.ScheduleOptions.MonthlyOptions.DayOfMonth)" } $inObj = [ordered] @{ 'Name' = $BackupSettings.Name 'Run Job Automatically' = ConvertTo-TextYN $BackupSettings.ScheduleOptions.Enabled 'Schedule Type' = $BackupSettings.ScheduleOptions.Type 'Schedule Options' = $ScheduleOptions 'Restore Points To Keep' = $BackupSettings.RestorePointsToKeep 'Encryption Enabled' = ConvertTo-TextYN $BackupSettings.EncryptionOptions 'Encryption Key' = $BackupSettings.EncryptionOptions.Key.Description 'Additional Address' = $BackupSettings.NotificationOptions.AdditionalAddress 'Email Subject' = $BackupSettings.NotificationOptions.NotificationSubject 'Notify On' = Switch ($BackupSettings.NotificationOptions.EnableAdditionalNotification) { "" {"-"; break} $Null {"-"; break} default {"Notify On Success: $(ConvertTo-TextYN $BackupSettings.NotificationOptions.NotifyOnSuccess)`r`nNotify On Warning: $(ConvertTo-TextYN $BackupSettings.NotificationOptions.NotifyOnWarning)`r`nNotify On Error: $(ConvertTo-TextYN $BackupSettings.NotificationOptions.NotifyOnError)`r`nNotify On Last Retry Only: $(ConvertTo-TextYN $BackupSettings.NotificationOptions.NotifyOnLastRetryOnly)"} } 'NextRun' = $BackupSettings.NextRun 'Target' = $BackupSettings.Target 'Enabled' = ConvertTo-TextYN $BackupSettings.Enabled 'LastResult' = $BackupSettings.LastResult } $OutObj += [pscustomobject]$inobj } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } if ($HealthCheck.Infrastructure.Settings) { $OutObj | Where-Object { $_.'Enabled' -like 'No'} | Set-Style -Style Warning -Property 'Enabled' $OutObj | Where-Object { $_.'Run Job Automatically' -like 'No'} | Set-Style -Style Warning -Property 'Run Job Automatically' $OutObj | Where-Object { $_.'Encryption Enabled' -like 'No'} | Set-Style -Style Critical -Property 'Encryption Enabled' $OutObj | Where-Object { $_.'LastResult' -like 'Warning'} | Set-Style -Style Warning -Property 'LastResult' $OutObj | Where-Object { $_.'LastResult' -like 'Failed'} | Set-Style -Style Critical -Property 'LastResult' } $TableParams = @{ Name = "Configuration Backup Settings - $VeeamBackupServer" List = $true ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Table @TableParams } } if ($BKPConf) { Section -Style Heading3 'Backup and Replication Database' { Paragraph "The Backup & Replication configuration database stores credentials to connect to virtual servers and other systems in the backup & replication infrastructure. All passwords stored in the database are encrypted. However, a user with administrator privileges on the backup server can decrypt the passwords, which presents a potential threat." BlankLine Paragraph "Reference: https://bp.veeam.com/vbr/Security/infrastructure_hardening.html#backup-and-replication-database" -Bold $BKPConf } } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } catch { Write-PscriboMessage -IsWarning $_.Exception.Message } } end {} } |