Detection rules › Kusto

Tailscale: Device key expiring within 7 days

Status
available
Severity
medium
Time window
6h
Group by
DeviceId
Source
github.com/Azure/Azure-Sentinel

Identifies tailnet devices whose machine key expires within the next 7 days and where key expiry is not disabled. Surface proactively so renewal can be scheduled rather than forced during an outage.

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Rule body

id: b1a2c3d4-1234-5678-90ab-cdef12345001
name: "Tailscale: Device key expiring within 7 days"
description: |
  Identifies tailnet devices whose machine key expires within the next 7 days and where key expiry is not disabled. Surface proactively so renewal can be scheduled rather than forced during an outage.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: TailscaleCCF
    dataTypes:
      - Tailscale_Devices_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  Tailscale_Devices_CL
  | where TimeGenerated > ago(6h)
  | summarize arg_max(TimeGenerated, *) by DeviceId
  | where KeyExpiryDisabled == false
  | where isnotnull(Expires)
  | where Expires between (now() .. now() + 7d)
  | extend DaysToExpiry = datetime_diff('day', Expires, now())
  | project TimeGenerated, DeviceName, Hostname, User, Os, DaysToExpiry, Expires, LastSeen
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0

Stages and Predicates

Stage 1: source

Tailscale_Devices_CL

Stage 2: where

| where TimeGenerated > ago(6h)

Stage 3: summarize

| summarize arg_max(TimeGenerated, *) by DeviceId

Stage 4: where

| where KeyExpiryDisabled == false

Stage 5: where

| where isnotnull(Expires)

Stage 6: where

| where Expires between (now() .. now() + 7d)

Stage 7: extend

| extend DaysToExpiry = datetime_diff('day', Expires, now())

Stage 8: project

| project TimeGenerated, DeviceName, Hostname, User, Os, DaysToExpiry, Expires, LastSeen

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
DaysToExpiryproject
DeviceNameproject
Expiresproject
Hostnameproject
LastSeenproject
Osproject
TimeGeneratedproject
Userproject