Add tailscale ccf solution#14482
Open
noodlemctwoodle wants to merge 28 commits into
Open
Conversation
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).
Contributor
There was a problem hiding this comment.
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 |
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
/logging/networkflow 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
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))vimNetworkSessionTailscale+ASimNetworkSessionTailscalewrapper) mappingTailscale_Network_CLto ASIM NetworkSession v0.2.6Design notes
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.IsRelayeddetects DERP relay flows via the127.3.3.40magic IP inphysicalTraffic./dns/nameservers,/dns/preferences,/dns/searchpaths). Both connectors land them into oneTailscale_Dns_CLtable with aConfigTypediscriminator 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./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.guidValueparameter (defaultValue: '[newGuid()]') that defers evaluation to inner-deploy scope, producing one shared GUID across every poller resource name. Mirrors the Proofpoint TAP pattern inSolutions/ProofPointTap/.Test plan
.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)Tailscale_*_CLCustomTables schemas)Package/mainTemplate.jsondeploys end-to-end to a live Sentinel workspace; Standard + Premium connectors both Connect successfully