Private/Audit/Invoke-GwsServiceChecks.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 # # Workspace Service Security dispatcher + checks for the SCuBA Sites, Classroom, and # Gemini baselines. Sites and Classroom settings are read from the Cloud Identity Policy # API (the same source used by the existing Collaboration/Drive/Admin checks). Gemini's # granular controls (Alpha features, conversation history/retention, sharing) are NOT # exposed by the Admin SDK or the Cloud Identity Policy API today — those checks honestly # return SKIP / Not Assessed with an Admin console path instead of fabricating a result. # Per the project honesty rule: never PASS on uncollectable data. function Invoke-GwsServiceChecks { [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable]$AuditData, [string]$OrgUnitPath = '/' ) $checkDefs = Get-AuditCategoryDefinitions -Category 'GwsServiceChecks' $findings = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($check in $checkDefs.checks) { $funcName = "Test-Fortification$($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) } # ══ GOOGLE SITES ═════════════════════════════════════════════════════════════ # ── GWS-SITES-001: Sites Service Disabled (GWS.SITES.1.1v1) ────────────────── function Test-FortificationGWSSITES001 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # Cloud Identity Policy API: sites.service_status { serviceState=enum(ENABLED/DISABLED) }. # SCuBA SHOULD-disable: the service being OFF in every targeted policy is the secure posture. # Grade the WEAKEST (most-enabled) OU — if ANY targeted policy has Sites ENABLED the tenant # WARNs (SHOULD, not SHALL; selective per-OU enablement is permitted with justification). $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 'sites.service_status' -Field 'serviceState') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No sites.service_status policy returned for this tenant. Verify in Admin Console > Apps > Google Workspace > Sites > Service status' ` -OrgUnitPath $OrgUnitPath } $states = @($vals | ForEach-Object { "$_" }) $note = "Service state: $((@($states) | Select-Object -Unique) -join ', ') (across $($states.Count) targeted policy/policies)" $enabled = @($states | Where-Object { $_ -match '(?i)^ENABLED$' }) $disabled = @($states | Where-Object { $_ -match '(?i)^DISABLED$' }) $unknown = @($states | Where-Object { $_ -notmatch '(?i)^(ENABLED|DISABLED)$' }) if ($unknown.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Unrecognized Sites service state — verify manually whether the service is disabled — $note" ` -OrgUnitPath $OrgUnitPath } if ($enabled.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Sites service is enabled in $($enabled.Count) of $($states.Count) targeted policy/policies — SCuBA recommends disabling it except where selectively justified — $note" ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'GWS.SITES.1.1 recommends the Sites service be OFF for everyone; enable selectively per OU/group only where justified' } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "Sites service disabled in all $($disabled.Count) targeted policy/policies — $note" ` -OrgUnitPath $OrgUnitPath } # ══ GOOGLE CLASSROOM ═════════════════════════════════════════════════════════ # ── GWS-CLASS-001: Who Can Join Classes (GWS.CLASSROOM.1.1v1) ──────────────── function Test-FortificationGWSCLASS001 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # Policy API: classroom.class_membership { whoCanJoinClasses=enum }. # Secure = domain-only. Documented enums: ANYONE_IN_DOMAIN (domain-only, secure), # ANYONE_IN_ALLOWLISTED_DOMAINS (allowlist, acceptable -> WARN), ANY_GOOGLE_WORKSPACE_USER # / ANYONE (open -> FAIL). Grade WEAKEST OU. Unknown enum -> WARN (never PASS blindly). $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 'classroom.class_membership' -Field 'whoCanJoinClasses') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No classroom.class_membership policy returned for this tenant. Verify in Admin Console > Apps > Additional Google services > Classroom > Class settings' ` -OrgUnitPath $OrgUnitPath } $levels = @($vals | ForEach-Object { "$_" }) $note = "Who can join classes: $((@($levels) | Select-Object -Unique) -join ', ') (across $($levels.Count) targeted policy/policies)" $open = @($levels | Where-Object { $_ -match '(?i)^(ANY_GOOGLE_WORKSPACE_USER|ANYONE)$' }) $secure = @($levels | Where-Object { $_ -match '(?i)^ANYONE_IN_DOMAIN$' }) $allow = @($levels | Where-Object { $_ -match '(?i)^ANYONE_IN_ALLOWLISTED_DOMAINS$' }) $known = @($levels | Where-Object { $_ -match '(?i)^(ANY_GOOGLE_WORKSPACE_USER|ANYONE|ANYONE_IN_DOMAIN|ANYONE_IN_ALLOWLISTED_DOMAINS)$' }) if ($open.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Class membership is open beyond the domain in $($open.Count) of $($levels.Count) targeted policy/policies — $note" ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'GWS.CLASSROOM.1.1 recommends restricting to users in your domain only' } } if ($known.Count -ne $levels.Count) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Unrecognized class-membership value — verify manually that joining is domain-restricted — $note" ` -OrgUnitPath $OrgUnitPath } if ($allow.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Class membership permits allowlisted domains in $($allow.Count) of $($levels.Count) targeted policy/policies — confirm the allowlist is intended — $note" ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "Class membership restricted to users in your domain in all $($secure.Count) targeted policy/policies — $note" ` -OrgUnitPath $OrgUnitPath } # ── GWS-CLASS-002: Which Classes Users Can Join (GWS.CLASSROOM.1.2v1) ──────── function Test-FortificationGWSCLASS002 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # Policy API: classroom.class_membership { whichClassesCanUsersJoin=enum }. # Documented enums: CLASSES_IN_DOMAIN (secure), CLASSES_IN_ALLOWLISTED_DOMAINS (allowlist # -> WARN), ANY_GOOGLE_WORKSPACE_CLASS (open -> FAIL). Grade WEAKEST OU; unknown -> WARN. $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 'classroom.class_membership' -Field 'whichClassesCanUsersJoin') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No classroom.class_membership policy returned for this tenant. Verify in Admin Console > Apps > Additional Google services > Classroom > Class settings' ` -OrgUnitPath $OrgUnitPath } $levels = @($vals | ForEach-Object { "$_" }) $note = "Which classes users can join: $((@($levels) | Select-Object -Unique) -join ', ') (across $($levels.Count) targeted policy/policies)" $open = @($levels | Where-Object { $_ -match '(?i)^ANY_GOOGLE_WORKSPACE_CLASS$' }) $secure = @($levels | Where-Object { $_ -match '(?i)^CLASSES_IN_DOMAIN$' }) $allow = @($levels | Where-Object { $_ -match '(?i)^CLASSES_IN_ALLOWLISTED_DOMAINS$' }) $known = @($levels | Where-Object { $_ -match '(?i)^(ANY_GOOGLE_WORKSPACE_CLASS|CLASSES_IN_DOMAIN|CLASSES_IN_ALLOWLISTED_DOMAINS)$' }) if ($open.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Users may join classes outside the domain in $($open.Count) of $($levels.Count) targeted policy/policies — $note" ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'GWS.CLASSROOM.1.2 recommends restricting to classes in your domain only' } } if ($known.Count -ne $levels.Count) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Unrecognized value — verify manually that users may only join domain classes — $note" ` -OrgUnitPath $OrgUnitPath } if ($allow.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Users may join classes in allowlisted domains in $($allow.Count) of $($levels.Count) targeted policy/policies — confirm the allowlist is intended — $note" ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "Users may only join classes in your domain in all $($secure.Count) targeted policy/policies — $note" ` -OrgUnitPath $OrgUnitPath } # ── GWS-CLASS-003: API Data Access Restricted (GWS.CLASSROOM.2.1v1) ────────── function Test-FortificationGWSCLASS003 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # Policy API: classroom.api_data_access { enableApiAccess=bool }. SCuBA: users should NOT # be able to authorize apps -> enableApiAccess=false is secure. Grade WEAKEST OU: FAIL if # API data access is enabled in any targeted policy. $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 'classroom.api_data_access' -Field 'enableApiAccess') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No classroom.api_data_access policy returned for this tenant. Verify in Admin Console > Apps > Additional Google services > Classroom > Data access' ` -OrgUnitPath $OrgUnitPath } $enabled = @($vals | Where-Object { $_ -eq $true }) if ($enabled.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Users can authorize apps to access Classroom data in $($enabled.Count) of $($vals.Count) targeted policy/policies" ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'GWS.CLASSROOM.2.1 recommends users not be able to authorize apps to access Classroom data' } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "Classroom API data access is disabled in all $($vals.Count) targeted policy/policies" ` -OrgUnitPath $OrgUnitPath } # ── GWS-CLASS-004: Roster Import Disabled (GWS.CLASSROOM.3.1v1) ────────────── function Test-FortificationGWSCLASS004 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # Policy API: classroom.roster_import { rosterImportOption=enum(OFF/ON_CLEVER) }. # SCuBA: roster import SHOULD be OFF. Grade WEAKEST OU; unknown enum -> WARN. $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 'classroom.roster_import' -Field 'rosterImportOption') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No classroom.roster_import policy returned for this tenant. Verify in Admin Console > Apps > Additional Google services > Classroom > Roster import' ` -OrgUnitPath $OrgUnitPath } $opts = @($vals | ForEach-Object { "$_" }) $note = "Roster import: $((@($opts) | Select-Object -Unique) -join ', ') (across $($opts.Count) targeted policy/policies)" $off = @($opts | Where-Object { $_ -match '(?i)^OFF$' }) $on = @($opts | Where-Object { $_ -match '(?i)^ON_' }) $known = @($opts | Where-Object { $_ -match '(?i)^(OFF|ON_CLEVER)$' }) if ($known.Count -ne $opts.Count) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Unrecognized roster-import value — verify manually that roster import is off — $note" ` -OrgUnitPath $OrgUnitPath } if ($on.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Roster import is enabled in $($on.Count) of $($opts.Count) targeted policy/policies — SCuBA recommends turning it off — $note" ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "Roster import is off in all $($off.Count) targeted policy/policies — $note" ` -OrgUnitPath $OrgUnitPath } # ── GWS-CLASS-005: Student Unenrollment Restricted (GWS.CLASSROOM.4.1v1) ───── function Test-FortificationGWSCLASS005 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # Policy API: classroom.student_unenrollment { whoCanUnenrollStudents=enum }. Secure = # teachers only. Known spellings: TEACHERS_ONLY (secure); STUDENTS_AND_TEACHERS / values # permitting students (insecure -> WARN, SHOULD control). Grade WEAKEST OU; unknown -> WARN. $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 'classroom.student_unenrollment' -Field 'whoCanUnenrollStudents') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No classroom.student_unenrollment policy returned for this tenant. Verify in Admin Console > Apps > Additional Google services > Classroom > Student unenrollment' ` -OrgUnitPath $OrgUnitPath } $opts = @($vals | ForEach-Object { "$_" }) $note = "Who can unenroll students: $((@($opts) | Select-Object -Unique) -join ', ') (across $($opts.Count) targeted policy/policies)" $teacher = @($opts | Where-Object { $_ -match '(?i)^TEACHERS_ONLY$' }) $student = @($opts | Where-Object { $_ -match '(?i)STUDENT' }) $known = @($opts | Where-Object { $_ -match '(?i)^(TEACHERS_ONLY|STUDENTS_AND_TEACHERS)$' }) if ($known.Count -ne $opts.Count) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Unrecognized student-unenrollment value — verify manually that only teachers may unenroll — $note" ` -OrgUnitPath $OrgUnitPath } if ($student.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Students may unenroll themselves in $($student.Count) of $($opts.Count) targeted policy/policies — SCuBA recommends teachers only — $note" ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "Only teachers may unenroll students in all $($teacher.Count) targeted policy/policies — $note" ` -OrgUnitPath $OrgUnitPath } # ── GWS-CLASS-006: Class Creation Restricted (GWS.CLASSROOM.5.1v1) ─────────── function Test-FortificationGWSCLASS006 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # Policy API: classroom.teacher_permissions { whoCanCreateClasses=enum }. Documented enums: # VERIFIED_TEACHERS_ONLY (secure), ALL_PENDING_AND_VERIFIED_TEACHERS (looser -> WARN), # ANYONE_IN_DOMAIN (open -> FAIL). Grade WEAKEST OU; unknown -> WARN. $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 'classroom.teacher_permissions' -Field 'whoCanCreateClasses') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'No classroom.teacher_permissions policy returned for this tenant. Verify in Admin Console > Apps > Additional Google services > Classroom > General settings > Teacher permissions' ` -OrgUnitPath $OrgUnitPath } $levels = @($vals | ForEach-Object { "$_" }) $note = "Who can create classes: $((@($levels) | Select-Object -Unique) -join ', ') (across $($levels.Count) targeted policy/policies)" $open = @($levels | Where-Object { $_ -match '(?i)^ANYONE_IN_DOMAIN$' }) $secure = @($levels | Where-Object { $_ -match '(?i)^VERIFIED_TEACHERS_ONLY$' }) $loose = @($levels | Where-Object { $_ -match '(?i)^ALL_PENDING_AND_VERIFIED_TEACHERS$' }) $known = @($levels | Where-Object { $_ -match '(?i)^(ANYONE_IN_DOMAIN|VERIFIED_TEACHERS_ONLY|ALL_PENDING_AND_VERIFIED_TEACHERS)$' }) if ($open.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "Anyone in the domain can create classes in $($open.Count) of $($levels.Count) targeted policy/policies — $note" ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'GWS.CLASSROOM.5.1 recommends restricting class creation to verified teachers only' } } if ($known.Count -ne $levels.Count) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Unrecognized class-creation value — verify manually that only verified teachers may create classes — $note" ` -OrgUnitPath $OrgUnitPath } if ($loose.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Pending (unverified) teachers may create classes in $($loose.Count) of $($levels.Count) targeted policy/policies — SCuBA recommends verified teachers only — $note" ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "Class creation restricted to verified teachers in all $($secure.Count) targeted policy/policies — $note" ` -OrgUnitPath $OrgUnitPath } # ══ GEMINI FOR WORKSPACE ═════════════════════════════════════════════════════ # HONESTY NOTE: the Cloud Identity Policy API and Admin SDK do not expose the granular # Gemini controls (Alpha features, conversation history, retention, sharing). ScubaGoggles # itself derives these from Admin audit-log events, which this read-only theater does not # replay. GEMINI-002..005 therefore return SKIP / Not Assessed with the Admin console path — # they never emit PASS. GEMINI-001 is a best-effort read of gemini_app.service_status if the # tenant exposes it; otherwise it also SKIPs. # ── GWS-GEMINI-001: Gemini App Access Restricted (GWS.GEMINI.1.1v1) ────────── function Test-FortificationGWSGEMINI001 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # Best-effort: the Policy API exposes gemini_app as an additional service, so its # service_status { serviceState } MAY be present. That is service on/off, NOT the precise # "OFF for users without a license" toggle — so even when readable we can only WARN that # the granular license-gating must be confirmed; we never claim a clean PASS for 1.1 from # service status alone. When the type is absent we SKIP (Not Assessed) honestly. $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 'gemini_app.service_status' -Field 'serviceState') if ($vals.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Gemini app access (OFF for users without a license) is not exposed via the Cloud Identity Policy API or Admin SDK. Not Assessed — verify in Admin Console > Generative AI > Gemini app > User access' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'The license-gating toggle for GWS.GEMINI.1.1 has no read API; ScubaGoggles derives Gemini settings from Admin audit-log events' } } $states = @($vals | ForEach-Object { "$_" }) $note = "Gemini app service state: $((@($states) | Select-Object -Unique) -join ', ') (across $($states.Count) targeted policy/policies)" $disabled = @($states | Where-Object { $_ -match '(?i)^DISABLED$' }) if ($disabled.Count -eq $states.Count) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "Gemini app service is disabled in all $($disabled.Count) targeted policy/policies — no unlicensed access surface — $note" ` -OrgUnitPath $OrgUnitPath } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "Gemini app service is enabled — the policy API exposes only service on/off, not the 'OFF for users without a license' toggle. Verify license-gating in Admin Console > Generative AI > Gemini app > User access — $note" ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'GWS.GEMINI.1.1 requires access OFF for users without a license; the license-gating toggle is not exposed by the read APIs' } } # ── GWS-GEMINI-002: Alpha Features Disabled (GWS.GEMINI.2.1v1) ─────────────── function Test-FortificationGWSGEMINI002 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # SKIP-only: no read API exposes the Gemini Alpha-features toggle. return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Gemini Alpha-features setting is not exposed via the Cloud Identity Policy API or Admin SDK. Not Assessed — verify in Admin Console > Generative AI > Gemini for Workspace > Alpha features (should be turned off)' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'No read API for GWS.GEMINI.2.1; ScubaGoggles derives this from Admin audit-log events, which this read-only theater does not replay' } } # ── GWS-GEMINI-003: Conversation History Enabled (GWS.GEMINI.3.1v1) ────────── function Test-FortificationGWSGEMINI003 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # SKIP-only: no read API exposes the Gemini conversation-history toggle. return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Gemini conversation-history setting is not exposed via the Cloud Identity Policy API or Admin SDK. Not Assessed — verify in Admin Console > Generative AI > Gemini app > Gemini conversation history (should be enabled)' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'No read API for GWS.GEMINI.3.1; ScubaGoggles derives this from Admin audit-log events' } } # ── GWS-GEMINI-004: Conversation Retention >= 18 Months (GWS.GEMINI.3.2v1) ─── function Test-FortificationGWSGEMINI004 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # SKIP-only: no read API exposes the Gemini conversation-retention period. return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Gemini conversation-retention period is not exposed via the Cloud Identity Policy API or Admin SDK. Not Assessed — verify in Admin Console > Generative AI > Gemini app > Gemini conversation history (retention should be at least 18 months)' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'No read API for GWS.GEMINI.3.2; ScubaGoggles derives this from Admin audit-log events' } } # ── GWS-GEMINI-005: Conversation Sharing Disabled (GWS.GEMINI.4.1v1) ───────── function Test-FortificationGWSGEMINI005 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition, [string]$OrgUnitPath = '/') # SKIP-only: no read API exposes the Gemini conversation-sharing toggle. return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Gemini conversation-sharing setting is not exposed via the Cloud Identity Policy API or Admin SDK. Not Assessed — verify in Admin Console > Generative AI > Gemini app > Sharing (conversation sharing via link should be OFF)' ` -OrgUnitPath $OrgUnitPath ` -Details @{ Note = 'No read API for GWS.GEMINI.4.1; ScubaGoggles derives this from Admin audit-log events' } } |