diff --git a/data-explorer/kusto/functions-library/functions-library.md b/data-explorer/kusto/functions-library/functions-library.md index 0f0ac9f424..5afa469786 100644 --- a/data-explorer/kusto/functions-library/functions-library.md +++ b/data-explorer/kusto/functions-library/functions-library.md @@ -3,7 +3,7 @@ title: Functions library description: This article describes user-defined functions that extend query environment capabilities. ms.reviewer: adieldar ms.topic: reference -ms.date: 12/16/2024 +ms.date: 01/05/2025 monikerRange: "microsoft-fabric || azure-data-explorer || azure-monitor || microsoft-sentinel" --- # Functions library @@ -20,6 +20,8 @@ The user-defined functions code is given in the articles. It can be used within |--|--| | [detect_anomalous_new_entity_fl()](detect-anomalous-new-entity-fl.md) | Detect the appearance of anomalous new entities in timestamped data. | | [detect_anomalous_spike_fl()](detect-anomalous-spike-fl.md) | Detect the appearance of anomalous spikes in numeric variables in timestamped data. | +| [graph_blast_radius_fl()](graph-blast-radius-fl.md) | Calculate the Blast Radius (list and score) of source nodes over path or edge data. +| [graph_exposure_perimeter_fl()](graph-exposure-perimeter-fl.md) | Calculate the Exposure Perimeter (list and score) of target nodes over path or edge data. | [graph_path_discovery_fl()](graph-path-discovery-fl.md) | Discover valid paths between relevant endpoints (sources and targets) over graph data (edge and nodes). ## General functions diff --git a/data-explorer/kusto/functions-library/graph-blast-radius-fl.md b/data-explorer/kusto/functions-library/graph-blast-radius-fl.md new file mode 100644 index 0000000000..ada9029896 --- /dev/null +++ b/data-explorer/kusto/functions-library/graph-blast-radius-fl.md @@ -0,0 +1,241 @@ +--- +title: graph_blast_radius_fl() +description: Learn how to use the graph_blast_radius_fl() function to calculate the Blast Radius of source nodes over path or edge data. +ms.reviewer: andkar +ms.topic: reference +ms.date: 01/05/2025 +monikerRange: "microsoft-fabric || azure-data-explorer || azure-monitor || microsoft-sentinel" +--- +# graph_blast_radius_fl() + +>[!INCLUDE [applies](../includes/applies-to-version/applies.md)] [!INCLUDE [fabric](../includes/applies-to-version/fabric.md)] [!INCLUDE [azure-data-explorer](../includes/applies-to-version/azure-data-explorer.md)] [!INCLUDE [monitor](../includes/applies-to-version/monitor.md)] [!INCLUDE [sentinel](../includes/applies-to-version/sentinel.md)] + +Calculate the Blast Radius (list and score) of source nodes over path or edge data. + +The function `graph_blast_radius_fl()` is a [UDF (user-defined function)](../query/functions/user-defined-functions.md) that allows to calculate the Blast Radius of each of the source nodes based on paths or edges data. Each row of input data contains a source node and a target node, which can represent direct connections (edges) between nodes and targets, or longer multi-hop paths between them. If the paths aren't available, we can first discover them using [graph-match()](../query/graph-match-operator.md) operator or [graph_path_discovery_fl()](graph-path-discovery-fl.md) function. Then `graph_blast_radius_fl()` can be executed on top of the output of path discovery. + +Blast Radius represents the connectivity of a specific source node to relevant targets. The more targets the source can access, the more effect it has if it's compromised by the attacker - hence the name. Nodes with high Blast Radius are important in cybersecurity domain due to the potential damage they might cause and to being highly valued by attackers. Thus, nodes with high Blast Radius should be protected accordingly - in terms of hardening and prioritizing security signals such as alerts. + +The function outputs a list of connected targets for each source and also a score representing targets' number. Optionally, in case there's a meaningful 'weight' for each target (such as criticality or cost), a weighted score is calculated as a sum of targets' weights. In addition, the limits for maximum total number of shown sources and maximum number of targets in each list are exposed as optional parameters for better control. + +## Syntax + +`graph_blast_radius_fl(`*sourceIdColumnName*, *targetIdColumnName*, [*targetWeightColumnName*], [*resultCountLimit*], [*listedIdsLimit*]`)` + +[!INCLUDE [syntax-conventions-note](../includes/syntax-conventions-note.md)] + +## Parameters + +| Name | Type | Required | Description | +|--|--|--|--| +| *sourceIdColumnName* | `string` | :heavy_check_mark: | The name of the column containing the source node Ids (either for edges or paths). | +| *targetIdColumnName* | `string` | :heavy_check_mark: | The name of the column containing the target node Ids (either for edges or paths). | +| *targetWeightColumnName* | `string` | | The name of the column containing the target nodes' weights (such as criticality). If no relevant weights are present, the weighted score is equal to 0. The default column name is *noWeightsColumn*. | +| *resultCountLimit* | `long` | | The maximum number of returned rows (sorted by descending score). The default value is 100000. | +| *listedIdsLimit* | `long` | | The maximum number of targets listed for each source. The default value is 50. | + + +## Function definition + +You can define the function by either embedding its code as a query-defined function, or creating it as a stored function in your database, as follows: + +### [Query-defined](#tab/query-defined) + +Define the function using the following [let statement](../query/let-statement.md). No permissions are required. + +> [!IMPORTANT] +> A [let statement](../query/let-statement.md) can't run on its own. It must be followed by a [tabular expression statement](../query/tabular-expression-statements.md). To run a working example of `graph_blast_radius_fl()`, see [Example](#example). + +```kusto +let graph_blast_radius_fl = (T:(*), sourceIdColumnName:string, targetIdColumnName:string, targetWeightColumnName:string = 'noWeightsColumn' + , resultCountLimit:long = 100000, listedIdsLimit:long = 50) +{ +let paths = ( + T + | extend sourceId = column_ifexists(sourceIdColumnName, '') + | extend targetId = column_ifexists(targetIdColumnName, '') + | extend targetWeight = tolong(column_ifexists(targetWeightColumnName, 0)) +); +let aggregatedPaths = ( + paths + | sort by sourceId, targetWeight desc + | summarize blastRadiusList = array_slice(make_set_if(targetId, isnotempty(targetId)), 0, (listedIdsLimit - 1)) + , blastRadiusScore = dcountif(targetId, isnotempty(targetId)) + , blastRadiusScoreWeighted = sum(targetWeight) + by sourceId + | extend isBlastRadiusListCapped = (blastRadiusScore > listedIdsLimit) +); +aggregatedPaths +| top resultCountLimit by blastRadiusScore desc +}; +// Write your query to use the function here. +``` + +### [Stored](#tab/stored) + +Define the stored function once using the following [`.create function`](../management/create-function.md). [Database User permissions](../access-control/role-based-access-control.md) are required. + +> [!IMPORTANT] +> You must run this code to create the function before you can use the function as shown in the [Example](#example). + +```kusto +.create-or-alter function with (docstring = "Calculate the Blast Radius (list and score) of source nodes over path or edge data", skipvalidation = "true", folder = 'Cybersecurity') +graph_blast_radius_fl (T:(*), sourceIdColumnName:string, targetIdColumnName:string, targetWeightColumnName:string = 'noWeightsColumn' + , resultCountLimit:long = 100000, listedIdsLimit:long = 50) +{ +let paths = ( + T + | extend sourceId = column_ifexists(sourceIdColumnName, '') + | extend targetId = column_ifexists(targetIdColumnName, '') + | extend targetWeight = tolong(column_ifexists(targetWeightColumnName, 0)) +); +let aggregatedPaths = ( + paths + | sort by sourceId, targetWeight desc + | summarize blastRadiusList = array_slice(make_set_if(targetId, isnotempty(targetId)), 0, (listedIdsLimit - 1)) + , blastRadiusScore = dcountif(targetId, isnotempty(targetId)) + , blastRadiusScoreWeighted = sum(targetWeight) + by sourceId + | extend isBlastRadiusListCapped = (blastRadiusScore > listedIdsLimit) +); +aggregatedPaths +| top resultCountLimit by blastRadiusScore desc +} +``` + +--- + +## Example + +The following example uses the [invoke operator](../query/invoke-operator.md) to run the function. + +### [Query-defined](#tab/query-defined) + +To use a query-defined function, invoke it after the embedded function definition. + +:::moniker range="azure-data-explorer" +> [!div class="nextstepaction"] +> Run the query +::: moniker-end + +```kusto +let connections = datatable (SourceNodeName:string, TargetNodeName:string, TargetNodeCriticality:int)[ + 'vm-work-1', 'webapp-prd', 3, + 'vm-custom', 'webapp-prd', 3, + 'webapp-prd', 'vm-custom', 1, + 'webapp-prd', 'test-machine', 1, + 'vm-custom', 'server-0126', 1, + 'vm-custom', 'hub_router', 2, + 'webapp-prd', 'hub_router', 2, + 'test-machine', 'vm-custom', 1, + 'test-machine', 'hub_router', 2, + 'hub_router', 'remote_DT', 1, + 'vm-work-1', 'storage_main_backup', 5, + 'hub_router', 'vm-work-2', 1, + 'vm-work-2', 'backup_prc', 3, + 'remote_DT', 'backup_prc', 3, + 'backup_prc', 'storage_main_backup', 5, + 'backup_prc', 'storage_DevBox', 1, + 'device_A1', 'sevice_B2', 2, + 'sevice_B2', 'device_A1', 2 +]; +let graph_blast_radius_fl = (T:(*), sourceIdColumnName:string, targetIdColumnName:string, targetWeightColumnName:string = 'noWeightsColumn' + , resultCountLimit:long = 100000, listedIdsLimit:long = 50) +{ +let paths = ( + T + | extend sourceId = column_ifexists(sourceIdColumnName, '') + | extend targetId = column_ifexists(targetIdColumnName, '') + | extend targetWeight = tolong(column_ifexists(targetWeightColumnName, 0)) +); +let aggregatedPaths = ( + paths + | sort by sourceId, targetWeight desc + | summarize blastRadiusList = array_slice(make_set_if(targetId, isnotempty(targetId)), 0, (listedIdsLimit - 1)) + , blastRadiusScore = dcountif(targetId, isnotempty(targetId)) + , blastRadiusScoreWeighted = sum(targetWeight) + by sourceId + | extend isBlastRadiusListCapped = (blastRadiusScore > listedIdsLimit) +); +aggregatedPaths +| top resultCountLimit by blastRadiusScore desc +}; +connections +| invoke graph_blast_radius_fl(sourceIdColumnName = 'SourceNodeName' + , targetIdColumnName = 'TargetNodeName' + , targetWeightColumnName = 'TargetNodeCriticality' +) +``` + +### [Stored](#tab/stored) + +> [!IMPORTANT] +> For this example to run successfully, you must first run the [Function definition](#function-definition) code to store the function. + +```kusto +let connections = datatable (SourceNodeName:string, TargetNodeName:string, TargetNodeCriticality:int)[ + 'vm-work-1', 'webapp-prd', 3, + 'vm-custom', 'webapp-prd', 3, + 'webapp-prd', 'vm-custom', 1, + 'webapp-prd', 'test-machine', 1, + 'vm-custom', 'server-0126', 1, + 'vm-custom', 'hub_router', 2, + 'webapp-prd', 'hub_router', 2, + 'test-machine', 'vm-custom', 1, + 'test-machine', 'hub_router', 2, + 'hub_router', 'remote_DT', 1, + 'vm-work-1', 'storage_main_backup', 5, + 'hub_router', 'vm-work-2', 1, + 'vm-work-2', 'backup_prc', 3, + 'remote_DT', 'backup_prc', 3, + 'backup_prc', 'storage_main_backup', 5, + 'backup_prc', 'storage_DevBox', 1, + 'device_A1', 'sevice_B2', 2, + 'sevice_B2', 'device_A1', 2 +]; +connections +| invoke graph_blast_radius_fl(sourceIdColumnName = 'SourceNodeName' + , targetIdColumnName = 'TargetNodeName' + , targetWeightColumnName = 'TargetNodeCriticality' +) +``` + +--- + +**Output** + + +| sourceId | blastRadiusList | blastRadiusScore | blastRadiusScoreWeighted | isBlastRadiusListCapped | +| ------------ | ----------------------------------------- | ---------------- | ------------------------ | ----------------------- | +| webapp-prd | ["vm-custom","test-machine","hub_router"] | 3 | 4 | FALSE | +| vm-custom | ["webapp-prd","server-0126","hub_router"] | 3 | 6 | FALSE | +| test-machine | ["vm-custom","hub_router"] | 2 | 3 | FALSE | +| vm-work-1 | ["webapp-prd","storage_main_backup"] | 2 | 8 | FALSE | +| backup_prc | ["storage_main_backup","storage_DevBox"] | 2 | 6 | FALSE | +| hub_router | ["remote_DT","vm-work-2"] | 2 | 2 | FALSE | +| vm-work-2 | ["backup_prc"] | 1 | 3 | FALSE | +| device_A1 | ["sevice_B2"] | 1 | 2 | FALSE | +| remote_DT | ["backup_prc"] | 1 | 3 | FALSE | +| sevice_B2 | ["device_A1"] | 1 | 2 | FALSE | + + + +Running the function takes the connections between and sources and targets, and aggregates the targets by source. For each source, Blast Radius represents the connected targets as score (regular and weighted) and list. + + +Each row in the output contains the following fields: + +* `sourceId`: ID of the source node taken from relevant column. +* `blastRadiusList`: a list of target nodes Ids (taken from relevant column) that the source node is connected to. The list is capped to maximum length limit of listedIdsLimit parameter. +* `blastRadiusScore`: the score is the count of target nodes that the source is connected to. High Blast Radius score indicates that the source node can potentially access lots of targets, and should be treated accordingly. +* `blastRadiusScoreWeighted`: the weighted score is the sum of the optional target nodes' weight column, representing their value - such as criticality or cost. If such weight exists, weighted Blast Radius score might be a more accurate metric of source node value due to potential access to high value targets. +* `isBlastRadiusListCapped`: boolean flag whether the list of targets was capped by listedIdsLimit parameter. If it's true, then other targets can be accessed from the source in addition to the listed one (up to the number of blastRadiusScore). + +In the example above, we run the `graph_blast_radius_fl()` function on top of connections between sources and targets. In the first row of the output, we can see that source node 'webapp-prd' is connected to three targets ('vm-custom', 'test-machine', 'hub_router'). We use the input data TargetNodeCriticality column as target weights, and get a cumulative weight of 4. Also, since the number of targets is 3 and the default list limit is 50, all of the targets are shown - so the value of isBlastRadiusListCapped column is FALSE. + +If the multi-hop paths aren't available, we can build multi-hop paths between sources and targets (for example, by running 'graph_path_discovery_fl()') and run 'graph_blast_radius_fl()' on top of the results. + +The output looks similar, but will reflect the Blast Radius calculated over multi-hop paths, thus being a better indicator of source nodes true connectivity to relevant targets. In order to find the full paths between source and target scenarios (for example, for disruption), [graph_path_discovery_fl()](graph-path-discovery-fl.md) function can be used with filters on relevant source and target nodes. + +The function `graph_blast_radius_fl()` can be used to calculate the Blast Radius of source nodes, calculated either over direct edges or longer paths. In cybersecurity domain, it can be used for several insights. Blast Radius scores (regular and weighted), represent source node's importance both from defenders and attackers perspectives. Nodes with high Blast Radius should be protected accordingly (for example, in terms of access hardening and vulnerability management); security signals (such as alerts) on such nodes should be prioritized. The Blast Radius list should be monitored for undesired connections between sources and targets and used in disruption scenarios (for example, if there was active compromise of the source, connections between it and important target should be broken). + diff --git a/data-explorer/kusto/functions-library/graph-exposure-perimeter-fl.md b/data-explorer/kusto/functions-library/graph-exposure-perimeter-fl.md new file mode 100644 index 0000000000..4f5a372516 --- /dev/null +++ b/data-explorer/kusto/functions-library/graph-exposure-perimeter-fl.md @@ -0,0 +1,243 @@ +--- +title: graph_exposure_perimeter_fl() +description: Learn how to use the graph_exposure_perimeter_fl() function to calculate the Exposure Perimeter of target nodes over path or edge data. +ms.reviewer: andkar +ms.topic: reference +ms.date: 01/05/2025 +monikerRange: "microsoft-fabric || azure-data-explorer || azure-monitor || microsoft-sentinel" +--- +# graph_exposure_perimeter_fl() + +>[!INCLUDE [applies](../includes/applies-to-version/applies.md)] [!INCLUDE [fabric](../includes/applies-to-version/fabric.md)] [!INCLUDE [azure-data-explorer](../includes/applies-to-version/azure-data-explorer.md)] [!INCLUDE [monitor](../includes/applies-to-version/monitor.md)] [!INCLUDE [sentinel](../includes/applies-to-version/sentinel.md)] + +Calculate the Exposure Perimeter (list and score) of target nodes over path or edge data. + +The function `graph_exposure_perimeter_fl()` is a [UDF (user-defined function)](../query/functions/user-defined-functions.md) that allows to calculate the Exposure Perimeter of each of the target nodes based on paths or edges data. Each row of input data contains a source node and a target node, which can represent direct connections (edges) between nodes and targets, or longer multi-hop paths between them. If the paths aren't available, we can first discover them using [graph-match()](../query/graph-match-operator.md) operator or [graph_path_discovery_fl()](graph-path-discovery-fl.md) function. Then `graph_exposure_perimeter_fl()` can be executed on top of the output of path discovery. + +Exposure Perimeter represents the accessibility of a specific target from relevant source nodes. The more sources can access the target, the more exposed it's to potential compromise by the attacker - hence the name. Nodes with high Exposure Perimeter are important in cybersecurity domain due to the likelihood they might be reached illegitimately and to being highly valued by attackers. Thus, nodes with high Exposure Perimeter should be protected accordingly - in terms of hardening and monitoring their perimeter. + +The function outputs a list of connected sources that can reach each target and also a score representing sources' number. Optionally, in case there's a meaningful 'weight' for each source (such as vulnerability or exposedness), a weighted score is calculated as a sum of sources' weights. In addition, the limits for maximum total number of shown targets and maximum number of sources in each list are exposed as optional parameters for better control. + +## Syntax + +`graph_exposure_perimeter_fl(`*sourceIdColumnName*, *targetIdColumnName*, [*sourceWeightColumnName*], [*resultCountLimit*], [*listedIdsLimit*]`)` + +[!INCLUDE [syntax-conventions-note](../includes/syntax-conventions-note.md)] + +## Parameters + +| Name | Type | Required | Description | +|--|--|--|--| +| *sourceIdColumnName* | `string` | :heavy_check_mark: | The name of the column containing the source node Ids (either for edges or paths). | +| *targetIdColumnName* | `string` | :heavy_check_mark: | The name of the column containing the target node Ids (either for edges or paths). | +| *sourceWeightColumnName* | `string` | | The name of the column containing the source nodes' weights (such as vulnerability). If no relevant weights are present, the weighted score is equal to 0. The default column name is 'noWeightsColumn'. | +| *resultCountLimit* | `long` | | The maximum number of returned rows (sorted by descending score). The default value is 100000. | +| *listedIdsLimit* | `long` | | The maximum number of targets listed for each source. The default value is 50. | + + +## Function definition + +You can define the function by either embedding its code as a query-defined function, or creating it as a stored function in your database, as follows: + +### [Query-defined](#tab/query-defined) + +Define the function using the following [let statement](../query/let-statement.md). No permissions are required. + +> [!IMPORTANT] +> A [let statement](../query/let-statement.md) can't run on its own. It must be followed by a [tabular expression statement](../query/tabular-expression-statements.md). To run a working example of `graph_exposure_perimeter_fl()`, see [Example](#example). + +```kusto +let exposure_perimeter_fl = (T:(*), sourceIdColumnName:string, targetIdColumnName:string, sourceWeightColumnName:string= 'noWeightsColumn' + , resultCountLimit:long = 100000, listedIdsLimit:long = 50) +{ +let paths = ( + T + | extend sourceId = column_ifexists(sourceIdColumnName, '') + | extend targetId = column_ifexists(targetIdColumnName, '') + | extend sourceWeight = tolong(column_ifexists(sourceWeightColumnName, 0)) +); +let aggregatedPaths = ( + paths + | sort by targetId, sourceWeight desc + | summarize exposurePerimeterList = array_slice(make_set_if(sourceId, isnotempty(sourceId)), 0, (listedIdsLimit - 1)) + , exposurePerimeterScore = dcountif(sourceId, isnotempty(sourceId)) + , exposurePerimeterScoreWeighted = sum(sourceWeight) + by targetId + | extend isExposurePerimeterCapped = (exposurePerimeterScore > listedIdsLimit) +); +aggregatedPaths +| top resultCountLimit by exposurePerimeterScore desc +}; +// Write your query to use the function here. +``` + +### [Stored](#tab/stored) + +Define the stored function once using the following [`.create function`](../management/create-function.md). [Database User permissions](../access-control/role-based-access-control.md) are required. + +> [!IMPORTANT] +> You must run this code to create the function before you can use the function as shown in the [Example](#example). + +```kusto +.create-or-alter function with (docstring = "Calculate the Exposure Perimeter (list and score) of target nodes over path or edge data", skipvalidation = "true", folder = 'Cybersecurity') +graph_exposure_perimeter_fl (T:(*), sourceIdColumnName:string, targetIdColumnName:string, sourceWeightColumnName:string = 'noWeightsColumn' + , resultCountLimit:long = 100000, listedIdsLimit:long = 50) +{ +let paths = ( + T + | extend sourceId = column_ifexists(sourceIdColumnName, '') + | extend targetId = column_ifexists(targetIdColumnName, '') + | extend sourceWeight = tolong(column_ifexists(sourceWeightColumnName, 0)) +); +let aggregatedPaths = ( + paths + | sort by targetId, sourceWeight desc + | summarize exposurePerimeterList = array_slice(make_set_if(sourceId, isnotempty(sourceId)), 0, (listedIdsLimit - 1)) + , exposurePerimeterScore = dcountif(sourceId, isnotempty(sourceId)) + , exposurePerimeterScoreWeighted = sum(sourceWeight) + by targetId + | extend isExposurePerimeterCapped = (exposurePerimeterScore > listedIdsLimit) +); +aggregatedPaths +| top resultCountLimit by exposurePerimeterScore desc +} +``` + +--- + +## Example + +The following example uses the [invoke operator](../query/invoke-operator.md) to run the function. + +### [Query-defined](#tab/query-defined) + +To use a query-defined function, invoke it after the embedded function definition. + +:::moniker range="azure-data-explorer" +> [!div class="nextstepaction"] +> Run the query +::: moniker-end + +```kusto +let connections = datatable (SourceNodeName:string, TargetNodeName:string, SourceNodeVulnerability:int)[ + 'vm-work-1', 'webapp-prd', 0, + 'vm-custom', 'webapp-prd', 4, + 'webapp-prd', 'vm-custom', 1, + 'webapp-prd', 'test-machine', 1, + 'vm-custom', 'server-0126', 4, + 'vm-custom', 'hub_router', 4, + 'webapp-prd', 'hub_router', 2, + 'test-machine', 'vm-custom', 5, + 'test-machine', 'hub_router', 5, + 'hub_router', 'remote_DT', 0, + 'vm-work-1', 'storage_main_backup', 0, + 'hub_router', 'vm-work-2', 0, + 'vm-work-2', 'backup_prc', 1, + 'remote_DT', 'backup_prc', 2, + 'backup_prc', 'storage_main_backup', 0, + 'backup_prc', 'storage_DevBox', 0, + 'device_A1', 'sevice_B2', 1, + 'sevice_B2', 'device_A1', 2 +]; +let exposure_perimeter_fl = (T:(*), sourceIdColumnName:string, targetIdColumnName:string, sourceWeightColumnName:string = 'noWeightsColumn' + , resultCountLimit:long = 100000, listedIdsLimit:long = 50) +{ +let paths = ( + T + | extend sourceId = column_ifexists(sourceIdColumnName, '') + | extend targetId = column_ifexists(targetIdColumnName, '') + | extend sourceWeight = tolong(column_ifexists(sourceWeightColumnName, 0)) +); +let aggregatedPaths = ( + paths + | sort by targetId, sourceWeight desc + | summarize exposurePerimeterList = array_slice(make_set_if(sourceId, isnotempty(sourceId)), 0, (listedIdsLimit - 1)) + , exposurePerimeterScore = dcountif(sourceId, isnotempty(sourceId)) + , exposurePerimeterScoreWeighted = sum(sourceWeight) + by targetId + | extend isExposurePerimeterCapped = (exposurePerimeterScore > listedIdsLimit) +); +aggregatedPaths +| top resultCountLimit by exposurePerimeterScore desc +}; +connections +| invoke exposure_perimeter_fl(sourceIdColumnName = 'SourceNodeName' + , targetIdColumnName = 'TargetNodeName' + , sourceWeightColumnName = 'SourceNodeVulnerability' +) +``` + +### [Stored](#tab/stored) + +> [!IMPORTANT] +> For this example to run successfully, you must first run the [Function definition](#function-definition) code to store the function. + +```kusto +let connections = datatable (SourceNodeName:string, TargetNodeName:string, SourceNodeVulnerability:int)[ + 'vm-work-1', 'webapp-prd', 0, + 'vm-custom', 'webapp-prd', 4, + 'webapp-prd', 'vm-custom', 1, + 'webapp-prd', 'test-machine', 1, + 'vm-custom', 'server-0126', 4, + 'vm-custom', 'hub_router', 4, + 'webapp-prd', 'hub_router', 2, + 'test-machine', 'vm-custom', 5, + 'test-machine', 'hub_router', 5, + 'hub_router', 'remote_DT', 0, + 'vm-work-1', 'storage_main_backup', 0, + 'hub_router', 'vm-work-2', 0, + 'vm-work-2', 'backup_prc', 1, + 'remote_DT', 'backup_prc', 2, + 'backup_prc', 'storage_main_backup', 0, + 'backup_prc', 'storage_DevBox', 0, + 'device_A1', 'sevice_B2', 1, + 'sevice_B2', 'device_A1', 2 +]; +connections +| invoke exposure_perimeter_fl(sourceIdColumnName = 'SourceNodeName' + , targetIdColumnName = 'TargetNodeName' + , sourceWeightColumnName = 'SourceNodeVulnerability' +) +``` + +--- + +**Output** + + +| targetId | exposurePerimeterList | exposurePerimeterScore | exposurePerimeterScoreWeighted | isExposurePerimeterCapped | +| ------------------- | ----------------------------------------- | ---------------------- | ------------------------------ | ------------------------- | +| hub_router | ["vm-custom","webapp-prd","test-machine"] | 3 | 11 | FALSE | +| storage_main_backup | ["vm-work-1","backup_prc"] | 2 | 0 | FALSE | +| vm-custom | ["webapp-prd","test-machine"] | 2 | 6 | FALSE | +| backup_prc | ["vm-work-2","remote_DT"] | 2 | 3 | FALSE | +| webapp-prd | ["vm-work-1","vm-custom"] | 2 | 4 | FALSE | +| test-machine | ["webapp-prd"] | 1 | 1 | FALSE | +| server-0126 | ["vm-custom"] | 1 | 4 | FALSE | +| remote_DT | ["hub_router"] | 1 | 0 | FALSE | +| vm-work-2 | ["hub_router"] | 1 | 0 | FALSE | +| storage_DevBox | ["backup_prc"] | 1 | 0 | FALSE | +| device_A1 | ["sevice_B2"] | 1 | 2 | FALSE | +| sevice_B2 | ["device_A1"] | 1 | 1 | FALSE | + + + +Running the function takes the connections between and sources and targets, and aggregates the sources by target. For each target, Exposure Perimeter represents the sources that can connect to it as score (regular and weighted) and list. + + +Each row in the output contains the following fields: + +* `targetId`: ID of the target node taken from relevant column. +* `exposurePerimeterList`: a list of source nodes Ids (taken from relevant column) that can connect to the target node. The list is capped to maximum length limit of listedIdsLimit parameter. +* `exposurePerimeterScore`: the score is the count of source nodes that can connect to the target. High Exposure Perimeter score indicates that the target node can be potentially accessed from lots of sources, and should be treated accordingly. +* `exposurePerimeterScoreWeighted`: the weighted score is the sum of the optional source nodes' weight column, representing their value - such as vulnerability or exposedness. If such weight exists, weighted Exposure Perimeter score might be a more accurate metric of target node value due to potential access from highly vulnerable or exposed sources. +* `isExposurePerimeterCapped`: boolean flag whether the list of sources was capped by listedIdsLimit parameter. If it's true, then other sources can access the target in addition to the listed ones (up to the number of exposurePerimeterScore). + +In the example above we run the `graph_exposure_perimeter_fl()` function on top of connections between sources and targets. In the first row of the output, we can see that target node 'hub_router' can be connected from three sources ('vm-custom', 'webapp-prd', 'test-machine'). We use the input data SourceNodeVulnerability column as source weights, and get a cumulative weight of 11. Also, since the number of sources is 3 and the default list limit is 50, all of the sources are shown - so the value of isExposurePerimeterCapped column is FALSE. + +In case the multi-hop paths aren't available, we can build multi-hop paths between sources and targets (for example, by running 'graph_path_discovery_fl()') and run 'graph_exposure_perimeter_fl()' on top of the results. + +The output looks similar, but will reflect the Exposure Perimeter calculated over multi-hop paths, thus being a better indicator of target nodes true accessibility from relevant sources. In order to find the full paths between source and target scenarios (for example, for disruption), [graph_path_discovery_fl()](graph-path-discovery-fl.md) function can be used with filters on relevant source and target nodes. + +The function `graph_exposure_perimeter_fl()` can be used to calculate the Exposure Perimeter of target nodes, either over direct edges or longer paths. In cybersecurity domain, it can be used for several insights. Exposure Perimeter scores (regular and weighted), represent target node's importance both from defenders and attackers perspectives. Nodes with high Exposure Perimeter (especially critical ones) should be protected accordingly (for example, in terms of access monitoring and hardening); security signals (such as alerts) on sources that can access these nodes should be prioritized. The Exposure Perimeter list should be monitored for undesired connections between sources and targets and used in disruption scenarios (for example, if there was active compromise of one of the sources, connections between it and important target should be broken). + diff --git a/data-explorer/kusto/functions-library/toc.yml b/data-explorer/kusto/functions-library/toc.yml index 3a74d3673d..9b1ea4190b 100644 --- a/data-explorer/kusto/functions-library/toc.yml +++ b/data-explorer/kusto/functions-library/toc.yml @@ -32,6 +32,12 @@ items: - name: get_packages_version_fl() displayName: functions library, python, version, package href: get-packages-version-fl.md +- name: graph_blast_radius_fl() + displayName: functions library, graph, blast radius, cyber, security, cybersecurity + href: graph-blast-radius-fl.md +- name: graph_exposure_perimeter_fl() + displayName: functions library, graph, exposure, perimeter, cyber, security, cybersecurity + href: graph-exposure-perimeter-fl.md - name: graph_path_discovery_fl() displayName: functions library, graph, path, discovery, cyber, security, cybersecurity, href: graph-path-discovery-fl.md diff --git a/data-explorer/manage-cluster-choose-sku.md b/data-explorer/manage-cluster-choose-sku.md index 90839a8ee6..a3afc15af2 100644 --- a/data-explorer/manage-cluster-choose-sku.md +++ b/data-explorer/manage-cluster-choose-sku.md @@ -95,7 +95,7 @@ The following SKU series are available for Azure Data Explorer cluster VMs. The |--|--|--|--| | [Lasv3](/azure/virtual-machines/lasv3-series) | 8, 16 , 32| AMD | No | | [Lsv3](/azure/virtual-machines/lsv3-series) | 8, 16 , 32| Intel | No | -| [Easv4](/azure/virtual-machines/eav4-easv4-series), [Easv5](/azure/virtual-machines/easv5-eadsv5-series), ECasv5| 8, 16 | AMD | Yes | +| [Easv4](/azure/virtual-machines/eav4-easv4-series), [Easv5](/azure/virtual-machines/sizes/memory-optimized/eadsv5-series), ECasv5| 8, 16 | AMD | Yes | | [Esv4](/azure/virtual-machines/ev4-esv4-series), [Esv5](/azure/virtual-machines/ev5-esv5-series) | 8, 16 | Intel | Yes | | [DSv2](/azure/virtual-machines/dv2-dsv2-series) | 8, 16 | Intel | Yes | @@ -103,7 +103,7 @@ The following SKU series are available for Azure Data Explorer cluster VMs. The | SKU Series | Available vCPU config | SKU type | Supports premium storage | |--|--|--|--| -| [Eadsv5](/azure/virtual-machines/easv5-eadsv5-series), [ECadsv5](/azure/virtual-machines/ecasv5-ecadsv5-series)| 2, 4, 8, 16 | AMD | No | +| [Eadsv5](/azure/virtual-machines/sizes/memory-optimized/eadsv5-series), [ECadsv5](/azure/virtual-machines/ecasv5-ecadsv5-series)| 2, 4, 8, 16 | AMD | No | | [Edv4](/azure/virtual-machines/edv4-edsv4-series), [Edv5](/azure/virtual-machines/edv5-edsv5-series) | 2, 4, 8, 16 | Intel | No | | [Eav4](/azure/virtual-machines/eav4-easv4-series) | 2, 4, 8, 16 | AMD | No | | [Dv2](/azure/virtual-machines/dv2-dsv2-series) | 2, 4, 8, 16 | Intel | No |