Checks/Applications/ARG.APP.001.ps1

Register-ArgusCheck @{
    Id          = 'ARG.APP.001'
    Title       = 'App registrations without owners'
    Area        = 'Applications'
    Category    = 'Application governance'
    Severity    = 'Medium'
    Description = 'App registrations with no owner. The findings show how many secrets and certificates each app holds: an ownerless app with credentials is the first to review.'
    Rationale   = 'Without an owner nobody is accountable for the app''s permissions, credentials and lifecycle, and nobody notices when it is no longer needed.'
    Remediation = @{
        Steps      = "1. Identify who uses the application (sign-in logs, credentials, API permissions help).`n2. Assign at least one owner (two is better) in App registrations > Owners.`n3. Delete applications that nobody claims after a grace period."
        PowerShell = "# Sample only - review before running. Adds an owner to one app registration.`nConnect-MgGraph -Scopes 'Application.ReadWrite.All'`nNew-MgApplicationOwnerByRef -ApplicationId '<ObjectId>' -BodyParameter @{ '@odata.id' = 'https://graph.microsoft.com/v1.0/directoryObjects/<OwnerObjectId>' }"
        PortalUrl  = 'https://entra.microsoft.com/'
    }
    References  = @(
        @{ Title = 'Security best practices for application properties (app ownership)'; Url = 'https://learn.microsoft.com/en-us/entra/identity-platform/security-best-practices-for-app-registration' }
        @{ Title = 'Assign enterprise application owners'; Url = 'https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/assign-app-owners' }
    )
    Requires    = @{
        Services    = @('Graph')
        GraphScopes = @('Application.Read.All')
        DataSets    = @('Applications')
    }
    Tags        = @('Applications', 'Ownership')
    Test        = {
        param($Context)
        $apps = @($Context.Data['Applications'] | Where-Object { $_ })
        $findings = @($apps | Where-Object { [int](Get-ArgusProperty $_ 'OwnerCount' 0) -eq 0 } | ForEach-Object {
                [pscustomobject]@{
                    AppName        = Get-ArgusProperty $_ 'displayName' ''
                    AppId          = Get-ArgusProperty $_ 'appId' ''
                    Created        = Format-ArgusDate (Get-ArgusProperty $_ 'createdDateTime')
                    SignInAudience = Get-ArgusProperty $_ 'signInAudience' ''
                    Secrets        = @(Get-ArgusProperty $_ 'passwordCredentials' @()).Count
                    Certificates   = @(Get-ArgusProperty $_ 'keyCredentials' @()).Count
                    ObjectId       = Get-ArgusProperty $_ 'id' ''
                }
            } | Sort-Object @{ Expression = { $_.Secrets + $_.Certificates }; Descending = $true }, AppName)
        $status = if ($findings.Count -gt 0) { 'Failed' } else { 'Passed' }
        New-ArgusResult -Status $status -Findings $findings -EvaluatedCount $apps.Count `
            -Summary '{AffectedCount} of {EvaluatedCount} app registrations have no owner.'
    }
}