Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Microsoft Sentinel Graph API with Graphistry\n",
"\n",
"This notebook demonstrates how to query Microsoft Sentinel Graph API and visualize threat intelligence data with Graphistry.\n",
"\n",
"## Requirements\n",
"\n",
"```bash\n",
"pip install graphistry[sentinel-graph]\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Install dependencies (uncomment if needed)\n",
"# !pip install graphistry[sentinel-graph]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"Import libraries and configure Graphistry."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import graphistry\n",
"from azure.identity import InteractiveBrowserCredential\n",
"\n",
"# Register with Graphistry\n",
"# IMPORTANT: Store credentials securely using environment variables\n",
"graphistry.register(\n",
" api=3,\n",
" protocol=\"https\",\n",
" server=\"hub.graphistry.com\"\n",
" # personal_key_id='YOUR_KEY_ID',\n",
" # personal_key_secret='YOUR_KEY_SECRET'\n",
")\n",
"\n",
"print(\"✓ Graphistry configured\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": "## Discover Available Graph Instances\n\nUse `sentinel_graph_list()` to see what graph instances are available in your tenant. You only need a placeholder `graph_instance` for this call — the value is not used by the list endpoint."
},
{
"cell_type": "code",
"source": "g = graphistry.configure_sentinel_graph(\n graph_instance=graph_instance_name,\n credential=credential,\n response_formats=[\"Graph\"] # default; use [\"Table\", \"Graph\"] to also get raw tabular data\n)\n\nprint(f\"✓ Sentinel Graph configured for instance: {graph_instance_name}\")",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Interactive browser authentication\n",
"credential = InteractiveBrowserCredential()\n",
"\n",
"# Replace 'YourGraphInstance' with your actual graph instance name\n",
"g = graphistry.configure_sentinel_graph(\n",
" graph_instance='YourGraphInstance',\n",
" credential=credential\n",
")\n",
"\n",
"print(\"✓ Sentinel Graph configured\")"
]
},
{
"cell_type": "code",
"metadata": {},
"source": "query = \"\"\"\nMATCH (n)-[e]->(m)\nRETURN *\nLIMIT 50\n\"\"\"\n\nviz = g.sentinel_graph(query)\nprint(f\"Query returned {len(viz._nodes)} nodes and {len(viz._edges)} edges\")\n\nviz.plot()"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Basic query to get nodes and edges\n",
"query = \"\"\"\n",
"MATCH (n)-[e]->(m)\n",
"RETURN *\n",
"LIMIT 50\n",
"\"\"\"\n",
"\n",
"viz = g.sentinel_graph(query)\n",
"print(f\"Query returned {len(viz._node)} nodes and {len(viz._edge)} edges\")\n",
"\n",
"viz.plot()"
]
},
{
"cell_type": "code",
"metadata": {},
"source": "print(\"=\" * 80)\nprint(\"NODES\")\nprint(\"=\" * 80)\nprint(f\"Shape: {viz._nodes.shape}\")\nprint(f\"Columns: {list(viz._nodes.columns)}\")\nprint(\"\\nSample nodes:\")\ndisplay(viz._nodes.head(3))\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"EDGES\")\nprint(\"=\" * 80)\nprint(f\"Shape: {viz._edges.shape}\")\nprint(f\"Columns: {list(viz._edges.columns)}\")\nprint(\"\\nSample edges:\")\ndisplay(viz._edges.head(3))"
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Access node and edge DataFrames\n",
"print(\"=\" * 80)\n",
"print(\"NODES\")\n",
"print(\"=\" * 80)\n",
"print(f\"Shape: {viz._node.shape}\")\n",
"print(f\"Columns: {list(viz._node.columns)}\")\n",
"print(\"\\nSample nodes:\")\n",
"display(viz._node.head(3))\n",
"\n",
"print(\"\\n\" + \"=\" * 80)\n",
"print(\"EDGES\")\n",
"print(\"=\" * 80)\n",
"print(f\"Shape: {viz._edge.shape}\")\n",
"print(f\"Columns: {list(viz._edge.columns)}\")\n",
"print(\"\\nSample edges:\")\n",
"display(viz._edge.head(3))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example 3: Enhanced Visualization\n",
"\n",
"Add visual encodings for better graph exploration."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"styled = (\n",
" viz\n",
" .encode_edge_color('edge', as_categorical=True)\n",
" .encode_point_color('label', as_categorical=True)\n",
" .encode_point_size('label', default_mapping=100)\n",
")\n",
"\n",
"styled.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example 4: Query with Filters\n",
"\n",
"Use WHERE clause to filter results."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Query with WHERE clause (adjust property name as needed for your graph)\n",
"filtered_query = \"\"\"\n",
"MATCH (a)-[e]->(b)\n",
"WHERE a.id IS NOT NULL\n",
"RETURN *\n",
"LIMIT 30\n",
"\"\"\"\n",
"\n",
"filtered_viz = g.sentinel_graph(filtered_query)\n",
"print(f\"Found {len(filtered_viz._edge)} edges\")\n",
"\n",
"filtered_viz.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example 5: Query Nodes Only\n",
"\n",
"Retrieve specific nodes from the graph."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Query nodes only\n",
"nodes_query = \"\"\"\n",
"MATCH (n)\n",
"RETURN n\n",
"LIMIT 20\n",
"\"\"\"\n",
"\n",
"nodes_viz = g.sentinel_graph(nodes_query)\n",
"nodes_viz.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example 6: Error Handling\n",
"\n",
"Demonstrate robust error handling."
]
},
{
"cell_type": "code",
"source": "# Request both Table and Graph formats in a single call\n# Graphistry automatically parses the Graph section for visualization\nboth_formats_viz = g.sentinel_graph(\n \"MATCH (n)-[e]->(m) RETURN * LIMIT 20\",\n response_formats=[\"Table\", \"Graph\"]\n)\n\nprint(f\"Nodes: {len(both_formats_viz._nodes)}, Edges: {len(both_formats_viz._edges)}\")\nboth_formats_viz.plot()",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": "## Requesting Both Graph and Table Formats\n\nPass `response_formats=[\"Table\", \"Graph\"]` to get both structured graph data and the raw tabular rows in a single API call. Graphistry will parse the `Graph` section; the `Table` section is available for additional inspection if needed.",
"metadata": {}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" # Invalid query syntax\n",
" bad_query = \"INVALID SYNTAX\"\n",
" result = g.sentinel_graph(bad_query)\n",
"except Exception as e:\n",
" print(f\"Query failed as expected: {type(e).__name__}\")\n",
" print(f\"Error message: {e}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Alternative Authentication: Service Principal\n",
"\n",
"For production environments, use service principal authentication with credentials stored securely."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Uncomment and configure for production use\n",
"#\n",
"# import os\n",
"# \n",
"# g_prod = graphistry.configure_sentinel_graph(\n",
"# graph_instance='YourGraphInstance', # Replace with your graph instance name\n",
"# tenant_id=os.environ.get('AZURE_TENANT_ID'),\n",
"# client_id=os.environ.get('AZURE_CLIENT_ID'),\n",
"# client_secret=os.environ.get('AZURE_CLIENT_SECRET')\n",
"# )\n",
"# \n",
"# result = g_prod.sentinel_graph('MATCH (n) RETURN n LIMIT 10')\n",
"# result.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Cleanup\n",
"\n",
"Clear cached authentication token."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"g.sentinel_graph_close()\n",
"print(\"✓ Sentinel Graph connection closed\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.13"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
4 changes: 4 additions & 0 deletions graphistry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
kusto_from_client,
kql,
kusto_graph,
configure_sentinel_graph,
sentinel_graph_from_credential,
sentinel_graph,
sentinel_graph_close,
gsql,
gsql_endpoint,
cosmos,
Expand Down
2 changes: 2 additions & 0 deletions graphistry/client_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from . import util
from .plugins_types.spanner_types import SpannerConfig
from .plugins_types.kusto_types import KustoConfig
from .plugins_types.sentinel_graph_types import SentinelGraphConfig



Expand Down Expand Up @@ -85,6 +86,7 @@ def __init__(self) -> None:
# NOTE: These are dataclasses, so we shallow copy them
self.kusto: Optional[KustoConfig] = None
self.spanner: Optional[SpannerConfig] = None
self.sentinel_graph: Optional[SentinelGraphConfig] = None

# TODO: Migrate to a pattern like Kusto or Spanner
self._bolt_driver: Optional[Any] = None
Expand Down
4 changes: 3 additions & 1 deletion graphistry/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
from .compute.cluster import ClusterMixin
from .plugins.kusto import KustoMixin
from .plugins.spanner import SpannerMixin
from .plugins.sentinel_graph import SentinelGraphMixin
from .client_session import AuthManagerProtocol
# NOTE: Cooperative mixins must call:
# super().__init__(*a, **kw) in their __init__ method
# to pass along args/kwargs to the next mixin in the chain
class Plotter(
KustoMixin, SpannerMixin,
KustoMixin, SpannerMixin, SentinelGraphMixin,
CosmosMixin, NeptuneMixin,
HeterographEmbedModuleMixin,
SearchToGraphMixin,
Expand Down Expand Up @@ -53,6 +54,7 @@ class Plotter(
- :py:class:`graphistry.gremlin.NeptuneMixin`: Integrates with AWS Neptune DB.
- :py:class:`graphistry.plugins.kusto.KustoMixin`: Integrates with Azure Kusto DB.
- :py:class:`graphistry.plugins.spanner.SpannerMixin`: Integrates with Google Spanner DB.
- :py:class:`graphistry.plugins.sentinel_graph.SentinelGraphMixin`: Integrates with Microsoft Sentinel Graph API.

Attributes:
All attributes are inherited from the mixins and base classes.
Expand Down
Loading
Loading