diff --git a/PowerShell/ScubaGear/Modules/Support/Support.psm1 b/PowerShell/ScubaGear/Modules/Support/Support.psm1 index 7d0b86191..fbbff0a5c 100644 --- a/PowerShell/ScubaGear/Modules/Support/Support.psm1 +++ b/PowerShell/ScubaGear/Modules/Support/Support.psm1 @@ -648,11 +648,264 @@ function Copy-ScubaModuleFile { } } +function New-Config { + <# + .SYNOPSIS + Generate a config file for the ScubaGear tool + .Description + Using provided user input generate a config file to run ScubaGear tailored to the end user + .Parameter ProductNames + A list of one or more M365 shortened product names that the tool will assess when it is executed. Acceptable product name values are listed below. + To assess Azure Active Directory you would enter the value aad. + To assess Exchange Online you would enter exo and so forth. + - Azure Active Directory: aad + - Defender for Office 365: defender + - Exchange Online: exo + - MS Power Platform: powerplatform + - SharePoint Online: sharepoint + - MS Teams: teams. + Use '*' to run all baselines. + .Parameter M365Environment + This parameter is used to authenticate to the different commercial/government environments. + Valid values include "commercial", "gcc", "gcchigh", or "dod". + - For M365 tenants with E3/E5 licenses enter the value **"commercial"**. + - For M365 Government Commercial Cloud tenants with G3/G5 licenses enter the value **"gcc"**. + - For M365 Government Commercial Cloud High tenants enter the value **"gcchigh"**. + - For M365 Department of Defense tenants enter the value **"dod"**. + Default value is 'commercial'. + .Parameter OPAPath + The folder location of the OPA Rego executable file. + The OPA Rego executable embedded with this project is located in the project's root folder. + If you want to execute the tool using a version of OPA Rego located in another folder, + then customize the variable value with the full path to the alternative OPA Rego exe file. + .Parameter LogIn + A `$true` or `$false` variable that if set to `$true` + will prompt you to provide credentials if you want to establish a connection + to the specified M365 products in the **$ProductNames** variable. + For most use cases, leave this variable to be `$true`. + A connection is established in the current PowerShell terminal session with the first authentication. + If you want to run another verification in the same PowerShell session simply set + this variable to be `$false` to bypass the reauthenticating in the same session. Default is $true. + Note: defender will ask for authentication even if this variable is set to `$false` + ;;;.Parameter Version + ;;;Will output the current ScubaGear version to the terminal without running this cmdlet. + .Parameter AppID + The application ID of the service principal that's used during certificate based + authentication. A valid value is the GUID of the application ID (service principal). + .Parameter CertificateThumbprint + The thumbprint value specifies the certificate that's used for certificate base authentication. + The underlying PowerShell modules retrieve the certificate from the user's certificate store. + As such, a copy of the certificate must be located there. + .Parameter Organization + Specify the organization that's used in certificate based authentication. + Use the tenant's tenantname.onmicrosoft.com domain for the parameter value. + .Parameter OutPath + The folder path where both the output JSON and the HTML report will be created. + The folder will be created if it does not exist. Defaults to current directory. + .Parameter OutFolderName + The name of the folder in OutPath where both the output JSON and the HTML report will be created. + Defaults to "M365BaselineConformance". The client's local timestamp will be appended. + .Parameter OutProviderFileName + The name of the Provider output JSON created in the folder created in OutPath. + Defaults to "ProviderSettingsExport". + .Parameter OutRegoFileName + The name of the Rego output JSON and CSV created in the folder created in OutPath. + Defaults to "TestResults". + .Parameter OutReportName + The name of the main html file page created in the folder created in OutPath. + Defaults to "BaselineReports". + .Parameter DisconnectOnExit + Set switch to disconnect all active connections on exit from ScubaGear (default: $false) + .Parameter ConfigFilePath + Local file path to a JSON or YAML formatted configuration file. + Configuration file parameters can be used in place of command-line + parameters. Additional parameters and variables not available on the + command line can also be included in the file that will be provided to the + tool for use in specific tests. + .Functionality + Public + #> + [CmdletBinding(DefaultParameterSetName='Report')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] + param ( + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $Description = "YAML configuration file with default description", #(Join-Path -Path $env:USERPROFILE -ChildPath ".scubagear\Tools"), + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet("teams", "exo", "defender", "aad", "powerplatform", "sharepoint", '*', IgnoreCase = $false)] + [string[]] + $ProductNames = @("aad", "defender", "exo", "sharepoint", "teams"), + + [Parameter(Mandatory = $false)] + [ValidateSet("commercial", "gcc", "gcchigh", "dod", IgnoreCase = $false)] + [ValidateNotNullOrEmpty()] + [string] + $M365Environment = "commercial", + + [Parameter(Mandatory = $false)] + [ValidateScript({Test-Path -PathType Container $_})] + [string] + $OPAPath = ".", #(Join-Path -Path $env:USERPROFILE -ChildPath ".scubagear\Tools"), + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet($true, $false)] + [boolean] + $LogIn = $true, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [ValidateSet($true, $false)] + [boolean] + $DisconnectOnExit = $false, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $OutPath = '.', + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $AppID, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $CertificateThumbprint, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $Organization, + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $OutFolderName = "M365BaselineConformance", + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $OutProviderFileName = "ProviderSettingsExport", + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $OutRegoFileName = "TestResults", + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $OutReportName = "BaselineReports", + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string] + $ConfigLocation = "./" + ) + + $Config = New-Object ([System.Collections.specialized.OrderedDictionary]) + + ($MyInvocation.MyCommand.Parameters ).Keys | ForEach-Object{ + $Val = (Get-Variable -Name $_ -EA SilentlyContinue).Value + if( $Val.length -gt 0 ) { + #$config[$_] = $val + $Config.add($_, $Val) + } + } + + $CapExclusionNamespace = @( + "MS.AAD.1.1v1", + "MS.AAD.2.1v1", + "MS.AAD.2.3v1", + "MS.AAD.3.1v1", + "MS.AAD.3.2v1", + "MS.AAD.3.3v1", + "MS.AAD.3.6v1", + "MS.AAD.3.7v1", + "MS.AAD.3.8v1" + ) + $RoleExclusionNamespace = "MS.AAD.7.4v1" + + $CommonSensitiveAccountFilterNamespace = @( + "MS.DEFENDER.1.4v1", + "MS.DEFENDER.1.5v1" + ) + + $UserImpersonationProtectionNamespace = "MS.DEFENDER.2.1v1" + + $AgencyDomainImpersonationProtectionNamespace = "MS.DEFENDER.2.2v1" + + $PartnerDomainImpersonationProtectionNamespace = "MS.DEFENDER.2.3v1" + + + $AadTemplate = New-Object ([System.Collections.specialized.OrderedDictionary]) + $AadCapExclusions = New-Object ([System.Collections.specialized.OrderedDictionary]) + $AadRoleExclusions = New-Object ([System.Collections.specialized.OrderedDictionary]) + + $DefenderTemplate = New-Object ([System.Collections.specialized.OrderedDictionary]) + $DefenderCommonSensitiveAccountFilter = New-Object ([System.Collections.specialized.OrderedDictionary]) + #$defenderUserImpersonationProtection = New-Object ([System.Collections.specialized.OrderedDictionary]) + #$defenderAgencyDomainImpersonationProtection = New-Object ([System.Collections.specialized.OrderedDictionary]) + #$defenderPartnerDomainImpersonationProtection = New-Object ([System.Collections.specialized.OrderedDictionary]) + + + + $AadCapExclusions = @{ CapExclusions = @{} } + $AadCapExclusions["CapExclusions"].add("Users", @("")) + $AadCapExclusions["CapExclusions"].add("Groups", @("")) + + $AadRoleExclusions = @{ RoleExclusions = @{} } + $AadRoleExclusions["RoleExclusions"].add("Users", @("")) + $AadRoleExclusions["RoleExclusions"].add("Groups", @("")) + + foreach ($Cap in $CapExclusionNamespace){ + $AadTemplate.add($Cap, $AadCapExclusions) + } + + $AadTemplate.add($RoleExclusionNamespace, $AadRoleExclusions) + + $DefenderCommonSensitiveAccountFilter = @{ SensitiveAccounts = @{} } + $DefenderCommonSensitiveAccountFilter['SensitiveAccounts'].add("IncludedUsers", @("")) + $DefenderCommonSensitiveAccountFilter['SensitiveAccounts'].add("IncludedGroups", @("")) + $DefenderCommonSensitiveAccountFilter['SensitiveAccounts'].add("IncludedDomains", @("")) + $DefenderCommonSensitiveAccountFilter['SensitiveAccounts'].add("ExcludedUsers", @("")) + $DefenderCommonSensitiveAccountFilter['SensitiveAccounts'].add("ExcludedGroups", @("")) + $DefenderCommonSensitiveAccountFilter['SensitiveAccounts'].add("ExcludedDomains", @("")) + + foreach ($Filter in $CommonSensitiveAccountFilterNamespace){ + $DefenderTemplate.add($Filter, $DefenderCommonSensitiveAccountFilter) + } + + $DefenderTemplate.add($UserImpersonationProtectionNamespace, @{ SensitiveUsers = @("") }) + $DefenderTemplate.add($AgencyDomainImpersonationProtectionNamespace, @{ AgencyDomains = @("") }) + $DefenderTemplate.add($PartnerDomainImpersonationProtectionNamespace, @{ PartnerDomains = @("") }) + + $Products = (Get-Variable -Name ProductNames -EA SilentlyContinue).Value + foreach ($Product in $Products){ + switch ($Product){ + "aad" { + $config.add("Aad", $AadTemplate) + } + "defender" { + $config.add("Defender", $DefenderTemplate) + } + } + } + convertto-yaml $Config | set-content "$($ConfigLocation)/SampleConfig.yaml" +} + Export-ModuleMember -Function @( 'Copy-ScubaBaselineDocument', 'Install-OPA', 'Initialize-SCuBA', 'Debug-SCuBA', 'Copy-ScubaSampleReport', - 'Copy-ScubaSampleConfigFile' + 'Copy-ScubaSampleConfigFile', + 'New-Config' ) diff --git a/PowerShell/ScubaGear/ScubaGear.psd1 b/PowerShell/ScubaGear/ScubaGear.psd1 index 91ad2a2e2..97a4234a1 100644 --- a/PowerShell/ScubaGear/ScubaGear.psd1 +++ b/PowerShell/ScubaGear/ScubaGear.psd1 @@ -84,7 +84,8 @@ FunctionsToExport = @( 'Initialize-SCuBA', 'Debug-SCuBA', 'Copy-ScubaSampleReport', - 'Copy-ScubaSampleConfigFile' + 'Copy-ScubaSampleConfigFile', + 'New-Config' ) # 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/CreateReport/Import-SecureBaseline.Tests.ps1 b/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Import-SecureBaseline.Tests.ps1 index 8bde8a11a..162fe14ca 100644 --- a/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Import-SecureBaseline.Tests.ps1 +++ b/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/Import-SecureBaseline.Tests.ps1 @@ -58,4 +58,4 @@ InModuleScope CreateReport { $Output.aad.Controls[1].MalformedDescription | Should -BeFalse -Because "Only warning for MS.AAD.2.1v1 policy description with to many lines." } } -} \ No newline at end of file +} diff --git a/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/New-Report.Tests.ps1 b/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/New-Report.Tests.ps1 index b13d19701..f28a8c510 100644 --- a/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/New-Report.Tests.ps1 +++ b/PowerShell/ScubaGear/Testing/Unit/PowerShell/CreateReport/New-Report.Tests.ps1 @@ -64,4 +64,4 @@ InModuleScope CreateReport { AfterAll { Remove-Module CreateReport -ErrorAction SilentlyContinue } -} \ No newline at end of file +} diff --git a/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/New-Config.Tests.ps1 b/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/New-Config.Tests.ps1 new file mode 100644 index 000000000..5e8ce0e7b --- /dev/null +++ b/PowerShell/ScubaGear/Testing/Unit/PowerShell/Support/New-Config.Tests.ps1 @@ -0,0 +1,55 @@ +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath '../../../../Modules/Support') + +InModuleScope Support { + Describe -Tag Support -Name 'New-Config' { + BeforeAll { + + [Flags()] + enum SerializationOptions { + None = 0 + Roundtrip = 1 + DisableAliases = 2 + EmitDefaults = 4 + JsonCompatible = 8 + DefaultToStaticType = 16 + WithIndentedSequences = 32 + } + + Mock -CommandName Write-Warning {} + + function ConvertTo-Yaml {throw 'this will be mocked'} + Mock -ModuleName Support -CommandName ConvertTo-Yaml { $args[0] } + + $TestPath = New-Item -Path (Join-Path -Path "TestDrive:" -ChildPath "SampleConfig") -Name "CreateSampleConfigFolder" -ItemType Directory + + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'CMDArgs')] + $CMDArgs = @{ + Description = "YAML configuration file with default description"; + ProductNames = @("aad", "defender", "exo", "sharepoint", "teams"); + M365Environment = "commercial"; + OPAPath = "."; + LogIn = $true; + DisconnectOnExit = $false; + OutPath = '.'; + AppID = '0'; + CertificateThumbprint = '0'; + Organization = '0'; + OutFolderName = "M365BaselineConformance"; + OutProviderFileName = "ProviderSettingsExport"; + OutRegoFileName = "TestResults"; + OutReportName = "BaselineReports"; + ConfigLocation = $TestPath; + } + } + It 'Creates a sample configuration' { + + { New-Config @CMDArgs } | Should -Not -Throw + + Test-Path -Path "$($TestPath)/SampleConfig.yaml" -PathType leaf | Should -Be $true + } + } + + AfterAll { + Remove-Module Support -ErrorAction SilentlyContinue + } +} diff --git a/README.md b/README.md index 39d000d35..3a01343c8 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ Invoke-SCuBA -M365Environment gcc -ConfigFilePath minimal_config.yaml ``` **Typical Use** : config file `typical_config.yaml` -Typical use includes multiple products, specified as a list, and an M365 environment variable. Note that additional product values are commented out and will not be included, but are retained in the config file to easily add them back later. +Typical use includes multiple products, specified as a list, and an M365 environment variable. Note that additional product values are commented out and will not be included, but are retained in the config file to easily add them back later. ScubaGear's Support module also has functionality to generate an empty sample config file. Runnning the `New-Config` Cmdlet will generate a full sample config called `SampleConfig.yaml` that can be filled out based on the guidance below. Also parameters can be passed to the `New-Config` Cmdlet to change values inside the sample config. ``` Description: YAML Typical Config ( multiple products ) ProductNames: