checks/GWS/Invoke-DriveSecurityChecks.ps1
|
# Guerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/Guerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Invoke-DriveSecurityChecks { [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable]$AuditData, [string]$OrgUnitPath = '/' ) $checkDefs = Get-AuditCategoryDefinitions -Category 'DriveSecurityChecks' $findings = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($check in $checkDefs.checks) { $funcName = "Test-$($check.id -replace '-', '')" if (Get-Command $funcName -ErrorAction SilentlyContinue) { try { $finding = & $funcName -AuditData $AuditData -CheckDefinition $check -OrgUnitPath $OrgUnitPath if ($finding) { $findings.Add($finding) } } catch { $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'ERROR' ` -CurrentValue "Check failed: $_" -OrgUnitPath $OrgUnitPath)) } } else { $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'SKIP' ` -CurrentValue 'Check not yet implemented' -OrgUnitPath $OrgUnitPath)) } } return @($findings) } # ── DRIVE-001: External Sharing Defaults ────────────────────────────────── function Test-DRIVE001 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # Drive sharing settings are OU-level policies not fully exposed via Directory API # Check if OrgUnitPolicies contain Drive sharing configuration $na = Get-NotAssessedFinding -CheckDefinition $CheckDefinition -ErrorMap $AuditData.Errors ` -SourceKey @('OrgUnits', 'CloudIdentityPolicies') -Subject 'Drive external-sharing policy' if ($na) { return $na } $policy = $AuditData.OrgUnitPolicies[$OrgUnitPath] if ($policy -and $null -ne $policy.driveExternalSharing) { $status = switch ($policy.driveExternalSharing) { 'OFF' { 'PASS' } 'ALLOWLISTED_DOMAINS' { 'PASS' } 'ON_WITH_WARNING' { 'WARN' } 'ON' { 'FAIL' } default { 'WARN' } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue "External sharing policy: $($policy.driveExternalSharing)" ` -OrgUnitPath $OrgUnitPath } # GWS-1: drive_and_docs.external_sharing { externalSharingMode=enum }. Grade WEAKEST-OU-WINS. # 'ALLOWED' is unrestricted external sharing (insecure) -> FAIL; restrictive values are better. $pol = $AuditData.CloudIdentityPolicies if (-not $pol) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Cloud Identity Policy API not available (cloud-identity.policies.readonly not delegated, or API disabled)' ` -OrgUnitPath $OrgUnitPath } $vals = @(Resolve-GooglePolicyValue -Policies $pol -Type 'drive_and_docs.external_sharing' -Field 'externalSharingMode') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No drive_and_docs.external_sharing policy returned for this tenant' -OrgUnitPath $OrgUnitPath } $note = "External sharing mode: $((@($vals) | Select-Object -Unique) -join ', ') (across $($vals.Count) targeted policy/policies)" # Known-insecure: unrestricted external sharing. $insecure = @($vals | Where-Object { "$_" -match '(?i)^ALLOWED$' }) if ($insecure.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Unrestricted external sharing permitted — $note" -OrgUnitPath $OrgUnitPath } # Known-restrictive values pass; anything unrecognized -> WARN (never PASS on unknown enum). $known = @($vals | Where-Object { "$_" -match '(?i)^(DISALLOWED|ALLOWED_WITH_WARNING|ALLOWLISTED_DOMAINS)$' }) if ($known.Count -eq $vals.Count) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "External sharing restricted — $note" -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Unrecognized external sharing mode — verify intent — $note" -OrgUnitPath $OrgUnitPath } # ── DRIVE-002: Link Sharing Default Settings ───────────────────────────── function Test-DRIVE002 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') $na = Get-NotAssessedFinding -CheckDefinition $CheckDefinition -ErrorMap $AuditData.Errors ` -SourceKey 'OrgUnits' -Subject 'Drive link-sharing policy' if ($na) { return $na } $policy = $AuditData.OrgUnitPolicies[$OrgUnitPath] if ($policy -and $null -ne $policy.defaultLinkSharing) { $status = if ($policy.defaultLinkSharing -eq 'RESTRICTED') { 'PASS' } elseif ($policy.defaultLinkSharing -eq 'DOMAIN') { 'WARN' } else { 'FAIL' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue "Default link sharing: $($policy.defaultLinkSharing)" ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Default link sharing setting not available via API. Verify in Admin Console that default is set to Restricted (specific people)' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'OU-level Drive link sharing defaults require manual verification in Admin Console' } } # ── DRIVE-003: Anyone With the Link Sharing Audit ──────────────────────── function Test-DRIVE003 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') $na = Get-NotAssessedFinding -CheckDefinition $CheckDefinition -ErrorMap $AuditData.Errors ` -SourceKey 'OrgUnits' -Subject 'Drive anyone-with-link policy' if ($na) { return $na } $policy = $AuditData.OrgUnitPolicies[$OrgUnitPath] if ($policy -and $null -ne $policy.anyoneWithLinkEnabled) { $status = if ($policy.anyoneWithLinkEnabled -eq $false) { 'PASS' } else { 'FAIL' } $currentValue = if ($policy.anyoneWithLinkEnabled) { "'Anyone with the link' sharing is enabled - files can be exposed to the internet" } else { "'Anyone with the link' sharing is disabled" } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $currentValue -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Verify in Admin Console that 'Anyone with the link' sharing is disabled or restricted to 'Domain users with the link'" ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'This setting controls whether users can create public links accessible by anyone on the internet' } } # ── DRIVE-004: Shared Drive Creation Restrictions ──────────────────────── function Test-DRIVE004 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # GWS-1: drive_and_docs.shared_drive_creation { allowSharedDriveCreation=bool }. # Insecure (weaker) when shared-drive creation is unrestricted anywhere. Weakest-OU-wins. $pol = $AuditData.CloudIdentityPolicies if (-not $pol) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Cloud Identity Policy API not available (cloud-identity.policies.readonly not delegated, or API disabled)' ` -OrgUnitPath $OrgUnitPath } $vals = @(Resolve-GooglePolicyValue -Policies $pol -Type 'drive_and_docs.shared_drive_creation' -Field 'allowSharedDriveCreation') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No drive_and_docs.shared_drive_creation policy returned for this tenant' -OrgUnitPath $OrgUnitPath } $allowed = @($vals | Where-Object { $_ -eq $true }) if ($allowed.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Shared Drive creation unrestricted in $($allowed.Count) of $($vals.Count) targeted policy/policies" ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'Shared Drive creation restricted' -OrgUnitPath $OrgUnitPath } # ── DRIVE-005: Shared Drive Member Management ──────────────────────────── function Test-DRIVE005 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Shared Drive member management settings not available via API. Verify in Admin Console that only managers can add members and change access levels' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Shared Drive member management policies are OU-level settings requiring manual verification' } } # ── DRIVE-006: Shared Drive External Sharing ───────────────────────────── function Test-DRIVE006 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') $na = Get-NotAssessedFinding -CheckDefinition $CheckDefinition -ErrorMap $AuditData.Errors ` -SourceKey 'OrgUnits' -Subject 'Shared Drive external-sharing policy' if ($na) { return $na } $policy = $AuditData.OrgUnitPolicies[$OrgUnitPath] if ($policy -and $null -ne $policy.sharedDriveExternalSharing) { $status = if ($policy.sharedDriveExternalSharing -eq $false) { 'PASS' } else { 'FAIL' } $currentValue = if ($policy.sharedDriveExternalSharing) { 'External sharing on Shared Drives is enabled' } else { 'External sharing on Shared Drives is disabled' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $currentValue -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Shared Drive external sharing settings not available via API. Verify in Admin Console > Apps > Drive > Sharing settings > Shared drive sharing' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Shared Drive external sharing is an OU-level policy requiring manual verification' } } # ── DRIVE-007: File Ownership Transfer Settings ────────────────────────── function Test-DRIVE007 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'File ownership transfer settings not available via API. Verify in Admin Console that ownership transfer is restricted appropriately' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Ownership transfer policies are OU-level settings requiring manual verification' } } # ── DRIVE-008: Drive for Desktop Allowed/Blocked ───────────────────────── function Test-DRIVE008 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') $na = Get-NotAssessedFinding -CheckDefinition $CheckDefinition -ErrorMap $AuditData.Errors ` -SourceKey @('OrgUnits', 'CloudIdentityPolicies') -Subject 'Drive for Desktop policy' if ($na) { return $na } $policy = $AuditData.OrgUnitPolicies[$OrgUnitPath] if ($policy -and $null -ne $policy.driveForDesktopEnabled) { $status = if ($policy.driveForDesktopEnabled -eq $false) { 'PASS' } else { 'WARN' } $currentValue = if ($policy.driveForDesktopEnabled) { 'Drive for Desktop is enabled - files may be synced to local devices' } else { 'Drive for Desktop is disabled' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $currentValue -OrgUnitPath $OrgUnitPath } # GWS-1: drive_and_docs.drive_for_desktop { allowDriveForDesktop=bool; restrictToAuthorizedDevices=bool }. # Enabled allows local file sync; weaker when unrestricted to authorized devices. Weakest-OU-wins. $pol = $AuditData.CloudIdentityPolicies if (-not $pol) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Cloud Identity Policy API not available (cloud-identity.policies.readonly not delegated, or API disabled)' ` -OrgUnitPath $OrgUnitPath } $allowVals = @(Resolve-GooglePolicyValue -Policies $pol -Type 'drive_and_docs.drive_for_desktop' -Field 'allowDriveForDesktop') if ($allowVals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No drive_and_docs.drive_for_desktop policy returned for this tenant' -OrgUnitPath $OrgUnitPath } $restrictVals = @(Resolve-GooglePolicyValue -Policies $pol -Type 'drive_and_docs.drive_for_desktop' -Field 'restrictToAuthorizedDevices') $enabled = @($allowVals | Where-Object { $_ -eq $true }) if ($enabled.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'Drive for Desktop is disabled' -OrgUnitPath $OrgUnitPath } # Enabled somewhere. If every targeted policy restricts to authorized devices, that's the safer posture. $restrictedAll = ($restrictVals.Count -gt 0 -and @($restrictVals | Where-Object { $_ -ne $true }).Count -eq 0) if ($restrictedAll) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "Drive for Desktop enabled but restricted to authorized devices ($($enabled.Count) of $($allowVals.Count) targeted policies)" ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Drive for Desktop enabled without authorized-device restriction in $($enabled.Count) of $($allowVals.Count) targeted policy/policies — files may sync to unmanaged devices" ` -OrgUnitPath $OrgUnitPath } # ── DRIVE-009: Third-Party App Drive Access ────────────────────────────── function Test-DRIVE009 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') $na = Get-NotAssessedFinding -CheckDefinition $CheckDefinition -ErrorMap $AuditData.Errors ` -SourceKey 'OAuthApps' -Subject 'OAuth app inventory' if ($na) { return $na } # Check OAuthApps for apps with Drive scopes if ($AuditData.OAuthApps) { $driveScopes = @('drive', 'drive.file', 'drive.readonly', 'drive.metadata') $driveApps = [System.Collections.Generic.List[string]]::new() foreach ($event in $AuditData.OAuthApps) { $appName = $event.Params.app_name $scope = $event.Params.scope if ($scope) { foreach ($ds in $driveScopes) { if ($scope -match $ds) { if ($appName -and -not $driveApps.Contains($appName)) { $driveApps.Add($appName) } break } } } } if ($driveApps.Count -gt 0) { $status = if ($driveApps.Count -gt 10) { 'FAIL' } elseif ($driveApps.Count -gt 5) { 'WARN' } else { 'PASS' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue "$($driveApps.Count) third-party app(s) have Drive access" ` -OrgUnitPath $OrgUnitPath ` -Details @{ AppsWithDriveAccess = @($driveApps) } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'No third-party apps with Drive access detected' ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'OAuth app data not available. Verify third-party app Drive access in Admin Console > Security > API controls' ` -OrgUnitPath $OrgUnitPath } # ── DRIVE-010: Drive DLP Rules Audit ───────────────────────────────────── function Test-DRIVE010 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # GWS-1: rule.dlp value objects { state=enum(ACTIVE/INACTIVE), action={ gmailAction|driveAction|alertCenterAction } }. # PASS if >= 1 ACTIVE rule whose action object is Drive-scoped (has a driveAction); WARN if none. $pol = $AuditData.CloudIdentityPolicies if (-not $pol) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Cloud Identity Policy API not available (cloud-identity.policies.readonly not delegated, or API disabled)' ` -OrgUnitPath $OrgUnitPath } $vals = @(Resolve-GooglePolicyValue -Policies $pol -Type 'rule.dlp') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No rule.dlp policy returned for this tenant' -OrgUnitPath $OrgUnitPath } # Count ACTIVE rules whose action object is Drive-scoped (anchored state match; action must have a driveAction). $activeDrive = @($vals | Where-Object { ($_.state -eq 'ACTIVE') -and $_.action -and ($_.action.PSObject.Properties.Name -contains 'driveAction') }) if ($activeDrive.Count -ge 1) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($activeDrive.Count) active Drive DLP rule(s) configured (of $($vals.Count) DLP rule(s))" ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "No active Drive-scoped DLP rule found ($($vals.Count) DLP rule(s) present). Configure a Drive DLP rule in Admin Console > Security > Data protection > Manage rules" ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'DLP rules should cover sensitive data types including PII, financial data, and health records' } } # ── DRIVE-011: Target Audience Settings ────────────────────────────────── function Test-DRIVE011 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Target audience settings not available via API. Verify in Admin Console > Directory > Target audiences that sharing suggestions are properly scoped' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Target audiences control suggested recipients when sharing files and should be configured to prevent accidental broad sharing' } } # ── DRIVE-012: Drive Add-ons Settings ──────────────────────────────────── function Test-DRIVE012 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Drive add-ons settings not available via API. Verify in Admin Console > Apps > Drive > Add-ons that installation is restricted to approved add-ons' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Uncontrolled Drive add-ons can access file content and metadata' } } # ── DRIVE-013: Offline Access Settings ─────────────────────────────────── function Test-DRIVE013 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') $na = Get-NotAssessedFinding -CheckDefinition $CheckDefinition -ErrorMap $AuditData.Errors ` -SourceKey 'OrgUnits' -Subject 'Drive offline-access policy' if ($na) { return $na } $policy = $AuditData.OrgUnitPolicies[$OrgUnitPath] if ($policy -and $null -ne $policy.driveOfflineEnabled) { $status = if ($policy.driveOfflineEnabled -eq $false) { 'PASS' } else { 'WARN' } $currentValue = if ($policy.driveOfflineEnabled) { 'Offline access is enabled - files may be cached on local devices' } else { 'Offline access is disabled' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $currentValue -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'Offline access setting not available via API. Verify in Admin Console > Apps > Drive > Features and Applications > Offline that offline access is controlled' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'Offline access caches files locally and should be disabled on shared or unmanaged devices' } } # ── DRIVE-014: GWS.DRIVEDOCS.4.1 — Drive SDK API access disabled ─────────── function Test-DRIVE014 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') Test-GwsPolicyBoolean -AuditData $AuditData -CheckDefinition $CheckDefinition -OrgUnitPath $OrgUnitPath ` -Type 'drive_and_docs.drive_sdk' -Field 'enableDriveSdkApiAccess' -SecureValue $false -Status 'FAIL' ` -BadMsg 'Drive SDK API access is enabled (third-party API read/write to Drive)' -GoodMsg 'Drive SDK API access is disabled' } # ── DRIVE-015: GWS.DRIVEDOCS.1.9 — External-file sharing warning enabled ─── function Test-DRIVE015 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') Test-GwsPolicyBoolean -AuditData $AuditData -CheckDefinition $CheckDefinition -OrgUnitPath $OrgUnitPath ` -Type 'drive_and_docs.external_file_warning' -Field 'highlightingEnabled' -SecureValue $true -Status 'WARN' ` -BadMsg 'External-file sharing warning is off' -GoodMsg 'External-file sharing warning is on' } # ── DRIVE-016: GWS.DRIVEDOCS.3.1 — File security update enforced ─────────── function Test-DRIVE016 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') Test-GwsPolicyBoolean -AuditData $AuditData -CheckDefinition $CheckDefinition -OrgUnitPath $OrgUnitPath ` -Type 'drive_and_docs.file_security_update' -Field 'allowUsersToManageUpdate' -SecureValue $false -Status 'WARN' ` -BadMsg 'Users are allowed to remove the file security update' -GoodMsg 'Users cannot remove the file security update' } # ── DRIVE-017: Default file access set to private to owner (GWS.DRIVEDOCS.1.8) ── function Test-DRIVE017 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') Test-GwsPolicyEnum -AuditData $AuditData -CheckDefinition $CheckDefinition -OrgUnitPath $OrgUnitPath ` -Type 'drive_and_docs.general_access_default' -Field 'defaultFileAccess' -CompliantValues @('PRIVATE_TO_OWNER') -Status 'FAIL' ` -BadMsg 'Default file access is not private to owner' -GoodMsg 'Default file access is private to owner' } # ── DRIVE-018: Shared Drive External-Sharing Exposure ───────────────────── # Enumerates every shared drive (collected via domain-admin access in # Get-GWSAuditData) and flags drives whose restrictions permit sharing outside # the organization. domainUsersOnly = true means access is confined to org # members; false or absent means items can be shared externally (and, with the # domain sharing setting on, made publicly accessible). Confirming an actual # public file link would need a per-file permission scan, which this does not do. function Test-DRIVE018 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') $na = Get-NotAssessedFinding -CheckDefinition $CheckDefinition -ErrorMap $AuditData.Errors ` -SourceKey 'SharedDrives' -Subject 'shared drives' if ($na) { return $na } $drives = @($AuditData.SharedDrives | Where-Object { $null -ne $_ }) if ($drives.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'No shared drives were returned by the domain-admin enumeration; there is no shared-drive external-sharing exposure to report.' ` -OrgUnitPath $OrgUnitPath -Details @{ DriveCount = 0 } } # domainUsersOnly enforced (true) = confined to the org. Missing or false = # external sharing permitted; an unrecognized value is never assumed safe. $exposed = foreach ($d in $drives) { $r = $d.restrictions $domainOnly = if ($r -and $null -ne $r.domainUsersOnly) { [bool]$r.domainUsersOnly } else { $false } if (-not $domainOnly) { [pscustomobject]@{ Name = if ("$($d.name)") { "$($d.name)" } else { "(unnamed)" } Id = "$($d.id)" Label = "$(if ("$($d.name)") { "$($d.name)" } else { "(unnamed)" }) (id $($d.id))" } } } $exposed = @($exposed) if ($exposed.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue ("All $($drives.Count) shared drive(s) restrict access to organization members " + '(domainUsersOnly enforced); none permits sharing outside the organization.') ` -OrgUnitPath $OrgUnitPath -Details @{ DriveCount = $drives.Count; ExposedCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue ("$($exposed.Count) of $($drives.Count) shared drive(s) permit sharing outside the " + "organization (domainUsersOnly not enforced): $((@($exposed.Label) | Select-Object -First 10) -join '; '). " + "Their contents can be shared with external accounts, and made publicly accessible if the domain sharing " + 'setting allows it. Restrict external sharing on each drive or document the exception. This reflects the ' + "drive-level restriction; confirming a live public link requires a per-file permission scan not performed here.") ` -OrgUnitPath $OrgUnitPath ` -Details @{ DriveCount = $drives.Count ExposedCount = $exposed.Count AffectedItems = @($exposed.Label) AffectedLabel = 'Shared drives permitting external sharing' } } # ── GWS.DRIVEDOCS.1.3 / 1.4 / 1.5 / 1.7 shared evaluator ────────────────── # These four SCuBA policies are all conditional on external sharing: each one # constrains a sub-setting of drive_and_docs.external_sharing that only has any # effect when externalSharingMode permits sharing outside the organization. All # of them live on the SAME policy value object as externalSharingMode, so the # mode and the sub-setting can be paired per targeted policy without needing the # policyQuery OU — reading the fields separately would let one OU's mode answer # for another OU's sub-setting. # # -Applies is a scriptblock over the mode; -Evaluate is a scriptblock over the # whole value object returning 'ok', 'bad', or 'missing'. Grading is # weakest-OU-wins. A value object where the policy applies but the sub-setting is # absent grades 'missing' and the whole check reports Not Assessed: an unreadable # setting is not a passing setting (an earlier generation of these checks read a # missing field as compliant, which is how a tenant scored a clean report on a # setting nobody had ever looked at). function Test-GwsDriveSharingSubSetting { param( [hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath, [scriptblock]$Applies, [scriptblock]$Evaluate, [string]$Status, [string]$BadMsg, [string]$GoodMsg ) $na = Get-NotAssessedFinding -CheckDefinition $CheckDefinition -ErrorMap $AuditData.Errors ` -SourceKey 'CloudIdentityPolicies' -Subject 'Drive external-sharing policy' if ($na) { return $na } $pol = $AuditData.CloudIdentityPolicies if (-not $pol) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Cloud Identity Policy API not available (cloud-identity.policies.readonly not delegated, or API disabled)' ` -OrgUnitPath $OrgUnitPath } $objs = @(Resolve-GooglePolicyValue -Policies $pol -Type 'drive_and_docs.external_sharing') if ($objs.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No drive_and_docs.external_sharing policy returned for this tenant' -OrgUnitPath $OrgUnitPath } $bad = 0; $missing = 0; $applicable = 0 foreach ($o in $objs) { $mode = if ($o.PSObject.Properties.Name -contains 'externalSharingMode') { "$($o.externalSharingMode)" } else { $null } # Mode itself unreadable: the policy's applicability is unknown, so it is # not assessed rather than assumed inapplicable (which would read as a pass). if (-not $mode) { $missing++; continue } if (-not (& $Applies $mode)) { continue } $applicable++ switch (& $Evaluate $o) { 'bad' { $bad++ } 'missing' { $missing++ } } } if ($bad -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status $Status ` -CurrentValue "$BadMsg in $bad of $($objs.Count) targeted policy/policies" -OrgUnitPath $OrgUnitPath ` -Details @{ PolicyCount = $objs.Count; ApplicableCount = $applicable; ViolatingCount = $bad } } if ($missing -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue ("External sharing is permitted but the setting this policy constrains was not returned " + "in $missing of $($objs.Count) targeted policy/policies — not assessed rather than assumed compliant") ` -OrgUnitPath $OrgUnitPath -Details @{ PolicyCount = $objs.Count; UnreadableCount = $missing } } if ($applicable -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue ("Not applicable: external sharing is disallowed in all $($objs.Count) targeted policy/policies, " + 'so this setting cannot expose content outside the organization') ` -OrgUnitPath $OrgUnitPath -Details @{ PolicyCount = $objs.Count; ApplicableCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$GoodMsg in all $applicable of $($objs.Count) targeted policy/policies where external sharing is permitted" ` -OrgUnitPath $OrgUnitPath -Details @{ PolicyCount = $objs.Count; ApplicableCount = $applicable } } # Reads a field off a policy value object as 'ok' / 'bad' / 'missing'. function Get-GwsPolicyFieldVerdict { param($Object, [string]$Field, $SecureValue) if ($Object.PSObject.Properties.Name -notcontains $Field) { return 'missing' } if ($Object.$Field -eq $SecureValue) { return 'ok' } return 'bad' } # ── DRIVE-019: GWS.DRIVEDOCS.1.3 — Warn when sharing outside allowlisted domains ── # Two modes, two different fields: an allowlisted-domains tenant is governed by # warnForSharingOutsideAllowlistedDomains, an open-sharing tenant by # warnForExternalSharing. Reading only one of them would score half the tenants # against a setting that does not apply to them. function Test-DRIVE019 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') Test-GwsDriveSharingSubSetting -AuditData $AuditData -CheckDefinition $CheckDefinition -OrgUnitPath $OrgUnitPath ` -Applies { param($mode) $mode -in @('ALLOWED', 'ALLOWLISTED_DOMAINS') } ` -Evaluate { param($o) $field = if ("$($o.externalSharingMode)" -eq 'ALLOWLISTED_DOMAINS') { 'warnForSharingOutsideAllowlistedDomains' } else { 'warnForExternalSharing' } Get-GwsPolicyFieldVerdict -Object $o -Field $field -SecureValue $true } ` -Status 'FAIL' ` -BadMsg 'Users are not warned when sharing Drive content outside the organization' ` -GoodMsg 'Users are warned when sharing Drive content outside the organization' } # ── DRIVE-020: GWS.DRIVEDOCS.1.4 — No sharing with non-Google accounts ──── function Test-DRIVE020 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') Test-GwsDriveSharingSubSetting -AuditData $AuditData -CheckDefinition $CheckDefinition -OrgUnitPath $OrgUnitPath ` -Applies { param($mode) $mode -in @('ALLOWED', 'ALLOWLISTED_DOMAINS') } ` -Evaluate { param($o) $field = if ("$($o.externalSharingMode)" -eq 'ALLOWLISTED_DOMAINS') { 'allowNonGoogleInvitesInAllowlistedDomains' } else { 'allowNonGoogleInvites' } Get-GwsPolicyFieldVerdict -Object $o -Field $field -SecureValue $false } ` -Status 'WARN' ` -BadMsg 'Drive content can be shared with recipients who have no Google account (visitor sharing, PIN-based access)' ` -GoodMsg 'Drive sharing is limited to Google accounts' } # ── DRIVE-021: GWS.DRIVEDOCS.1.5 — Publishing to the web disabled ───────── function Test-DRIVE021 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') Test-GwsDriveSharingSubSetting -AuditData $AuditData -CheckDefinition $CheckDefinition -OrgUnitPath $OrgUnitPath ` -Applies { param($mode) $mode -ne 'DISALLOWED' } ` -Evaluate { param($o) Get-GwsPolicyFieldVerdict -Object $o -Field 'allowPublishingFiles' -SecureValue $false } ` -Status 'WARN' ` -BadMsg 'Users can publish Drive content to the web, making it readable by anyone with the link' ` -GoodMsg 'Publishing Drive content to the web is disabled' } # ── DRIVE-022: GWS.DRIVEDOCS.1.7 — No distributing content to outside drives ── function Test-DRIVE022 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') Test-GwsDriveSharingSubSetting -AuditData $AuditData -CheckDefinition $CheckDefinition -OrgUnitPath $OrgUnitPath ` -Applies { param($mode) $mode -ne 'DISALLOWED' } ` -Evaluate { param($o) Get-GwsPolicyFieldVerdict -Object $o -Field 'allowedPartiesForDistributingContent' -SecureValue 'NONE' } ` -Status 'WARN' ` -BadMsg 'Users can upload or move content into shared drives owned by another organization' ` -GoodMsg 'Content cannot be moved into shared drives owned by another organization' } |