Query Details

Summary Of First Party Service Principals

Query

// Overview of all First Party Apps enriched with Sign-in events, activities in Microsoft Graph and Entra ID Audit Logs and enriched with WorkloadIdentityInfo
// Include also Microsoft apps without AppId from the Audit Logs (AadAuditActivityByUnknown)
// Requires AuditLogs, MicrosoftGraphActivityLogs and deployment of WorkloadIdentityInfo
// More details on deploying WorkloadIdentityInfo: https://www.cloud-architekt.net/entra-workload-id-advanced-detection-enrichment/#publish-watchlist-workloadidentityinfo-with-sentinelenrichment
let Lookback = 90d;
// Get list of TenantIds and Classified First Party Apps from WorkloadIdentityInfo
let FirstPartyAppOwnerTenantId = dynamic(['f8cdef31-a31e-4b4a-93e4-5f571e91255a', '72f988bf-86f1-41af-91ab-2d7cd011db47']);
let FirstPartyApps = _GetWatchlist('WorkloadIdentityInfo')
    | where IsFirstPartyApp == "true" or AppOwnerTenantId in~ (FirstPartyAppOwnerTenantId) or AppDisplayName contains "entraops"
    | extend Identity = tostring(ServicePrincipalObjectId)
    | extend AppId = tostring(AppId);
// Get list of signins from First Party Apps
let SignInEvents = FirstPartyApps 
    | join kind=inner (
        AADServicePrincipalSignInLogs
        | where TimeGenerated >ago(Lookback)
    ) on AppId
    | summarize UniqueTokenIdentifiers = make_set(UniqueTokenIdentifier), IPAddresses = make_set(IPAddress), Locations = make_set(Location), Application = make_set(AppDisplayName), Resource = make_set(ResourceDisplayName) by AppId;
// Get list of Graph Activity from 1st Party Apps
let GraphActivity = FirstPartyApps
| join kind=inner (
MicrosoftGraphActivityLogs
    | where TimeGenerated > ago(Lookback)
    // Filter out GET operations
    | where RequestMethod != "GET"
    | extend Roles = split(Roles, ' ')
    | extend Identity = ServicePrincipalId
    | extend ParsedUri = parse_url(RequestUri)
    | extend NormalizedRequestUri = tostring(ParsedUri.Path)
    | extend NormalizedRequestUri = replace_string(NormalizedRequestUri, '//', '/')
    | extend NormalizedRequestUri = replace_regex(NormalizedRequestUri, @'[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}', @'<UUID>')
    | extend Operations = bag_pack_columns(
                        RequestMethod,
                        NormalizedRequestUri
                    )
    | summarize
        GraphOperations = make_set(Operations),
        GraphTotalResponseSizeBytes = sum(ResponseSizeBytes)
        by Identity
    ) on Identity
| project AppDisplayName, AppId, AppOwnerTenantId, VerifiedPublisher, GraphOperations, CreatedDateTime, AssignedRoles, GraphTotalResponseSizeBytes;
// Get list of empty initator in Microsoft Entra Audit log which are identifier for other backend jobs
let AadAuditActivityByUnknown = AuditLogs
    | where TimeGenerated >ago(Lookback) and InitiatedBy == "{}"
    | extend OperationId = Id
    | extend AppDisplayName = Identity
    | extend AadOperation = bag_pack_columns(
        ActivityDateTime,
        ActivityDisplayName,
        CorrelationId,
        OperationName,
        OperationId
      )
    | summarize AadOperations = make_set( AadOperation ) by AppDisplayName
    | extend OperationsActivity = iff(isnotempty(AadOperations), true, false);
// Get list of Microsoft Entra Audit log from 1st Party Apps
let AadAuditActivity = _GetWatchlist('WorkloadIdentityInfo')
    | where IsFirstPartyApp == "true" or AppOwnerTenantId in~ (FirstPartyAppOwnerTenantId)
    | extend Identity = tostring(AppDisplayName)
    | join kind=inner ( AuditLogs
        | extend OperationId = Id
        | where TimeGenerated >ago(Lookback)
    ) on Identity
    | extend AadOperation = bag_pack_columns(
        ActivityDateTime,
        ActivityDisplayName,
        CorrelationId,
        OperationName,
        OperationId
      )
    | summarize AadOperations = make_set( AadOperation ) by tostring(AppId);
// Get list of operations to issue credential on 1st Party Apps
let CredentialOperations = AuditLogs
    | where TimeGenerated >ago(Lookback)
    // Captures "Add service principal", "Add service principal credentials", and "Update application - Certificates and secrets management" events
    | where OperationName has_any ("Add service principal", "Certificates and secrets management", "Update application")
    | where Result =~ "success"
    | extend InitiatingUserOrAppId = iff(isnotempty(InitiatedBy.user.id), tostring(InitiatedBy.user.id), tostring(InitiatedBy.app.id))
    | extend InitiatingIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), tostring(InitiatedBy.user.ipAddress), tostring(InitiatedBy.app.ipAddress))
    | mv-apply TargetResource = TargetResources on 
        (
        where TargetResource.type =~ "Application" or TargetResource.type =~ "ServicePrincipal"
        | extend
            TargetName = tostring(TargetResource.displayName),
            ResourceId = tostring(TargetResource.id),
            WorkloadIdentityObjectType = tostring(TargetResource.type),
            keyEvents = TargetResource.modifiedProperties
        )
    | mv-apply Property = keyEvents on 
        (
        where Property.displayName =~ "KeyDescription" or Property.displayName =~ "FederatedIdentityCredentials"
        | extend
            new_value_set = parse_json(tostring(Property.newValue)),
            old_value_set = parse_json(tostring(Property.oldValue))
        )
    | extend diff = set_difference(new_value_set, old_value_set)
    | where isnotempty(diff)
    | parse diff with * "KeyIdentifier=" keyIdentifier: string ",KeyType=" keyType: string ",KeyUsage=" keyUsage: string ",DisplayName=" keyDisplayName: string "]" *
    | where keyUsage =~ "Verify" or isnotempty(parse_json(tostring(diff[0].Audiences))[0])
    | mv-apply AdditionalDetail = AdditionalDetails on 
        (
        where AdditionalDetail.key =~ "User-Agent"
        | extend UserAgent = tostring(AdditionalDetail.value)
        )
    | mv-apply AdditionalDetail = AdditionalDetails on 
        (
        where AdditionalDetail.key =~ "AppId"
        | extend AppId = tostring(AdditionalDetail.value)
        )
    | join kind=inner ( FirstPartyApps ) on AppId
    | extend CredentialName = iff(isnotempty(keyDisplayName), keyDisplayName, diff[0].Name)
    | extend CredentialIdentifier = iff(isnotempty(keyIdentifier), keyIdentifier, diff[0].Subject)
    | extend CredentialType = iff(isnotempty(keyType), keyType, keyEvents[0].displayName)
    | extend CredentialUsage = iff(isnotempty(keyUsage), keyUsage, tostring(diff[0].Audiences))
    | extend CredentialOperation = bag_pack_columns(
        TimeGenerated,
        OperationName,
        CredentialName,
        CredentialType,
        CredentialUsage,
        UserAgent,
        InitiatingUserOrAppId,
        InitiatingIpAddress
        )
    | summarize CredentialOperations = make_set(CredentialOperation) by AppId;
// Merge data from different queries of known Service Principals
let KnownServicePrincipals = FirstPartyApps
    | join kind=leftouter ( SignInEvents ) on AppId
    | join kind=leftouter ( AadAuditActivity ) on AppId
    | join kind=leftouter ( GraphActivity ) on AppId
    | join kind=leftouter ( CredentialOperations ) on AppId
    | extend AddedCredential = iff(isnotempty(CredentialOperations), true, false)
    | extend SignInActivity = iff(isnotempty(UniqueTokenIdentifiers), true, false)
    | extend OperationsActivity = iff(isnotempty(GraphOperations) or isnotempty(AadOperations), true, false)
    | project AppId, AppDisplayName, AppOwnerTenantId, CreatedDateTime, SignInActivity, OperationsActivity, AddedCredential, VerifiedPublisher, IPAddresses, Locations, Application, Resource, GraphOperations, GraphTotalResponseSizeBytes, AadOperations, CredentialOperations;
union KnownServicePrincipals, AadAuditActivityByUnknown
    | sort by tostring(AppDisplayName) asc

Explanation

This KQL query provides a comprehensive overview of all First Party Apps, enriched with various types of activity logs and additional information. Here's a simplified breakdown:

  1. Lookback Period: The query looks back 90 days.

  2. First Party Apps Identification:

    • Retrieves a list of First Party Apps from a watchlist called WorkloadIdentityInfo.
    • Filters apps based on specific criteria (e.g., IsFirstPartyApp is true, certain tenant IDs, or app names containing "entraops").
  3. Sign-In Events:

    • Joins the list of First Party Apps with Azure Active Directory (AAD) sign-in logs.
    • Summarizes unique token identifiers, IP addresses, locations, applications, and resources by AppId.
  4. Microsoft Graph Activity:

    • Joins the list of First Party Apps with Microsoft Graph activity logs.
    • Filters out GET operations and normalizes request URIs.
    • Summarizes Graph operations and total response size by Identity.
  5. Entra ID Audit Logs (Unknown Initiators):

    • Retrieves audit logs with empty initiators (indicating backend jobs).
    • Summarizes operations by AppDisplayName.
  6. Entra ID Audit Logs (First Party Apps):

    • Joins the list of First Party Apps with Entra ID audit logs.
    • Summarizes operations by AppId.
  7. Credential Operations:

    • Retrieves audit logs related to credential operations (e.g., adding service principals, managing certificates and secrets).
    • Extracts and processes details about these operations.
    • Joins with the list of First Party Apps and summarizes credential operations by AppId.
  8. Merging Data:

    • Merges data from the different queries (sign-in events, audit logs, Graph activity, credential operations) for known service principals.
    • Adds flags to indicate the presence of sign-in activity, operations activity, and added credentials.
    • Projects relevant columns for the final output.
  9. Union with Unknown Initiators:

    • Combines the known service principals data with the audit logs of unknown initiators.
    • Sorts the final result by AppDisplayName in ascending order.

In essence, this query aggregates and enriches data from multiple sources to provide a detailed view of First Party Apps' activities, including sign-ins, Graph activities, audit logs, and credential operations.

Details

Thomas Naunheim profile picture

Thomas Naunheim

Released: August 9, 2024

Tables

AADServicePrincipalSignInLogsMicrosoftGraphActivityLogsAuditLogsWorkloadIdentityInfo

Keywords

AuditLogsMicrosoftGraphActivityLogsWorkloadIdentityInfoAADServicePrincipalSignInLogsFirstPartyAppsSignInEventsGraphActivityAadAuditActivityByUnknownCredentialOperationsKnownServicePrincipals

Operators

letdynamic_GetWatchlistwhereorin~containsextendtostringjoinkindonsummarizemake_setbyprojectagosplitparse_urlreplace_stringreplace_regexbag_pack_columnsiffisnotemptymv-applyparse_jsonset_differenceparseunionsortasc.

Actions