diff --git a/PowerShell/ScubaGear/Modules/Support/Support.psm1 b/PowerShell/ScubaGear/Modules/Support/Support.psm1 index 15524993f2..8df9277096 100644 --- a/PowerShell/ScubaGear/Modules/Support/Support.psm1 +++ b/PowerShell/ScubaGear/Modules/Support/Support.psm1 @@ -1,9 +1,9 @@ function Copy-ScubaBaselineDocument { <# .SYNOPSIS - Execute the SCuBAGear tool security baselines for specified M365 products. + Copy security baselines documents to a user specified location. .Description - This is the main function that runs the Providers, Rego, and Report creation all in one PowerShell script call. + This function makes copies of the security baseline documents included with the installed ScubaGear module. .Parameter Destination Where to copy the baselines. Defaults to \ScubaGear\baselines .Example @@ -28,7 +28,7 @@ function Copy-ScubaBaselineDocument { New-Item -ItemType Directory -Path $Destination | Out-Null } - @("teams", "exo", "defender", "aad", "powerplatform", "sharepoint") | ForEach-Object { + @("teams", "exo", "defender", "aad", "powerbi", "powerplatform", "sharepoint") | ForEach-Object { $SourceFileName = Join-Path -Path $PSScriptRoot -ChildPath "..\..\baselines\$_.md" $TargetFileName = Join-Path -Path $Destination -ChildPath "$_.md" Copy-Item -Path $SourceFileName -Destination $Destination -Force:$Force -ErrorAction Stop 2> $null @@ -36,6 +36,111 @@ function Copy-ScubaBaselineDocument { } } +function Copy-ScubaSampleReport { + <# + .SYNOPSIS + Copy sample reports to user defined location. + .Description + This function makes copies of the sample reports included with the installed ScubaGear module. + .Parameter Destination + Where to copy the samples. Defaults to \ScubaGear\samples\reports + .Example + Copy-ScubaSampleReport + .Functionality + Public + .NOTES + SuppressMessage for PSReviewUnusedParameter due to linter bug. Open issue to remove if/when fixed. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] + param ( + [Parameter(Mandatory = $false)] + [ValidateScript({Test-Path -Path $_ -IsValid})] + [string] + $DestinationDirectory = (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/samples/reports"), + [Parameter(Mandatory = $false)] + [switch] + $Force + ) + + $SourceDirectory = Join-Path -Path $PSScriptRoot -ChildPath "..\..\Sample-Reports\" + Copy-ScubaModuleFile -SourceDirectory $SourceDirectory -DestinationDirectory $DestinationDirectory -Force:$Force +} + +function Copy-ScubaSampleConfigFile { + <# + .SYNOPSIS + Copy sample configuration files to user defined location. + .Description + This function makes copies of the sample configuration files included with the installed ScubaGear module. + .Parameter Destination + Where to copy the samples. Defaults to \ScubaGear\samples\config-files + .Example + Copy-ScubaSampleConfigFile + .Functionality + Public + .NOTES + SuppressMessage for PSReviewUnusedParameter due to linter bug. Open issue to remove if/when fixed. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] + param ( + [Parameter(Mandatory = $false)] + [ValidateScript({Test-Path -Path $_ -IsValid})] + [string] + $DestinationDirectory = (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/samples/config-files"), + [Parameter(Mandatory = $false)] + [switch] + $Force + ) + + $SourceDirectory = Join-Path -Path $PSScriptRoot -ChildPath "..\..\Sample-Config-Files\" + Copy-ScubaModuleFile -SourceDirectory $SourceDirectory -DestinationDirectory $DestinationDirectory -Force:$Force +} + +function Copy-ScubaModuleFile { + <# + .SYNOPSIS + Copy Scuba module files (read-only) to user defined location. + .Description + This function makes copies of files included with the installed ScubaGear module. + .Parameter Destination + Where to copy the files. + .Example + Copy-ScubaModuleFile =Destination SomeWhere + .Functionality + Private + .NOTES + SuppressMessage for PSReviewUnusedParameter due to linter bug. Open issue to remove if/when fixed. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] + param ( + [Parameter(Mandatory=$true)] + [ValidateScript({Test-Path -Path $_ -PathType Container})] + [string] + $SourceDirectory, + [Parameter(Mandatory = $true)] + [ValidateScript({Test-Path -Path $_ -IsValid})] + [string] + $DestinationDirectory, + [Parameter(Mandatory = $false)] + [switch] + $Force + ) + + if (-not (Test-Path -Path $DestinationDirectory -PathType Container)){ + New-Item -ItemType Directory -Path $DestinationDirectory | Out-Null + } + + try { + Get-ChildItem -Path $SourceDirectory | Copy-Item -Destination $DestinationDirectory -Recurse -Container -Force:$Force -ErrorAction Stop 2> $null + Get-ChildItem -Path $DestinationDirectory -File -Recurse | ForEach-Object {$_.IsReadOnly = $true} + } + catch { + throw "Scuba copy module files failed." + } +} + Export-ModuleMember -Function @( - 'Copy-ScubaBaselineDocument' + 'Copy-ScubaBaselineDocument', + 'Copy-ScubaSampleReport', + 'Copy-ScubaSampleConfigFile' ) diff --git a/sample-config-files/aad-config.yaml b/PowerShell/ScubaGear/Sample-Config-Files/aad-config.yaml similarity index 100% rename from sample-config-files/aad-config.yaml rename to PowerShell/ScubaGear/Sample-Config-Files/aad-config.yaml diff --git a/sample-config-files/basic_config.yaml b/PowerShell/ScubaGear/Sample-Config-Files/basic_config.yaml similarity index 100% rename from sample-config-files/basic_config.yaml rename to PowerShell/ScubaGear/Sample-Config-Files/basic_config.yaml diff --git a/sample-config-files/creds_config.yaml b/PowerShell/ScubaGear/Sample-Config-Files/creds_config.yaml similarity index 100% rename from sample-config-files/creds_config.yaml rename to PowerShell/ScubaGear/Sample-Config-Files/creds_config.yaml diff --git a/sample-config-files/defender-config.yaml b/PowerShell/ScubaGear/Sample-Config-Files/defender-config.yaml similarity index 100% rename from sample-config-files/defender-config.yaml rename to PowerShell/ScubaGear/Sample-Config-Files/defender-config.yaml diff --git a/sample-config-files/full_config.yaml b/PowerShell/ScubaGear/Sample-Config-Files/full_config.yaml similarity index 100% rename from sample-config-files/full_config.yaml rename to PowerShell/ScubaGear/Sample-Config-Files/full_config.yaml diff --git a/sample-config-files/sample-config.json b/PowerShell/ScubaGear/Sample-Config-Files/sample-config.json similarity index 100% rename from sample-config-files/sample-config.json rename to PowerShell/ScubaGear/Sample-Config-Files/sample-config.json diff --git a/sample-config-files/sample-config.yaml b/PowerShell/ScubaGear/Sample-Config-Files/sample-config.yaml similarity index 100% rename from sample-config-files/sample-config.yaml rename to PowerShell/ScubaGear/Sample-Config-Files/sample-config.yaml diff --git a/sample-config-files/typical_config.yaml b/PowerShell/ScubaGear/Sample-Config-Files/typical_config.yaml similarity index 100% rename from sample-config-files/typical_config.yaml rename to PowerShell/ScubaGear/Sample-Config-Files/typical_config.yaml diff --git a/sample-report/BaselineReports.html b/PowerShell/ScubaGear/Sample-Reports/BaselineReports.html similarity index 100% rename from sample-report/BaselineReports.html rename to PowerShell/ScubaGear/Sample-Reports/BaselineReports.html diff --git a/sample-report/IndividualReports/AADReport.html b/PowerShell/ScubaGear/Sample-Reports/IndividualReports/AADReport.html similarity index 100% rename from sample-report/IndividualReports/AADReport.html rename to PowerShell/ScubaGear/Sample-Reports/IndividualReports/AADReport.html diff --git a/sample-report/IndividualReports/DefenderReport.html b/PowerShell/ScubaGear/Sample-Reports/IndividualReports/DefenderReport.html similarity index 100% rename from sample-report/IndividualReports/DefenderReport.html rename to PowerShell/ScubaGear/Sample-Reports/IndividualReports/DefenderReport.html diff --git a/sample-report/IndividualReports/EXOReport.html b/PowerShell/ScubaGear/Sample-Reports/IndividualReports/EXOReport.html similarity index 100% rename from sample-report/IndividualReports/EXOReport.html rename to PowerShell/ScubaGear/Sample-Reports/IndividualReports/EXOReport.html diff --git a/sample-report/IndividualReports/PowerPlatformReport.html b/PowerShell/ScubaGear/Sample-Reports/IndividualReports/PowerPlatformReport.html similarity index 100% rename from sample-report/IndividualReports/PowerPlatformReport.html rename to PowerShell/ScubaGear/Sample-Reports/IndividualReports/PowerPlatformReport.html diff --git a/sample-report/IndividualReports/SharePointReport.html b/PowerShell/ScubaGear/Sample-Reports/IndividualReports/SharePointReport.html similarity index 100% rename from sample-report/IndividualReports/SharePointReport.html rename to PowerShell/ScubaGear/Sample-Reports/IndividualReports/SharePointReport.html diff --git a/sample-report/IndividualReports/TeamsReport.html b/PowerShell/ScubaGear/Sample-Reports/IndividualReports/TeamsReport.html similarity index 100% rename from sample-report/IndividualReports/TeamsReport.html rename to PowerShell/ScubaGear/Sample-Reports/IndividualReports/TeamsReport.html diff --git a/sample-report/IndividualReports/images/angle-down-solid.svg b/PowerShell/ScubaGear/Sample-Reports/IndividualReports/images/angle-down-solid.svg similarity index 100% rename from sample-report/IndividualReports/images/angle-down-solid.svg rename to PowerShell/ScubaGear/Sample-Reports/IndividualReports/images/angle-down-solid.svg diff --git a/sample-report/IndividualReports/images/angle-right-solid.svg b/PowerShell/ScubaGear/Sample-Reports/IndividualReports/images/angle-right-solid.svg similarity index 100% rename from sample-report/IndividualReports/images/angle-right-solid.svg rename to PowerShell/ScubaGear/Sample-Reports/IndividualReports/images/angle-right-solid.svg diff --git a/sample-report/IndividualReports/images/cisa_logo.png b/PowerShell/ScubaGear/Sample-Reports/IndividualReports/images/cisa_logo.png similarity index 100% rename from sample-report/IndividualReports/images/cisa_logo.png rename to PowerShell/ScubaGear/Sample-Reports/IndividualReports/images/cisa_logo.png diff --git a/sample-report/ProviderSettingsExport.json b/PowerShell/ScubaGear/Sample-Reports/ProviderSettingsExport.json similarity index 100% rename from sample-report/ProviderSettingsExport.json rename to PowerShell/ScubaGear/Sample-Reports/ProviderSettingsExport.json diff --git a/sample-report/TestResults.csv b/PowerShell/ScubaGear/Sample-Reports/TestResults.csv similarity index 100% rename from sample-report/TestResults.csv rename to PowerShell/ScubaGear/Sample-Reports/TestResults.csv diff --git a/sample-report/TestResults.json b/PowerShell/ScubaGear/Sample-Reports/TestResults.json similarity index 100% rename from sample-report/TestResults.json rename to PowerShell/ScubaGear/Sample-Reports/TestResults.json diff --git a/PowerShell/ScubaGear/ScubaGear.psd1 b/PowerShell/ScubaGear/ScubaGear.psd1 index 6a688b2602..89ea9d16df 100644 --- a/PowerShell/ScubaGear/ScubaGear.psd1 +++ b/PowerShell/ScubaGear/ScubaGear.psd1 @@ -79,7 +79,9 @@ FunctionsToExport = @( 'Invoke-SCuBA', 'Invoke-RunCached', 'Disconnect-SCuBATenant', - 'Copy-ScubaBaselineDocument' + 'Copy-ScubaBaselineDocument', + 'Copy-ScubaSampleReport', + 'Copy-ScubaSampleConfigFile' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. diff --git a/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/Copy-ScubaSampleConfigFile.Tests.ps1 b/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/Copy-ScubaSampleConfigFile.Tests.ps1 new file mode 100644 index 0000000000..1130bd8238 --- /dev/null +++ b/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/Copy-ScubaSampleConfigFile.Tests.ps1 @@ -0,0 +1,46 @@ +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "../../../../Modules/Support/Support.psm1") -Function 'Copy-ScubaConfigFile' -Force + +InModuleScope Support { + Describe "Copy sample config files to specified directory" { + $SampleFiles = (Get-ChildItem PowerShell/ScubaGear/Sample-Config-Files).Name + BeforeAll{ + $SampleConfigCopyFolder = Join-Path -Path $env:Temp -ChildPath 'samples/config-files' + if (Test-Path -Path $SampleConfigCopyFolder){ + Remove-Item -Path $SampleConfigCopyFolder -Recurse -Force + } + } + It "Call Copy-ScubaSampleConfigFile with bad destination" { + {Copy-ScubaSampleConfigFile -Destination "$SampleConfigCopyFolder\`tInvalid-"} | + Should -Throw -Because "directory does not exist." + } + It "Call Copy-ScubaSampleConfigFile with good destination"{ + Test-Path -Path $SampleConfigCopyFolder -PathType Container | Should -Not -BeTrue + {Copy-ScubaSampleConfigFile -Destination $SampleConfigCopyFolder} | + Should -Not -Throw + } + It "Top level sample file, <_>, is copied" -ForEach $SampleFiles { + $ItemPath = Join-Path -Path $SampleConfigCopyFolder -ChildPath $_ + Test-Path -Path $ItemPath -PathType Leaf | Should -BeTrue + } + It "Sample document, <_>, is read only" -ForEach $SampleFiles{ + $ItemPath = Join-Path -Path $SampleConfigCopyFolder -ChildPath $_ + Get-Item -Path $ItemPath | Select-Object IsReadyOnly | Should -BeTrue + } + It "Call Copy-ScubaSampleConfigFile already exists - Not Force update"{ + $PreviousCreateTime = [System.DateTime](Get-Item -Path (Join-Path -Path $SampleConfigCopyFolder -ChildPath "aad-config.yaml")).CreationTime + Test-Path -Path $SampleConfigCopyFolder -PathType Container | Should -BeTrue + {Copy-ScubaSampleConfigFile -Destination $SampleConfigCopyFolder} | + Should -Throw -ExpectedMessage "Scuba copy module files failed." + $CurrentCreateTime = [System.DateTime](Get-Item -Path (Join-Path -Path $SampleConfigCopyFolder -ChildPath "aad-config.yaml")).CreationTime + $PreviousCreateTime -eq $CurrentCreateTime | Should -BeTrue + } + It "Call Copy-ScubaSampleConfigFile already exists - Force Update"{ + $PreviousCreateTime = [System.DateTime](Get-Item -Path (Join-Path -Path $SampleConfigCopyFolder -ChildPath "aad-config.yaml")).CreationTime + Test-Path -Path $SampleConfigCopyFolder -PathType Container | Should -BeTrue + {Copy-ScubaSampleConfigFile -Destination $SampleConfigCopyFolder -Force} | + Should -Not -Throw + $CurrentCreateTime = [System.DateTime](Get-Item -Path (Join-Path -Path $SampleConfigCopyFolder -ChildPath "aad-config.yaml")).CreationTime + ($CurrentCreateTime -ge $PreviousCreateTime) | Should -BeTrue -Because "$($CurrentCreateTime) vs $($PreviousCreateTime)" + } + } +} diff --git a/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/Copy-ScubaSampleReport.Tests.ps1 b/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/Copy-ScubaSampleReport.Tests.ps1 new file mode 100644 index 0000000000..85add44ae7 --- /dev/null +++ b/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/Copy-ScubaSampleReport.Tests.ps1 @@ -0,0 +1,63 @@ +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "../../../../Modules/Support/Support.psm1") -Function 'Copy-ScubaSampleReport' -Force + +InModuleScope Support { + Describe "Copy Sample Reports to specified directory" { + $SampleFiles = @( + "BaselineReports.html", + "ProviderSettingsExport.json", + "TestResults.csv", + "TestResults.json" + ) + $SampleFolders = @( + "individualReports" + "individualReports/images" + ) + BeforeAll{ + $SampleReportsCopyFolder = Join-Path -Path $env:Temp -ChildPath 'samples/reports' + if (Test-Path -Path $SampleReportsCopyFolder){ + Remove-Item -Path $SampleReportsCopyFolder -Recurse -Force + } + } + It "Call Copy-ScubaSampleReports with bad destination" { + {Copy-ScubaSampleReport -Destination "$SampleReportsCopyFolder\`tInvalid-"} | + Should -Throw -Because "directory does not exist." + } + It "Call Copy-ScubaSampleReports with good destination"{ + Test-Path -Path $SampleReportsCopyFolder -PathType Container | Should -Not -BeTrue + {Copy-ScubaSampleReport -Destination $SampleReportsCopyFolder} | + Should -Not -Throw + } + It "Top level sample file, <_>, is copied" -ForEach $SampleFiles { + $ItemPath = Join-Path -Path $SampleReportsCopyFolder -ChildPath $_ + Test-Path -Path $ItemPath -PathType Leaf | Should -BeTrue + } + It "Sample sub directory, <_>, is copied" -ForEach $SampleFolders { + $ItemPath = Join-Path -Path $SampleReportsCopyFolder -ChildPath $_ + Test-Path -Path $ItemPath -PathType Container | Should -BeTrue + } + It "Sample document, <_>, is read only" -ForEach $SampleFiles{ + $ItemPath = Join-Path -Path $SampleReportsCopyFolder -ChildPath "$_" + Get-Item -Path $ItemPath | Select-Object IsReadyOnly | Should -BeTrue + } + It "Sample folder, <_>, is read only" -ForEach $SampleFolders{ + $ItemPath = Join-Path -Path $SampleReportsCopyFolder -ChildPath "$_" + Get-Item -Path $ItemPath | Select-Object IsReadyOnly | Should -BeTrue + } + It "Call Copy-ScubaSampleReports already exists - Not Force update"{ + $PreviousCreateTime = [System.DateTime](Get-Item -Path (Join-Path -Path $SampleReportsCopyFolder -ChildPath "BaselineReports.html")).CreationTime + Test-Path -Path $SampleReportsCopyFolder -PathType Container | Should -BeTrue + {Copy-ScubaSampleReport -Destination $SampleReportsCopyFolder} | + Should -Throw -ExpectedMessage "Scuba copy module files failed." + $CurrentCreateTime = [System.DateTime](Get-Item -Path (Join-Path -Path $SampleReportsCopyFolder -ChildPath "BaselineReports.html")).CreationTime + $PreviousCreateTime -eq $CurrentCreateTime | Should -BeTrue + } + It "Call Copy-ScubaSampleReports already exists - Force Update"{ + $PreviousCreateTime = [System.DateTime](Get-Item -Path (Join-Path -Path $SampleReportsCopyFolder -ChildPath "BaselineReports.html")).CreationTime + Test-Path -Path $SampleReportsCopyFolder -PathType Container | Should -BeTrue + {Copy-ScubaSampleReport -Destination $SampleReportsCopyFolder -Force} | + Should -Not -Throw + $CurrentCreateTime = [System.DateTime](Get-Item -Path (Join-Path -Path $SampleReportsCopyFolder -ChildPath "BaselineReports.html")).CreationTime + ($CurrentCreateTime -ge $PreviousCreateTime) | Should -BeTrue -Because "$($CurrentCreateTime) vs $($PreviousCreateTime)" + } + } +} diff --git a/Testing/Functional/SmokeTest/SmokeTest001.Tests.ps1 b/Testing/Functional/SmokeTest/SmokeTest001.Tests.ps1 index 9f814a6de1..1559a8da46 100644 --- a/Testing/Functional/SmokeTest/SmokeTest001.Tests.ps1 +++ b/Testing/Functional/SmokeTest/SmokeTest001.Tests.ps1 @@ -79,5 +79,53 @@ Describe "Smoke Test: Generate Output" { ){ Test-Path -Path "./$OutputFolder/$Item" -PathType $ItemType | Should -Be $true - } } + } + } + Context "Verify exported functions for ScubaGear module" { + BeforeAll{ + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'ScubaGearExportedFunctions', + Justification = 'Variable is used in another scope')] + $ScubaGearExportedFunctions = @( + 'Disconnect-SCuBATenant', + 'Invoke-RunCached', + 'Invoke-SCuBA', + 'Copy-ScubaBaselineDocument', + 'Copy-ScubaSampleConfigFile', + 'Copy-ScubaSampleReport' + ) + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'ExportedCommands', + Justification = 'Variable is used in another scope')] + $ExportedCommands = (Get-Module -Name ScubaGear).ExportedCommands + } + It "Is <_> exported?" -ForEach $ScubaGearExportedFunctions { + $ExportedCommands | Should -Contain $_ + } + } + Context "Verify Copy* exported commands" -ForEach @( + @{Command='Copy-ScubaBaselineDocument'; CopiedFiles=@( + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/aad.md"), + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/defender.md"), + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/exo.md"), + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/powerbi.md"), + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/powerplatform.md"), + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/sharepoint.md"), + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/teams.md") + )}, + @{Command='Copy-ScubaSampleConfigFile'; CopiedFiles=@( + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/samples/config-files/aad-config.yaml"), + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/samples/config-files/defender-config.yaml"), + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/samples/config-files/sample-config.json"), + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/samples/config-files/sample-config.yaml") + )}, + @{Command='Copy-ScubaSampleReport'; CopiedFiles=@( + (Join-Path -Path $env:USERPROFILE -ChildPath "ScubaGear/samples/reports/BaselineReports.html") + )} + ){ + It "Validate call to " { + {& $Command -Force} | Should -Not -Throw + } + It "Validate copied file <_>" -ForEach $CopiedFiles { + Test-Path -Path $_ -PathType Leaf | Should -BeTrue + } + } } \ No newline at end of file diff --git a/sample-config-files/README.md b/sample-config-files/README.md new file mode 100644 index 0000000000..14b31b264c --- /dev/null +++ b/sample-config-files/README.md @@ -0,0 +1 @@ +The sample configuration files have been moved into the ScubaGear PowerShell module for easier distribution. The configuration files can now be found [here](../PowerShell/ScubaGear/Sample-Config-Files) \ No newline at end of file diff --git a/sample-report/README.md b/sample-report/README.md new file mode 100644 index 0000000000..3a0ccaed64 --- /dev/null +++ b/sample-report/README.md @@ -0,0 +1 @@ +The sample reports have been moved into the ScubaGear PowerShell module for easier distribution. The reports can now be found [here](../PowerShell/ScubaGear/Sample-Reports) \ No newline at end of file