Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing automations #148

Merged
merged 20 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions source/Classes/CISAuthenticationParameters.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class CISAuthenticationParameters {
[string]$ClientCertThumbPrint
[string]$ClientId
[string]$TenantId
[string]$OnMicrosoftUrl
[string]$SpAdminUrl

# Constructor with validation
CISAuthenticationParameters(
[string]$ClientCertThumbPrint,
[string]$ClientId,
[string]$TenantId,
[string]$OnMicrosoftUrl,
[string]$SpAdminUrl
) {
# Validate ClientCertThumbPrint
if (-not $ClientCertThumbPrint -or $ClientCertThumbPrint.Length -ne 40 -or $ClientCertThumbPrint -notmatch '^[0-9a-fA-F]{40}$') {
throw [ArgumentException]::new("ClientCertThumbPrint must be a 40-character hexadecimal string.")
}
# Validate ClientId
if (-not $ClientId -or $ClientId -notmatch '^[0-9a-fA-F\-]{36}$') {
throw [ArgumentException]::new("ClientId must be a valid GUID in the format 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'.")
}
# Validate TenantId
if (-not $TenantId -or $TenantId -notmatch '^[0-9a-fA-F\-]{36}$') {
throw [ArgumentException]::new("TenantId must be a valid GUID in the format 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'.")
}
# Validate OnMicrosoftUrl
if (-not $OnMicrosoftUrl -or $OnMicrosoftUrl -notmatch '^[a-zA-Z0-9]+\.onmicrosoft\.com$') {
throw [ArgumentException]::new("OnMicrosoftUrl must be in the format 'example.onmicrosoft.com'.")
}
# Validate SpAdminUrl
if (-not $SpAdminUrl -or $SpAdminUrl -notmatch '^https:\/\/[a-zA-Z0-9\-]+\-admin\.sharepoint\.com$') {
throw [ArgumentException]::new("SpAdminUrl must be in the format 'https://[name]-admin.sharepoint.com'.")
}
# Assign validated properties
$this.ClientCertThumbPrint = $ClientCertThumbPrint
$this.ClientId = $ClientId
$this.TenantId = $TenantId
$this.OnMicrosoftUrl = $OnMicrosoftUrl
$this.SpAdminUrl = $SpAdminUrl
}
}
4 changes: 2 additions & 2 deletions source/M365FoundationsCISReport.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Copyright = '(c) 2024 Douglas S. Rios (DrIOSx). All rights reserved.'
Description = 'Automated assessment of 50 CIS 365 Foundations v3.0.0 benchmark.'

# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.0'
# PowerShellVersion = '5.1'

# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''
Expand All @@ -51,7 +51,7 @@ PowerShellVersion = '5.0'
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @()
# RequiredModules = @()

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
Expand Down
53 changes: 28 additions & 25 deletions source/Private/Assert-ModuleAvailability.ps1
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
function Assert-ModuleAvailability {
[CmdletBinding()]
[OutputType([void]) ]
param(
[string]$ModuleName,
[string]$RequiredVersion,
[string[]]$SubModules = @()
)
process {
try {
$module = Get-Module -ListAvailable -Name $ModuleName | Where-Object { $_.Version -ge [version]$RequiredVersion }
if ($null -eq $module) {
Write-Verbose "Installing $ModuleName module..."
Install-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force -AllowClobber -Scope CurrentUser | Out-Null
}
elseif ($module.Version -lt [version]$RequiredVersion) {
Write-Verbose "Updating $ModuleName module to required version..."
Update-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force | Out-Null
}
else {
Write-Verbose "$ModuleName module is already at required version or newer."
}
if ($SubModules.Count -gt 0) {
foreach ($subModule in $SubModules) {
Write-Verbose "Importing submodule $ModuleName.$subModule..."
Get-Module "$ModuleName.$subModule" | Import-Module -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null
}
}
else {
Write-Verbose "Importing module $ModuleName..."
Import-Module -Name $ModuleName -RequiredVersion $RequiredVersion -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null
}

try {
$module = Get-Module -ListAvailable -Name $ModuleName | Where-Object { $_.Version -ge [version]$RequiredVersion }

if ($null -eq $module) {
Write-Host "Installing $ModuleName module..." -ForegroundColor Yellow
Install-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force -AllowClobber -Scope CurrentUser | Out-Null
}
elseif ($module.Version -lt [version]$RequiredVersion) {
Write-Host "Updating $ModuleName module to required version..." -ForegroundColor Yellow
Update-Module -Name $ModuleName -RequiredVersion $RequiredVersion -Force | Out-Null
}
else {
Write-Host "$ModuleName module is already at required version or newer." -ForegroundColor Gray
}

if ($SubModules.Count -gt 0) {
foreach ($subModule in $SubModules) {
Write-Host "Importing submodule $ModuleName.$subModule..." -ForegroundColor DarkGray
Import-Module -Name "$ModuleName.$subModule" -RequiredVersion $RequiredVersion -ErrorAction Stop | Out-Null
}
} else {
Write-Host "Importing module $ModuleName..." -ForegroundColor DarkGray
Import-Module -Name $ModuleName -RequiredVersion $RequiredVersion -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null
catch {
throw "Assert-ModuleAvailability:`n$_"
}
}
catch {
Write-Warning "An error occurred with module $ModuleName`: $_"
}

}
158 changes: 86 additions & 72 deletions source/Private/Connect-M365Suite.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,121 +2,135 @@ function Connect-M365Suite {
[OutputType([void])]
[CmdletBinding()]
param (
[Parameter(Mandatory = $false)]
[Parameter(
Mandatory = $false
)]
[string]$TenantAdminUrl,

[Parameter(Mandatory)]
[Parameter(
Mandatory = $false
)]
[CISAuthenticationParameters]$AuthParams, # Custom authentication parameters
[Parameter(
Mandatory
)]
[string[]]$RequiredConnections,

[Parameter(Mandatory = $false)]
[Parameter(
Mandatory = $false
)]
[switch]$SkipConfirmation
)

$VerbosePreference = "SilentlyContinue"
if (!$SkipConfirmation) {
$VerbosePreference = "Continue"
}
else {
$VerbosePreference = "SilentlyContinue"
}
$tenantInfo = @()
$connectedServices = @()

try {
if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "AzureAD | EXO | Microsoft Graph") {
Write-Host "Connecting to Azure Active Directory..." -ForegroundColor Yellow
Connect-AzureAD -WarningAction SilentlyContinue | Out-Null
$tenantDetails = Get-AzureADTenantDetail -WarningAction SilentlyContinue
$tenantInfo += [PSCustomObject]@{
Service = "Azure Active Directory"
TenantName = $tenantDetails.DisplayName
TenantID = $tenantDetails.ObjectId
}
$connectedServices += "AzureAD"
Write-Host "Successfully connected to Azure Active Directory." -ForegroundColor Green
}

if ($RequiredConnections -contains "Microsoft Graph" -or $RequiredConnections -contains "EXO | Microsoft Graph") {
Write-Host "Connecting to Microsoft Graph with scopes: Directory.Read.All, Domain.Read.All, Policy.Read.All, Organization.Read.All" -ForegroundColor Yellow
try {
Write-Verbose "Connecting to Microsoft Graph"
if ($AuthParams) {
# Use application-based authentication
Connect-MgGraph -CertificateThumbprint $AuthParams.ClientCertThumbPrint -AppId $AuthParams.ClientId -TenantId $AuthParams.TenantId -NoWelcome | Out-Null
}
else {
# Use interactive authentication with scopes
Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -NoWelcome | Out-Null
$graphOrgDetails = Get-MgOrganization
$tenantInfo += [PSCustomObject]@{
Service = "Microsoft Graph"
TenantName = $graphOrgDetails.DisplayName
TenantID = $graphOrgDetails.Id
}
$connectedServices += "Microsoft Graph"
Write-Host "Successfully connected to Microsoft Graph with specified scopes." -ForegroundColor Green
}
catch {
Write-Host "Failed to connect to MgGraph, attempting device auth." -ForegroundColor Yellow
Connect-MgGraph -Scopes "Directory.Read.All", "Domain.Read.All", "Policy.Read.All", "Organization.Read.All" -UseDeviceCode -NoWelcome | Out-Null
$graphOrgDetails = Get-MgOrganization
$tenantInfo += [PSCustomObject]@{
Service = "Microsoft Graph"
TenantName = $graphOrgDetails.DisplayName
TenantID = $graphOrgDetails.Id
}
$connectedServices += "Microsoft Graph"
Write-Host "Successfully connected to Microsoft Graph with specified scopes." -ForegroundColor Green
}
$graphOrgDetails = Get-MgOrganization
$tenantInfo += [PSCustomObject]@{
Service = "Microsoft Graph"
TenantName = $graphOrgDetails.DisplayName
TenantID = $graphOrgDetails.Id
}
$connectedServices += "Microsoft Graph"
Write-Verbose "Successfully connected to Microsoft Graph.`n"
}

if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO" -or $RequiredConnections -contains "EXO | Microsoft Graph") {
Write-Host "Connecting to Exchange Online..." -ForegroundColor Yellow
Connect-ExchangeOnline -ShowBanner:$false | Out-Null
Write-Verbose "Connecting to Exchange Online..."
if ($AuthParams) {
# Use application-based authentication
Connect-ExchangeOnline -AppId $AuthParams.ClientId -CertificateThumbprint $AuthParams.ClientCertThumbPrint -Organization $AuthParams.OnMicrosoftUrl -ShowBanner:$false | Out-Null
}
else {
# Use interactive authentication
Connect-ExchangeOnline -ShowBanner:$false | Out-Null
}
$exoTenant = (Get-OrganizationConfig).Identity
$tenantInfo += [PSCustomObject]@{
Service = "Exchange Online"
Service = "Exchange Online"
TenantName = $exoTenant
TenantID = "N/A"
TenantID = "N/A"
}
$connectedServices += "EXO"
Write-Host "Successfully connected to Exchange Online." -ForegroundColor Green
Write-Verbose "Successfully connected to Exchange Online.`n"
}

if ($RequiredConnections -contains "SPO") {
Write-Host "Connecting to SharePoint Online..." -ForegroundColor Yellow
Connect-SPOService -Url $TenantAdminUrl | Out-Null
$spoContext = Get-SPOCrossTenantHostUrl
$tenantName = Get-UrlLine -Output $spoContext
Write-Verbose "Connecting to SharePoint Online..."
if ($AuthParams) {
# Use application-based authentication
Connect-PnPOnline -Url $AuthParams.SpAdminUrl -ClientId $AuthParams.ClientId -Tenant $AuthParams.OnMicrosoftUrl -Thumbprint $AuthParams.ClientCertThumbPrint | Out-Null
}
else {
# Use interactive authentication
Connect-SPOService -Url $TenantAdminUrl | Out-Null
}
# Assuming that Get-SPOCrossTenantHostUrl and Get-UrlLine are valid commands in your context
if ($AuthParams) {
$spoContext = Get-PnPSite
$tenantName = $spoContext.Url
}
else {
$spoContext = Get-SPOCrossTenantHostUrl
$tenantName = Get-UrlLine -Output $spoContext
}
$tenantInfo += [PSCustomObject]@{
Service = "SharePoint Online"
Service = "SharePoint Online"
TenantName = $tenantName
}
$connectedServices += "SPO"
Write-Host "Successfully connected to SharePoint Online." -ForegroundColor Green
Write-Verbose "Successfully connected to SharePoint Online.`n"
}

if ($RequiredConnections -contains "Microsoft Teams" -or $RequiredConnections -contains "Microsoft Teams | EXO") {
Write-Host "Connecting to Microsoft Teams..." -ForegroundColor Yellow
Connect-MicrosoftTeams | Out-Null
Write-Verbose "Connecting to Microsoft Teams..."
if ($AuthParams) {
# Use application-based authentication
Connect-MicrosoftTeams -TenantId $AuthParams.TenantId -CertificateThumbprint $AuthParams.ClientCertThumbPrint -ApplicationId $AuthParams.ClientId | Out-Null
}
else {
# Use interactive authentication
Connect-MicrosoftTeams | Out-Null
}
$teamsTenantDetails = Get-CsTenant
$tenantInfo += [PSCustomObject]@{
Service = "Microsoft Teams"
Service = "Microsoft Teams"
TenantName = $teamsTenantDetails.DisplayName
TenantID = $teamsTenantDetails.TenantId
TenantID = $teamsTenantDetails.TenantId
}
$connectedServices += "Microsoft Teams"
Write-Host "Successfully connected to Microsoft Teams." -ForegroundColor Green
Write-Verbose "Successfully connected to Microsoft Teams.`n"
}

# Display tenant information and confirm with the user
if (-not $SkipConfirmation) {
Write-Host "Connected to the following tenants:" -ForegroundColor Yellow
Write-Verbose "Connected to the following tenants:"
foreach ($tenant in $tenantInfo) {
Write-Host "Service: $($tenant.Service)" -ForegroundColor Cyan
Write-Host "Tenant Context: $($tenant.TenantName)`n" -ForegroundColor Green
#Write-Host "Tenant ID: $($tenant.TenantID)"
Write-Verbose "Service: $($tenant.Service)"
Write-Verbose "Tenant Context: $($tenant.TenantName)`n"
#Write-Verbose "Tenant ID: $($tenant.TenantID)"
}
$confirmation = Read-Host "Do you want to proceed with these connections? (Y/N)"
if ($confirmation -notlike 'Y') {
Write-Host "Connection setup aborted by user." -ForegroundColor Red
if ($confirmation -notLike 'Y') {
Write-Verbose "Connection setup aborted by user."
Disconnect-M365Suite -RequiredConnections $connectedServices
throw "User aborted connection setup."
}
}
}
catch {
$CatchError = $_
$VerbosePreference = "Continue"
Write-Host "There was an error establishing one or more connections: $_" -ForegroundColor Red
throw $_
throw $CatchError
}

$VerbosePreference = "Continue"
}
21 changes: 13 additions & 8 deletions source/Private/Disconnect-M365Suite.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function Disconnect-M365Suite {
# Clean up sessions
try {
if ($RequiredConnections -contains "EXO" -or $RequiredConnections -contains "AzureAD | EXO" -or $RequiredConnections -contains "Microsoft Teams | EXO") {
Write-Host "Disconnecting from Exchange Online..." -ForegroundColor Green
Write-Verbose "Disconnecting from Exchange Online..."
Disconnect-ExchangeOnline -Confirm:$false | Out-Null
}
}
Expand All @@ -18,7 +18,7 @@ function Disconnect-M365Suite {

try {
if ($RequiredConnections -contains "AzureAD" -or $RequiredConnections -contains "AzureAD | EXO") {
Write-Host "Disconnecting from Azure AD..." -ForegroundColor Green
Write-Verbose "Disconnecting from Azure AD..."
Disconnect-AzureAD | Out-Null
}
}
Expand All @@ -28,7 +28,7 @@ function Disconnect-M365Suite {

try {
if ($RequiredConnections -contains "Microsoft Graph") {
Write-Host "Disconnecting from Microsoft Graph..." -ForegroundColor Green
Write-Verbose "Disconnecting from Microsoft Graph..."
Disconnect-MgGraph | Out-Null
}
}
Expand All @@ -38,8 +38,14 @@ function Disconnect-M365Suite {

try {
if ($RequiredConnections -contains "SPO") {
Write-Host "Disconnecting from SharePoint Online..." -ForegroundColor Green
Disconnect-SPOService | Out-Null
if (($script:PnpAuth)) {
Write-Verbose "Disconnecting from PnPOnline..."
Disconnect-PnPOnline | Out-Null
}
else {
Write-Verbose "Disconnecting from SharePoint Online..."
Disconnect-SPOService | Out-Null
}
}
}
catch {
Expand All @@ -48,13 +54,12 @@ function Disconnect-M365Suite {

try {
if ($RequiredConnections -contains "Microsoft Teams" -or $RequiredConnections -contains "Microsoft Teams | EXO") {
Write-Host "Disconnecting from Microsoft Teams..." -ForegroundColor Green
Write-Verbose "Disconnecting from Microsoft Teams..."
Disconnect-MicrosoftTeams | Out-Null
}
}
catch {
Write-Warning "Failed to disconnect from Microsoft Teams: $_"
}

Write-Host "All necessary sessions have been disconnected." -ForegroundColor Green
Write-Verbose "All necessary sessions have been disconnected."
}
Loading
Loading