This document does not reflect any official standard, it is intended for team-internal use but may others help also.
- Place a spaces before and after the '=' character for readability.
- allign the code using instead of spaces. Keep the '=' character and default values alligned.
let parser=(
starttime:datetime=datetime(null)
, endtime:datetime=datetime(null)
, eventresult:string='*'
, disabled:bool=false)
{let parser = (
starttime:datetime = datetime(null)
, endtime:datetime = datetime(null)
, srcipaddr_has_any_prefix:dynamic = dynamic([])
, dstipaddr_has_any_prefix:dynamic = dynamic([])
, ipaddr_has_any_prefix:dynamic = dynamic([])
, dstportnumber:int = int(null)
, hostname_has_any:dynamic = dynamic([])
, dvcaction:dynamic = dynamic([])
, eventresult:string = '*'
, disabled:bool = false
)
{- Don't go crazy on indention of code. This doesn't help when reading the code in the kusto editor window.
let AuthProductName=(starttime:datetime=datetime(null)
, endtime:datetime=datetime(null)
, targetusername_has:string="*"
, disabled:bool=false){
let AuthProductName = (
starttime:datetime = datetime(null)
, endtime:datetime = datetime(null)
, targetusername_has:string = "*"
, disabled:bool = false
)
{CommonSecurityLog | where not(disabled) | where TimeGenerated <= ago(5m)
CommonSecurityLog
| where not(disabled)
| where TimeGenerated <= ago(5m)| parse-kv DeviceCustomString3 as (Host:string, Referer:string, ['User-Agent']:string, Accept:string, ['Content-Type']:string, ['X-Forwarded-For']:string) with (pair_delimiter=@'\r\n', kv_delimiter=': ')| parse-kv DeviceCustomString3 as (
Host:string
, Referer:string
, ['User-Agent']:string
, Accept:string
, ['Content-Type']:string
, ['X-Forwarded-For']:string
)
with (
pair_delimiter=@'\r\n'
, kv_delimiter=': '
)To give clarity on where something is happening in de code split the lines between the operator and the data.
- note: this is not the case for the
| whereoperator OR when executed on a column.
| project-rename Src = SourceIP
, Dst = DestinationIP
, HttpRequestMethod = RequestMethod
, HttpReferrer = Referer
, HttpContentFormat = Accept
, HttpContentType = ['Content-Type']
, UserAgent = ['User-Agent']
, HttpRequestXff = ['X-Forwarded-For']| project-rename
Src = SourceIP
, Dst = DestinationIP
, HttpRequestMethod = RequestMethod
, HttpReferrer = Referer
, HttpContentFormat = Accept
, HttpContentType = ['Content-Type']
, UserAgent = ['User-Agent']
, HttpRequestXff = ['X-Forwarded-For']Extra spaces result in future edits where the only change is a space being added or removed
| project-rename..
TargetDvcHostname = Computer
, EventOriginalUid=EventOriginId..........
, EventOriginalType=EventID..
| extend EventCount=int(1)
, EventSchemaVersion='0.1.0'
, ActorUserIdType='SID'
, TargetUserIdType='SID'
, EventVendor='Microsoft'..
, EventStartTime =TimeGenerated...
, EventEndTime=TimeGenerated...| project-rename
TargetDvcHostname = Computer
, EventOriginalUid = EventOriginId
, EventOriginalType = EventID
| extend
EventCount = int(1)
, EventSchemaVersion = var_schemaVersion
, ActorUserIdType = 'SID'
, TargetUserIdType = 'SID'
, EventVendor = var_eventVendor
, EventStartTime = TimeGenerated
, EventEndTime = TimeGeneratedWhite-space is (mostly) irrelevant to KQL, but its proper use is key to writing easily readable code.
Use a single space after commas and semicolons, and around pairs of curly braces.
Every braceable statement should also have the opening brace on the end of a line, and the closing brace at the beginning of a line.
let SeverityLookup = datatable(var_LogSeverity:string, EventSeverity:string)
[ "low", "Low"
,"medium", "Medium"
,"high", "High"
,"", "Informational"];
let ActionLookup = datatable(DvcOriginalAction:string, DvcAction:string)
[ "drop", "Drop"
, "forward", "Allow"
, "source-reset", "Reset Source"
, "dest-reset", "Reset Destination"
, "source-dest-reset", "Reset"
, "proxy", "Allow"
, "challenge", "Reset"
, "quarantine", "Reset"
, "drop-and-quarantine", "Drop"
, "allow", "Allow"];let SeverityLookup = datatable (var_LogSeverity:string, EventSeverity:string) [
"low", "Low"
, "medium", "Medium"
, "high", "High"
, "", "Informational"
];
let ActionLookup = datatable (DvcOriginalAction:string, DvcAction:string) [
"drop", "Drop"
, "forward", "Allow"
, "source-reset", "Reset Source"
, "dest-reset", "Reset Destination"
, "source-dest-reset", "Reset"
, "proxy", "Allow"
, "challenge", "Reset"
, "quarantine", "Reset"
, "drop-and-quarantine", "Drop"
, "allow", "Allow"
];- Large comment blocks don't improve the readability of the code and also brings allignment issues.
| lookup SeverityLookup on LogSeverity
// ************************
// <Constants>
// ************************
| extend
EventSchemaVersion = '0.1.1'
, EventSchema = "Authentication"
, EventCount = 1
, TargetUsernameType = "Simple"
// ************************
// <Aliases>
// ************************
| extend
EventOriginalUid = tostring(ExternalID)
, TargetDomain = DstDomain
, TargetDomainType = DstDomainType
, User = TargetUsername
, UserType = TargetUsernameType
, Dst = TargetIpAddr
| extend
AdditionalFields = pack(| lookup SeverityLookup on LogSeverity
// extending with constant values
| extend
EventSchemaVersion = '0.1.1'
, EventSchema = 'Authentication'
, EventCount = 1
, TargetUsernameType = "Simple"
// adding aliases
| extend
EventOriginalUid = tostring(ExternalID)
, TargetDomain = DstDomain
, TargetDomainType = DstDomainType
, User = TargetUsername
, UserType = TargetUsernameType
, Dst = TargetIpAddr
| extend
AdditionalFields = pack(Use line breaks between funtions and let variable declarations.
Try to keep code that is related close to create a logical flow when reading the code.
It does help to add a linebreak between let statements that are not related.
let LogonEvents=dynamic([4624,4625]);
let LogoffEvents=dynamic([4634,4647]);
let LogonTypes=datatable(LogonType:int, EventSubType:string)[
2, 'Interactive',
3, 'Network',
4, 'Batch',
5, 'Service',
7, 'Unlock'];
let LogonStatus=datatable
(EventStatus:string,EventOriginalResultDetails:string, EventResultDetails:string)[
'0x80090325', 'SEC_E_UNTRUSTED_ROOT','Other',
'0xc0000064', 'STATUS_NO_SUCH_USER','No such user or password',
'0xc000006f', 'STATUS_INVALID_LOGON_HOURS','Logon violates policy',
'0xc0000070', 'STATUS_INVALID_WORKSTATION','Logon violates policy',let LogonEvents = dynamic([4624,4625]);
let LogoffEvents = dynamic([4634,4647]);
let LogonTypes=datatable(LogonType:int, EventSubType:string) [
2, 'Interactive'
, 3, 'Network'
, 4, 'Batch'
, 5, 'Service'
, 7, 'Unlock'
];
let LogonStatus=datatable(EventStatus:string,EventOriginalResultDetails:string, EventResultDetails:string) [
, '0x80090325', 'SEC_E_UNTRUSTED_ROOT','Other'
, '0xc0000064', 'STATUS_NO_SUCH_USER','No such user or password'
, '0xc000006f', 'STATUS_INVALID_LOGON_HOURS','Logon violates policy'
, '0xc0000070', 'STATUS_INVALID_WORKSTATION','Logon violates policy'
];let FQDN_regex = @'^([_a-zA-Z0-9][_a-zA-Z0-9-]{0,62}\.)+[a-zA-Z]{2,63}$'; // -- based on https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation without lookahead.
let DNS_domain_regex = @'^([_a-zA-Z0-9][_a-zA-Z0-9-]{0,62}\.)*[a-zA-Z]{2,63}$'; // -- Allow underscores in domain names, used by Microsoft DNS server
let domain_regex = @'^([_a-zA-Z0-9][_a-zA-Z0-9-]{0,62}\.)*[a-zA-Z]{2,63}$';
let Hostname_regex = @'^[a-zA-Z0-9-]{1,61}$';
let MD5_regex = @'[a-zA-Z0-0]{32}';
let SHA1_regex = @'[a-zA-Z0-0]{40}';
let SHA256_regex = @'[a-zA-Z0-0]{64}';
let SHA512_regex = @'[a-zA-Z0-0]{128}';
let IPprotocol = materialize (externaldata (code: string, value: string)
[@"https://www.iana.org/assignments/protocol-numbers/protocol-numbers-1.csv"] with (format="csv", IgnoreFirstRecord=true) | project value);
let DnsQueryTypeName = materialize (externaldata (value: string)
[@"https://www.iana.org/assignments/dns-parameters/dns-parameters-4.csv"] with (format="csv", IgnoreFirstRecord=true));
let DnsResponseCodeName = materialize (externaldata (code: string, value: string)
[@"https://www.iana.org/assignments/dns-parameters/dns-parameters-6.csv"] with (format="csv", IgnoreFirstRecord=true) | project toupper(value));
//let DnsQueryClassName = materialize (externaldata (dec: string, dex: string, value: string)
//[@"https://www.iana.org/assignments/dns-parameters/dns-parameters-2.csv"] with (format="csv", IgnoreFirstRecord=true) | project value);
//let schemas_in_data = materialize (T | summarize schemas = make_set(EventSchema));
let ASimFields = materialize(externaldata (ColumnName: string, ColumnType: string, Class: string, Schema: string, LogicalType:string, ListOfValues: string, AliasedField: string)
[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/ASIM/dev/ASimTester/ASimTester.csv"] with (format="csv", IgnoreFirstRecord=true) | project-rename dict_schema = Schema);let MACaddr_regex = @'^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}$';
let FQDN_regex = @'^([_a-zA-Z0-9][_a-zA-Z0-9-]{0,62}\.)+[a-zA-Z]{2,63}$';
let DNS_domain_regex = @'^([_a-zA-Z0-9][_a-zA-Z0-9-]{0,62}\.)*[a-zA-Z]{2,63}$';
let domain_regex = @'^([_a-zA-Z0-9][_a-zA-Z0-9-]{0,62}\.)*[a-zA-Z]{2,63}$';
let Hostname_regex = @'^[a-zA-Z0-9-]{1,61}$';
let MD5_regex = @'[a-zA-Z0-0]{32}';
let SHA1_regex = @'[a-zA-Z0-0]{40}';
let SHA256_regex = @'[a-zA-Z0-0]{64}';
let SHA512_regex = @'[a-zA-Z0-0]{128}';
// let schemas_in_data = materialize (T | summarize schemas = make_set(EventSchema));
let IPprotocol = materialize (externaldata (code: string, value: string) [
@"https://www.iana.org/assignments/protocol-numbers/protocol-numbers-1.csv"
]
with (
format = "csv"
, IgnoreFirstRecord = true
)
| project
value
);
// let DnsQueryClassName = materialize (externaldata (dec: string, dex: string, value: string)
let DnsQueryTypeName = materialize (
externaldata (value: string) [
@"https://www.iana.org/assignments/dns-parameters/dns-parameters-4.csv"
]
with (
format = "csv"
, IgnoreFirstRecord = true
)
);