зеркало из
1
0
Форкнуть 0

Add units tests for Export-AzRuleData #28 (#30)

- Add units tests for Export-AzRuleData #28
- Update wording for Azure.Storage rule descriptions.
- Add code coverage reporting. #29
This commit is contained in:
Bernie White 2019-05-19 17:57:11 +10:00 коммит произвёл GitHub
Родитель 4a08cee862
Коммит 39051188bc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 199 добавлений и 38 удалений

Просмотреть файл

@ -1,6 +1,8 @@
## Unreleased
- Add units tests for Export-AzRuleData and update filters. [#28](https://github.com/BernieWhite/PSRule.Rules.Azure/issues/28)
## v0.1.0-B190543
- Fix cannot find the type for custom attribute error. [#21](https://github.com/BernieWhite/PSRule.Rules.Azure/issues/21)

Просмотреть файл

@ -85,8 +85,8 @@ The output of this example is:
RuleName Outcome Message
-------- ------- -------
Azure.Storage.UseReplication Fail Storage accounts not using GRS may be at risk
Azure.Storage.SecureTransferRequ... Fail Storage accounts should only allow secure traffic
Azure.Storage.SoftDelete Fail Soft delete is enabled on Storage Accounts
Azure.Storage.SecureTransferRequ... Fail Storage accounts should only accept secure traffic
Azure.Storage.SoftDelete Fail Enable soft delete on Storage Accounts
```
A summary of results can be displayed by using `Invoke-PSRule -As Summary`.

Просмотреть файл

@ -26,10 +26,10 @@ Azure.SQL.FirewallRuleCount | Determine if there is an excessive number of firew
Azure.SQL.AllowAzureAccess | Determine if access from Azure servers is required
Azure.SQL.ThreatDetection | Enable threat detection for Azure SQL logical server
Azure.SQL.Auditing | Enable auditing for Azure SQL logical server
Azure.Storage.UseReplication | Use GRS for storage accounts that don't store disks
Azure.Storage.SecureTransferRequired | Configure storage accounts to only access encrypted traffic i.e. HTTPS/SMB
Azure.Storage.UseReplication | Storage accounts not using GRS may be at risk
Azure.Storage.SecureTransferRequired | Storage accounts should only accept secure traffic
Azure.Storage.UseEncryption | Storage Service Encryption (SSE) should be enabled
Azure.Storage.SoftDelete | Soft delete is enabled on Storage Accounts
Azure.Storage.SoftDelete | Enable soft delete on Storage Accounts
Azure.Subscription.SecurityCenterContact | Security Center email and phone contact details should be set
Azure.Subscription.SecurityCenterProvisioning | Enable auto-provisioning on VMs to improve Security Center insights
Azure.VirtualMachine.UseManagedDisks | Virtual machines should use managed disks

Просмотреть файл

@ -62,7 +62,7 @@ function CopyModuleFiles {
}
}
task VersionModule PSRule, {
task VersionModule ModuleDependencies, {
$modulePath = Join-Path -Path $ArtifactPath -ChildPath PSRule.Rules.Azure;
$manifestPath = Join-Path -Path $modulePath -ChildPath PSRule.Rules.Azure.psd1;
Write-Verbose -Message "[VersionModule] -- Checking module path: $modulePath";
@ -103,11 +103,9 @@ task VersionModule PSRule, {
Update-ModuleManifest -Path $manifestPath -Prerelease $revision;
}
}
$manifest = Get-Content -Path $manifestPath -Raw;
$manifest.Replace('RequiredModules = @()', "RequiredModules = @(@{ ModuleName = 'PSRule'; ModuleVersion = '0.5.0' }, @{ ModuleName = 'Az.Accounts'; ModuleVersion = '1.4.0' }, @{ ModuleName = 'Az.StorageSync'; ModuleVersion = '0.8.0' }, @{ ModuleName = 'Az.Security'; ModuleVersion = '0.7.4' }, @{ ModuleName = 'Az.Storage'; ModuleVersion = '1.1.1' }, @{ ModuleName = 'Az.Websites'; ModuleVersion = '1.1.2' }, @{ ModuleName = 'Az.Sql'; ModuleVersion = '1.7.0' })") | Set-Content -Path $manifestPath;
}
# Synopsis: Publish to PowerShell Gallery
task ReleaseModule VersionModule, {
$modulePath = (Join-Path -Path $ArtifactPath -ChildPath PSRule.Rules.Azure);
Write-Verbose -Message "[ReleaseModule] -- Checking module path: $modulePath";
@ -116,7 +114,6 @@ task ReleaseModule VersionModule, {
Write-Error -Message "[ReleaseModule] -- Module path does not exist";
}
elseif (![String]::IsNullOrEmpty($NuGetApiKey)) {
# Publish to PowerShell Gallery
Publish-Module -Path $modulePath -NuGetApiKey $NuGetApiKey;
}
}
@ -168,6 +165,28 @@ task platyPS {
Import-Module -Name PlatyPS -Verbose:$False;
}
# Synopsis: Install module dependencies
task ModuleDependencies NuGet, PSRule, {
if ($Null -eq (Get-InstalledModule -Name Az.Accounts -ErrorAction Ignore)) {
Install-Module -Name Az.Accounts -Scope CurrentUser -Force;
}
if ($Null -eq (Get-InstalledModule -Name Az.Resources -ErrorAction Ignore)) {
Install-Module -Name Az.Resources -Scope CurrentUser -Force;
}
if ($Null -eq (Get-InstalledModule -Name Az.Storage -ErrorAction Ignore)) {
Install-Module -Name Az.Storage -Scope CurrentUser -Force -AllowClobber;
}
if ($Null -eq (Get-InstalledModule -Name Az.Security -ErrorAction Ignore)) {
Install-Module -Name Az.Security -Scope CurrentUser -Force;
}
if ($Null -eq (Get-InstalledModule -Name Az.Sql -ErrorAction Ignore)) {
Install-Module -Name Az.Sql -Scope CurrentUser -Force;
}
if ($Null -eq (Get-InstalledModule -Name Az.Websites -ErrorAction Ignore)) {
Install-Module -Name Az.Websites -Scope CurrentUser -Force;
}
}
task CopyModule {
CopyModuleFiles -Path src/PSRule.Rules.Azure -DestinationPath out/modules/PSRule.Rules.Azure;
@ -183,6 +202,11 @@ task TestRules PSRule, Pester, PSScriptAnalyzer, {
# Run Pester tests
$pesterParams = @{ Path = $PWD; OutputFile = 'reports/pester-unit.xml'; OutputFormat = 'NUnitXml'; PesterOption = @{ IncludeVSCodeMarker = $True }; PassThru = $True; };
if ($CodeCoverage) {
$pesterParams.Add('CodeCoverage', (Join-Path -Path $PWD -ChildPath 'out/modules/**/*.psm1'));
$pesterParams.Add('CodeCoverageOutputFile', (Join-Path -Path $PWD -ChildPath reports/pester-coverage.xml));
}
if (!(Test-Path -Path reports)) {
$Null = New-Item -Path reports -ItemType Directory -Force;
}

Просмотреть файл

@ -48,7 +48,14 @@ DotNetFrameworkVersion = '4.7.2'
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @()
RequiredModules = @(
@{ ModuleName = 'PSRule'; ModuleVersion = '0.5.0' }
@{ ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2' }
@{ ModuleName = 'Az.Security'; ModuleVersion = '0.7.4' }
@{ ModuleName = 'Az.Storage'; ModuleVersion = '1.3.0' }
@{ ModuleName = 'Az.Sql'; ModuleVersion = '1.9.0' }
@{ ModuleName = 'Az.Websites'; ModuleVersion = '1.2.1' }
)
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()

Просмотреть файл

@ -4,10 +4,6 @@
Set-StrictMode -Version latest;
# Set up some helper variables to make it easier to work with the module
# $PSModule = $ExecutionContext.SessionState.Module;
# $PSModuleRoot = $PSModule.ModuleBase;
#
# Localization
#
@ -36,7 +32,7 @@ function Export-AzRuleData {
process {
# Get subscriptions
$context = GetAzureContext -Subscription $Subscription -Tenant $Tenant -Verbose:$VerbosePreference;
$context = FindAzureContext -Subscription $Subscription -Tenant $Tenant -Verbose:$VerbosePreference;
if ($Null -eq $context) {
return;
@ -48,30 +44,31 @@ function Export-AzRuleData {
foreach ($c in $context) {
$filePath = Join-Path -Path $OutputPath -ChildPath "$($c.Subscription.Id).json";
GetAzureResource -Context $c -Verbose:$VerbosePreference | ConvertTo-Json -Depth 10 | Set-Content -Path $filePath;
GetAzureResource -Context $c -Verbose:$VerbosePreference | ExportAzureResource -Path $filePath -Verbose:$VerbosePreference
}
}
}
#endreigon Public functions
#endregion Public functions
#
# Helper functions
#
function GetAzureContext {
function FindAzureContext {
[CmdletBinding()]
[OutputType([Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer[]])]
param (
[Parameter(Mandatory = $False)]
[String[]]$Subscription,
[String[]]$Subscription = $Null,
[Parameter(Mandatory = $False)]
[String[]]$Tenant
[String[]]$Tenant = $Null
)
process {
# Get subscriptions
$context = Get-AzContext -ListAvailable;
# Get subscription contexts
$context = GetAzureContext;
if ($Null -eq $context) {
Write-Error -Message "Could not find an existing context. Use Connect-AzAccount to establish a PowerShell context with Azure.";
@ -79,14 +76,24 @@ function GetAzureContext {
}
$filteredContext = $context | Where-Object -FilterScript {
($Null -eq $Tenant -or $Tenant.Length -eq 0 -or $Tenant -contains $_.Tenant.Id) -and
($Null -eq $Subscription -or $Subscription.Length -eq 0 -or $Subscription -contains $_.Subscription.Id -or $Subscription -contains $_.Subscription.Name)
($Null -eq $Tenant -or $Tenant.Length -eq 0 -or ($_.Tenant.Id -in $Tenant)) -and
($Null -eq $Subscription -or $Subscription.Length -eq 0 -or ($_.Subscription.Id -in $Subscription) -or ($_.Subscription.Name -in $Subscription))
}
return $filteredContext;
}
}
function GetAzureContext {
[CmdletBinding()]
[OutputType([Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer[]])]
param ( )
process {
# Get contexts
return Get-AzContext -ListAvailable;
}
}
function GetAzureResource {
[CmdletBinding()]
param (
@ -100,6 +107,22 @@ function GetAzureResource {
}
}
function ExportAzureResource {
[CmdletBinding()]
[OutputType([void])]
param (
[Parameter(Mandatory = $True)]
[String]$Path,
[Parameter(Mandatory = $False, ValueFromPipeline = $True)]
[PSObject]$InputObject
)
process {
$InputObject | ConvertTo-Json -Depth 100 | Set-Content -Path $Path;
}
}
function VisitSqlServer {
param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
@ -282,7 +305,7 @@ function ExpandResource {
"Microsoft.Sql/servers" { VisitSqlServer @PSBoundParameters; }
"Microsoft.DataFactory/factories" { VisitDataFactoryV2 @PSBoundParameters; }
# "Microsoft.Storage/storageAccounts" { VisitStorageAccount @PSBoundParameters; }
"Microsoft.StorageSync/storageSyncServices" { VisitStorageSyncService @PSBoundParameters; }
# "Microsoft.StorageSync/storageSyncServices" { VisitStorageSyncService @PSBoundParameters; }
# "Microsoft.Web/sites" { VisitWebApp @PSBoundParameters; }
"Microsoft.RecoveryServices/vaults" { VisitRecoveryServices @PSBoundParameters; }
"Microsoft.Compute/virtualMachines" { VisitVirtualMachine @PSBoundParameters; }
@ -310,4 +333,4 @@ function SetResourceType {
# Export module
#
Export-ModuleMember -Function 'Export-AzRuleData'
Export-ModuleMember -Function 'Export-AzRuleData';

Просмотреть файл

@ -2,35 +2,28 @@
# Validation rules for Azure Storage Accounts
#
# Description: Use GRS for storage accounts that don't store disks
# Description: Storage accounts not using GRS may be at risk
Rule 'Azure.Storage.UseReplication' -If { ResourceType 'Microsoft.Storage/storageAccounts' } -Tag @{ severity = 'Single point of failure'; category = 'Reliability' } {
Hint 'Storage accounts not using GRS may be at risk'
$TargetObject.sku.name -eq 'Standard_GRS'
Within 'sku.name' 'Standard_GRS', 'Standard_RAGRS'
}
# Description: Configure storage accounts to only access encrypted traffic i.e. HTTPS/SMB
# Description: Storage accounts should only accept secure traffic
Rule 'Azure.Storage.SecureTransferRequired' -If { ResourceType 'Microsoft.Storage/storageAccounts' } -Tag @{ severity = 'Important'; category = 'Security configuration' } {
Hint 'Storage accounts should only allow secure traffic'
$TargetObject.Properties.supportsHttpsTrafficOnly
}
# Description: Storage Service Encryption (SSE) should be enabled
Rule 'Azure.Storage.UseEncryption' -If { ResourceType 'Microsoft.Storage/storageAccounts' } -Tag @{ severity = 'Important'; category = 'Security configuration' } {
Hint 'Storage accounts should have encryption enabled'
($Null -ne $TargetObject.Properties.encryption) -and
($Null -ne $TargetObject.Properties.encryption.services.blob) -and
($Null -ne $TargetObject.Properties.encryption.services.file) -and
($TargetObject.Properties.encryption.services.blob.enabled -and $TargetObject.Properties.encryption.services.file.enabled)
}
# Description: Soft delete is enabled on Storage Accounts
# Description: Enable soft delete on Storage Accounts
Rule 'Azure.Storage.SoftDelete' -If { ResourceType 'Microsoft.Storage/storageAccounts' } -Tag @{ severity = 'Important'; category = 'Data recovery' } {
$serviceProperties = $TargetObject.resources | Where-Object -FilterScript {
$_.ResourceType -eq 'serviceProperties'
}
$serviceProperties.DeleteRetentionPolicy.Enabled
}

Просмотреть файл

@ -0,0 +1,112 @@
#
# Unit tests for module cmdlets
#
[CmdletBinding()]
param (
)
# Setup error handling
$ErrorActionPreference = 'Stop';
Set-StrictMode -Version latest;
if ($Env:SYSTEM_DEBUG -eq 'true') {
$VerbosePreference = 'Continue';
}
# Setup tests paths
$rootPath = $PWD;
Import-Module (Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Rules.Azure) -Force;
#region Mocks
function MockContext {
process {
return @(
(New-Object -TypeName Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext -ArgumentList @(
[PSCustomObject]@{
Subscription = [PSCustomObject]@{
Id = '00000000-0000-0000-0000-000000000001'
Name = 'Test subscription 1'
State = 'Enabled'
}
Tenant = [PSCustomObject]@{
Id = '00000000-0000-0000-0000-000000000001'
}
}
)),
(New-Object -TypeName Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext -ArgumentList @(
[PSCustomObject]@{
Subscription = [PSCustomObject]@{
Id = '00000000-0000-0000-0000-000000000002'
Name = 'Test subscription 2'
State = 'Enabled'
}
Tenant = [PSCustomObject]@{
Id = '00000000-0000-0000-0000-000000000002'
}
}
))
(New-Object -TypeName Microsoft.Azure.Commands.Profile.Models.Core.PSAzureContext -ArgumentList @(
[PSCustomObject]@{
Subscription = [PSCustomObject]@{
Id = '00000000-0000-0000-0000-000000000003'
Name = 'Test subscription 3'
State = 'Enabled'
}
Tenant = [PSCustomObject]@{
Id = '00000000-0000-0000-0000-000000000002'
}
}
))
)
}
}
#endregion Mocks
#region Export-AzRuleData
Describe 'Export-AzRuleData' -Tag 'Cmdlet' {
Context 'With defaults' {
Mock -CommandName 'FindAzureContext' -ModuleName 'PSRule.Rules.Azure' -Verifiable -MockWith ${function:MockContext};
Mock -CommandName 'GetAzureResource' -ModuleName 'PSRule.Rules.Azure' -Verifiable -MockWith {
return [PSCustomObject]@{
Name = 'Resource1'
}
}
Mock -CommandName 'ExportAzureResource' -ModuleName 'PSRule.Rules.Azure' -Verifiable;
$Null = Export-AzRuleData;
It 'Exports resources' {
Assert-VerifiableMock;
Assert-MockCalled -CommandName 'GetAzureResource' -ModuleName 'PSRule.Rules.Azure' -Times 3;
}
}
Context 'With filters' {
Mock -CommandName 'ExportAzureResource' -ModuleName 'PSRule.Rules.Azure';
Mock -CommandName 'GetAzureContext' -ModuleName 'PSRule.Rules.Azure' -MockWith ${function:MockContext};
It 'Uses subscription name filter' {
Mock -CommandName 'GetAzureResource' -ModuleName 'PSRule.Rules.Azure';
$Null = Export-AzRuleData -Subscription 'Test subscription 1';
Assert-MockCalled -CommandName 'GetAzureResource' -ModuleName 'PSRule.Rules.Azure' -Times 1;
}
It 'Uses subscription Id filter' {
Mock -CommandName 'GetAzureResource' -ModuleName 'PSRule.Rules.Azure';
$Null = Export-AzRuleData -Subscription '00000000-0000-0000-0000-000000000002';
Assert-MockCalled -CommandName 'GetAzureResource' -ModuleName 'PSRule.Rules.Azure' -Times 1;
}
It 'Uses tenant Id filter' {
Mock -CommandName 'GetAzureResource' -ModuleName 'PSRule.Rules.Azure';
$Null = Export-AzRuleData -Tenant '00000000-0000-0000-0000-000000000002';
Assert-MockCalled -CommandName 'GetAzureResource' -ModuleName 'PSRule.Rules.Azure' -Times 2;
}
}
}
#endregion Export-AzRuleData