From 7325da0f663acb0cb132bf536c2aef52a21dd92f Mon Sep 17 00:00:00 2001 From: Ian Hellen Date: Tue, 1 Aug 2023 12:02:39 -0700 Subject: [PATCH 1/2] Added options for MicrosoftSentinel authentication - az_connect now honors "cloud" parameter for auth_methods in azure_auth.py - azure_data.py - added ability to specify cloud parameter in connect and also pass credential to az_auth - sentinel_core.py - - added ability to specify cloud parameter in connect and also pass credential to az_auth also changed internal token attribute to _token - fixed test_sentinel_dynamic_summary.py to use same msticpyconfig throughout test. --- msticpy/auth/azure_auth.py | 6 +++- msticpy/context/azure/azure_data.py | 13 +++++++- msticpy/context/azure/sentinel_analytics.py | 4 +-- msticpy/context/azure/sentinel_bookmarks.py | 4 +-- msticpy/context/azure/sentinel_core.py | 32 +++++++++++++------ .../context/azure/sentinel_dynamic_summary.py | 6 ++-- msticpy/context/azure/sentinel_incidents.py | 14 ++++---- msticpy/context/azure/sentinel_search.py | 6 ++-- msticpy/context/azure/sentinel_ti.py | 10 +++--- msticpy/context/azure/sentinel_utils.py | 2 +- msticpy/context/azure/sentinel_watchlists.py | 8 ++--- tests/context/azure/sentinel_test_fixtures.py | 2 +- tests/context/azure/test_sentinel_core.py | 6 ++-- .../azure/test_sentinel_dynamic_summary.py | 12 ++++--- 14 files changed, 78 insertions(+), 47 deletions(-) diff --git a/msticpy/auth/azure_auth.py b/msticpy/auth/azure_auth.py index 3d7ed0268..59e208081 100644 --- a/msticpy/auth/azure_auth.py +++ b/msticpy/auth/azure_auth.py @@ -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 ------- @@ -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 diff --git a/msticpy/context/azure/azure_data.py b/msticpy/context/azure/azure_data.py index 2ec12b700..ed2d3b314 100644 --- a/msticpy/context/azure/azure_data.py +++ b/msticpy/context/azure/azure_data.py @@ -137,6 +137,7 @@ def connect( auth_methods: Optional[List] = None, tenant_id: Optional[str] = None, silent: bool = False, + **kwargs, ): """ Authenticate to the Azure SDK. @@ -150,17 +151,27 @@ 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 + """ 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.") diff --git a/msticpy/context/azure/sentinel_analytics.py b/msticpy/context/azure/sentinel_analytics.py index 0fff37484..f0985c833 100644 --- a/msticpy/context/azure/sentinel_analytics.py +++ b/msticpy/context/azure/sentinel_analytics.py @@ -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(), @@ -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(), ) diff --git a/msticpy/context/azure/sentinel_bookmarks.py b/msticpy/context/azure/sentinel_bookmarks.py index dee2f5d88..8214cc8e2 100644 --- a/msticpy/context/azure/sentinel_bookmarks.py +++ b/msticpy/context/azure/sentinel_bookmarks.py @@ -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(), @@ -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(), ) diff --git a/msticpy/context/azure/sentinel_core.py b/msticpy/context/azure/sentinel_core.py index 2cb7383dc..2045c2424 100644 --- a/msticpy/context/azure/sentinel_core.py +++ b/msticpy/context/azure/sentinel_core.py @@ -95,6 +95,7 @@ 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 @@ -151,6 +152,17 @@ 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"): @@ -161,12 +173,12 @@ def connect( 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( + self._token = kwargs.pop("token", None) + super().connect( + auth_methods=auth_methods, tenant_id=tenant_id, silent=silent, **kwargs + ) + if not self._token: + self._token = get_token( self.credentials, tenant_id=tenant_id, cloud=self.user_cloud # type: ignore ) @@ -197,11 +209,13 @@ 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 diff --git a/msticpy/context/azure/sentinel_dynamic_summary.py b/msticpy/context/azure/sentinel_dynamic_summary.py index 952f4612a..0644789ca 100644 --- a/msticpy/context/azure/sentinel_dynamic_summary.py +++ b/msticpy/context/azure/sentinel_dynamic_summary.py @@ -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(), ) @@ -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(), @@ -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(), ) diff --git a/msticpy/context/azure/sentinel_incidents.py b/msticpy/context/azure/sentinel_incidents.py index 4f2af3637..1f538f8b2 100644 --- a/msticpy/context/azure/sentinel_incidents.py +++ b/msticpy/context/azure/sentinel_incidents.py @@ -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(), ) @@ -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(), ) @@ -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(), @@ -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(), @@ -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(), @@ -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(), @@ -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(), diff --git a/msticpy/context/azure/sentinel_search.py b/msticpy/context/azure/sentinel_search.py index 9880c31d3..5f201abba 100644 --- a/msticpy/context/azure/sentinel_search.py +++ b/msticpy/context/azure/sentinel_search.py @@ -74,7 +74,7 @@ def create_search( search_body = _build_sent_data(search_items) search_create_response = httpx.put( search_url, - headers=get_api_headers(self.token), # type: ignore + headers=get_api_headers(self._token), # type: ignore json=search_body, timeout=60, ) @@ -108,7 +108,7 @@ def check_search_status(self, search_name: str) -> bool: + f"/{search_name}_SRCH?api-version=2021-12-01-preview" ) search_check_response = httpx.get( - search_url, headers=get_api_headers(self.token) # type: ignore + search_url, headers=get_api_headers(self._token) # type: ignore ) if search_check_response.status_code != 200: raise CloudError(response=search_check_response) @@ -140,7 +140,7 @@ def delete_search(self, search_name: str): + f"/{search_name}_SRCH?api-version=2021-12-01-preview" ) search_delete_response = httpx.delete( - search_url, headers=get_api_headers(self.token) # type: ignore + search_url, headers=get_api_headers(self._token) # type: ignore ) if search_delete_response.status_code != 202: raise CloudError(response=search_delete_response) diff --git a/msticpy/context/azure/sentinel_ti.py b/msticpy/context/azure/sentinel_ti.py index 710d697c1..eef332bb6 100644 --- a/msticpy/context/azure/sentinel_ti.py +++ b/msticpy/context/azure/sentinel_ti.py @@ -171,7 +171,7 @@ def create_indicator( data["kind"] = "indicator" response = httpx.post( ti_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(), @@ -247,7 +247,7 @@ def get_indicator(self, indicator_id: str) -> dict: params = {"api-version": "2021-10-01"} response = httpx.get( ti_url, - headers=get_api_headers(self.token), # type: ignore + headers=get_api_headers(self._token), # type: ignore params=params, timeout=get_http_timeout(), ) @@ -302,7 +302,7 @@ def update_indicator(self, indicator_id: str, **kwargs): params = {"api-version": "2021-10-01"} response = httpx.put( ti_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(), @@ -350,7 +350,7 @@ def delete_indicator(self, indicator_id: str): params = {"api-version": "2021-10-01"} response = httpx.delete( ti_url, - headers=get_api_headers(self.token), # type: ignore + headers=get_api_headers(self._token), # type: ignore params=params, timeout=get_http_timeout(), ) @@ -406,7 +406,7 @@ def query_indicators(self, **kwargs) -> pd.DataFrame: params = {"api-version": "2021-10-01"} response = httpx.post( ti_url, - headers=get_api_headers(self.token), # type: ignore + headers=get_api_headers(self._token), # type: ignore params=params, content=str(data_items), timeout=get_http_timeout(), diff --git a/msticpy/context/azure/sentinel_utils.py b/msticpy/context/azure/sentinel_utils.py index b964ee55f..603db57c8 100644 --- a/msticpy/context/azure/sentinel_utils.py +++ b/msticpy/context/azure/sentinel_utils.py @@ -46,7 +46,7 @@ def _get_items(self, url: str, params: Optional[dict] = None) -> httpx.Response: params = {"api-version": "2020-01-01"} return httpx.get( url, - headers=get_api_headers(self.token), # type: ignore + headers=get_api_headers(self._token), # type: ignore params=params, timeout=get_http_timeout(), ) diff --git a/msticpy/context/azure/sentinel_watchlists.py b/msticpy/context/azure/sentinel_watchlists.py index 48ccaf0ee..ae54fc889 100644 --- a/msticpy/context/azure/sentinel_watchlists.py +++ b/msticpy/context/azure/sentinel_watchlists.py @@ -106,7 +106,7 @@ def create_watchlist( request_data = _build_sent_data(data_items, props=True) response = httpx.put( watchlist_url, - headers=get_api_headers(self.token), # type: ignore + headers=get_api_headers(self._token), # type: ignore params=params, content=str(request_data), timeout=get_http_timeout(), @@ -222,7 +222,7 @@ def add_watchlist_item( ) response = httpx.put( watchlist_url, - headers=get_api_headers(self.token), # type: ignore + headers=get_api_headers(self._token), # type: ignore params={"api-version": "2021-04-01"}, content=str({"properties": {"itemsKeyValue": item}}), timeout=get_http_timeout(), @@ -260,7 +260,7 @@ def delete_watchlist( params = {"api-version": "2021-04-01"} response = httpx.delete( watchlist_url, - headers=get_api_headers(self.token), # type: ignore + headers=get_api_headers(self._token), # type: ignore params=params, timeout=get_http_timeout(), ) @@ -298,7 +298,7 @@ def delete_watchlist_item(self, watchlist_name: str, watchlist_item_id: str): ) response = httpx.delete( watchlist_url, - headers=get_api_headers(self.token), # type: ignore + headers=get_api_headers(self._token), # type: ignore params={"api-version": "2023-02-01"}, timeout=get_http_timeout(), ) diff --git a/tests/context/azure/sentinel_test_fixtures.py b/tests/context/azure/sentinel_test_fixtures.py index 7ae3fb0c5..ef57d4cb4 100644 --- a/tests/context/azure/sentinel_test_fixtures.py +++ b/tests/context/azure/sentinel_test_fixtures.py @@ -60,5 +60,5 @@ def sent_loader(mock_creds, get_token, monkeypatch): ) sentinel.connect() sentinel.connected = True - sentinel.token = "fd09863b-5cec-4833-ab9c-330ad07b0c1a" + sentinel._token = "fd09863b-5cec-4833-ab9c-330ad07b0c1a" return sentinel diff --git a/tests/context/azure/test_sentinel_core.py b/tests/context/azure/test_sentinel_core.py index dbd701dc2..30ecfa75a 100644 --- a/tests/context/azure/test_sentinel_core.py +++ b/tests/context/azure/test_sentinel_core.py @@ -62,7 +62,7 @@ def test_azuresent_connect_token(get_token: Mock, az_data_connect: Mock): sentinel_inst.connect(auth_methods=["env"], token=token) tenant_id = sentinel_inst._check_config(["tenant_id"])["tenant_id"] - assert sentinel_inst.token == token + assert sentinel_inst._token == token az_data_connect.assert_called_once_with( auth_methods=["env"], tenant_id=tenant_id, silent=False ) @@ -75,7 +75,7 @@ def test_azuresent_connect_token(get_token: Mock, az_data_connect: Mock): setattr(sentinel_inst, "set_default_workspace", MagicMock()) sentinel_inst.connect(auth_methods=["env"]) - assert sentinel_inst.token == token + assert sentinel_inst._token == token get_token.assert_called_once_with( sentinel_inst.credentials, tenant_id=tenant_id, @@ -103,7 +103,7 @@ def sentinel_inst_loader(mock_creds): sentinel_inst = MicrosoftSentinel(sub_id="123", res_grp="RG", ws_name="WSName") sentinel_inst.connect() sentinel_inst.connected = True - sentinel_inst.token = "123" + sentinel_inst._token = "123" return sentinel_inst diff --git a/tests/context/azure/test_sentinel_dynamic_summary.py b/tests/context/azure/test_sentinel_dynamic_summary.py index 8997018d4..d1cf651fd 100644 --- a/tests/context/azure/test_sentinel_dynamic_summary.py +++ b/tests/context/azure/test_sentinel_dynamic_summary.py @@ -274,9 +274,8 @@ def sentinel_loader(mock_creds, get_token, monkeypatch): ws_name=settings.get("WorkspaceName", "Default"), ) sent._default_workspace = ws_key - sent.connect(workspace=ws_key) + sent.connect(workspace=ws_key, token=["PLACEHOLDER"]) # nosec sent.connected = True - sent.token = "fd09863b-5cec-4833-ab9c-330ad07b0c1a" # nosec return sent @@ -439,9 +438,12 @@ def test_sent_get_dynamic_summary_plus_items(qry_prov, sentinel_loader): ) qry_prov_instance.MSSentinel.get_dynamic_summary_by_id.return_value = dyn_summary_df - dyn_summary = sentinel_loader.get_dynamic_summary( - summary_id="test", summary_items=True - ) + with custom_mp_config( + get_test_data_path().parent.joinpath("msticpyconfig-test.yaml") + ): + dyn_summary = sentinel_loader.get_dynamic_summary( + summary_id="test", summary_items=True + ) check.equal(dyn_summary.summary_name, "test2") summary_items = dyn_summary_df[dyn_summary_df["SummaryDataType"] == "SummaryItem"] From 2df25b8820413ebd524e146fb742107a0313a1e9 Mon Sep 17 00:00:00 2001 From: Ian Hellen Date: Tue, 1 Aug 2023 14:30:42 -0700 Subject: [PATCH 2/2] Adding logging to azure_data and sentinel_core init and connect code --- msticpy/context/azure/azure_data.py | 9 +++++++ msticpy/context/azure/sentinel_core.py | 34 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/msticpy/context/azure/azure_data.py b/msticpy/context/azure/azure_data.py index ed2d3b314..b1d701543 100644 --- a/msticpy/context/azure/azure_data.py +++ b/msticpy/context/azure/azure_data.py @@ -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 @@ -55,6 +56,8 @@ __version__ = VERSION __author__ = "Pete Bryan" +logger = logging.getLogger(__name__) + _CLIENT_MAPPING = { "sub_client": SubscriptionClient, "resource_client": ResourceManagementClient, @@ -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() @@ -168,6 +172,10 @@ def connect( 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( @@ -186,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: diff --git a/msticpy/context/azure/sentinel_core.py b/msticpy/context/azure/sentinel_core.py index 2045c2424..663fa0094 100644 --- a/msticpy/context/azure/sentinel_core.py +++ b/msticpy/context/azure/sentinel_core.py @@ -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 @@ -32,6 +33,8 @@ __version__ = VERSION __author__ = "Pete Bryan" +logger = logging.getLogger(__name__) + # pylint: disable=too-many-ancestors, too-many-instance-attributes class MicrosoftSentinel( @@ -101,8 +104,17 @@ def __init__( 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"] @@ -112,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 ) @@ -126,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 @@ -168,21 +183,32 @@ def connect( 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] ) + 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 ] @@ -222,10 +248,12 @@ def _create_api_paths_for_workspace( 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: @@ -268,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 @@ -276,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 @@ -288,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]]: