Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ianhelle/mssentinel auth 2023 08 01 #690

Merged
merged 6 commits into from
Aug 8, 2023
Merged
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
6 changes: 5 additions & 1 deletion msticpy/auth/azure_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ def az_connect(
Set True to hide all output during connection, by default False
credential : AzureCredential
If an Azure credential is passed, it will be used directly.
cloud : str, optional
What Azure cloud to connect to.
By default it will attempt to use the cloud setting from config file.
If this is not set it will default to Azure Public Cloud

Returns
-------
Expand All @@ -74,7 +78,7 @@ def az_connect(
list_auth_methods

"""
az_cloud_config = AzureCloudConfig()
az_cloud_config = AzureCloudConfig(cloud=kwargs.get("cloud"))
# Use auth_methods param or configuration defaults
data_provs = get_provider_settings(config_section="DataProviders")
auth_methods = auth_methods or az_cloud_config.auth_methods
Expand Down
22 changes: 21 additions & 1 deletion msticpy/context/azure/azure_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# --------------------------------------------------------------------------
"""Uses the Azure Python SDK to collect and return details related to Azure."""
import datetime
import logging
from typing import Any, Dict, List, Optional, Tuple

import attr
Expand Down Expand Up @@ -55,6 +56,8 @@
__version__ = VERSION
__author__ = "Pete Bryan"

logger = logging.getLogger(__name__)

_CLIENT_MAPPING = {
"sub_client": SubscriptionClient,
"resource_client": ResourceManagementClient,
Expand Down Expand Up @@ -129,6 +132,7 @@ def __init__(self, connect: bool = False, cloud: Optional[str] = None):
self.compute_client: Optional[ComputeManagementClient] = None
self.cloud = cloud or AzureCloudConfig().cloud
self.endpoints = get_all_endpoints(self.cloud) # type: ignore
logger.info("Initialized AzureData")
if connect:
self.connect()

Expand All @@ -137,6 +141,7 @@ def connect(
auth_methods: Optional[List] = None,
tenant_id: Optional[str] = None,
silent: bool = False,
**kwargs,
):
"""
Authenticate to the Azure SDK.
Expand All @@ -150,17 +155,31 @@ def connect(
tenant for the identity will be used.
silent : bool, optional
Set true to prevent output during auth process, by default False
cloud : str, optional
What Azure cloud to connect to.
By default it will attempt to use the cloud setting from config file.
If this is not set it will default to Azure Public Cloud
**kwargs
Additional keyword arguments to pass to the az_connect function.

Raises
------
CloudError
If no valid credentials are found or if subscription client can't be created

See Also
--------
msticpy.auth.azure_auth.az_connect : function to authenticate to Azure SDK

"""
if kwargs.get("cloud"):
logger.info("Setting cloud to %s", kwargs["cloud"])
self.cloud = kwargs["cloud"]
self.azure_cloud_config = AzureCloudConfig(self.cloud)
auth_methods = auth_methods or self.az_cloud_config.auth_methods
tenant_id = tenant_id or self.az_cloud_config.tenant_id
self.credentials = az_connect(
auth_methods=auth_methods, tenant_id=tenant_id, silent=silent
auth_methods=auth_methods, tenant_id=tenant_id, silent=silent, **kwargs
)
if not self.credentials:
raise CloudError("Could not obtain credentials.")
Expand All @@ -175,6 +194,7 @@ def connect(
)
if not self.sub_client:
raise CloudError("Could not create a Subscription client.")
logger.info("Connected to Azure Subscription Client")
self.connected = True

def get_subscriptions(self) -> pd.DataFrame:
Expand Down
4 changes: 2 additions & 2 deletions msticpy/context/azure/sentinel_analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def create_analytic_rule( # pylint: disable=too-many-arguments, too-many-locals
params = {"api-version": "2020-01-01"}
response = httpx.put(
analytic_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
content=str(data),
timeout=get_http_timeout(),
Expand Down Expand Up @@ -305,7 +305,7 @@ def delete_analytic_rule(
params = {"api-version": "2020-01-01"}
response = httpx.delete(
analytic_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
timeout=get_http_timeout(),
)
Expand Down
4 changes: 2 additions & 2 deletions msticpy/context/azure/sentinel_bookmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def create_bookmark(
params = {"api-version": "2020-01-01"}
response = httpx.put(
bookmark_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
content=str(data),
timeout=get_http_timeout(),
Expand Down Expand Up @@ -123,7 +123,7 @@ def delete_bookmark(
params = {"api-version": "2020-01-01"}
response = httpx.delete(
bookmark_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
timeout=get_http_timeout(),
)
Expand Down
66 changes: 57 additions & 9 deletions msticpy/context/azure/sentinel_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""Uses the Microsoft Sentinel APIs to interact with Microsoft Sentinel Workspaces."""

import contextlib
import logging
from typing import Any, Dict, List, Optional

import pandas as pd
Expand All @@ -32,6 +33,8 @@
__version__ = VERSION
__author__ = "Pete Bryan"

logger = logging.getLogger(__name__)


# pylint: disable=too-many-ancestors, too-many-instance-attributes
class MicrosoftSentinel(
Expand Down Expand Up @@ -95,13 +98,23 @@ def __init__(
self.sent_urls: Dict[str, str] = {}
self.sent_data_query: Optional[SentinelQueryProvider] = None # type: ignore
self.url: Optional[str] = None
self._token: Optional[str] = None

workspace = kwargs.get("workspace", ws_name)
self._default_workspace: Optional[str] = workspace
self.workspace_config = WorkspaceConfig(workspace)

logger.info("Initializing Microsoft Sentinel connector")
logger.info(
"Params: Cloud=%s; ResourceId=%s; Workspace=%s",
self.cloud,
self._resource_id,
workspace,
)

if self._resource_id:
# If a resource ID is supplied, use that
logger.info("Initializing from resource ID")
self.url = self._build_sent_paths(self._resource_id, self.base_url) # type: ignore
res_id_parts = parse_resource_id(self._resource_id)
self.default_subscription = res_id_parts["subscription_id"]
Expand All @@ -111,8 +124,10 @@ def __init__(
self.workspace_config = WorkspaceConfig(
workspace=self._default_workspace
)
logger.info("Workspace settings found for %s", self._default_workspace)
else:
# Otherwise - use details from specified workspace or default from settings
logger.info("Initializing from workspace settings")
self.default_subscription = self.workspace_config.get(
"subscription_id", sub_id
)
Expand All @@ -125,6 +140,7 @@ def __init__(
res_grp=self._default_resource_group,
ws_name=workspace_name,
)
logger.info("Resource ID set to %s", self._resource_id)
self._default_workspace = workspace_name
self.url = self._build_sent_paths(
self._resource_id, self.base_url # type: ignore
Expand All @@ -151,26 +167,48 @@ def connect(
Specify cloud tenant to use
silent : bool, optional
Set true to prevent output during auth process, by default False
cloud : str, optional
What Azure cloud to connect to.
By default it will attempt to use the cloud setting from config file.
If this is not set it will default to Azure Public Cloud
credential: AzureCredential, optional
Credentials to use for authentication. This will use the credential
directly and bypass the MSTICPy Azure credential selection process.

See Also
--------
msticpy.auth.azure_auth.az_connect : function to authenticate to Azure SDK

"""
if workspace := kwargs.get("workspace"):
# override any previous default setting
self.workspace_config = WorkspaceConfig(workspace)
logger.info("Using workspace settings found for %s", workspace)
if not self.workspace_config:
self.workspace_config = WorkspaceConfig()
logger.info(
"Using default workspace settings for %s",
self.workspace_config.get(WorkspaceConfig.CONF_WS_NAME_KEY),
)
tenant_id = (
tenant_id or self.workspace_config[WorkspaceConfig.CONF_TENANT_ID_KEY]
)

super().connect(auth_methods=auth_methods, tenant_id=tenant_id, silent=silent)
if "token" in kwargs:
self.token = kwargs["token"]
else:
self.token = get_token(
logger.info("Using tenant id %s", tenant_id)
self._token = kwargs.pop("token", None)
super().connect(
auth_methods=auth_methods, tenant_id=tenant_id, silent=silent, **kwargs
)
if not self._token:
logger.info("Getting token for %s", tenant_id)
self._token = get_token(
self.credentials, tenant_id=tenant_id, cloud=self.user_cloud # type: ignore
)

with contextlib.suppress(KeyError):
logger.info(
"Setting default subscription to %s from workspace settings",
self.default_subscription,
)
self.default_subscription = self.workspace_config[
WorkspaceConfig.CONF_SUB_ID_KEY
]
Expand All @@ -197,21 +235,25 @@ def _create_api_paths_for_workspace(
"""Save configuration and build API URLs for workspace."""
if workspace_name:
self.workspace_config = WorkspaceConfig(workspace=workspace_name)
az_resource_id = az_resource_id or self._resource_id
if not az_resource_id:
az_resource_id = self._build_sent_res_id(
az_resource_id = (
az_resource_id
or self._resource_id
or self._build_sent_res_id(
subscription_id, resource_group, workspace_name # type: ignore
)
)
az_resource_id = validate_res_id(az_resource_id)
self.url = self._build_sent_paths(az_resource_id, self.base_url) # type: ignore

self.sent_urls = {
name: f"{self.url}{mapping}" for name, mapping in _PATH_MAPPING.items()
}
logger.info("API URLs set to %s", self.sent_urls)

def set_default_subscription(self, subscription_id: str):
"""Set the default subscription to use to `subscription_id`."""
subs_df = self.get_subscriptions()
logger.info("Setting default subscription to %s", subscription_id)
if subscription_id in subs_df["Subscription ID"].values:
self.default_subscription = subscription_id
else:
Expand Down Expand Up @@ -254,6 +296,7 @@ def set_default_workspace(
ws_res_id: Optional[str] = None
# if workspace not supplied trying looking up in subscription
if not workspace:
logger.info("Trying to set default workspace from subscription %s", sub_id)
workspaces = self.get_sentinel_workspaces(sub_id=sub_id)
if len(workspaces) == 1:
# if only one, use that one
Expand All @@ -262,6 +305,7 @@ def set_default_workspace(

# if workspace is one that we have configuration for, get the details from there.
if self._default_workspace in WorkspaceConfig.list_workspaces():
logger.info("Workspace %s found in settings", self._default_workspace)
self.workspace_config = WorkspaceConfig(workspace=self._default_workspace)
elif ws_res_id:
# otherwise construct partial settings
Expand All @@ -274,6 +318,10 @@ def set_default_workspace(
"ResourceGroup": res_id_parts["resource_group"],
}
)
logger.info(
"Workspace not found in settings, using partial workspace config %s",
self.workspace_config,
)

@property
def default_workspace_settings(self) -> Optional[Dict[str, Any]]:
Expand Down
6 changes: 3 additions & 3 deletions msticpy/context/azure/sentinel_dynamic_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def get_dynamic_summary(
params = {"api-version": _DYN_SUM_API_VERSION}
response = httpx.get(
dyn_sum_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
timeout=get_http_timeout(),
)
Expand Down Expand Up @@ -219,7 +219,7 @@ def _create_dynamic_summary(
params = {"api-version": _DYN_SUM_API_VERSION}
response = httpx.put(
dyn_sum_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
content=summary.to_json_api(),
timeout=get_http_timeout(),
Expand Down Expand Up @@ -325,7 +325,7 @@ def delete_dynamic_summary(
params = {"api-version": _DYN_SUM_API_VERSION}
response = httpx.delete(
dyn_sum_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
timeout=get_http_timeout(),
)
Expand Down
14 changes: 7 additions & 7 deletions msticpy/context/azure/sentinel_incidents.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def get_entities(self, incident: str) -> list:
ent_parameters = {"api-version": "2021-04-01"}
ents = httpx.post(
entities_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=ent_parameters,
timeout=get_http_timeout(),
)
Expand Down Expand Up @@ -134,7 +134,7 @@ def get_incident_alerts(self, incident: str) -> list:
alerts_parameters = {"api-version": "2021-04-01"}
alerts_resp = httpx.post(
alerts_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=alerts_parameters,
timeout=get_http_timeout(),
)
Expand Down Expand Up @@ -252,7 +252,7 @@ def update_incident(
data = _build_sent_data(update_items, etag=incident_dets.iloc[0]["etag"])
response = httpx.put(
incident_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
content=str(data),
timeout=get_http_timeout(),
Expand Down Expand Up @@ -329,7 +329,7 @@ def create_incident( # pylint: disable=too-many-arguments, too-many-locals
data = _build_sent_data(data_items, props=True)
response = httpx.put(
incident_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
content=str(data),
timeout=get_http_timeout(),
Expand All @@ -347,7 +347,7 @@ def create_incident( # pylint: disable=too-many-arguments, too-many-locals
params = {"api-version": "2021-04-01"}
response = httpx.put(
relations_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
content=str(data),
timeout=get_http_timeout(),
Expand Down Expand Up @@ -426,7 +426,7 @@ def post_comment(
data = _build_sent_data({"message": comment})
response = httpx.put(
comment_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
content=str(data),
timeout=get_http_timeout(),
Expand Down Expand Up @@ -467,7 +467,7 @@ def add_bookmark_to_incident(self, incident: str, bookmark: str):
params = {"api-version": "2021-04-01"}
response = httpx.put(
bookmark_url,
headers=get_api_headers(self.token), # type: ignore
headers=get_api_headers(self._token), # type: ignore
params=params,
content=str(data),
timeout=get_http_timeout(),
Expand Down
Loading
Loading