{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "metadata": {
    "description": "Microsoft Sentinel Codeless Connector Framework (CCF) connector for the Onplana audit log. Sentinel polls GET https://api.onplana.com/api/audit-logs with an Onplana personal access token carrying the AUDIT_READ scope; no Onplana code runs in Sentinel. Deploys a custom table, a data collection endpoint and rule, the connector definition shown in the Data connectors gallery, and the RestApiPoller that does the polling. See README.md in this folder."
  },
  "parameters": {
    "workspace": {
      "type": "string",
      "metadata": { "description": "Name of the Log Analytics workspace that Microsoft Sentinel is enabled on." }
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": { "description": "Region of the workspace." }
    },
    "apiKey": {
      "type": "securestring",
      "metadata": { "description": "An Onplana personal access token with only the AUDIT_READ scope, minted by an Owner or Admin of the Enterprise workspace (Settings > Developer). Starts with pat_." }
    },
    "apiBaseUrl": {
      "type": "string",
      "defaultValue": "https://api.onplana.com",
      "metadata": { "description": "Onplana API origin. Change only for a dedicated (Enterprise+) deployment." }
    },
    "pollIntervalMinutes": {
      "type": "int",
      "defaultValue": 5,
      "minValue": 1,
      "maxValue": 60,
      "metadata": { "description": "Length of each polling window in minutes." }
    }
  },
  "variables": {
    "tableName": "OnplanaAudit_CL",
    "streamName": "Custom-OnplanaAudit",
    "connectorDefinitionName": "OnplanaAuditLogDefinition",
    "dataConnectorName": "OnplanaAuditLogPoller",
    "dceName": "[concat('onplana-audit-dce-', uniqueString(resourceGroup().id, parameters('workspace')))]",
    "dcrName": "[concat('onplana-audit-dcr-', uniqueString(resourceGroup().id, parameters('workspace')))]",
    "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace'))]"
  },
  "resources": [
    {
      "type": "Microsoft.OperationalInsights/workspaces/tables",
      "apiVersion": "2022-10-01",
      "name": "[concat(parameters('workspace'), '/', variables('tableName'))]",
      "properties": {
        "plan": "Analytics",
        "schema": {
          "name": "[variables('tableName')]",
          "columns": [
            { "name": "TimeGenerated",  "type": "datetime", "description": "When Onplana recorded the event (createdAt)." },
            { "name": "EventId",        "type": "string",   "description": "Onplana audit row id." },
            { "name": "Action",         "type": "string",   "description": "What happened, for example LOGIN, CREATE_PROJECT, API_TOKEN_CREATED." },
            { "name": "Resource",       "type": "string",   "description": "The kind of thing acted on, for example AUTH, PROJECT, TASK, USER." },
            { "name": "ResourceId",     "type": "string",   "description": "Id of the thing acted on." },
            { "name": "UserId",         "type": "string",   "description": "Id of the actor (SCIM when the identity provider acted)." },
            { "name": "UserEmail",      "type": "string",   "description": "Email of the actor." },
            { "name": "OrganizationId", "type": "string",   "description": "Onplana workspace id." },
            { "name": "IpAddress",      "type": "string",   "description": "Client IP address of the request." },
            { "name": "UserAgent",      "type": "string",   "description": "Client user agent of the request." },
            { "name": "ActorType",      "type": "string",   "description": "OWNER, ADMIN, USER, GUEST, SYSTEM, SUPER_ADMIN or PLATFORM_ADMIN." },
            { "name": "ClientSurface",  "type": "string",   "description": "Channel: WEB, MAKER, EXTENSION, DESKTOP, MOBILE, MCP, PAT, SYSTEM or UNKNOWN. Empty on rows written before 2026-07-25." },
            { "name": "ClientLabel",    "type": "string",   "description": "The client behind the channel. Free text supplied by the client; treat it like a user agent." },
            { "name": "IsSystem",       "type": "boolean",  "description": "True when Onplana itself acted." },
            { "name": "Details",        "type": "dynamic",  "description": "Event details as Onplana recorded them." }
          ]
        }
      }
    },
    {
      "type": "Microsoft.Insights/dataCollectionEndpoints",
      "apiVersion": "2022-06-01",
      "name": "[variables('dceName')]",
      "location": "[parameters('location')]",
      "properties": {
        "networkAcls": { "publicNetworkAccess": "Enabled" }
      }
    },
    {
      "type": "Microsoft.Insights/dataCollectionRules",
      "apiVersion": "2022-06-01",
      "name": "[variables('dcrName')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspace'), variables('tableName'))]",
        "[resourceId('Microsoft.Insights/dataCollectionEndpoints', variables('dceName'))]"
      ],
      "properties": {
        "dataCollectionEndpointId": "[resourceId('Microsoft.Insights/dataCollectionEndpoints', variables('dceName'))]",
        "streamDeclarations": {
          "[variables('streamName')]": {
            "columns": [
              { "name": "id",             "type": "string" },
              { "name": "createdAt",      "type": "datetime" },
              { "name": "action",         "type": "string" },
              { "name": "resource",       "type": "string" },
              { "name": "resourceId",     "type": "string" },
              { "name": "userId",         "type": "string" },
              { "name": "userEmail",      "type": "string" },
              { "name": "organizationId", "type": "string" },
              { "name": "ipAddress",      "type": "string" },
              { "name": "userAgent",      "type": "string" },
              { "name": "actorType",      "type": "string" },
              { "name": "clientSurface",  "type": "string" },
              { "name": "clientLabel",    "type": "string" },
              { "name": "isSystem",       "type": "boolean" },
              { "name": "details",        "type": "dynamic" }
            ]
          }
        },
        "destinations": {
          "logAnalytics": [
            { "workspaceResourceId": "[variables('workspaceResourceId')]", "name": "onplanaWorkspace" }
          ]
        },
        "dataFlows": [
          {
            "streams": [ "[variables('streamName')]" ],
            "destinations": [ "onplanaWorkspace" ],
            "outputStream": "[concat('Custom-', variables('tableName'))]",
            "transformKql": "source | project TimeGenerated = createdAt, EventId = id, Action = action, Resource = resource, ResourceId = resourceId, UserId = userId, UserEmail = userEmail, OrganizationId = organizationId, IpAddress = ipAddress, UserAgent = userAgent, ActorType = actorType, ClientSurface = clientSurface, ClientLabel = clientLabel, IsSystem = isSystem, Details = details"
          }
        ]
      }
    },
    {
      "type": "Microsoft.OperationalInsights/workspaces/providers/dataConnectorDefinitions",
      "apiVersion": "2022-09-01-preview",
      "name": "[concat(parameters('workspace'), '/Microsoft.SecurityInsights/', variables('connectorDefinitionName'))]",
      "kind": "Customizable",
      "properties": {
        "connectorUiConfig": {
          "id": "[variables('connectorDefinitionName')]",
          "title": "Onplana Audit Log",
          "publisher": "Onplana",
          "descriptionMarkdown": "Brings the Onplana workspace audit log (sign-ins, permission and role changes, token creation, project, task and settings changes) into Microsoft Sentinel. Sentinel polls the Onplana API every few minutes with a personal access token that can read the audit log and nothing else.",
          "graphQueriesTableName": "[variables('tableName')]",
          "graphQueries": [
            { "metricName": "Total events received", "legend": "Onplana audit events", "baseQuery": "[variables('tableName')]" }
          ],
          "sampleQueries": [
            { "description": "Latest Onplana audit events", "query": "[concat(variables('tableName'), ' | sort by TimeGenerated desc | take 50')]" },
            { "description": "Tokens created that can read the audit log", "query": "[concat(variables('tableName'), ' | where Action == \"AUDIT_READ_TOKEN_CREATED\"')]" },
            { "description": "Events by actor in the last day", "query": "[concat(variables('tableName'), ' | where TimeGenerated > ago(1d) | summarize count() by UserEmail, Action')]" }
          ],
          "dataTypes": [
            { "name": "[variables('tableName')]", "lastDataReceivedQuery": "[concat(variables('tableName'), ' | summarize Time = max(TimeGenerated) | where isnotempty(Time)')]" }
          ],
          "connectivityCriteria": [
            { "type": "HasDataConnectors" }
          ],
          "permissions": {
            "resourceProvider": [
              {
                "provider": "Microsoft.OperationalInsights/workspaces",
                "permissionsDisplayText": "Read and write permissions on the workspace are required.",
                "providerDisplayName": "Workspace",
                "scope": "Workspace",
                "requiredPermissions": { "write": true, "read": true, "delete": true }
              }
            ],
            "customs": [
              {
                "name": "Onplana Enterprise",
                "description": "An Onplana workspace on Enterprise, with audit log tokens for a SIEM turned on, and an Owner or Admin to create the token."
              }
            ]
          },
          "instructionSteps": [
            {
              "title": "Create an Onplana token that reads the audit log",
              "description": "In Onplana, as an Owner or Admin, open Settings > Developer > Personal access tokens, create a token with only the 'Audit log for a SIEM' (AUDIT_READ) scope, and copy it. It is shown once."
            },
            {
              "title": "Connect",
              "description": "Paste the token below and select Connect.",
              "instructions": [
                { "type": "Textbox", "parameters": { "label": "Onplana token (pat_...)", "placeholder": "pat_...", "type": "password", "name": "apikey" } },
                { "type": "ConnectionToggleButton", "parameters": { "connectLabel": "Connect", "name": "connect" } }
              ]
            }
          ]
        }
      }
    },
    {
      "type": "Microsoft.OperationalInsights/workspaces/providers/dataConnectors",
      "apiVersion": "2022-12-01-preview",
      "name": "[concat(parameters('workspace'), '/Microsoft.SecurityInsights/', variables('dataConnectorName'))]",
      "kind": "RestApiPoller",
      "dependsOn": [
        "[resourceId('Microsoft.Insights/dataCollectionRules', variables('dcrName'))]",
        "[resourceId('Microsoft.OperationalInsights/workspaces/providers/dataConnectorDefinitions', parameters('workspace'), 'Microsoft.SecurityInsights', variables('connectorDefinitionName'))]"
      ],
      "properties": {
        "connectorDefinitionName": "[variables('connectorDefinitionName')]",
        "dataType": "[variables('tableName')]",
        "dcrConfig": {
          "streamName": "[variables('streamName')]",
          "dataCollectionEndpoint": "[reference(resourceId('Microsoft.Insights/dataCollectionEndpoints', variables('dceName')), '2022-06-01').logsIngestion.endpoint]",
          "dataCollectionRuleImmutableId": "[reference(resourceId('Microsoft.Insights/dataCollectionRules', variables('dcrName')), '2022-06-01').immutableId]"
        },
        "auth": {
          "type": "APIKey",
          "ApiKeyName": "Authorization",
          "ApiKeyIdentifier": "Bearer",
          "ApiKey": "[parameters('apiKey')]"
        },
        "request": {
          "apiEndpoint": "[concat(parameters('apiBaseUrl'), '/api/audit-logs')]",
          "httpMethod": "GET",
          "queryWindowInMin": "[parameters('pollIntervalMinutes')]",
          "queryTimeFormat": "yyyy-MM-ddTHH:mm:ssZ",
          "startTimeAttributeName": "since",
          "endTimeAttributeName": "until",
          "queryParameters": { "limit": 1000 },
          "rateLimitQPS": 1,
          "retryCount": 3,
          "timeoutInSeconds": 60,
          "headers": { "Accept": "application/json", "User-Agent": "Microsoft-Sentinel-Onplana-CCF" }
        },
        "paging": {
          "pagingType": "NextPageToken",
          "nextPageTokenJsonPath": "$.nextCursor",
          "nextPageParaName": "cursor",
          "hasNextFlagJsonPath": "$.hasMore"
        },
        "response": {
          "eventsJsonPaths": [ "$.events" ],
          "format": "json"
        }
      }
    }
  ]
}
