{
  "name": "TailscaleCCF",
  "apiVersion": "2022-09-01-preview",
  "type": "Microsoft.SecurityInsights/dataConnectorDefinitions",
  "kind": "Customizable",
  "properties": {
    "connectorUiConfig": {
      "title": "Tailscale Standard (CCF)",
      "publisher": "Community",
      "logo": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIGlkPSJMYXllcl8xIiB4PSIwIiB5PSIwIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48c3R5bGU+LnN0MHtvcGFjaXR5Oi4yO2VuYWJsZS1iYWNrZ3JvdW5kOm5ld308L3N0eWxlPjxwYXRoIGQ9Ik02NS42IDEyNy43YzM1LjMgMCA2My45LTI4LjYgNjMuOS02My45UzEwMC45IDAgNjUuNiAwIDEuOCAyOC42IDEuOCA2My45czI4LjYgNjMuOCA2My44IDYzLjgiIGNsYXNzPSJzdDAiLz48cGF0aCBkPSJNNjUuNiAzMTguMWMzNS4zIDAgNjMuOS0yOC42IDYzLjktNjMuOXMtMjguNi02My45LTYzLjktNjMuOVMxLjggMjE5IDEuOCAyNTQuMnMyOC42IDYzLjkgNjMuOCA2My45Ii8+PHBhdGggZD0iTTY1LjYgNTEyYzM1LjMgMCA2My45LTI4LjYgNjMuOS02My45cy0yOC42LTYzLjktNjMuOS02My45LTYzLjggMjguNy02My44IDYzLjlTMzAuNCA1MTIgNjUuNiA1MTIiIGNsYXNzPSJzdDAiLz48cGF0aCBkPSJNMjU3LjIgMzE4LjFjMzUuMyAwIDYzLjktMjguNiA2My45LTYzLjlzLTI4LjYtNjMuOS02My45LTYzLjktNjMuOSAyOC42LTYzLjkgNjMuOSAyOC42IDYzLjkgNjMuOSA2My45bTAgMTkzLjljMzUuMyAwIDYzLjktMjguNiA2My45LTYzLjlzLTI4LjYtNjMuOS02My45LTYzLjktNjMuOSAyOC42LTYzLjkgNjMuOSAyOC42IDYzLjkgNjMuOSA2My45Ii8+PHBhdGggZD0iTTI1Ny4yIDEyNy43YzM1LjMgMCA2My45LTI4LjYgNjMuOS02My45UzI5Mi41IDAgMjU3LjIgMHMtNjMuOSAyOC42LTYzLjkgNjMuOSAyOC42IDYzLjggNjMuOSA2My44bTE4OS4yIDBjMzUuMyAwIDYzLjktMjguNiA2My45LTYzLjlTNDgxLjYgMCA0NDYuNCAwYy0zNS4zIDAtNjMuOSAyOC42LTYzLjkgNjMuOXMyOC42IDYzLjggNjMuOSA2My44IiBjbGFzcz0ic3QwIi8+PHBhdGggZD0iTTQ0Ni40IDMxOC4xYzM1LjMgMCA2My45LTI4LjYgNjMuOS02My45cy0yOC42LTYzLjktNjMuOS02My45LTYzLjkgMjguNi02My45IDYzLjkgMjguNiA2My45IDYzLjkgNjMuOSIvPjxwYXRoIGQ9Ik00NDYuNCA1MTJjMzUuMyAwIDYzLjktMjguNiA2My45LTYzLjlzLTI4LjYtNjMuOS02My45LTYzLjktNjMuOSAyOC42LTYzLjkgNjMuOSAyOC42IDYzLjkgNjMuOSA2My45IiBjbGFzcz0ic3QwIi8+PC9zdmc+",
      "descriptionMarkdown": "Comprehensive Tailscale telemetry for **Personal (Free) and Standard** tier tailnets. Polls nine endpoints in one Connect:\n\n- `/logging/configuration` - configuration audit events (includes ACL, DNS, tag/group, settings changes)\n- `/devices` - device inventory (hostname, OS, IPs, tags, lastSeen, expiry)\n- `/users` - user inventory (role, status, deviceCount, connection state)\n- `/keys?all=true` - auth keys, API tokens, and OAuth client metadata\n- `/webhooks` - webhook configuration\n- `/dns/nameservers`, `/dns/preferences`, `/dns/searchpaths` - DNS state (merged into single `Tailscale_Dns_CL` table with `ConfigType` discriminator)\n- `/settings` - tailnet settings flags (device approval, key duration, etc.)\n\nSplit-DNS state (per-domain DNS overrides) is captured via the audit log rather than a separate snapshot table - every change is recorded with the full before/after document and actor attribution, which is richer than a periodic snapshot.\n\n**OAuth scopes required on the Tailscale client:** `logs:configuration:read`, `devices:core:read`, `users:read`, `auth_keys:read`, `webhooks:read`, `dns:read`, `feature_settings:read` (or the bundled `all:read`). For Premium and Enterprise tailnets that also need network flow logs and posture integrations, install **Tailscale Premium (CCF)** instead.",
      "graphQueries": [
        {
          "metricName": "Audit events",
          "legend": "Tailscale_Audit_CL",
          "baseQuery": "Tailscale_Audit_CL"
        },
        {
          "metricName": "Device snapshots",
          "legend": "Tailscale_Devices_CL",
          "baseQuery": "Tailscale_Devices_CL"
        },
        {
          "metricName": "User snapshots",
          "legend": "Tailscale_Users_CL",
          "baseQuery": "Tailscale_Users_CL"
        },
        {
          "metricName": "Auth keys",
          "legend": "Tailscale_Keys_CL",
          "baseQuery": "Tailscale_Keys_CL"
        },
        {
          "metricName": "Webhooks",
          "legend": "Tailscale_Webhooks_CL",
          "baseQuery": "Tailscale_Webhooks_CL"
        },
        {
          "metricName": "Tailnet settings",
          "legend": "Tailscale_Settings_CL",
          "baseQuery": "Tailscale_Settings_CL"
        },
        {
          "metricName": "DNS config snapshots",
          "legend": "Tailscale_Dns_CL",
          "baseQuery": "Tailscale_Dns_CL"
        }
      ],
      "dataTypes": [
        {
          "name": "Tailscale_Audit_CL",
          "lastDataReceivedQuery": "[format('{0} | summarize Time=max(TimeGenerated)', 'Tailscale_Audit_CL')]"
        },
        {
          "name": "Tailscale_Devices_CL",
          "lastDataReceivedQuery": "[format('{0} | summarize Time=max(TimeGenerated)', 'Tailscale_Devices_CL')]"
        },
        {
          "name": "Tailscale_Users_CL",
          "lastDataReceivedQuery": "[format('{0} | summarize Time=max(TimeGenerated)', 'Tailscale_Users_CL')]"
        },
        {
          "name": "Tailscale_Keys_CL",
          "lastDataReceivedQuery": "[format('{0} | summarize Time=max(TimeGenerated)', 'Tailscale_Keys_CL')]"
        },
        {
          "name": "Tailscale_Webhooks_CL",
          "lastDataReceivedQuery": "[format('{0} | summarize Time=max(TimeGenerated)', 'Tailscale_Webhooks_CL')]"
        },
        {
          "name": "Tailscale_Settings_CL",
          "lastDataReceivedQuery": "[format('{0} | summarize Time=max(TimeGenerated)', 'Tailscale_Settings_CL')]"
        },
        {
          "name": "Tailscale_Dns_CL",
          "lastDataReceivedQuery": "[format('{0} | summarize Time=max(TimeGenerated)', 'Tailscale_Dns_CL')]"
        }
      ],
      "sampleQueries": [
        {
          "description": "Recent audit events",
          "query": "Tailscale_Audit_CL\n| sort by TimeGenerated desc\n| take 100"
        },
        {
          "description": "Current device inventory (latest snapshot per device)",
          "query": "Tailscale_Devices_CL\n| summarize arg_max(TimeGenerated, *) by DeviceId\n| project DeviceName, User, Os, ClientVersion, LastSeen, Expires, Tags"
        },
        {
          "description": "Users by role",
          "query": "Tailscale_Users_CL\n| summarize arg_max(TimeGenerated, *) by UserId\n| summarize Users = count() by Role"
        },
        {
          "description": "Auth keys without an expiry",
          "query": "Tailscale_Keys_CL\n| summarize arg_max(TimeGenerated, *) by KeyId\n| where isnull(Expires) or Expires == datetime(null)"
        },
        {
          "description": "Tailnet device-approval status",
          "query": "Tailscale_Settings_CL\n| sort by TimeGenerated desc\n| take 1\n| project DevicesApprovalOn, DevicesAutoUpdatesOn, UsersApprovalOn"
        },
        {
          "description": "Current DNS state (all DNS config in one query)",
          "query": "Tailscale_Dns_CL\n| summarize arg_max(TimeGenerated, *) by ConfigType\n| project ConfigType, Nameservers, MagicDNS, SearchPaths"
        }
      ],
      "connectivityCriteria": [
        {
          "type": "HasDataConnectors"
        }
      ],
      "availability": {
        "status": 1,
        "isPreview": true
      },
      "permissions": {
        "resourceProvider": [
          {
            "provider": "Microsoft.OperationalInsights/workspaces",
            "permissionsDisplayText": "Read/Write on the workspace",
            "providerDisplayName": "Workspace",
            "scope": "Workspace",
            "requiredPermissions": {
              "write": true,
              "read": true,
              "delete": true
            }
          }
        ]
      },
      "instructionSteps": [
        {
          "title": "Connect Tailscale",
          "description": "Generate an OAuth client at https://login.tailscale.com/admin/settings/oauth with these **Read** scopes: Logs > Configuration, General > DNS, General > Users, Devices > Core, Keys > Auth Keys, Keys > Webhooks, Settings > Feature Settings (or tick `all:read` to grant all read scopes at once). Find your tailnet name on the Keys page.",
          "instructions": [
            {
              "type": "Textbox",
              "parameters": {
                "label": "Tailscale tailnet",
                "placeholder": "tail-XXXX.ts.net",
                "type": "text",
                "name": "tailnetName"
              }
            },
            {
              "type": "Textbox",
              "parameters": {
                "label": "OAuth Client ID",
                "placeholder": "k...",
                "type": "text",
                "name": "clientId"
              }
            },
            {
              "type": "Textbox",
              "parameters": {
                "label": "OAuth Client Secret",
                "placeholder": "tskey-client-...",
                "type": "password",
                "name": "clientSecret"
              }
            },
            {
              "type": "ConnectionToggleButton",
              "parameters": {
                "connectLabel": "Connect",
                "disconnectLabel": "Disconnect"
              }
            }
          ]
        }
      ],
      "id": "TailscaleCCF"
    }
  }
}