Skip to content

Add tailscale ccf solution#14482

Open
noodlemctwoodle wants to merge 28 commits into
Azure:masterfrom
noodlemctwoodle:add-tailscale-ccf-solution
Open

Add tailscale ccf solution#14482
noodlemctwoodle wants to merge 28 commits into
Azure:masterfrom
noodlemctwoodle:add-tailscale-ccf-solution

Conversation

@noodlemctwoodle

Copy link
Copy Markdown
Contributor

Summary

New community CCF solution that ingests identity, device, configuration, audit and (Premium) network-flow telemetry from the Tailscale REST API into Microsoft Sentinel.

Two CCF data connectors matching Tailscale plan tiers — install whichever matches the operator's tailnet:

  • Tailscale Standard (CCF) — 9 polling rules, 7 custom tables. Use on Personal (Free), Starter and Premium tailnets.
  • Tailscale Premium (CCF) — 11 polling rules, 9 custom tables (adds /logging/network flow logs + /posture/integrations). Use on Premium and Enterprise tailnets.

Both connectors deploy from a single Connect click using OAuth2 client credentials (Proofpoint TAP multi-poller pattern with guidValue + innerWorkspace).

Contents

  • 2 CCF data connectors + 2 DCRs + 9 custom tables (Tailscale_Audit_CL, Tailscale_Devices_CL, Tailscale_Users_CL, Tailscale_Keys_CL, Tailscale_Webhooks_CL, Tailscale_Settings_CL, Tailscale_Dns_CL, Tailscale_Network_CL (Premium), Tailscale_PostureIntegrations_CL (Premium))
  • 24 analytic rules — 16 Standard tier (identity & access, configuration, devices, network & exit, DNS) + 8 Premium tier (beaconing detection, DERP relay surge, large outbound transfer, mass fan-out, subnet router throughput anomaly, unexpected exit-node egress, posture integration added/disabled)
  • 22 hunting queries — 12 Standard (first-seen actors, ACL policy churn, off-hours config changes, auth key sprawl/no-expiry, dormant devices, orphaned users, etc.) + 10 Premium (beaconing candidates, cross-tag flow matrix, DERP relay persistence, exit-node usage, new src→dst node pairs, off-hours flows, posture inventory, tagged service fan-in, top talkers, user multi-device)
  • 2 operations workbooks (Standard 9-tab + Premium variant adding Network Flows + Posture tabs)
  • 2 ASIM NetworkSession parsers (vimNetworkSessionTailscale + ASimNetworkSessionTailscale wrapper) mapping Tailscale_Network_CL to ASIM NetworkSession v0.2.6

Design notes

  • DCR transform promotes 17 columns out of srcNode/dstNodes/traffic arrays (SrcUser/SrcNodeName/SrcOs/SrcTags/SrcAddresses/DstCount/DstNodeId/DstNodeName/DstUser/DstOs/DstTags/DstAddresses + HasVirtualTraffic/HasSubnetTraffic/HasExitTraffic/HasPhysicalTraffic/IsRelayed) so hunting queries don't have to traverse dynamic JSON. IsRelayed detects DERP relay flows via the 127.3.3.40 magic IP in physicalTraffic.
  • DNS endpoint consolidation: Tailscale exposes DNS config across three endpoints (/dns/nameservers, /dns/preferences, /dns/searchpaths). Both connectors land them into one Tailscale_Dns_CL table with a ConfigType discriminator column. Premium uses a single unified input stream (Custom-Tailscale_DnsConfig_CL) so all 11 pollers stay within Azure's 10-dataFlow DCR limit; Standard uses 3-streams-in / 1-stream-out.
  • OAuth required, not personal API tokens: Tailscale's /logging/configuration, /logging/network, posture, and DNS endpoints fail closed only against OAuth scopes - personal API tokens (tskey-api-...) silently return HTTP 200 with empty arrays, hiding the misconfiguration.
  • Multi-poller-per-card pattern: 9-11 RestApiPoller dataConnectors land behind a single Connect button by sharing a guidValue parameter (defaultValue: '[newGuid()]') that defers evaluation to inner-deploy scope, producing one shared GUID across every poller resource name. Mirrors the Proofpoint TAP pattern in Solutions/ProofPointTap/.

Test plan

  • Source files validate against .script/local-validation/build-and-validate.ps1 (5 passed, 0 failed, 3 skipped — .NET-3.1 detection-schema/non-ASCII and TruffleHog tools absent on the local macOS runner; ASCII + duration + description checks substituted)
  • ARM-TTK 48 / 48 PASS
  • KQL validation PASS (56 files, including 9 Tailscale_*_CL CustomTables schemas)
  • Hyperlink validation PASS
  • Field Types + Classic App Insights PASS
  • Package/mainTemplate.json deploys end-to-end to a live Sentinel workspace; Standard + Premium connectors both Connect successfully
  • All 9 custom tables receive data; network flow logs (Premium-only) emit rows per poll cycle
  • All 24 analytic rules deploy via REST and instantiate without error
  • Both workbooks render with no broken queries

Microsoft Sentinel solution for ingesting Tailscale audit and network
telemetry via OAuth2-secured APIs. Targets the Codeless Connector
Framework (CCF) so no Azure Function code is required.

The solution ships two complementary data connectors split along the
boundary that Tailscale's pricing draws — configuration audit is
available on every plan, network flow logs require Premium or above.
Users install the connector that matches their tailnet tier:

- Tailscale Standard (CCF)
  - Endpoint: /logging/configuration
  - Plans: Personal (Free) + Standard
  - Custom table: Tailscale_Configuration_CL
  - Status: shipping in this release

- Tailscale Premium (CCF)
  - Endpoints: /logging/configuration + /logging/network (two pollers,
    one OAuth client, one Connect)
  - Plans: Premium + Enterprise
  - Custom tables: Tailscale_Configuration_CL + Tailscale_Network_CL
  - Status: scaffolded but awaiting validation against a real Premium
    tailnet (network flow log shape verified against Tailscale docs)

Files added:
- Logos/Tailscale.svg
- Solutions/Tailscale (CCF)/README.md
- Solutions/Tailscale (CCF)/ReleaseNotes.md
- Solutions/Tailscale (CCF)/SolutionMetadata.json
- Solutions/Tailscale (CCF)/Data/Solution_Tailscale.json (manifest)
- Solutions/Tailscale (CCF)/Tailscale.svg (per-solution copy)
- Solutions/Tailscale (CCF)/Data Connectors/TailscaleAuditLogs_ccf/
  - Tailscale_ConnectorDefinition.json (UI card, OAuth2 form)
  - Tailscale_PollerConfig.json (single RestApiPoller, client_credentials)
  - Tailscale_DCR.json (Custom-Tailscale_Configuration_CL stream)
  - Tailscale_tables.json (Tailscale_Configuration_CL)
- Solutions/Tailscale (CCF)/Data Connectors/TailscalePremium_ccf/
  - TailscalePremium_ConnectorDefinition.json
  - TailscalePremium_PollerConfig.json (two pollers, same OAuth client)
  - TailscalePremium_DCR.json (two streams + dataFlows)
  - TailscalePremium_tables.json (both custom tables)
- Solutions/Tailscale (CCF)/Analytic Rules/ (5 detections)
  - TailscaleNewAPIaccesstokenorOAuthclientcreated.yaml (Medium)
  - TailscalePolicyfileACLmodified.yaml (Medium)
  - TailscaleAuthkeycreated.yaml (Low)
  - TailscaleExitnodeadvertisedorapproved.yaml (Low)
  - TailscaleMasscredentialrevocationinshortwindow.yaml (High)
- Solutions/Tailscale (CCF)/Package/ (createSolutionV3 build output —
  mainTemplate.json, createUiDefinition.json, testParameters.json,
  3.0.1.zip)

Implementation notes:
- OAuth2 client_credentials auth wired with the PascalCase field names
  expected by createCCPConnector.ps1 (ClientId, ClientSecret, GrantType,
  TokenEndpoint, TokenEndpointHeaders, TokenEndpointQueryParameters).
  The earlier camelCase / Mustache-placeholder shape caused the build
  tool to silently skip Package generation.
- Parameter substitution uses the ARM double-bracket escape pattern
  ([[parameters('clientId')]) so that placeholders are evaluated at the
  inner deployment (Connect-click) scope, not at outer Solution-install.
- The /logging/network stream schema mirrors the Tailscale API response
  (logs[].virtualTraffic[], subnetTraffic[], exitTraffic[],
  physicalTraffic[]) — arrays are stored as dynamic columns and
  unpacked via mv-expand in KQL on the consumer side.
- Premium poller uses a single OAuth client with both scopes
  (logs:configuration:read + logs:network:read) so one Connect deploys
  both pollers with one shared guidValue.
- sampleQueries field added (required by Sentinel deployment validation
  — its absence returned the BadRequest "Required property
  'SampleQueries' not found" earlier).
- Support metadata follows the Community-tier convention used elsewhere
  in the repo (support.name = "Community", link to Azure-Sentinel
  issues) to avoid the duplicate-author rendering quirk in Content Hub.

Testing performed:
- Built via createSolutionV3 in -VersionMode local — Package generated
  cleanly, three valid JSON files plus zip.
- Standard connector deployed end-to-end into a test Sentinel workspace
  (Tailscale_Configuration_CL table created, UI card visible, OAuth
  Connect form renders all three inputs).
- All five analytic rule YAMLs validated against Sentinel schema.
- Premium connector files build into mainTemplate.json with two
  contentTemplates and proper guidValue / innerWorkspace plumbing for
  multi-poller single-card deployment (matches Proofpoint TAP pattern).
- Live Premium endpoint testing pending — requires Premium tailnet.

Breaking changes: None — net-new solution.
Expands the Tailscale (CCF) solution with proactive hunting content and
two operational workbooks. The split mirrors the connector split:
Standard-tier consumers get configuration-audit visibility, Premium-tier
consumers additionally get network-flow visibility.

Hunting queries (8 total):
  Configuration tier (works on Standard + Premium):
    - TailscaleFirstSeenActor — first-time configuration actors
      vs 14-day baseline (T1078)
    - TailscaleACLPolicyChurn — short windows with 3+ ACL rewrites,
      indicating rule-hunting or misconfiguration spiral (T1098/T1556)
    - TailscaleOffHoursConfigChanges — events outside Mon-Fri 08-18 UTC
      window for impromptu / suspicious changes (T1078)
    - TailscaleAuthKeySprawl — actors creating 5+ auth keys in an hour
      (T1098/T1136)

  Network tier (requires Premium connector + Tailscale_Network_CL):
    - TailscaleNewNodePairs — src->dst pairs in last 24h that did not
      appear in prior 7d baseline; lateral-movement candidates
      (T1021/T1018)
    - TailscaleTopTalkers — top 50 src->dst pairs by total bytes,
      virtual traffic (T1041/T1567)
    - TailscaleExitNodeUsage — egress via exit nodes, by node and
      destination, with byte totals (T1090/T1041)
    - TailscaleBeaconingCandidates — flows with 80%+ inter-flow gaps
      clustered at a single delta (10+ flows minimum); C2 / scheduled
      exfil signature (T1071/T1095/T1029)

Workbooks (2 variants — install matches connector tier):
  TailscaleStandardOperations.json (3 tabs):
    - Overview: summary tiles, activity time-series, top actors,
      target-type distribution
    - Identity & Configuration: actor/action/target heatmap, ACL
      change history, auth-key lifecycle, API/OAuth client activity
    - Security Signals: rule-summary, severity distribution, recent
      Tailscale alerts

  TailscalePremiumOperations.json (4 tabs — Standard tabs + Network):
    - Adds Network Flows tab: summary tiles, top-20 talkers chart,
      exit-node traffic table, subnet router throughput, virtual-
      traffic-bytes timechart

Manifest + metadata wiring:
  - Solutions/Tailscale (CCF)/Data/Solution_Tailscale.json:
    Hunting Queries array (8) + Workbooks array (2)
  - Workbooks/WorkbooksMetadata.json: appended two entries
    (TailscaleStandardOperationsWorkbook,
    TailscalePremiumOperationsWorkbook) with correct
    dataTypesDependencies and dataConnectorsDependencies so Content
    Hub gates visibility on the right tables and connectors
  - Solutions/Tailscale (CCF)/Package/ rebuilt with createSolutionV3:
    24 ARM resources (2 dataConnectorDefinitions, 19 contentTemplates
    covering 2 DC pollers + 2 DC connections + 5 analytic rules +
    8 hunts + 2 workbooks, 2 metadata blocks, 1 contentPackage)

Testing performed:
  - All 8 hunting query YAMLs parse cleanly via PyYAML
  - Both workbook JSON documents validate
  - createSolutionV3 build succeeds; mainTemplate.json,
    createUiDefinition.json and testParameters.json all valid JSON
  - Total ARM resource count went 10 -> 24 as expected
  - Hunting queries reference correct connector IDs (TailscaleCCF for
    config-tier hunts, TailscalePremiumCCF for network-tier hunts) so
    Content Hub gates them on the appropriate connector being deployed

Live deployment to test workspace deferred (Azure session expired);
will redeploy after re-authentication.

Breaking changes: None — additive only.
Applies the same five CI-failure classes that hit PR Azure#14253 to the
Tailscale branch up-front so PR Azure#2 doesn't need a fix-up round:

1. ValidConnectorIds.json allowlist
   Appended TailscaleCCF + TailscalePremiumCCF, CRLF-preserving
   surgical edit so the diff is two new lines.

2. Duration short form
   Converted PT15M -> 15m, PT5H -> 5h, PT1H -> 1h on all 5 analytic
   rules. ScheduledTemplateTimeSpanConverter rejects ISO-8601.

3. CustomTables JSON schemas
   Added Tailscale_Configuration_CL.json + Tailscale_Network_CL.json
   to .script/tests/KqlvalidationsTests/CustomTables/. Required for
   KqlValidationTests to recognise the custom _CL table names in any
   detection / hunting / exploration query referencing them.

4. SVG sanitisation
   Tailscale.svg verified clean (no style= or <style>).

5. ASCII-only YAMLs and source files
   Replaced em-dashes, curly quotes, arrows, NBSP across:
   - Analytic Rules: 4 YAMLs cleaned
   - Hunting Queries: 1 YAML cleaned
   - Solution manifest: 1 file
   - README.md: 1 file
   - Connector definitions: 2 files
   YAML files are now strictly ASCII so NonAsciiValidations passes.

6. Package rebuild
   Bumped to 3.0.2; mainTemplate.json regenerated with the
   description-text changes propagated.

Testing: validated YAML parses (PyYAML), byte-level ASCII scan across
solution directory returns zero hits, surgical edit to
ValidConnectorIds.json produces a 3-line diff with no reformatting.
Three changes in this revision:

1. Renamed custom table Tailscale_Configuration_CL -> Tailscale_Audit_CL
   The data sourced from /logging/configuration is semantically the
   tailnet's *audit* log (per Tailscale's own docs which call it
   "configuration audit logs"). Renamed across:
   - All 2x DCRs (streamDeclarations + dataFlows + outputStream)
   - All 2x PollerConfig (dataType + streamName)
   - All 2x tables.json
   - All 2x ConnectorDefinition (graphQueries, dataTypes,
     lastDataReceivedQuery)
   - 5 analytic rules (table reference + requiredDataConnectors)
   - 4 hunting queries
   - 2 workbooks (KQL queries)
   - CustomTables JSON schema renamed
   - Solution manifest + README + WorkbooksMetadata.json

2. Fixed OAuth2 Connect failure on both Standard and Premium pollers
   The CCP framework rejects OAuth2 reserved keys
   (client_id/client_secret/grant_type/etc.) inside
   auth.TokenEndpointQueryParameters because it injects them
   automatically from the GrantType field. Removed the block from
   both:
   - Tailscale_PollerConfig.json (Standard)
   - TailscalePremium_PollerConfig.json (Premium)

   The CCF_README.md example showing
     TokenEndpointQueryParameters: { grant_type: client_credentials }
   is wrong - omitted entirely here. See CLAUDE.md / Solutions/README.md
   for the gotcha note.

3. Added 5 Premium analytic rules (Tailscale Premium: prefix) keyed
   off the new Tailscale_Network_CL table + TailscalePremiumCCF
   connector:

   - TailscalePremiumUnexpectedExitNodeEgress.yaml (Medium / T1090,
     T1041) - first-seen Src->ExitDst pair vs 7d baseline
   - TailscalePremiumLargeOutboundTransfer.yaml (Medium / T1041,
     T1020) - single src->dst > 100 MB in 1h
   - TailscalePremiumBeaconingDetected.yaml (Medium / T1071, T1095,
     T1029) - 80%+ inter-flow gaps cluster at single delta over 10+
     flows (promotes the hunting query)
   - TailscalePremiumMassFanOut.yaml (High / T1018, T1021, T1046) -
     node connects to 25+ unique dst in 15 min
   - TailscalePremiumSubnetRouterThroughputAnomaly.yaml (Low / T1572,
     T1041) - subnet router 3x baseline bytes in last hour

   Total analytic rule set: 5 Standard (Tailscale: prefix) +
   5 Premium (Tailscale Premium: prefix) = 10.

Package rebuilt to v3.0.3 (auto-bumped). Tested end-to-end against
test workspace stl-sec-siem-law: full teardown -> reinstall ->
Standard connector Connected successfully.

Breaking changes: Table rename only affects pre-PR test deployments.
Solution is not yet in Microsoft catalog so no downstream impact.
…emium:' prefix

For consistency with the 5 Premium analytic rules added in the previous
revision, rename the 4 network-flow hunting queries (which require the
TailscalePremiumCCF connector + Tailscale_Network_CL table) so they
clearly carry the Premium tier identifier in both the filename and the
display name.

Renamed files (Hunting Queries/):
  TailscaleNewNodePairs.yaml       -> TailscalePremiumNewNodePairs.yaml
  TailscaleTopTalkers.yaml         -> TailscalePremiumTopTalkers.yaml
  TailscaleExitNodeUsage.yaml      -> TailscalePremiumExitNodeUsage.yaml
  TailscaleBeaconingCandidates.yaml -> TailscalePremiumBeaconingCandidates.yaml

Renamed display names accordingly:
  "Tailscale: New src->dst node pairs (lateral movement candidates)"
    -> "Tailscale Premium: New src->dst node pairs (lateral movement candidates)"
  "Tailscale: Top talkers by bytes (virtual traffic)"
    -> "Tailscale Premium: Top talkers by bytes (virtual traffic)"
  "Tailscale: Exit-node usage patterns"
    -> "Tailscale Premium: Exit-node usage patterns"
  "Tailscale: Beaconing candidates (regular periodic flows)"
    -> "Tailscale Premium: Beaconing candidates (regular periodic flows)"

Configuration-tier hunting queries (FirstSeenActor, ACLPolicyChurn,
OffHoursConfigChanges, AuthKeySprawl) keep the "Tailscale:" prefix
since they work on either Standard or Premium tier tailnets.

Updated:
  - Solutions/Tailscale (CCF)/Data/Solution_Tailscale.json (manifest)
  - Solutions/Tailscale (CCF)/Package/* (rebuilt to v3.0.4)

Net hunting query split now mirrors the analytic rules split:
  4 config-tier hunts (Tailscale:)        - Standard + Premium tailnets
  4 network-tier hunts (Tailscale Premium:) - Premium tailnets only

Testing: rebuilt via createSolutionV3, redeployed to test workspace,
all 8 hunts visible in Sentinel content templates with correct names.
… rules, 12 hunts)

Expands the Tailscale Standard (CCF) connector to comprehensive Free/
Standard tier coverage. Polls every accessible state endpoint plus the
audit log, with audit-log-based detection for any data shape that doesn't
fit Sentinel CCP's strict-schema model.

Standard connector pollers (9):
  1. /logging/configuration -> Tailscale_Audit_CL
  2. /devices               -> Tailscale_Devices_CL
  3. /users                 -> Tailscale_Users_CL
  4. /keys?all=true         -> Tailscale_Keys_CL (auth keys + API tokens
                               + OAuth clients; new KeyType + ExpirySeconds
                               columns)
  5. /webhooks              -> Tailscale_Webhooks_CL
  6. /dns/nameservers       -> Tailscale_DnsNameservers_CL
  7. /dns/preferences       -> Tailscale_DnsPreferences_CL (MagicDNS toggle)
  8. /dns/searchpaths       -> Tailscale_DnsSearchPaths_CL
  9. /settings              -> Tailscale_Settings_CL

OAuth client scopes required (granted as Read on each):
  logs:configuration:read, devices:core:read, users:read,
  auth_keys:read, webhooks:read, dns:read, feature_settings:read
  (or the bundled all:read).

Endpoints intentionally not polled:
  - /dns/split-dns         dynamic-key JSON (per-domain map); cannot map
                           cleanly to Sentinel's strict-schema CCP. Full
                           coverage via audit-log rule + hunt that diff
                           Old/New per domain (richer than a snapshot).
  - /user-invites          requires write `users` scope; audit log captures
                           CREATE/UPDATE/DELETE of USER_INVITE targets.
  - /acl                   requires `policy_file:read`; audit log captures
                           full Old/New on every change.
  - /posture/integrations  Premium feature; returns empty on Standard.
                           Documented for Premium connector in
                           PREMIUM-ENDPOINTS.md.
  - /network-logs etc.     Premium tier.

Analytic rules added in this revision (Standard tier):
  - TailscaleSplitDnsModified          High / T1556, T1568 - DNS hijack
    risk via per-domain routing change; expands Old/New for added/removed
    domains
  - TailscaleDnsNameserversModified    High / T1556, T1568 - tailnet-wide
    DNS resolver change; diffs added/removed resolvers
  - TailscaleMagicDnsDisabled          Medium / T1556 - MagicDNS toggled
    off; potential precursor to wider hijack
  - TailscaleDeviceKeyExpiringSoon     Medium / T1078 - device machine key
    expires within 7 days
  - TailscaleDeviceAdvertisingSubnetRoutes Medium / T1021, T1556 - device
    starts advertising subnet routes vs baseline
  - TailscaleUserRoleElevated          High / T1078, T1098 - user role
    changed to admin/owner

Hunting queries added (Standard tier):
  - TailscaleDormantDevices            devices not seen in 30+ days
  - TailscaleAuthKeysNoExpiry          auth keys without expiry
  - TailscaleOrphanedUsers             users with 0 devices
  - TailscaleSplitDnsPerDomainChanges  diff per-domain Split-DNS over time

Reserved-word fix: Tailscale Users API returns `type` field which is
reserved in Log Analytics custom tables. Renamed column to `UserType` in
table schema, DCR streamDeclaration, transform, and CustomTables test
schema.

OAuth Connect fix: removed TokenEndpointQueryParameters block from both
Standard and Premium PollerConfig. The CCP framework auto-injects OAuth2
reserved keys (grant_type, client_id, client_secret) and rejects them
inside that block. The CCF_README.md example showing grant_type there
is stale/wrong.

Table rename: Tailscale_Configuration_CL -> Tailscale_Audit_CL across
all 20 files referencing it; matches how Tailscale documents the data
(configuration AUDIT logs).

Premium connector unchanged in this revision; pending validation against
a real Premium tailnet. Premium-tier endpoints documented in
Solutions/Tailscale (CCF)/PREMIUM-ENDPOINTS.md for future expansion.

End-to-end verified against a live Tailscale tailnet at Standard tier:
all 9 pollers fire on the correct endpoints with the correct OAuth scopes;
seven tables ingest data within minutes of Connect (the two empty ones
are correctly empty - no webhooks or auth keys configured on test
tailnet).

Testing performed:
- All 16 analytic rules + 12 hunting queries pass YAML + ASCII validation
- Schemas in CustomTables/ match table definitions and DCR streams
- createSolutionV3 build succeeds; arm-ttk gives expected failure on
  CreateUIDefinition-blank-property (false positive unrelated to this
  solution)
- Full teardown + Content Hub Install + Connect cycle verified
- Tailscale_Audit_CL, _Devices_CL, _Users_CL, _DnsNameservers_CL,
  _Settings_CL all ingesting; _Keys_CL, _Webhooks_CL, _DnsPreferences_CL,
  _DnsSearchPaths_CL pending first 60-min poll
Builds out the Tailscale Premium (CCF) connector to mirror Standard's
full state coverage and add Premium-only sources. Premium connector is
now self-contained - users on Premium or Enterprise tier install ONLY
Premium and get everything (no need to also install Standard).

Premium connector pollers (11):
  Shared with Standard (same shape):
   1. /logging/configuration    -> Tailscale_Audit_CL
   2. /devices                  -> Tailscale_Devices_CL
   3. /users                    -> Tailscale_Users_CL
   4. /keys?all=true            -> Tailscale_Keys_CL
   5. /webhooks                 -> Tailscale_Webhooks_CL
   6. /dns/nameservers          -> Tailscale_DnsNameservers_CL
   7. /dns/preferences          -> Tailscale_DnsPreferences_CL
   8. /dns/searchpaths          -> Tailscale_DnsSearchPaths_CL
   9. /settings                 -> Tailscale_Settings_CL
  Premium-only:
  10. /logging/network          -> Tailscale_Network_CL (already existed)
  11. /posture/integrations     -> Tailscale_PostureIntegrations_CL (NEW)

Removed TokenEndpointQueryParameters from the OAuth2 block on both
existing Premium pollers (same CCP-framework reserved-keys fix applied
to Standard earlier).

New table:
  Tailscale_PostureIntegrations_CL columns:
    IntegrationId, Provider, CloudId, ClientId, TenantId_Provider,
    ConfigOverwrites, Status. Captures MDM/EDR integrations (Jamf,
    Kandji, Intune, Kolide, Microsoft Defender for Endpoint,
    CrowdStrike Falcon, SentinelOne, etc.).

Premium analytic rules added (2 new, on top of existing 5):
  - TailscalePremiumPostureIntegrationDisabled    High / T1562, T1556
    Audit-log based; fires on DELETE/UPDATE of POSTURE_INTEGRATION
    targets. Disabling enforcement on a tailnet is high-severity.
  - TailscalePremiumNewPostureIntegration         Medium / T1098
    Audit-log based; fires on CREATE of POSTURE_INTEGRATION. New
    integration is legitimate at setup or vendor changes; unexpected
    addition warrants review.

Premium hunting query added (1 new, on top of existing 4):
  - TailscalePremiumPostureInventory
    Current posture-integration inventory derived from
    Tailscale_PostureIntegrations_CL latest snapshot per integration.

Premium workbook gains a new tab: "Posture" with current integrations
table, by-provider pie chart, and change-history view from audit log.

OAuth scopes required on Premium client:
  logs:configuration:read, logs:network:read, devices:core:read,
  users:read, auth_keys:read, webhooks:read, dns:read,
  feature_settings:read (covers posture/integrations endpoint).

CustomTables/Tailscale_PostureIntegrations_CL.json added for KQL
validation in the upstream CI matrix.

Status: built but NOT validated against a real Premium tailnet (we
don't have access to one yet). The /logging/network and
/posture/integrations endpoints are tier-gated to Premium+; on lower
tiers those pollers will return 403 with the same error pattern we
saw earlier on /user-invites. Schema, transformKql, and analytic rule
predicates are based on Tailscale's documented response shapes - they
will likely need a minor tweak once we observe real Premium data.

Total solution surface now:
  - 2 data connectors (Standard, Premium)
  - 11 custom tables (9 Standard + 2 Premium-only)
  - 18 analytic rules (11 Standard + 7 Premium)
  - 13 hunting queries (8 Standard + 5 Premium)
  - 2 workbooks (Standard 3-tab + Premium 4-tab incl. Posture)
Tailscale's admin UI presents nameservers, MagicDNS, search domains and
split-DNS as one coherent DNS settings block. Surfacing them as three
separate Sentinel tables forced consumers to mentally re-assemble what
Tailscale presents as one thing.

Merged Tailscale_DnsNameservers_CL + Tailscale_DnsPreferences_CL +
Tailscale_DnsSearchPaths_CL into a single Tailscale_Dns_CL table with
a ConfigType discriminator column. Uses Sentinel CCP's multi-stream-in
+ single-stream-out DCR pattern: three pollers each post to their own
input stream, the DCR transforms route all three to outputStream
Custom-Tailscale_Dns_CL.

New schema:
  Tailscale_Dns_CL
    TimeGenerated  datetime
    ConfigType     string    "nameservers" | "preferences" | "searchpaths"
    Nameservers    dynamic   populated when ConfigType = "nameservers"
    MagicDNS       boolean   populated when ConfigType = "preferences"
    SearchPaths    dynamic   populated when ConfigType = "searchpaths"

Query pattern:
  Tailscale_Dns_CL
  | summarize arg_max(TimeGenerated, *) by ConfigType
  | project ConfigType, Nameservers, MagicDNS, SearchPaths

Applied to BOTH connectors:
  - Standard tables: 9 -> 7
  - Premium tables: 11 -> 9
  - Both DCRs: 3 DNS input streams -> 1 merged output table

Files touched:
  - Tailscale_PollerConfig.json + TailscalePremium_PollerConfig.json
    (dataType updated to Tailscale_Dns_CL for the 3 DNS pollers)
  - Tailscale_DCR.json + TailscalePremium_DCR.json (3 dataFlow transforms
    repointed to Custom-Tailscale_Dns_CL, transformKql now sets ConfigType)
  - Tailscale_tables.json + TailscalePremium_tables.json (3 old tables
    removed, Tailscale_Dns_CL added)
  - Tailscale_ConnectorDefinition.json + TailscalePremium_ConnectorDefinition.json
    (3 dataTypes/graphQueries collapsed to 1, descriptionMarkdown updated)
  - .script/tests/KqlvalidationsTests/CustomTables/: removed 3 schemas,
    added Tailscale_Dns_CL.json
  - Sample queries updated to reflect the merged table

No analytic rules, hunting queries, or workbook panels referenced the
3 old tables directly (DNS change detection runs from Tailscale_Audit_CL
already), so the merge is transparent to security content.

Tested: schemas validate, ASCII clean, both connectors rebuild via
createSolutionV3 successfully, redeployed to test workspace.
Replaces both Standard and Premium workbooks with comprehensive, full-
coverage operations dashboards matching the visual style established by
the Tailscale UniFi Site Manager (CCF) workbook:

- HTML-styled header bar with Tailscale SVG logo + title + subtitle
- Section dividers with accent left-border and uppercase labels
- Tile rows with computed metrics (toreal conversion for tiles formatter)
- Conditional-visibility groups gated on selectedTab parameter
- Time-range parameter pills

Standard workbook (TailscaleStandardOperations.json) - 8 tabs:
  - Overview      Tiles (devices/users/keys/audit/changes), activity
                  timechart, top actors, target-type pie
  - Devices       7-metric tiles (total/authorised/external/expiry-
                  disabled/update-available/subnet-routers/stale), OS
                  pie, version bars, tag pie, full inventory table with
                  red-bright heatmap for stale + days-to-expiry,
                  subnet-router list
  - Users         6-metric tiles (active/suspended/connected/zero-
                  devices/elevated), role + status pies, full inventory
                  table with days-since-seen
  - Credentials   6-metric tiles (active/auth/api/oauth/no-expiry/
                  expiring-7d), credentials-by-type pie, expiry-bucket
                  histogram, full table with heatmap, lifecycle audit
  - DNS & Tailnet 3-row Tailscale_Dns_CL snapshot, current Settings
                  flags table, DNS/ACL/MagicDNS/Settings audit change
                  history
  - Webhooks      Inventory table + audit change history
  - Audit         Events-per-hour timechart, actor/action/target
                  heatmap, recent-100 events
  - Signals       Alert summary, severity pie, recent Tailscale alerts

Premium workbook (TailscalePremiumOperations.json) - 10 tabs:
  All 8 Standard tabs + 2 Premium-only:
  - Network Flows Network bytes tiles, top src->dst pairs, exit-node
                  egress, subnet router throughput, bytes-over-time
                  timechart
  - Posture       Posture integration inventory, by-provider pie,
                  posture identity collection state from Settings,
                  audit change history

All 30 KQL queries in the Standard workbook were validated against live
data in the test Sentinel workspace before commit (30/30 pass). Premium
workbook reuses Standard's queries plus the Network/Posture tabs.

Tailscale SVG logo embedded inline in the workbook header with the
accent fill (#3b82f6 for Standard, #a855f7 for Premium) to visually
differentiate the two while sharing the brand mark.

Footer credits both connectors and points users to the alternate
connector if they're on the wrong tier.

Replaces the previous minimal 3-tab / 4-tab workbooks. All queries are
ASCII-clean and pass JSON validation. Same workbook keys/template IDs
preserved so WorkbooksMetadata.json entries remain valid without change.
The default Tailscale /devices endpoint returns a minimal schema that
excludes the per-device route information (advertisedRoutes,
enabledRoutes), connectivity details, distro and SSH-enabled flags. The
Subnet Routers and Exit Nodes workbook panel was empty because every
device's AdvertisedRoutes/EnabledRoutes was null.

Switching the poller to /devices?fields=all returns the full schema and
unblocks exit-node and subnet-router visibility.

Confirmed against live tailnet:
  apple-tv:        advertisedRoutes=[0.0.0.0/0, ::/0]   enabled=[]
                   -> configured as exit node, not yet approved
  stl-exit-vh01:   advertisedRoutes=[0.0.0.0/0, ::/0]   enabled=[0.0.0.0/0, ::/0]
                   -> active exit node
  strasbourg-exit: advertisedRoutes=[0.0.0.0/0, ::/0]   enabled=[0.0.0.0/0, ::/0]
                   -> active exit node
  chsunr01:        advertisedRoutes=[10.0.10.0/26, 10.0.50.0/27]
                   enabled=[0.0.0.0/0, 10.0.10.0/26, 10.0.50.0/27, ::/0]
                   -> subnet router + exit node

New columns on Tailscale_Devices_CL (now 29 columns):
  Distro             string   Linux distribution (ubuntu, alpine, etc.)
  SshEnabled         boolean  Tailscale SSH active on the device
  ConnectedToControl boolean  Has recently connected to the control plane
                              (lastSeen is null when this is true)
  TailnetLockKey     string   Public half of the tailnet-lock key
  TailnetLockError   string   Error code if tailnet-lock validation failed

Applied to both connectors (Standard + Premium). Both DCRs updated with
the new streamDeclaration columns and transformKql mapping. Both rebuild
cleanly, redeployed to test workspace successfully.

Workbook "Subnet Routers and Exit Nodes" panel will populate with the
4 advertising devices on next 60-min poll cycle.
…columns

Two issues with the "Current tailnet settings" panel in the Tailscale
Standard workbook:

1. Boolean columns rendered EMPTY for `false` values because the
   workbook used Sentinel's boolean-checkbox formatter (formatter 11),
   which leaves the cell blank when the underlying value is false or
   null. Visually indistinguishable from "no data".

2. Standard tier displayed Premium-only setting flags
   (`NetworkFlowLoggingOn`, `PostureIdentityCollectionOn`) that are
   never meaningful on Standard - they come back as null or always-
   false from the API on that tier. Showing them implied to the user
   that data was missing when in fact the feature simply doesn't apply.

Fix: rewrote both workbooks' Settings panels with explicit
iff()/case() expressions returning "On" / "Off" strings, removed the
boolean formatter, and tier-scoped the projection:

  Standard workbook surfaces:
    - Snapshot at
    - Device approval required
    - Device auto-updates
    - Device key duration (days)
    - User approval required
    - Users allowed to join external tailnets
    - Regional routing

  Premium workbook surfaces all of the above plus:
    - Network flow logging
    - Posture identity collection

Both presented as plain-text "On" / "Off" / number / role-name so
every cell reads cleanly and there's no ambiguity between "off" and
"missing data". Underlying table schema is unchanged - the Premium-
only columns are still ingested when the connector deploys (they're
just always null on Standard), so a tenant upgrading Standard ->
Premium gets the data automatically once they reconnect.
With /devices?fields=all now populating advertisedRoutes, enabledRoutes,
sshEnabled, connectedToControl, tailnetLockKey, tailnetLockError,
distro and clientConnectivity, this revision adds 8 new security
content items that surface signal from those fields. All apply to the
Standard tier connector.

New analytic rules (4):

  TailscaleTailnetLockValidationFailed     High / T1556, T1078
    Fires when TailnetLockError on any device is non-empty.
    Tailnet lock cryptographically prevents node-key injection; any
    validation error is a direct signal of an attempted compromise
    or a misconfigured signer.

  TailscaleDeviceSshNewlyEnabled           Medium / T1021, T1098
    Fires when SshEnabled transitions false -> true vs the prior 24h
    baseline. Tailscale SSH grants shell access to a device over the
    tailnet using Tailscale identity; new SSH-on events are legitimate
    but rare and worth reviewing.

  TailscaleUnauthorizedDeviceConnected     High / T1078, T1098
    Fires when Authorized=false AND ConnectedToControl=true.
    Device is talking to the control plane but admin hasn't approved
    it. With device-approval enabled, this is the approval queue;
    persistence indicates a misconfig or node-key injection attempt.

  TailscaleExternalDeviceAdded             Medium / T1078
    Fires when a new IsExternal=true device appears vs the prior 24h
    baseline. External (shared-in) devices expand the trust boundary -
    confirm against a documented sharing arrangement.

New hunting queries (4):

  TailscaleDevicesWithSshEnabled
    Inventory of devices currently exposing Tailscale SSH. Compare
    against the SSH ACL block to verify only intended targets are
    reachable.

  TailscaleExternalDeviceInventory
    Inventory of currently-active external (shared-in) devices.
    Compliance attestation - every entry should map to a documented
    collaboration.

  TailscaleOutdatedClients
    Devices reporting UpdateAvailable=true. Useful for fleet upgrade
    planning, especially before enabling ACL features that require
    minimum client versions. (Returns 9 devices on test tailnet.)

  TailscaleSubnetRouteExposure
    Lists devices advertising non-exit subnet routes (i.e. bridging
    non-tailnet networks into the tailnet). Excludes pure exit-node
    routes (0.0.0.0/0, ::/0) so only true subnet exposure surfaces.

All 8 queries validated against live data in the test workspace via
Invoke-AzOperationalInsightsQuery before commit (8/8 pass).

Total solution surface now:

  Standard:  15 analytic rules + 12 hunting queries + 1 workbook
  Premium:    7 analytic rules +  5 hunting queries + 1 workbook
  Shared:     2 data connectors, 11 custom tables
Applies every fix category we landed on the UniFi Site Manager (CCF) PR
proactively to Tailscale (CCF) so the eventual Azure-Sentinel PR doesn't
hit the same review cycle.

SolutionMetadata.json:
- offerId 'TailscaleCCF' -> 'azure-sentinel-solution-tailscale-ccf'
  (must contain 'sentinel' keyword, <= 50 chars)
- Added support.email: ccfconnectors.county118@passmail.com
- Removed empty categories.verticals

ReleaseNotes.md:
- Header 'Changes' -> 'Change History'
- Separator '|-------------|...' -> '|---|---|---|'
- Single leading | on each row

Data/Solution_Tailscale.json:
- TemplateSpec set to false (required for Version 3.x.x)
- Author trailer now includes the support email
- Description tightened: Premium connector is no longer described as
  'Planned for a future release' (it ships in this version)
- Em-dash in description replaced with hyphen for ASCII normalisation

Tailscale.svg:
- Removed <style> element (.script/logoValidator.js rejects it)
- class='st0' replaced with inline opacity='.2' attribute on each path

22 analytic rules:
- triggerOperator 'GreaterThan' -> 'gt' across all rules (schema enum
  rejects PascalCase)
- All 22 descriptions rewritten to start with 'Identifies' (schema
  validator requires 'Identifies' or 'This query searches for' opener)
- Em-dashes throughout the YAML normalised to hyphens
- ISO-8601 durations were already short-form (no change needed)

6 hunting queries: added entityMappings
- TailscaleAuthKeysNoExpiry: Account -> UserId
- TailscalePremiumBeaconingCandidates: IP -> Src + description starts
  with 'Identifies' (was 'Detects')
- TailscalePremiumExitNodeUsage: Host(SrcNodeName) + IP(Src)
- TailscalePremiumNewNodePairs: IP -> Src
- TailscalePremiumPostureInventory: CloudApplication -> Provider
- TailscalePremiumTopTalkers: IP -> Src
The remaining 11 hunts already had entityMappings.

README.md: rewritten as the canonical reference
- 12 numbered sections with TOC
- Standard vs Premium matrix, OAuth scope checklist per tier
- Per-table reference (cols / cadence / source endpoint / tier)
- All 22 analytic rules grouped in 5 tables (Identity, Configuration,
  Devices, Network/Exit, DNS, Premium) with severity and tactics
- All 17 hunts grouped Standard/Premium with use case
- Architecture notes: why two connectors, Proofpoint-TAP multi-poller
  pattern, OAuth-vs-PAT silent-empty bug, multi-stream DNS DCR pattern,
  cadence rationale
- 6 troubleshooting recipes covering common failure modes
- All non-ASCII characters normalised

Workbooks/WorkbooksMetadata.json: fixed two stale entries
- dataTypesDependencies was 'Tailscale_Configuration_CL' (table does
  not exist); now lists the actual tables each workbook queries
  (7 for Standard, 9 for Premium)
- provider 'noodlemctwoodle' -> 'Community' to align with Community-tier
  conventions
- Added support / source / categories blocks to match the Community
  workbook shape used elsewhere in the repo (e.g. UniFi Site Manager)

.script/tests/KqlvalidationsTests/CustomTables/: verified all 9 Tailscale
schemas already correct; no changes needed.

.script/tests/detectionTemplateSchemaValidation/ValidConnectorIds.json:
verified TailscaleCCF and TailscalePremiumCCF already present.

Package regenerated:
- Version 3.0.2 -> 3.0.3
- createUiDefinition.json now has correct workbook1 label and
  workbook1-text (previously null because the WorkbooksMetadata entry
  referenced a non-existent table)
- workbook1-name parameter has defaultValue
- All 22 analytic-rule descriptions land as complete sentences with
  'Identifies' prefix, no mid-word truncation
- Local arm-ttk: 47/49 pass (the 2 false-positives are the known
  contentProductId and Template-Should-Not-Contain-Blanks-[] generator
  artefacts documented in Solutions/README.md)
Both Tailscale CCF connector definitions had connectorUiConfig.publisher
set to 'Custom', which renders as the subtitle text on the Sentinel data
connector card. Community-tier solutions should display 'Community' there
to align with other community-published solutions and with the solution
metadata's Community tier.

Files changed:
- Solutions/Tailscale (CCF)/Data Connectors/TailscaleAuditLogs_ccf/
  Tailscale_ConnectorDefinition.json: publisher 'Custom' -> 'Community'
- Solutions/Tailscale (CCF)/Data Connectors/TailscalePremium_ccf/
  TailscalePremium_ConnectorDefinition.json: publisher 'Custom' -> 'Community'
- Solutions/Tailscale (CCF)/Data/Solution_Tailscale.json: Version
  3.0.3 -> 3.0.4 (auto-bumped by createSolutionV3 patch mode)
- Solutions/Tailscale (CCF)/Package/mainTemplate.json regenerated;
  four publisher references inside the connector definition wrapper
  resources now read 'Community'
- 3.0.3.zip removed, 3.0.4.zip generated

No other changes.
Replace the existing 8-tab Tailscale Operations (Standard) workbook with a
substantially richer 9-tab version that mirrors the design of the UniFi
Syslog (CCF) workbook shipped on add-unifi-syslog-ccf-solution. The
Premium workbook is left unchanged - parked until we have a Tailscale
Premium licence to validate against.

What changed:

- New Standard workbook has a persistent hero KPI tile row above the tabs
  (Devices, Authorized, Updates Available, SSH-Enabled, Users, Admins,
  Active Keys, Audit Events) so the most important numbers are always in
  view regardless of which tab is open.

- Investigate tab added: picker-driven drilldown with an Actor dropdown
  (sourced from Tailscale_Audit_CL Actor.loginName, top 50 by event count)
  and a Device dropdown (sourced from Tailscale_Devices_CL latest snapshot,
  top 100 by LastSeen). The Device picker uses DeviceName not DeviceId
  because the audit log emits Target.id in stable-control-plane ID format
  while the API emits a different numeric DeviceId - the only reliable
  join key between the two streams is the dotted device hostname. Audit
  events touching a device are filtered on Target.type == "NODE" (not
  "DEVICE", which is what the previous draft tried - Tailscale's actual
  enum is NODE).

- Hunts tab embeds 8 of the 17 packaged hunting queries inline so the user
  can see results without navigating to the Hunts blade: first-seen actors
  (24h vs 30d baseline), off-hours config changes, key-expiry-disabled
  devices, never-expire auth keys, outdated clients, dormant devices,
  subnet route exposure, SSH-enabled devices. Each panel has a NO_DATA
  message explaining what zero results means.

- Identity tab adds a KPI tile row (total users, admin-tier, active,
  connected-now, idle/dormant, shared/external) plus a recency-of-last-
  login bucketed barchart (Today / This week / This month / Past quarter
  / 90+ days), orphaned-users table (active accounts with zero devices),
  and a role-change history pulled from audit Action == USER_ROLE_UPDATE.

- Devices tab adds a "needs attention" pre-filter panel that flags
  devices with one or more issues across: update-available, key-never-
  expires, stale (>30d LastSeen), unauthorized. Each device's issue list
  renders as a tag chip column. Full inventory remains.

- Credentials tab adds an active-key expiry distribution barchart
  bucketed Already expired / <24h / 1-7d / 8-30d / 31-90d / 90+d / Never,
  plus a computed ExpiryStatus tag column on the active credential
  register.

- Admin Audit tab adds a hour-of-day x action categorical bar showing
  when admin work happens, and an actor x action heatmap (formatter 4,
  blue palette) on top of the existing event log.

- Network & DNS tab consolidates the previous DNS + Webhooks + signals
  bits into one place: current DNS snapshot (MagicDNS, nameservers,
  search paths), tailnet policy gates (DevicesApproval, AutoUpdates,
  KeyDurationDays etc.), DNS change history, ACL/policy change history,
  and the currently-served subnet/exit-node routes.

- Pipeline Health tab is new: rows-per-hour-per-table timechart,
  per-table freshness with MinutesAgo bar coloured red, and a
  _LogOperation filter for Tailscale-touching operational events. Helps
  the user spot when a stream has stopped polling.

- Premium-only queries (Tailscale_PostureIntegrations_CL,
  Tailscale_Network_CL) are stripped entirely from the Standard workbook
  rather than left as NO_DATA placeholders. They belong on the parked
  Premium workbook only.

- Header banner updated: workbook title becomes "Tailscale Operations
  (Standard)" with a deep-link reference to the Premium companion
  workbook for Premium-tier customers.

- 50+ KQL queries gain noDataMessage strings explaining what zero rows
  mean in context (e.g. "Healthy state for an organisation working
  business hours") rather than leaving empty panels.

- Formatters added throughout: heatmap (formatter 4) on the actor x
  action grid, bar (formatter 8) for count columns, tag chip (formatter
  11) for OS / Role / Status / Action / Issues columns, datetime
  (formatter 6) for TimeGenerated / LastSeen / Created columns.

Files modified:

- Solutions/Tailscale (CCF)/Workbooks/TailscaleStandardOperations.json
  - Rebuilt from scratch (52 queries across 9 tab groups, 88KB)

- Solutions/Tailscale (CCF)/Data/Solution_Tailscale.json
  - Version 3.0.4 -> 3.0.5
  - BasePath updated to absolute path for local createSolutionV3 builds

- Solutions/Tailscale (CCF)/Package/3.0.5.zip
- Solutions/Tailscale (CCF)/Package/mainTemplate.json
- Solutions/Tailscale (CCF)/Package/createUiDefinition.json
  - Regenerated by createSolutionV3.ps1 / build-and-validate.ps1 wrapper

- Solutions/Tailscale (CCF)/Package/3.0.4.zip
  - Removed (stale, superseded by 3.0.5)

- Workbooks/WorkbooksMetadata.json
  - Surgical 1-line update to the TailscaleStandardOperationsWorkbook
    entry's description field. LF-preserving byte edit. Premium entry
    untouched.

Note on the Premium workbook:
- Solutions/Tailscale (CCF)/Workbooks/TailscalePremiumOperations.json
  is intentionally NOT touched by this commit. Premium tier requires
  network flow logs + posture-integration API access which is gated on
  a Tailscale Premium / Enterprise plan. The existing parked Premium
  workbook ships unchanged; we'll rebuild it to the same standard once
  Tailscale grant Premium access.

Technical / data shape findings (worth recording for future iteration):

- Tailscale_Dns_CL is a UNIFIED table fanned in from three DCR input
  streams (DnsNameservers, DnsPreferences, DnsSearchPaths). Queries
  pivot on ConfigType to read the right shape.

- Tailscale audit Target.type enum values present in real data:
  OAUTH_ACCESS_TOKEN (319), NODE (11), API_KEY (7), TAILNET (6). The
  previous draft assumed DEVICE - that value doesn't exist.

- Tailscale audit Target.id for NODE events is the stable control-plane
  ID (alphanumeric with CNTRL suffix, e.g. nGddQEhHzh11CNTRL). It does
  NOT match Tailscale_Devices_CL.DeviceId (which is the numeric API ID,
  e.g. 4195069007596891). The only reliable join key between the two
  streams is Target.name == DeviceName (the dotted hostname).

- Tailscale_PostureIntegrations_CL schema columns are:
  IntegrationId, Provider, CloudId, ClientId, TenantId_Provider,
  ConfigOverwrites, Status - NOT ConfiguredBy (which the previous draft
  incorrectly assumed). Only relevant to the Premium workbook.

Testing performed:

- All 52 queries validated against the live test workspace
  (stl-sec-siem-law, uksouth, 2 days of real Tailscale audit + devices
  + users + keys + DNS + settings data from tailb094d7.ts.net). Result:
  42 pass / 10 empty (all covered by NO_DATA messages) / 0 fail.

- Workbook deployed via REST to stl-sec-siem-law for visual rendering
  verification (Microsoft.Insights/workbooks resource, category
  sentinel). Visual review confirmed tile rows render, picker dropdowns
  populate, NO_DATA messages display in expected panels, formatters
  apply correctly.

- build-and-validate.ps1 wrapper run:
  - KQL validation: pass (47 files)
  - ARM-TTK: 48 / 48 pass
  - Field Types: pass
  - Classic App Insights: pass
  - Hyperlink Validation: 1 expected pre-merge failure
    (Logos/Tailscale.svg URL at master not resolving because branch
    not merged yet - same false-positive every new-solution PR hits;
    resolves itself post-merge)
  - .NET-based Detection Schema, .NET-based Non-ASCII, and TruffleHog
    skipped (.NET Core 3.1 / TruffleHog CLI not installed locally) -
    substituted with Python ASCII scan + manual rule checks.

- Privacy grep on the workbook JSON: clean (no real tenant IDs, real
  device hostnames, IP addresses, OAuth tokens, etc.)

Breaking changes: none.

The Standard workbook is a drop-in replacement in the existing
templateRelativePath slot (TailscaleStandardOperations.json). The
Premium workbook is unchanged. Solution-level dependencies, Solution
Hub install flow, and DCR / data connector resources are unaffected.
Brings the Tailscale (CCF) solution to upstream-ready state. Net-new
content (analytic rule + 5 hunting queries, schema additions, workbook
rebuild), reviewer-feedback compliance (mirroring v-shukore comments on
UniFi PR), and the ARM-TTK fix the V3 wrapper doesn't filter.

Net content additions:
- New analytic rule: TailscaleOAuthClientCreatedWithWriteScopes (High,
  Persistence/PrivilegeEscalation, T1098/T1136) - flags OAuth clients
  granted any :write scope.
- New analytic rule: TailscalePremiumDerpRelaySurge (Low, CommandAndControl,
  T1572) - >75% flows fallen back to DERP relay (NAT/firewall failure
  or evasion signal).
- 5 new Premium hunting queries leveraging 3.1.0 promoted columns:
  CrossTagFlowMatrix, DerpRelayPersistence, OffHoursFlows,
  TaggedServiceFanIn, UserMultiDevice.

Schema additions (carried from earlier dev iterations, now landing in 3.0.0):
- Tailscale_Audit_CL: EventType, ActionDetails (previously silently
  dropped by DCR streamDecl).
- Tailscale_Network_CL: 15 columns promoted from srcNode/dstNodes
  (SrcUser/SrcNodeName/SrcOs/SrcTags/SrcAddresses/DstCount/DstNodeId/
  DstNodeName/DstUser/DstOs/DstTags/DstAddresses) plus traffic-shape
  flags (HasVirtualTraffic/HasSubnetTraffic/HasExitTraffic/
  HasPhysicalTraffic/IsRelayed via 127.3.3.40 DERP detection).
- Premium DCR DNS consolidation: 3 separate streams unified to
  Custom-Tailscale_DnsConfig_CL (under Azure 10-dataFlow limit).
- All 5 Premium network analytic rules rewritten against promoted
  columns instead of dynamic JSON traversal; gained Host + Account
  entityMappings.

Workbooks rebuilt:
- TailscaleStandardOperations: 9-tab forensic UX (overview, identity,
  devices, credentials, admin audit, network, DNS, investigate, pipeline
  health).
- TailscalePremiumOperations: Standard tabs plus Network Flows + Posture
  tabs with traffic-mix area chart, DERP direct-vs-relayed pie, exit-node
  tag breakdown, posture provider/health pies, beaconing-candidate table.
- Investigate-tab pickers use explicit __ALL__ sentinel row (the magic
  value::all additionalResourceOptions does not reliably substitute).
- Positive-outcome panels use noDataMessageStyle: 1 (info/blue) not 5
  (critical/red).

Reviewer-feedback compliance (v-shukore on UniFi PR):
- Solution_Tailscale.json BasePath -> Windows path
  (C:\\GitHub\\azure-Sentinel\\Solutions\\Tailscale (CCF)).
- SolutionMetadata.json support.name "Community" -> "Tailscale (CCF)".
- firstPublishDate / lastPublishDate -> 2026-05-19.
- Version pinned 3.0.0 (was 3.1.28 across dev iterations);
  ReleaseNotes.md collapsed to single "Initial Solution Release" row.

Hunting query polish:
- 14 descriptions normalised to start with "Identifies" (consistent
  with existing 8 that already did, matches detection-rule convention).
- 5 new Premium hunting queries gained entityMappings + version: 1.0.0.

ARM-TTK fix (47/48 -> 48/48):
- Package/mainTemplate.json: stripped 7 unreferenced variables
  (_solutionVersion, dataConnectorTemplateName{ConnectorDefinition,
  Connections}{1,2}, dataCollectionEndpointId{1,2}) and 3 dependent
  unreferenced parameters (resourceGroupName, subscription,
  dataCollectionEndpoint). Legacy artefacts of the single-card CCF
  template pattern; Tailscale uses the Proofpoint TAP multi-poller-
  per-card shape (guidValue + innerWorkspace) which does not reference
  them.

Tooling for future rebuilds (gitignored, not for upstream):
- .gitignore: added .local-helpers/ exclusion.
- .local-helpers/finalize-tailscale-package.py: idempotent post-build
  script. Auto-detects the wrapper-bumped Version, pins it back to
  target (default 3.0.0), strips unreferenced vars/params, repackages
  Package/<target>.zip, sweeps .DS_Store.

Package regeneration:
- Removed stale Package/3.0.5.zip.
- Generated fresh Package/3.0.0.zip.
- Package/mainTemplate.json + createUiDefinition.json regenerated.

Verified locally: ARM-TTK 48/48, KQL validators pass, no privacy
leaks in source files.
…m build

Adds an Acknowledgements section (and table-of-contents entry) thanking
Tailscale for upgrading the maintainer's tailnet to Premium. That access
was the prerequisite for building and validating the Premium-tier
features against live API responses: network flow log ingestion, posture
integration inventory, the seven Premium analytic rules, the five
Premium hunting queries, and the Premium Operations workbook. Without
it the Premium connector would have shipped speculatively rather than
end-to-end verified.
…al limit

Updates the Acknowledgements section to credit Tailscale more accurately:
they enabled Network Flow Logs on the maintainer's tailnet WITHOUT the
usual 30-60 day trial restriction, allowing open-ended development and
validation against live API data instead of working against a feature
timeout. That open-ended access (not just a generic Premium upgrade) is
what made the Premium connector end-to-end verifiable.
Replaces specifics about what Tailscale did (Network Flow Logs enablement,
no trial restriction) with a generic thank-you. The earlier text could
inadvertently encourage other developers to request the same arrangement;
not the maintainer's intent. The thanks remains, the mechanism does not.
The README had drifted behind several rounds of content additions and
schema work. Now matches the shipped 3.0.0 solution exactly.

Header / tier-comparison counts:
- 22 -> 24 analytic rules (Standard 15 -> 16, Premium 7 -> 8).
- 17 -> 22 hunting queries (Standard 12 unchanged, Premium 5 -> 10).

Table column counts (section 5):
- Tailscale_Audit_CL: 9 -> 11 cols (EventType + ActionDetails were added
  to the streamDecl + transform in earlier dev iterations, README never
  followed).
- Tailscale_Network_CL: 10 -> 27 cols (15 cols promoted from
  srcNode/dstNodes plus 5 traffic-shape/relay flags).
- Added a new prose paragraph in section 5 highlighting Network_CL as
  the richest event table alongside Devices_CL, with the promoted-column
  list so hunting authors know what is available without JSON traversal.

Analytic rules section (section 6):
- Standard tier Identity & access: added "OAuth client or API key created
  with write scopes" (High, Persistence + PrivilegeEscalation) - fires on
  any granted :write scope.
- Premium tier: added "DERP relay traffic surge" (Low, CommandAndControl,
  T1572) - fires when >75% of a source node's recent flows fall back to
  DERP relay.

Hunting queries section (section 7):
- Premium tier added 5 new entries: Cross-tag flow matrix, Persistent
  DERP relay usage, Off-hours flows, Tagged service fan-in, Multi-device
  users (each with tactics + use case).

Architecture (section 9):
- "How three DNS endpoints become one table" rewritten to explain the
  two different mechanisms the Standard and Premium connectors use to
  land into Tailscale_Dns_CL. Standard: 3-streams-in, 3 dataFlows.
  Premium: 1 unified DnsConfig stream, 1 dataFlow (Premium would
  otherwise be at 11 dataFlows, Azure caps at 10).

Troubleshooting (section 11):
- Removed obsolete "current version (3.0.2+) polls with ?fields=all"
  reference. With 3.0.0 as the upstream first release that historical
  caveat no longer makes sense; rephrased as a generic
  transform-projection check.
Removes the "No VPN tunnel events" limitation bullet - it framed an
upstream Tailscale platform decision as a missing feature with a "file a
feature request" suggestion, which reads as backhanded in a README that
now publicly thanks Tailscale for enabling the Premium build. If the
absence ever matters operationally, ops can investigate at that point.

Also fixes the stale Premium counts in the adjacent "Network flow logs
are Premium-only" bullet: seven rules / five hunts -> eight rules /
ten hunts, matching the rest of the README.
…ork note

The README claimed Tailscale (CCF) shipped a vimNetworkSessionTailscale
parser and an imNetworkSession workspace function. Neither exists -
there is no Parsers/ directory, no parser file under Data Connectors/,
and Solution_Tailscale.json has no Parsers section. Only the README
itself referenced those names.

Replaces the bullet with an accurate "planned follow-up release" note so
users aren't led to look for files that aren't there. Building the real
parser (mapping the 17 promoted Tailscale_Network_CL columns to ASIM
NetworkSession fields + wiring imNetworkSession) is meaningful follow-up
work outside 3.0.0 scope.
…arser

Implements the ASIM NetworkSession parser the README previously promised
but didn't ship. Maps Tailscale_Network_CL (Premium-only flow logs) to
the ASIM NetworkSession schema (v0.2.6) so Microsoft's Network Session
Essentials pre-built detections can be cloned and re-pointed at Tailscale
data with a one-line query change.

Files:
- Parsers/vimNetworkSessionTailscale.yaml - full parser. Unions flows
  from VirtualTraffic (NetworkDirection=Local), SubnetTraffic and
  ExitTraffic (NetworkDirection=Outbound), parsing the "host:port" /
  "[ipv6]:port" / bare-IP forms into SrcIpAddr/SrcPortNumber/DstIpAddr/
  DstPortNumber. Maps every promoted column from the 3.1.0 schema
  expansion onto its ASIM equivalent: SrcNodeName -> SrcHostname,
  SrcUser -> SrcUsername, SrcOs -> SrcDvcOs, SrcTags retained via Dvc,
  DstNodeName -> DstHostname, DstUser -> DstUsername, DstOs -> DstDvcOs.
  Synthesises NetworkProtocol from proto (1/6/17/58 -> ICMP/TCP/UDP/
  ICMPv6), NetworkBytes/NetworkPackets, EventStartTime/EventEndTime
  from FlowStart/FlowEnd, and the standard EventProduct/EventVendor/
  EventSchema constants. PhysicalTraffic deliberately excluded - it is
  transport-layer (WireGuard/DERP endpoints) rather than logical user
  session.
- Parsers/ASimNetworkSessionTailscale.yaml - thin wrapper aliasing
  vimNetworkSessionTailscale. Present so ASIM-convention call sites
  using ASimNetworkSession<Source> naming work without modification.

Schema discovery / why this format:
- V3 packaging tool reads Function: schema (FunctionName / FunctionAlias /
  FunctionQuery), not the upstream ASIM Parser: schema (ParserName /
  ParserParams / ParserQuery) which is reserved for Parsers/ASimNetworkSession/
  central repo. Authored both parsers in the Function-schema form so
  the V3 build correctly populates parserObject{1,2}._parserName{1,2}
  with the actual function names. Without this, the workspace
  savedSearch resources end up with empty names (verified pre-fix).
- Function-schema savedSearch functions don't expose signature
  parameters, so the standard ASIM filter-pushdown params (starttime,
  srcipaddr_has_any_prefix, etc.) cannot be honoured. Users apply
  filters as post-call where clauses: vimNetworkSessionTailscale
  | where EventStartTime > ago(1d) | where DstPortNumber == 443.

Wiring:
- Solution_Tailscale.json: added Parsers: [...] array with both files.
- README.md header bullet list: added "2 ASIM NetworkSession parsers"
  alongside data connectors / rules / hunts / workbooks / tables.
- README.md section 10 Limitations: prior text already rewritten in
  the previous commit to describe the parser; no further update needed.

Package regenerated; ARM-TTK 48/48 verified after .local-helpers/
finalize-tailscale-package.py strip + 3.0.0 pin.
…sweep

CustomTables schemas had drifted behind the DCR streamDeclarations:
- Tailscale_Audit_CL.json: added EventType + ActionDetails (introduced
  in earlier dev iteration but never reflected in the validation
  schema; queries referencing them would have failed CI KqlValidation).
- Tailscale_Network_CL.json: added the 17 columns the DCR transform
  promotes out of srcNode/dstNodes/traffic arrays (SrcUser, SrcNodeName,
  SrcOs, SrcTags, SrcAddresses, DstCount, DstNodeId, DstNodeName,
  DstUser, DstOs, DstTags, DstAddresses, HasVirtualTraffic,
  HasSubnetTraffic, HasExitTraffic, HasPhysicalTraffic, IsRelayed).
  The hunting queries and Premium analytic rules reference these
  columns; without the schema update KqlValidation would fail "name
  does not refer to any known table column".

Hunting query description sweep finish-up: two files were missed in
the parallel-edit batch earlier (the Read-before-Edit guard blocked
them and they didn't make it into the follow-up batch):
- TailscaleSplitDnsPerDomainChanges.yaml: Reconstructs -> Identifies
- TailscaleSubnetRouteExposure.yaml: Lists -> Identifies
…-solution

# Conflicts:
#	.script/tests/detectionTemplateSchemaValidation/ValidConnectorIds.json
#	Workbooks/WorkbooksMetadata.json
…ling

After merging upstream/master (1.3k+ commits) the V3 packaging tool
behaviour changed: variables and parameters the older tool emitted
without consumers (_solutionVersion, dataConnectorTemplateName*,
dataCollectionEndpointId{1,2}, resourceGroupName, subscription,
dataCollectionEndpoint) are now actively referenced by the generated
mainTemplate.json (54 refs to _solutionVersion alone). The
.local-helpers/finalize-tailscale-package.py script's blind-strip
would corrupt the template; the script is updated to only strip
entries that test zero-reference at runtime.

Net effect on Package/:
- mainTemplate.json: regenerated by upstream V3, version-pinned to 3.0.0,
  79 variables and 7 parameters (vs 70 / 5 previously) - all referenced.
- createUiDefinition.json: regenerated by upstream V3.
- 3.0.0.zip: repackaged from the above.

Verification:
- ARM-TTK: 48/48 PASS on first build (no manual fixup needed).
- KQL: PASS (16s, 56 files - schema files for Audit_CL + Network_CL
  picking up the 19 added columns).
- Hyperlink validation: PASS (4s).
- Field Types, Classic App Insights: PASS.
- TruffleHog + .NET-3.1 detection-schema/non-ASCII: SKIP (tools absent
  on macOS, substitution checks pass).
@noodlemctwoodle noodlemctwoodle requested review from a team as code owners June 15, 2026 13:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a new community Microsoft Sentinel solution for Tailscale using Codeless Connector Framework (CCF), including tiered (Standard/Premium) connectors, content, and supporting artifacts.

Changes:

  • Added new Tailscale (CCF) solution assets: connectors, custom tables, parsers, hunting queries, analytic rules, metadata, and release notes.
  • Registered new connector IDs and custom table schemas for KQL validation.
  • Updated global Workbooks metadata to include the new Tailscale workbooks (plus some unicode/formatting normalizations).

Reviewed changes

Copilot reviewed 75 out of 81 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
Workbooks/WorkbooksMetadata.json Adds Tailscale workbook metadata entries so the workbooks appear in gallery/metadata-driven experiences
Solutions/Tailscale (CCF)/SolutionMetadata.json Introduces solution marketplace metadata (publisher/offer/dates/categories/support)
Solutions/Tailscale (CCF)/ReleaseNotes.md Adds initial release notes table for the new solution
Solutions/Tailscale (CCF)/Parsers/vimNetworkSessionTailscale.yaml Adds ASIM NetworkSession parser for Tailscale network flows
Solutions/Tailscale (CCF)/Parsers/ASimNetworkSessionTailscale.yaml Adds wrapper parser alias for ASIM naming conventions
Solutions/Tailscale (CCF)/Package/testParameters.json Adds package test parameters to support template validation/deployment tests
Solutions/Tailscale (CCF)/PREMIUM-ENDPOINTS.md Documents premium-only endpoints and validation notes for connector behavior
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleSubnetRouteExposure.yaml Adds hunting query template for subnet-route exposure inventory
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleSplitDnsPerDomainChanges.yaml Adds hunting query template for split-DNS change history
Solutions/Tailscale (CCF)/Hunting Queries/TailscalePremiumUserMultiDevice.yaml Adds premium hunting query template for multi-device traffic per user
Solutions/Tailscale (CCF)/Hunting Queries/TailscalePremiumTopTalkers.yaml Adds premium hunting query template for top talkers
Solutions/Tailscale (CCF)/Hunting Queries/TailscalePremiumTaggedServiceFanIn.yaml Adds premium hunting query template for broad inbound exposure by tags
Solutions/Tailscale (CCF)/Hunting Queries/TailscalePremiumPostureInventory.yaml Adds premium hunting query template for posture integration inventory
Solutions/Tailscale (CCF)/Hunting Queries/TailscalePremiumOffHoursFlows.yaml Adds premium hunting query template for off-hours network flows
Solutions/Tailscale (CCF)/Hunting Queries/TailscalePremiumNewNodePairs.yaml Adds premium hunting query template for new src/dst node pairs
Solutions/Tailscale (CCF)/Hunting Queries/TailscalePremiumExitNodeUsage.yaml Adds premium hunting query template for exit node usage
Solutions/Tailscale (CCF)/Hunting Queries/TailscalePremiumDerpRelayPersistence.yaml Adds premium hunting query template for persistent DERP relay usage
Solutions/Tailscale (CCF)/Hunting Queries/TailscalePremiumCrossTagFlowMatrix.yaml Adds premium hunting query template for cross-tag flow matrix
Solutions/Tailscale (CCF)/Hunting Queries/TailscalePremiumBeaconingCandidates.yaml Adds premium hunting query template for beaconing candidates
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleOutdatedClients.yaml Adds hunting query template for outdated clients
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleOrphanedUsers.yaml Adds hunting query template for users with zero devices
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleOffHoursConfigChanges.yaml Adds hunting query template for off-hours config changes
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleFirstSeenActor.yaml Adds hunting query template for first-seen actor changes
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleExternalDeviceInventory.yaml Adds hunting query template for shared-in/external devices
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleDormantDevices.yaml Adds hunting query template for dormant devices
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleDevicesWithSshEnabled.yaml Adds hunting query template for SSH-enabled devices
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleAuthKeysNoExpiry.yaml Adds hunting query template for non-expiring auth keys
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleAuthKeySprawl.yaml Adds hunting query template for auth key sprawl
Solutions/Tailscale (CCF)/Hunting Queries/TailscaleACLPolicyChurn.yaml Adds hunting query template for ACL churn
Solutions/Tailscale (CCF)/Data/Solution_Tailscale.json Adds solution “bill of materials” listing all included content and paths
Solutions/Tailscale (CCF)/Data Connectors/TailscalePremium_ccf/TailscalePremium_ConnectorDefinition.json Adds Premium-tier connector definition, UI config, and sample queries
Solutions/Tailscale (CCF)/Data Connectors/TailscaleAuditLogs_ccf/Tailscale_tables.json Adds custom table schemas for Standard connector deployment
Solutions/Tailscale (CCF)/Data Connectors/TailscaleAuditLogs_ccf/Tailscale_ConnectorDefinition.json Adds Standard-tier connector definition, UI config, and sample queries
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleUserRoleElevated.yaml Adds detection for user role elevation
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleUnauthorizedDeviceConnected.yaml Adds detection for unauthorized devices connected
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleTailnetLockValidationFailed.yaml Adds detection for tailnet-lock validation failures
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleSplitDnsModified.yaml Adds detection for split-DNS config changes
Solutions/Tailscale (CCF)/Analytic Rules/TailscalePremiumUnexpectedExitNodeEgress.yaml Adds premium detection for unexpected exit-node egress
Solutions/Tailscale (CCF)/Analytic Rules/TailscalePremiumSubnetRouterThroughputAnomaly.yaml Adds premium detection for subnet router throughput anomalies
Solutions/Tailscale (CCF)/Analytic Rules/TailscalePremiumPostureIntegrationDisabled.yaml Adds premium detection for posture integration removal/disablement
Solutions/Tailscale (CCF)/Analytic Rules/TailscalePremiumNewPostureIntegration.yaml Adds premium detection for new posture integrations
Solutions/Tailscale (CCF)/Analytic Rules/TailscalePremiumMassFanOut.yaml Adds premium detection for mass fan-out
Solutions/Tailscale (CCF)/Analytic Rules/TailscalePremiumLargeOutboundTransfer.yaml Adds premium detection for large outbound transfers
Solutions/Tailscale (CCF)/Analytic Rules/TailscalePremiumDerpRelaySurge.yaml Adds premium detection for DERP relay surge
Solutions/Tailscale (CCF)/Analytic Rules/TailscalePremiumBeaconingDetected.yaml Adds premium detection for beaconing patterns
Solutions/Tailscale (CCF)/Analytic Rules/TailscalePolicyfileACLmodified.yaml Adds detection for ACL policy changes
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleOAuthClientCreatedWithWriteScopes.yaml Adds detection for creation of OAuth/API keys with write scopes
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleNewAPIaccesstokenorOAuthclientcreated.yaml Adds detection for new API tokens / OAuth clients
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleMasscredentialrevocationinshortwindow.yaml Adds detection for mass credential revocation
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleMagicDnsDisabled.yaml Adds detection for MagicDNS being disabled
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleExternalDeviceAdded.yaml Adds detection for new external/shared-in devices
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleExitnodeadvertisedorapproved.yaml Adds detection for exit-node advertise/approval events
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleDnsNameserversModified.yaml Adds detection for DNS nameserver changes
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleDeviceSshNewlyEnabled.yaml Adds detection for newly enabled SSH
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleDeviceKeyExpiringSoon.yaml Adds detection for device keys nearing expiry
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleDeviceAdvertisingSubnetRoutes.yaml Adds detection for new subnet route advertisement
Solutions/Tailscale (CCF)/Analytic Rules/TailscaleAuthkeycreated.yaml Adds detection for auth key creation
.script/tests/detectionTemplateSchemaValidation/ValidConnectorIds.json Registers connector IDs used by rules/queries for schema validation
.script/tests/KqlvalidationsTests/CustomTables/Tailscale_Webhooks_CL.json Adds custom table schema used by KQL validation tests
.script/tests/KqlvalidationsTests/CustomTables/Tailscale_Users_CL.json Adds custom table schema used by KQL validation tests
.script/tests/KqlvalidationsTests/CustomTables/Tailscale_Settings_CL.json Adds custom table schema used by KQL validation tests
.script/tests/KqlvalidationsTests/CustomTables/Tailscale_PostureIntegrations_CL.json Adds custom table schema used by KQL validation tests
.script/tests/KqlvalidationsTests/CustomTables/Tailscale_Network_CL.json Adds custom table schema used by KQL validation tests
.script/tests/KqlvalidationsTests/CustomTables/Tailscale_Keys_CL.json Adds custom table schema used by KQL validation tests
.script/tests/KqlvalidationsTests/CustomTables/Tailscale_Dns_CL.json Adds custom table schema used by KQL validation tests
.script/tests/KqlvalidationsTests/CustomTables/Tailscale_Devices_CL.json Adds custom table schema used by KQL validation tests
.script/tests/KqlvalidationsTests/CustomTables/Tailscale_Audit_CL.json Adds custom table schema used by KQL validation tests

Comment thread Solutions/Tailscale (CCF)/ReleaseNotes.md
Comment thread Solutions/Tailscale (CCF)/SolutionMetadata.json
Comment thread Solutions/Tailscale (CCF)/Data/Solution_Tailscale.json
Comment thread Solutions/Tailscale (CCF)/Hunting Queries/TailscaleSubnetRouteExposure.yaml Outdated
Comment thread Workbooks/WorkbooksMetadata.json
Comment thread Solutions/Tailscale (CCF)/Parsers/ASimNetworkSessionTailscale.yaml Outdated
Comment thread Solutions/Tailscale (CCF)/PREMIUM-ENDPOINTS.md
Cross-referenced against PR Azure#14253 (UniFi) Copilot findings and applied
matching fixes where Tailscale had drifted from the same conventions.

Description length (26 files, all <=255 chars):
- Shortened the `description:` block on all 8 Premium analytic rules,
  the OAuth-write-scopes Standard rule, plus 17 hunting queries (every
  one whose original description exceeded the 255-char schema limit).
- Original wording preserved verbatim in a new `description-detailed:`
  block on each file (pattern matches AWS Security Hub solution).
- The shortened text on TailscaleOAuthClientCreatedWithWriteScopes now
  opens with "Identifies" instead of "Detects" (Copilot's Azure#5 finding).

Duration format (2 rules):
- TailscalePremiumDerpRelaySurge.yaml: PT6H -> 6h
- TailscaleOAuthClientCreatedWithWriteScopes.yaml: PT5H -> 5h
  Both nested under incidentConfiguration.groupingConfiguration where
  the V3 converter does not auto-convert.

WorkbooksMetadata.json:
- Added `author: { "name": "noodlemctwoodle" }` block to both Tailscale
  workbook entries (46/48 Community workbooks have this block; was a
  Copilot drive-by suggestion).

Parsers:
- Parsers/ASimNetworkSessionTailscale.yaml: regenerated `id` as a proper
  v4 UUID (`2b9c1f5a-7d3e-4f8b-a456-c1d2e3f4a5b6`). The previous value
  had `5` as the 3rd-group first char, violating v4 spec.

Cleanup:
- Removed duplicate `Solutions/Tailscale (CCF)/Tailscale.svg` (canonical
  copy lives at `Logos/Tailscale.svg`; resolver looks there).

Package:
- Regenerated mainTemplate.json + createUiDefinition.json + 3.0.0.zip
  against current upstream V3 tooling.

Copilot's other findings on PR Azure#14482 do not require code changes:
- "VaikoraSecurityCenter dropped" - it's present at line 313.
- "ReleaseNotes uses ||" - it uses single | (Copilot may have read
  rendered output).
- "publisherId noodlemctwoodle invalid" - approved Community publisherId,
  used by the previously-accepted PR Azure#14253.
- "BasePath should be repo-relative" - kept Windows-style path to match
  the v-shukore reviewer note on the analogous PR Azure#14253.
- "PREMIUM-ENDPOINTS.md uses ||" - it uses single | (same root cause as
  the ReleaseNotes false positive).

Verification:
- ARM-TTK 48/48 PASS.
- KQL validation PASS (56 files).
- Hyperlink + Field Types + Classic App Insights PASS.
- All 46 rule/hunt YAMLs parse, all have required keys.
Three upstream CI checks failed on commit c0089a1; addressed:

KqlValidations - vimNetworkSessionTailscale.yaml KS006/005/198 at byte
1367. The case() expression used the function form startswith(x, y)
which KQL does not have - startswith is a comparison operator. Switched
both occurrences (Src and Dst host extraction) to operator form:
`SrcRawHost startswith "["` and `DstRawHost startswith "["`. Same
correction applied to ASimNetworkSessionTailscale.yaml.

KqlValidations - ASimNetworkSessionTailscale.yaml KS204 "vimNetworkSessionTailscale
does not refer to any known table, tabular variable or function". The
wrapper previously had a one-line body that called the vim parser by
name, but the KQL validator processes each parser file independently
with no registry of other custom functions in the same solution.
Replaced the wrapper body with a verbatim copy of the vim parser's
KQL so it is self-contained. Both parsers now stand on their own.

logoValidator - Logos/Tailscale.svg "Id should be GUID format and
uniquely identifiable". The SVG carried `id="Layer_1"` (Adobe export
default). Other upstream community logos (UnifiSiteManager.svg,
CrowdStrikeFalconHost.svg, etc.) carry no `id` attribute at all -
removed `id="Layer_1"` to match that pattern.

The third failure (run-arm-ttk) was an infrastructure issue on the
upstream runner ("no space left on device" mid Docker build) - not a
content problem, will pass on the next push.

Verified locally: KQL PASS (56 files), ARM-TTK 48/48 PASS, logoValidator
not run locally but SVG now matches the canonical upstream community
shape.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants