Private/Audit/Get-FortificationData.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Get-FortificationData { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$AccessToken, [Parameter(Mandatory)] [string]$ServiceAccountKeyPath, [Parameter(Mandatory)] [string]$AdminEmail, [string[]]$Categories = @('All'), [int]$UserSampleSize = 500, [string]$TargetOU = '/', [switch]$Quiet ) # ── Category-to-data-source mapping ────────────────────────────────── $categoryDataNeeds = @{ Authentication = @('Users', 'OrgUnits') EmailSecurity = @('Domains', 'DnsRecords', 'GmailSettings', 'Users') DriveSecurity = @('Users', 'OrgUnits') OAuthSecurity = @('OAuthApps', 'DomainWideDelegation', 'Users') AdminManagement = @('Users', 'Roles', 'RoleAssignments', 'Groups', 'OrgUnits') Collaboration = @('OrgUnits', 'Groups') DeviceManagement = @('MobileDevices', 'ChromeDevices', 'ChromePolicies') LoggingAlerting = @('AlertRules') } # Resolve which data sources are required $requiredSources = [System.Collections.Generic.HashSet[string]]::new( [StringComparer]::OrdinalIgnoreCase ) if ($Categories -contains 'All') { # Collect every known data source foreach ($sources in $categoryDataNeeds.Values) { foreach ($s in $sources) { [void]$requiredSources.Add($s) } } # Also include tenant-level sources that are always needed [void]$requiredSources.Add('Customer') } else { foreach ($cat in $Categories) { if ($categoryDataNeeds.ContainsKey($cat)) { foreach ($s in $categoryDataNeeds[$cat]) { [void]$requiredSources.Add($s) } } } [void]$requiredSources.Add('Customer') } # ── Initialize result hashtable ────────────────────────────────────── $data = @{ Tenant = @{ CustomerId = '' Domain = '' Domains = @() OrgUnits = @() } Users = @() Groups = @() GroupSettings = @{} Roles = @() RoleAssignments = @() MobileDevices = @() ChromeDevices = @() DnsRecords = @{} GmailSettings = @{} OrgUnitPolicies = @{ '/' = @{} } AlertRules = @() ChromePolicies = @() OAuthApps = @() DomainWideDelegation = @() Errors = @{} } # Helper: determine whether a data source is needed $needsSource = { param([string]$Name) $requiredSources.Contains($Name) } # ── 1. Customer info ───────────────────────────────────────────────── if (& $needsSource 'Customer') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving tenant info' } try { $customer = Invoke-GoogleAdminApi ` -AccessToken $AccessToken ` -Uri 'https://admin.googleapis.com/admin/directory/v1/customers/my_customer' ` -Quiet:$Quiet if ($customer) { $data.Tenant.CustomerId = $customer.id ?? $customer.customerId ?? '' $data.Tenant.Domain = $customer.customerDomain ?? '' } } catch { $data.Errors['Customer'] = $_.Exception.Message } } # ── 2. Domains ─────────────────────────────────────────────────────── if (& $needsSource 'Domains') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving verified domains' } try { $domainsResult = Invoke-GoogleAdminApi ` -AccessToken $AccessToken ` -Uri 'https://admin.googleapis.com/admin/directory/v1/customer/my_customer/domains' ` -Paginate ` -ItemsProperty 'domains' ` -Quiet:$Quiet $data.Tenant.Domains = @($domainsResult ?? @()) } catch { $data.Errors['Domains'] = $_.Exception.Message } } # ── 3. OrgUnits ────────────────────────────────────────────────────── if (& $needsSource 'OrgUnits') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving organizational units' } try { $orgUnits = Invoke-GoogleAdminApi ` -AccessToken $AccessToken ` -Uri 'https://admin.googleapis.com/admin/directory/v1/customer/my_customer/orgunits' ` -QueryParameters @{ type = 'all' } ` -Paginate ` -ItemsProperty 'organizationUnits' ` -Quiet:$Quiet $data.Tenant.OrgUnits = @($orgUnits ?? @()) } catch { $data.Errors['OrgUnits'] = $_.Exception.Message } } # ── 4. Users ───────────────────────────────────────────────────────── if (& $needsSource 'Users') { $ouDetail = if ($TargetOU -and $TargetOU -ne '/') { $TargetOU } else { $null } if (-not $Quiet) { if ($ouDetail) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving users' -Detail "OU: $ouDetail" } else { Write-ProgressLine -Phase AUDITING -Message 'Retrieving users' } } try { $userQueryParams = @{ customer = 'my_customer' maxResults = '500' projection = 'full' orderBy = 'email' } if ($ouDetail) { $userQueryParams['query'] = "orgUnitPath='$TargetOU'" } $users = Invoke-GoogleAdminApi ` -AccessToken $AccessToken ` -Uri 'https://admin.googleapis.com/admin/directory/v1/users' ` -QueryParameters $userQueryParams ` -Paginate ` -ItemsProperty 'users' ` -Quiet:$Quiet $data.Users = @($users ?? @()) } catch { $data.Errors['Users'] = $_.Exception.Message } } # ── 5. Groups ──────────────────────────────────────────────────────── if (& $needsSource 'Groups') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving groups' } try { $groups = Invoke-GoogleAdminApi ` -AccessToken $AccessToken ` -Uri 'https://admin.googleapis.com/admin/directory/v1/groups' ` -QueryParameters @{ customer = 'my_customer' maxResults = '200' } ` -Paginate ` -ItemsProperty 'groups' ` -Quiet:$Quiet $data.Groups = @($groups ?? @()) } catch { $data.Errors['Groups'] = $_.Exception.Message } } # ── 6. Roles ───────────────────────────────────────────────────────── if (& $needsSource 'Roles') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving admin roles' } try { $roles = Invoke-GoogleAdminApi ` -AccessToken $AccessToken ` -Uri 'https://admin.googleapis.com/admin/directory/v1/customer/my_customer/roles' ` -Paginate ` -ItemsProperty 'items' ` -Quiet:$Quiet $data.Roles = @($roles ?? @()) } catch { $data.Errors['Roles'] = $_.Exception.Message } } # ── 7. Role Assignments ────────────────────────────────────────────── if (& $needsSource 'RoleAssignments') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving role assignments' } try { $roleAssignments = Invoke-GoogleAdminApi ` -AccessToken $AccessToken ` -Uri 'https://admin.googleapis.com/admin/directory/v1/customer/my_customer/roleassignments' ` -Paginate ` -ItemsProperty 'items' ` -Quiet:$Quiet $data.RoleAssignments = @($roleAssignments ?? @()) } catch { $data.Errors['RoleAssignments'] = $_.Exception.Message } } # ── 8. Mobile Devices ──────────────────────────────────────────────── if (& $needsSource 'MobileDevices') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving mobile devices' } try { $mobileDevices = Invoke-GoogleAdminApi ` -AccessToken $AccessToken ` -Uri 'https://admin.googleapis.com/admin/directory/v1/customer/my_customer/devices/mobile' ` -QueryParameters @{ maxResults = '100' } ` -Paginate ` -ItemsProperty 'mobiledevices' ` -Quiet:$Quiet $data.MobileDevices = @($mobileDevices ?? @()) } catch { $data.Errors['MobileDevices'] = $_.Exception.Message } } # ── 9. Chrome Devices ──────────────────────────────────────────────── if (& $needsSource 'ChromeDevices') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving Chrome OS devices' } try { $chromeDevices = Invoke-GoogleAdminApi ` -AccessToken $AccessToken ` -Uri 'https://admin.googleapis.com/admin/directory/v1/customer/my_customer/devices/chromeos' ` -QueryParameters @{ maxResults = '100' } ` -Paginate ` -ItemsProperty 'chromeosdevices' ` -Quiet:$Quiet $data.ChromeDevices = @($chromeDevices ?? @()) } catch { $data.Errors['ChromeDevices'] = $_.Exception.Message } } # ── 10. DNS Records ────────────────────────────────────────────────── if (& $needsSource 'DnsRecords') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Checking DNS mail security records' } $domains = @($data.Tenant.Domains) foreach ($domainObj in $domains) { $domainName = if ($domainObj -is [string]) { $domainObj } else { $domainObj.domainName } if (-not $domainName) { continue } try { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Resolving DNS' -Detail $domainName } $dnsResult = Resolve-DomainMailSecurity -Domain $domainName $data.DnsRecords[$domainName] = $dnsResult } catch { $data.Errors["DnsRecords:$domainName"] = $_.Exception.Message } } } # ── 11. Gmail Settings (per-user sample) ───────────────────────────── if (& $needsSource 'GmailSettings') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Sampling Gmail user settings' } # Select a sample of users up to $UserSampleSize $userPool = @($data.Users | Where-Object { $_ }) $sampleUsers = if ($userPool.Count -le $UserSampleSize) { $userPool } else { $userPool | Select-Object -First $UserSampleSize } $userCount = 0 foreach ($user in $sampleUsers) { $userEmail = $user.primaryEmail if (-not $userEmail) { continue } $userCount++ if (-not $Quiet -and ($userCount % 50 -eq 0 -or $userCount -eq 1)) { Write-ProgressLine -Phase AUDITING ` -Message 'Gmail settings' ` -Detail "$userCount / $($sampleUsers.Count)" } try { # Get an impersonated token for this user's Gmail $gmailToken = Get-GoogleAccessToken ` -ServiceAccountKeyPath $ServiceAccountKeyPath ` -AdminEmail $AdminEmail ` -Scopes @('https://www.googleapis.com/auth/gmail.settings.basic') ` -ImpersonateUser $userEmail $userSettings = @{} $mailNotEnabled = $false # Auto-forwarding try { $userSettings['autoForwarding'] = Invoke-GoogleAdminApi ` -AccessToken $gmailToken ` -Uri 'https://gmail.googleapis.com/gmail/v1/users/me/settings/autoForwarding' ` -Quiet:$Quiet } catch { if ($_.Exception.Message -match 'Mail service not enabled') { $mailNotEnabled = $true } $userSettings['autoForwarding'] = $null } if (-not $mailNotEnabled) { # IMAP try { $userSettings['imap'] = Invoke-GoogleAdminApi ` -AccessToken $gmailToken ` -Uri 'https://gmail.googleapis.com/gmail/v1/users/me/settings/imap' ` -Quiet:$Quiet } catch { $userSettings['imap'] = $null } # POP try { $userSettings['pop'] = Invoke-GoogleAdminApi ` -AccessToken $gmailToken ` -Uri 'https://gmail.googleapis.com/gmail/v1/users/me/settings/pop' ` -Quiet:$Quiet } catch { $userSettings['pop'] = $null } # Send-As aliases try { $userSettings['sendAs'] = Invoke-GoogleAdminApi ` -AccessToken $gmailToken ` -Uri 'https://gmail.googleapis.com/gmail/v1/users/me/settings/sendAs' ` -Paginate ` -ItemsProperty 'sendAs' ` -Quiet:$Quiet } catch { $userSettings['sendAs'] = $null } } $data.GmailSettings[$userEmail] = $userSettings } catch { $data.Errors["GmailSettings:$userEmail"] = $_.Exception.Message } } if (-not $Quiet) { Write-ProgressLine -Phase AUDITING ` -Message 'Gmail settings complete' ` -Detail "$userCount users sampled" } } # ── 12. OAuth Token Grants (Reports API) ───────────────────────────── if (& $needsSource 'OAuthApps') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving OAuth token grants' } try { $reportsToken = Get-GoogleAccessToken ` -ServiceAccountKeyPath $ServiceAccountKeyPath ` -AdminEmail $AdminEmail ` -Scopes @('https://www.googleapis.com/auth/admin.reports.audit.readonly') $oauthEvents = Invoke-GoogleReportsApi ` -AccessToken $reportsToken ` -ApplicationName 'token' ` -StartTime ([datetime]::UtcNow.AddDays(-7)) ` -Quiet:$Quiet $data.OAuthApps = @($oauthEvents ?? @()) } catch { $data.Errors['OAuthApps'] = $_.Exception.Message } } # ── 13. Domain-Wide Delegation ─────────────────────────────────────── if (& $needsSource 'DomainWideDelegation') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving domain-wide delegation grants' } try { $dwdToken = Get-GoogleAccessToken ` -ServiceAccountKeyPath $ServiceAccountKeyPath ` -AdminEmail $AdminEmail ` -Scopes @('https://www.googleapis.com/auth/admin.directory.domain.readonly') $dwdResult = Invoke-GoogleAdminApi ` -AccessToken $dwdToken ` -Uri 'https://admin.googleapis.com/admin/directory/v1/customer/my_customer/domainwidedelegation' ` -Paginate ` -ItemsProperty 'items' ` -Quiet:$Quiet $data.DomainWideDelegation = @($dwdResult ?? @()) } catch { $data.Errors['DomainWideDelegation'] = $_.Exception.Message } } # ── 14. Alert Rules ────────────────────────────────────────────────── if (& $needsSource 'AlertRules') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving alert rules' } try { $alertToken = Get-GoogleAccessToken ` -ServiceAccountKeyPath $ServiceAccountKeyPath ` -AdminEmail $AdminEmail ` -Scopes @('https://www.googleapis.com/auth/apps.alerts') $alertResult = Invoke-GoogleAdminApi ` -AccessToken $alertToken ` -Uri 'https://alertcenter.googleapis.com/v1beta1/alerts' ` -Paginate ` -ItemsProperty 'alerts' ` -Quiet:$Quiet $data.AlertRules = @($alertResult ?? @()) } catch { $data.Errors['AlertRules'] = $_.Exception.Message } } # ── 15. Chrome Policies ────────────────────────────────────────────── if (& $needsSource 'ChromePolicies') { if (-not $Quiet) { Write-ProgressLine -Phase AUDITING -Message 'Retrieving Chrome policies' } try { $chromeToken = Get-GoogleAccessToken ` -ServiceAccountKeyPath $ServiceAccountKeyPath ` -AdminEmail $AdminEmail ` -Scopes @('https://www.googleapis.com/auth/chrome.management.policy.readonly') $chromeResult = Invoke-GoogleAdminApi ` -AccessToken $chromeToken ` -Uri 'https://chromepolicy.googleapis.com/v1/customers/my_customer/policies:resolve' ` -Method Post ` -Body @{ policyTargetKey = @{ targetResource = 'orgunits/03ph8a2z0' } pageSize = 1000 } ` -Paginate ` -ItemsProperty 'resolvedPolicies' ` -Quiet:$Quiet $data.ChromePolicies = @($chromeResult ?? @()) } catch { $data.Errors['ChromePolicies'] = $_.Exception.Message } } # ── Summary ────────────────────────────────────────────────────────── if (-not $Quiet) { $errorCount = $data.Errors.Count $summary = "Collection complete: $($data.Users.Count) users, " + "$($data.Groups.Count) groups, " + "$($data.Tenant.Domains.Count) domains" if ($errorCount -gt 0) { $summary += " ($errorCount errors)" } Write-ProgressLine -Phase AUDITING -Message $summary } return $data } |