Skip to content

Instantly share code, notes, and snippets.

@maskati
Last active November 5, 2025 14:46
Show Gist options
  • Select an option

  • Save maskati/75619cf98578b97c66f5cc546e9ba980 to your computer and use it in GitHub Desktop.

Select an option

Save maskati/75619cf98578b97c66f5cc546e9ba980 to your computer and use it in GitHub Desktop.
Azure Network Canary

Azure Network Canary

This Azure template deploys a low-interaction network honeypot VM that acts as a canary detection sensor. It monitors and logs all incoming TCP connection attempts (TCP SYN packets), providing early warning of network reconnaissance and attack activities.

The deployment creates a minimal B1ls VM (1 vCPU, 0.5GB RAM) running Azure Linux 3. This demo deployment also deploys a public IP address to demonstrate functionality based on Internet port scans. The VM is configured with cloud-init to use nftables to log TCP SYN packets to the systemd-journal. Journal SYN probe logs are continuously read, transformed and stored in the Canary_CL custom Log Analytics table with columns TimeGenerated, SourceIP and DestinationPort.

Important

The VM is deployed with SSH port 22 open (secured with SSH key authentication). Connection attempts for ports other than SSH 22 are dropped with TCP RST. These factors somewhat increase scanner interest in the target.

Deployment

To deploy first generate an SSH key pair (e.g. ssh-keygen -t ed25519 -a 100 -C "sshkey-canary" -f "ssh.key") then use the contents of ssh.key.pub for the sshPublicKey parameter.

Deploy to Azure

Analysis

Source IP addresses can be mapped to geolocations using the Kusto function geo_info_from_ip_address. The geolocations can be clustered into S2 cells using geo_point_to_s2cell which can then be visualized on a map. For example:

Canary_CL
| extend GeoInfo = geo_info_from_ip_address(SourceIP)
| extend Country = tostring(GeoInfo.country)
| extend State = tostring(GeoInfo.state)
| extend City = tostring(GeoInfo.city)
| extend Location = strcat(Country, iff(isempty(State), "", strcat(" / ", State)), iff(isempty(City), "", strcat(" / ", City)))
| extend Longitude = toreal(GeoInfo.longitude)
| extend Latitude = toreal(GeoInfo.latitude)
| extend S2Cell = geo_point_to_s2cell(Longitude, Latitude, 20)
| summarize S2CellCount = count(), S2CellName = min(Location) by S2Cell
| project S2CellPoint = geo_s2cell_to_central_point(S2Cell), S2CellCount, S2CellName
| render piechart with (kind = map)

Note

Map visualizations are not supported on the Azure Monitor Log Analytics agent, but can be viewed by connecting Azure Data Explorer web UI to Azure Monitor.

{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.38.33.27573",
"templateHash": "8235445283706564533"
}
},
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
},
"adminUsername": {
"type": "string",
"defaultValue": "azureuser",
"minLength": 1,
"maxLength": 64,
"metadata": {
"description": "Admin username for the VM. Disallowed values: \"administrator\", \"admin\", \"user\", \"user1\", \"test\", \"user2\", \"test1\", \"user3\", \"admin1\", \"1\", \"123\", \"a\", \"actuser\", \"adm\", \"admin2\", \"aspnet\", \"backup\", \"console\", \"david\", \"guest\", \"john\", \"owner\", \"root\", \"server\", \"sql\", \"support\", \"support_388945a0\", \"sys\", \"test2\", \"test3\", \"user4\", \"user5\"."
}
},
"sshPublicKey": {
"type": "string",
"minLength": 80,
"metadata": {
"description": "SSH Public Key for VM access. Recommended to use ssh-ed25519 created with `ssh-keygen -t ed25519 -a 100 -C \"sshkey-canary\" -f \"ssh.key\"`."
}
}
},
"variables": {
"$fxv#0": "I2Nsb3VkLWNvbmZpZwoKZGlza19zZXR1cDoKICBlcGhlbWVyYWwwOgogICAgdGFibGVfdHlwZTogZ3B0CiAgICAjIDUwJSB0byB0ZW1wIGRpc2ssIDUwJSB0byBzd2FwZmlsZSAoZS5nLiBmb3IgNEdCIFZNIHRlbXAgZGlzayBjcmVhdGVzIDJHQiBzd2FwKQogICAgbGF5b3V0OiBbNTAsIFs1MCwgODJdXQogICAgb3ZlcndyaXRlOiB0cnVlCmZzX3NldHVwOgogIC0gZGV2aWNlOiBlcGhlbWVyYWwwLjEKICAgIGZpbGVzeXN0ZW06IHN3YXAKICAtIGRldmljZTogZXBoZW1lcmFsMC4yCiAgICBmaWxlc3lzdGVtOiBleHQ0Cm1vdW50czoKICAjIHN3YXAKICAtIFsiZXBoZW1lcmFsMC4xIiwgIm5vbmUiLCAic3dhcCIsICJzdyIsICIwIiwgIjAiXQogICMgdGVtcCBkaXNrIG1vdW50ZWQgYXQgL21udCBhcyBwZXIgYXp1cmUgbGludXggY29udmVudGlvbgogIC0gWyJlcGhlbWVyYWwwLjIiLCAiL21udCJdCgpwYWNrYWdlX3VwZ3JhZGU6IHRydWUKCnBhY2thZ2VzOgogIC0gbmZ0YWJsZXMKCndyaXRlX2ZpbGVzOgogIC0gb3duZXI6IHJvb3Q6cm9vdAogICAgcGF0aDogL2V0Yy9zeXNjb25maWcvbmZ0YWJsZXMuY29uZgogICAgcGVybWlzc2lvbnM6ICcwNjAwJwogICAgY29udGVudDogfAogICAgICBmbHVzaCBydWxlc2V0CiAgICAgIHRhYmxlIGluZXQgZmlsdGVyIHsKICAgICAgICBjaGFpbiBpbnB1dCB7CiAgICAgICAgICB0eXBlIGZpbHRlciBob29rIGlucHV0IHByaW9yaXR5IGZpbHRlcjsKICAgICAgICAgIHRjcCBmbGFncyBzeW4gLyBzeW4sYWNrIGxpbWl0IHJhdGUgMTAvc2Vjb25kIGJ1cnN0IDIwIHBhY2tldHMgbG9nIHByZWZpeCAiSU4tU1lOOiAiIGxldmVsIGluZm87CiAgICAgICAgfQogICAgICAgIGNoYWluIGZvcndhcmQgewogICAgICAgICAgdHlwZSBmaWx0ZXIgaG9vayBmb3J3YXJkIHByaW9yaXR5IGZpbHRlcjsKICAgICAgICB9CiAgICAgICAgY2hhaW4gb3V0cHV0IHsKICAgICAgICAgIHR5cGUgZmlsdGVyIGhvb2sgb3V0cHV0IHByaW9yaXR5IGZpbHRlcjsKICAgICAgICB9CiAgICAgIH0KCnJ1bmNtZDoKICAtIGVjaG8gIipSVU5DTUQqIFN0YXJ0aW5nIHJ1bmNtZCIKICAtIHN5c3RlbWN0bCBlbmFibGUgbmZ0YWJsZXMKICAtIHN5c3RlbWN0bCBzdGFydCBuZnRhYmxlcwogIC0gc3lzdGVtY3RsIHN0YXR1cyBuZnRhYmxlcwogIC0gbmZ0IGxpc3QgcnVsZXNldAogIC0gZWNobyAiKlJVTkNNRCogQ29tcGxldGVkIHJ1bmNtZCIK"
},
"resources": [
{
"type": "Microsoft.OperationalInsights/workspaces/tables",
"apiVersion": "2023-09-01",
"name": "[format('{0}/{1}', 'log-canary', 'Canary_CL')]",
"properties": {
"plan": "Analytics",
"retentionInDays": 31,
"totalRetentionInDays": 31,
"schema": {
"name": "Canary_CL",
"displayName": "Canary_CL",
"description": "Logs of incoming TCP SYN packets targeting the canary",
"columns": [
{
"name": "TimeGenerated",
"type": "datetime",
"description": "The time at which the data was generated"
},
{
"name": "SourceIP",
"type": "string",
"description": "Source IP address of the SYN packet"
},
{
"name": "DestinationPort",
"type": "int",
"description": "Destination port of the SYN packet"
}
]
}
},
"dependsOn": [
"[resourceId('Microsoft.OperationalInsights/workspaces', 'log-canary')]"
]
},
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"apiVersion": "2025-04-01",
"name": "[format('{0}/{1}', 'vm-canary', 'AzureMonitorAgentExtension')]",
"location": "[parameters('location')]",
"properties": {
"publisher": "Microsoft.Azure.Monitor",
"type": "AzureMonitorLinuxAgent",
"typeHandlerVersion": "1.37",
"autoUpgradeMinorVersion": true,
"enableAutomaticUpgrade": true
},
"dependsOn": [
"[resourceId('Microsoft.Compute/virtualMachines', 'vm-canary')]"
]
},
{
"type": "Microsoft.OperationalInsights/workspaces",
"apiVersion": "2023-09-01",
"name": "log-canary",
"location": "[parameters('location')]",
"properties": {
"retentionInDays": 31,
"workspaceCapping": {
"dailyQuotaGb": 1
}
}
},
{
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2024-10-01",
"name": "vnet-canary",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"10.0.0.0/16"
]
},
"subnets": [
{
"name": "default",
"properties": {
"addressPrefix": "10.0.0.0/24"
}
}
]
}
},
{
"type": "Microsoft.Network/networkSecurityGroups",
"apiVersion": "2024-10-01",
"name": "nsg-canary",
"location": "[parameters('location')]",
"properties": {
"securityRules": [
{
"name": "Allow-All-Inbound",
"properties": {
"priority": 1001,
"access": "Allow",
"direction": "Inbound",
"protocol": "Tcp",
"sourceAddressPrefix": "*",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "*"
}
}
]
}
},
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2024-10-01",
"name": "pip-canary",
"location": "[parameters('location')]",
"sku": {
"name": "Standard"
},
"properties": {
"publicIPAllocationMethod": "Static"
}
},
{
"type": "Microsoft.Network/networkInterfaces",
"apiVersion": "2024-10-01",
"name": "nic-canary",
"location": "[parameters('location')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', 'pip-canary')]"
},
"subnet": {
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'vnet-canary', 'default')]"
}
}
}
],
"networkSecurityGroup": {
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-canary')]"
}
},
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', 'nsg-canary')]",
"[resourceId('Microsoft.Network/publicIPAddresses', 'pip-canary')]",
"[resourceId('Microsoft.Network/virtualNetworks', 'vnet-canary')]"
]
},
{
"type": "Microsoft.Compute/sshPublicKeys",
"apiVersion": "2025-04-01",
"name": "sshkey-canary",
"location": "[parameters('location')]",
"properties": {
"publicKey": "[parameters('sshPublicKey')]"
}
},
{
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2025-04-01",
"name": "vm-canary",
"location": "[parameters('location')]",
"identity": {
"type": "SystemAssigned"
},
"properties": {
"hardwareProfile": {
"vmSize": "Standard_B1ls"
},
"osProfile": {
"computerName": "virtualmachine",
"adminUsername": "[parameters('adminUsername')]",
"linuxConfiguration": {
"disablePasswordAuthentication": true,
"ssh": {
"publicKeys": [
{
"keyData": "[reference(resourceId('Microsoft.Compute/sshPublicKeys', 'sshkey-canary'), '2025-04-01').publicKey]",
"path": "[format('/home/{0}/.ssh/authorized_keys', parameters('adminUsername'))]"
}
]
}
},
"customData": "[variables('$fxv#0')]"
},
"storageProfile": {
"imageReference": {
"publisher": "MicrosoftCBLMariner",
"offer": "azure-linux-3",
"sku": "azure-linux-3-kernel-hwe",
"version": "latest"
},
"osDisk": {
"createOption": "FromImage",
"managedDisk": {
"storageAccountType": "StandardSSD_LRS"
},
"caching": "ReadOnly",
"diffDiskSettings": {
"option": "Local",
"placement": "CacheDisk"
},
"deleteOption": "Delete"
}
},
"securityProfile": {
"encryptionAtHost": true,
"securityType": "TrustedLaunch",
"uefiSettings": {
"secureBootEnabled": true,
"vTpmEnabled": true
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', 'nic-canary')]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": true
}
}
},
"dependsOn": [
"[resourceId('Microsoft.Network/networkInterfaces', 'nic-canary')]",
"[resourceId('Microsoft.Compute/sshPublicKeys', 'sshkey-canary')]"
]
},
{
"type": "Microsoft.Insights/dataCollectionRules",
"apiVersion": "2023-03-11",
"name": "dcr-agentsettings",
"location": "[parameters('location')]",
"kind": "AgentSettings",
"properties": {
"description": "Agent settings to configure Azure Monitor Agent",
"agentSettings": {
"logs": [
{
"name": "MaxDiskQuotaInMB",
"value": "4000"
}
]
}
}
},
{
"type": "Microsoft.Insights/dataCollectionRuleAssociations",
"apiVersion": "2023-03-11",
"scope": "[format('Microsoft.Compute/virtualMachines/{0}', 'vm-canary')]",
"name": "agentSettings",
"properties": {
"dataCollectionRuleId": "[resourceId('Microsoft.Insights/dataCollectionRules', 'dcr-agentsettings')]",
"description": "Agent settings to configure Azure Monitor Agent - association"
},
"dependsOn": [
"[resourceId('Microsoft.Insights/dataCollectionRules', 'dcr-agentsettings')]",
"[resourceId('Microsoft.Compute/virtualMachines', 'vm-canary')]"
]
},
{
"type": "Microsoft.Insights/dataCollectionRules",
"apiVersion": "2023-03-11",
"name": "dcr-canary",
"location": "[parameters('location')]",
"kind": "Linux",
"properties": {
"description": "Collect canary SYN packet logs",
"dataSources": {
"syslog": [
{
"name": "sysLogsDataSource-kern-info",
"facilityNames": [
"kern"
],
"logLevels": [
"Info"
],
"streams": [
"Microsoft-Syslog"
]
}
]
},
"destinations": {
"logAnalytics": [
{
"name": "Canary-Table-Out",
"workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', 'log-canary')]"
}
]
},
"dataFlows": [
{
"streams": [
"Microsoft-Syslog"
],
"destinations": [
"Canary-Table-Out"
],
"transformKql": " source\n | where SyslogMessage startswith_cs \"IN-SYN: \"\n | extend SourceIP = tostring(extract(@\"SRC=(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\", 1, SyslogMessage))\n | extend DestinationPort = toint(extract(@\"DPT=(\\d+)\", 1, SyslogMessage))\n | project TimeGenerated, SourceIP, DestinationPort\n ",
"outputStream": "[format('Custom-{0}', 'Canary_CL')]"
}
]
},
"dependsOn": [
"[resourceId('Microsoft.OperationalInsights/workspaces', 'log-canary')]",
"[resourceId('Microsoft.OperationalInsights/workspaces/tables', 'log-canary', 'Canary_CL')]"
]
},
{
"type": "Microsoft.Insights/dataCollectionRuleAssociations",
"apiVersion": "2023-03-11",
"scope": "[format('Microsoft.Compute/virtualMachines/{0}', 'vm-canary')]",
"name": "dcr-canary-association",
"properties": {
"dataCollectionRuleId": "[resourceId('Microsoft.Insights/dataCollectionRules', 'dcr-canary')]",
"description": "Collect canary SYN packet logs - association"
},
"dependsOn": [
"[resourceId('Microsoft.Insights/dataCollectionRules', 'dcr-canary')]",
"[resourceId('Microsoft.Compute/virtualMachines', 'vm-canary')]"
]
}
],
"outputs": {
"sshCommand": {
"type": "string",
"value": "[format('ssh -i ssh.key {0}@{1}', parameters('adminUsername'), reference(resourceId('Microsoft.Network/publicIPAddresses', 'pip-canary'), '2024-10-01').ipAddress)]"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment