diff --git a/application/single_app/config.py b/application/single_app/config.py index 70f8cf21c..a0c5b5b0a 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -96,7 +96,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.250.217" +VERSION = "0.250.218" IS_DEVELOPMENT = is_development_env_enabled() SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax') diff --git a/application/single_app/functions_appinsights.py b/application/single_app/functions_appinsights.py index 6d1a1c6e2..4409556ab 100644 --- a/application/single_app/functions_appinsights.py +++ b/application/single_app/functions_appinsights.py @@ -20,19 +20,40 @@ "accesstoken", "accountkey", "apikey", + "authkey", "authorization", "clientsecret", "connectionstring", "cookie", "credential", + "encryptionkey", + "keypair", + "masterkey", "password", + "primarykey", "privatekey", "sas", + "secondarykey", "secret", + "sessionkey", "sharedaccesssignature", + "signingkey", + "storagekey", "subscriptionkey", "token", ) +# Names that carry a credential only when they are the whole key. Matching these as +# substrings would redact benign configuration such as key_encoding or partition_key_path, +# so they are compared against the fully normalized key instead. +SENSITIVE_LOG_KEY_EXACT = ( + "key", + "keys", + "pass", + "passphrase", + "pwd", + "sig", + "signature", +) EXTERNAL_EVENT_SENSITIVE_KEY_FRAGMENTS = ( "email", "userid", @@ -120,6 +141,8 @@ def _is_sensitive_log_key(key: Any) -> bool: normalized_key = _normalize_log_key(key) if not normalized_key: return False + if normalized_key in SENSITIVE_LOG_KEY_EXACT: + return True return any(fragment in normalized_key for fragment in SENSITIVE_LOG_KEY_FRAGMENTS) diff --git a/deployers/bicep/postconfig.py b/deployers/bicep/postconfig.py index cd571af8d..f386c220b 100644 --- a/deployers/bicep/postconfig.py +++ b/deployers/bicep/postconfig.py @@ -1,5 +1,5 @@ # postconfig.py -from azure.cosmos import CosmosClient +import azure.cosmos as azure_cosmos from azure.cosmos.exceptions import CosmosResourceNotFoundError from azure.identity import AzureCliCredential import json @@ -291,10 +291,10 @@ def get_core_service_keys( cosmosKey = os.getenv("var_cosmosDb_key") if cosmosKey: - client = CosmosClient(cosmosEndpoint, cosmosKey) + client = azure_cosmos.CosmosClient(cosmosEndpoint, cosmosKey) else: credential.get_token("https://cosmos.azure.com/.default") - client = CosmosClient(cosmosEndpoint, credential=credential) + client = azure_cosmos.CosmosClient(cosmosEndpoint, credential=credential) database_name = "SimpleChat" container_name = "settings" diff --git a/deployers/version.txt b/deployers/version.txt index 79728fe87..4a4127c37 100644 --- a/deployers/version.txt +++ b/deployers/version.txt @@ -1 +1 @@ -1.0.24 +1.0.25 diff --git a/docs/explanation/fixes/LOG_CREDENTIAL_KEY_REDACTION_FIX.md b/docs/explanation/fixes/LOG_CREDENTIAL_KEY_REDACTION_FIX.md new file mode 100644 index 000000000..bc8f801ed --- /dev/null +++ b/docs/explanation/fixes/LOG_CREDENTIAL_KEY_REDACTION_FIX.md @@ -0,0 +1,116 @@ +# Log Credential Key Redaction Fix + +Fixed/Implemented in version: **0.250.218** + +## Issue Description + +CodeQL reported five high-severity `py/clear-text-logging-sensitive-data` alerts against the +shared logging sinks in `application/single_app/functions_appinsights.py`, plus two +`py/import-of-mutable-attribute` warnings in helper scripts. + +The logging alerts were not false positives. `log_event` sanitizes its inputs before they reach +any sink, but the redaction decision for structured properties was made by +`_is_sensitive_log_key`, which matched a fixed list of substrings. Several credential field names +used by this codebase did not contain any of those substrings and were therefore logged in +clear text. + +The most significant gap was **`auth_key`**, which is the field name the plugin/action +connection-test routes use for the caller-supplied secret, and the plugin manifest's +**`auth.key`**, which `plugin.schema.json` describes as holding "the secret value for the plugin +... such as a SQL connection string, a password for a service principal." + +Reproduction before the fix: + +```text +log_event("credential redaction probe", extra={"auth_key": "SuperSecretCredentialValue123"}) +-> [LOG] credential redaction probe -- {'auth_key': 'SuperSecretCredentialValue123'} +``` + +## Root Cause Analysis + +`_normalize_log_key` strips non-alphanumeric characters, so `auth_key`, `authKey`, and +`auth-key` all normalize to `authkey`. The `SENSITIVE_LOG_KEY_FRAGMENTS` tuple contained +`accountkey`, `apikey`, `privatekey`, and `subscriptionkey`, but not `authkey`, and no fragment +is a substring of `authkey`. The same was true for a property named exactly `key`, and for +`pwd`, `key_pair`, `master_key`, `primary_key`, `secondary_key`, `encryption_key`, +`signing_key`, `session_key`, and `storage_key`. + +A value under one of these keys was only redacted by luck, when the value itself happened to +match `SECRET_ASSIGNMENT_RE` (for example a connection string containing `Password=`). A bare +API key or token under `auth_key` was emitted verbatim. + +Eighteen credential key names were affected in total. + +Widening the match to "any key containing `key`" was not acceptable, because it would redact +benign configuration such as `key_encoding`, `key_prefix_hints`, and `partition_key_path`, +removing diagnostic value from logs. + +## Technical Details + +Files modified: + +* `application/single_app/functions_appinsights.py` +* `scripts/resolve_multiendpoint_gpt.py` +* `deployers/bicep/postconfig.py` +* `deployers/version.txt` +* `functional_tests/test_privacy_logging_telemetry_audit.py` +* `functional_tests/test_log_credential_key_redaction.py` (new) + +Code changes summary: + +* Added the missing credential fragments to `SENSITIVE_LOG_KEY_FRAGMENTS`: `authkey`, + `encryptionkey`, `keypair`, `masterkey`, `primarykey`, `secondarykey`, `sessionkey`, + `signingkey`, and `storagekey`. +* Added a new `SENSITIVE_LOG_KEY_EXACT` tuple for names that carry a credential only when they + are the entire key: `key`, `keys`, `pass`, `passphrase`, `pwd`, `sig`, and `signature`. + `_is_sensitive_log_key` now checks the fully normalized key against this tuple before falling + back to substring matching. Matching these as substrings would have redacted `key_encoding` + and `partition_key_path`, so the exact-match list keeps the fix surgical. +* Replaced the direct `from azure.cosmos import CosmosClient` bindings in the two remaining + helper scripts with `import azure.cosmos as azure_cosmos` and module-qualified + `azure_cosmos.CosmosClient(...)` calls, matching the pattern established in + `COSMOSCLIENT_IMPORT_BINDING_CODEQL_FIX.md` (v0.250.047). No direct `CosmosClient` imports + remain in the repository. +* Restored `functional_tests/test_privacy_logging_telemetry_audit.py`, which had been failing + since v0.242.072 because it asserted an exact `config.py` version. It now uses + `assert_app_version_at_least`, per the repository's version-assertion guidance, so the privacy + audit runs again. + +## Validation + +Test results: + +* `functional_tests/test_log_credential_key_redaction.py` (new): 6/6 passed. Verified to fail + before the fix, reporting all 18 unredacted credential key names and a reproduced `auth_key` + leak, which confirms it is a real regression guard rather than a tautology. +* `functional_tests/test_privacy_logging_telemetry_audit.py`: 5/5 passed, previously erroring + out before running any assertion. +* `functional_tests/test_log_event_call_contract.py`: passed. +* Route policy and plugin suites: passed. + +Before and after: + +| Property | Before | After | +|---|---|---| +| `{"auth_key": ""}` | logged in clear text | `***REDACTED***` | +| `{"auth": {"key": ""}}` | logged unless the value matched a `secret=` pattern | `***REDACTED***` | +| `{"pwd": ""}` | logged in clear text | `***REDACTED***` | +| `{"key_encoding": "utf8"}` | visible | visible (unchanged) | +| `{"partition_key_path": "/id"}` | visible | visible (unchanged) | + +Impact: + +* Credential values supplied to action connection tests can no longer reach stdout or + Application Insights in clear text. +* Diagnostic value is preserved: benign configuration keys containing "key" remain readable, and + sensitive keys still report presence through the `_present` property in the structured + log record. +* Runtime behavior is otherwise unchanged. + +## Note on the remaining CodeQL alerts + +The five `py/clear-text-logging-sensitive-data` alerts point at the logging sinks themselves. +CodeQL does not model `sanitize_log_message` and `sanitize_log_properties` as sanitizers, so it +may continue to report those sinks even though the data reaching them is redacted. This change +addresses the underlying gap the alerts exposed; if the alerts persist, they can be triaged in +the repository's code scanning view with this fix as the justification. diff --git a/docs/explanation/fixes/index.md b/docs/explanation/fixes/index.md index 3fa9c7f67..fffb485ff 100644 --- a/docs/explanation/fixes/index.md +++ b/docs/explanation/fixes/index.md @@ -12,6 +12,7 @@ category: Version History - [Public Workspace Prompt Migration Fix](PUBLIC_WORKSPACE_PROMPT_MIGRATION_FIX.md) - [Azure OpenAI Model Discovery Identity Fix](v0.250.001/AZURE_OPENAI_MODEL_DISCOVERY_IDENTITY_FIX.md) - [CosmosClient Import Binding CodeQL Fix](COSMOSCLIENT_IMPORT_BINDING_CODEQL_FIX.md) +- [Log Credential Key Redaction Fix](LOG_CREDENTIAL_KEY_REDACTION_FIX.md) - [Conversation Cache Invalidation Authorization Fix](CONVERSATION_CACHE_INVALIDATION_AUTHORIZATION_FIX.md) - [Chat Completion Background Unread Guard Fix](CHAT_COMPLETION_BACKGROUND_UNREAD_GUARD_FIX.md) - [Settings Container RU Write Suppression Fix](SETTINGS_CONTAINER_RU_WRITE_SUPPRESSION_FIX.md) diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index 19791110c..248d506a9 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -2,6 +2,24 @@ For feature-focused and fix-focused drill-downs by version, see [Features by Version](/explanation/features/) and [Fixes by Version](/explanation/fixes/). +### **(v0.250.218)** + +#### Bug Fixes + +* **Credential Field Names Logged in Clear Text** + * Fixed a gap where credential values could be written to application logs and Application Insights in clear text. The log redactor matched only a fixed list of key-name substrings, so field names this codebase actually uses for secrets were missed. The most significant were `auth_key`, used by the action connection-test routes for the caller-supplied secret, and the plugin manifest's `auth.key`, which holds connection strings and service principal passwords. + * Eighteen credential key names were affected in total, including `pwd`, `key_pair`, `master_key`, `primary_key`, `secondary_key`, `encryption_key`, `signing_key`, `session_key`, and `storage_key`. + * Benign configuration keys that merely contain the word "key", such as `key_encoding`, `key_prefix_hints`, and `partition_key_path`, deliberately stay visible so logs keep their diagnostic value. + * (Ref: `functions_appinsights.py`, `test_log_credential_key_redaction.py`, `LOG_CREDENTIAL_KEY_REDACTION_FIX.md`) + +* **CosmosClient Import Bindings in Helper Scripts** + * Completed the v0.250.047 import-binding cleanup by updating the two remaining scripts that bound `CosmosClient` directly, so patching `azure.cosmos.CosmosClient` is observed consistently. No direct `CosmosClient` imports remain in the repository. + * (Ref: `scripts/resolve_multiendpoint_gpt.py`, `deployers/bicep/postconfig.py`) + +* **Privacy Logging Audit Test Restored** + * The privacy logging and telemetry audit had been failing since v0.242.072 because it asserted an exact `config.py` version and never reached its assertions. It now asserts a version floor, per the repository's version-assertion guidance, so the audit runs again. + * (Ref: `test_privacy_logging_telemetry_audit.py`) + ### **(v0.250.217)** #### New Features diff --git a/functional_tests/test_log_credential_key_redaction.py b/functional_tests/test_log_credential_key_redaction.py new file mode 100644 index 000000000..6c46e183f --- /dev/null +++ b/functional_tests/test_log_credential_key_redaction.py @@ -0,0 +1,330 @@ +#!/usr/bin/env python3 +# test_log_credential_key_redaction.py +""" +Functional test for credential key redaction in application logging. +Version: 0.250.218 +Implemented in: 0.250.218 + +This test ensures that credential-bearing property names reach the logging sinks +redacted. Before this fix, `_is_sensitive_log_key` matched only a fixed list of +substrings, so the field names this codebase actually uses for plugin secrets -- +notably `auth_key` and the plugin manifest's `auth.key` -- were logged in clear +text through `log_event`. + +It also guards the opposite failure: benign configuration keys that merely contain +the word "key" (`key_encoding`, `partition_key_path`, `key_prefix_hints`) must stay +visible so logs remain useful for diagnostics. +""" + +import io +import os +import sys +from contextlib import redirect_stdout + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'application', 'single_app')) + +from test_support.versioning import assert_app_version_at_least + + +SECRET_VALUE = "SuperSecretCredentialValue123" + + +def install_cosmos_stub(): + """Let config.py import without contacting a live Cosmos account.""" + import azure.cosmos as azure_cosmos + + class StubContainer: + def read_item(self, item, partition_key=None): + if item == "app_settings": + return {"id": "app_settings", "settings": {}} + raise KeyError(item) + + def upsert_item(self, item): + return item + + def query_items(self, *args, **kwargs): + return [] + + class StubDatabase: + def create_container_if_not_exists(self, id, **kwargs): + return StubContainer() + + class StubClient: + def __init__(self, *args, **kwargs): + pass + + def create_database_if_not_exists(self, *args, **kwargs): + return StubDatabase() + + original_client = azure_cosmos.CosmosClient + azure_cosmos.CosmosClient = StubClient + return azure_cosmos, original_client + + +def get_appinsights_module(): + azure_cosmos, original_client = install_cosmos_stub() + try: + import functions_appinsights + return functions_appinsights + finally: + azure_cosmos.CosmosClient = original_client + + +# Credential-bearing names that must never be logged in clear text. Each of these +# is either used by this codebase or is a common Azure credential field name. +SENSITIVE_KEY_NAMES = ( + "auth_key", + "authKey", + "auth-key", + "key", + "keys", + "pwd", + "pass", + "passphrase", + "password", + "api_key", + "account_key", + "client_secret", + "connection_string", + "access_token", + "bearer_token", + "key_pair", + "master_key", + "primary_key", + "secondary_key", + "encryption_key", + "signing_key", + "session_key", + "storage_key", + "private_key", + "subscription_key", + "sig", + "signature", + "shared_access_signature", + "credential", + "authorization", +) + +# Benign configuration names that must stay readable in logs. +NON_SENSITIVE_KEY_NAMES = ( + "key_encoding", + "key_prefix_hints", + "partition_key_path", + "key_vault_name", + "column_family", + "max_results", + "max_value_bytes", + "timeout", + "read_only", + "keyboard", + "keyword", + "monkey", + "turkey", + "public_key_id", + "agent_signal", + "connection_mode", +) + + +def test_sensitive_key_names_are_classified_as_secrets(): + """Test that credential field names are recognized as sensitive.""" + print("Testing credential key classification...") + + try: + functions_appinsights = get_appinsights_module() + + missed = [ + key_name for key_name in SENSITIVE_KEY_NAMES + if not functions_appinsights._is_sensitive_log_key(key_name) + ] + assert not missed, f"These credential key names are not treated as sensitive: {missed}" + + print("Credential key names are classified as sensitive.") + return True + except Exception as exc: + print(f"Test failed: {exc}") + import traceback + traceback.print_exc() + return False + + +def test_benign_configuration_keys_stay_visible(): + """Test that ordinary configuration keys are not over-redacted.""" + print("Testing that benign configuration keys stay visible...") + + try: + functions_appinsights = get_appinsights_module() + + over_redacted = [ + key_name for key_name in NON_SENSITIVE_KEY_NAMES + if functions_appinsights._is_sensitive_log_key(key_name) + ] + assert not over_redacted, ( + f"These benign configuration keys are redacted and would lose diagnostic value: {over_redacted}" + ) + + print("Benign configuration keys remain visible.") + return True + except Exception as exc: + print(f"Test failed: {exc}") + import traceback + traceback.print_exc() + return False + + +def test_sanitize_log_properties_redacts_nested_credentials(): + """Test that credentials are redacted at any nesting depth.""" + print("Testing nested credential redaction...") + + try: + functions_appinsights = get_appinsights_module() + sanitize = functions_appinsights.sanitize_log_properties + + shapes = [ + {"auth_key": SECRET_VALUE}, + # The shape of a plugin manifest auth block from plugin.schema.json. + {"auth": {"type": "key", "key": SECRET_VALUE}}, + {"plugin": {"auth": {"key": SECRET_VALUE}}}, + {"items": [{"auth_key": SECRET_VALUE}]}, + {"settings": {"nested": {"deeper": {"password": SECRET_VALUE}}}}, + {"credentials": [{"pwd": SECRET_VALUE}, {"master_key": SECRET_VALUE}]}, + ] + + for shape in shapes: + sanitized_text = str(sanitize(shape)) + assert SECRET_VALUE not in sanitized_text, ( + f"Secret survived sanitization for shape {shape!r}: {sanitized_text}" + ) + + print("Nested credentials are redacted at every depth.") + return True + except Exception as exc: + print(f"Test failed: {exc}") + import traceback + traceback.print_exc() + return False + + +def test_log_event_does_not_emit_credentials(): + """Test end to end that log_event never prints a credential in clear text.""" + print("Testing log_event output for credential leaks...") + + try: + functions_appinsights = get_appinsights_module() + + leaking_cases = [ + ("auth_key property", {"auth_key": SECRET_VALUE}), + ("plugin manifest auth block", {"auth": {"type": "key", "key": SECRET_VALUE}}), + ("bare key property", {"key": SECRET_VALUE}), + ("pwd property", {"pwd": SECRET_VALUE}), + ("list of credential objects", {"items": [{"auth_key": SECRET_VALUE}]}), + ("account key property", {"account_key": SECRET_VALUE}), + ] + + for label, extra in leaking_cases: + captured_output = io.StringIO() + with redirect_stdout(captured_output): + functions_appinsights.log_event("credential redaction probe", extra=extra) + assert SECRET_VALUE not in captured_output.getvalue(), ( + f"log_event leaked a credential for {label}: {captured_output.getvalue()}" + ) + + # Message text using the key=value convention is still redacted. + captured_output = io.StringIO() + with redirect_stdout(captured_output): + functions_appinsights.log_event(f"connect failed password={SECRET_VALUE}") + assert SECRET_VALUE not in captured_output.getvalue(), ( + "log_event leaked a credential embedded in the message text" + ) + + print("log_event does not emit credentials in clear text.") + return True + except Exception as exc: + print(f"Test failed: {exc}") + import traceback + traceback.print_exc() + return False + + +def test_logger_extra_reports_presence_without_values(): + """Test that the structured log record reports presence rather than the secret.""" + print("Testing structured log record properties...") + + try: + functions_appinsights = get_appinsights_module() + + logger_extra = functions_appinsights._build_logger_extra( + "probe", {"auth_key": SECRET_VALUE, "column_family": "events"} + ) + serialized = str(logger_extra) + + assert SECRET_VALUE not in serialized, f"Secret reached the log record: {serialized}" + assert any(key.endswith("_present") for key in logger_extra), ( + "The log record should report that a credential was supplied without its value" + ) + + print("Structured log record omits credential values.") + return True + except Exception as exc: + print(f"Test failed: {exc}") + import traceback + traceback.print_exc() + return False + + +def test_cosmos_client_imports_are_module_qualified(): + """Test that helper scripts look up CosmosClient on the module at runtime.""" + print("Testing CosmosClient import bindings...") + + try: + assert_app_version_at_least("0.250.218") + + root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + checked_files = ( + os.path.join(root_dir, "scripts", "resolve_multiendpoint_gpt.py"), + os.path.join(root_dir, "deployers", "bicep", "postconfig.py"), + ) + + for file_path in checked_files: + with open(file_path, "r", encoding="utf-8") as file_handle: + source = file_handle.read() + + assert "from azure.cosmos import CosmosClient" not in source, ( + f"{os.path.basename(file_path)} still binds CosmosClient directly, so patching " + "azure.cosmos.CosmosClient would not be observed" + ) + assert "import azure.cosmos as azure_cosmos" in source, ( + f"{os.path.basename(file_path)} should import the Cosmos module" + ) + assert "azure_cosmos.CosmosClient(" in source, ( + f"{os.path.basename(file_path)} should construct the client through the module" + ) + + print("CosmosClient is resolved through the module in helper scripts.") + return True + except Exception as exc: + print(f"Test failed: {exc}") + import traceback + traceback.print_exc() + return False + + +if __name__ == "__main__": + tests = [ + test_sensitive_key_names_are_classified_as_secrets, + test_benign_configuration_keys_stay_visible, + test_sanitize_log_properties_redacts_nested_credentials, + test_log_event_does_not_emit_credentials, + test_logger_extra_reports_presence_without_values, + test_cosmos_client_imports_are_module_qualified, + ] + + results = [] + for test in tests: + print(f"\nRunning {test.__name__}...") + results.append(test()) + + success = all(results) + print(f"\nResults: {sum(results)}/{len(results)} tests passed") + sys.exit(0 if success else 1) diff --git a/functional_tests/test_privacy_logging_telemetry_audit.py b/functional_tests/test_privacy_logging_telemetry_audit.py index 39e0de63e..c32176996 100644 --- a/functional_tests/test_privacy_logging_telemetry_audit.py +++ b/functional_tests/test_privacy_logging_telemetry_audit.py @@ -2,8 +2,9 @@ #!/usr/bin/env python3 """ Functional test for privacy logging and telemetry audit fixes. -Version: 0.242.072 +Version: 0.250.218 Implemented in: 0.242.058 +Credential key redaction coverage extended in: 0.250.218 This test ensures logging, telemetry, and document-processing diagnostics redact secret-bearing fields and avoid raw agent or uploaded document content in audit @@ -16,10 +17,13 @@ import types import traceback +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from test_support.versioning import assert_app_version_at_least + ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) APP_DIR = os.path.join(ROOT_DIR, 'application', 'single_app') -CONFIG_FILE = os.path.join(APP_DIR, 'config.py') APPINSIGHTS_FILE = os.path.join(APP_DIR, 'functions_appinsights.py') PLUGIN_LOGGER_FILE = os.path.join(APP_DIR, 'semantic_kernel_plugins', 'plugin_invocation_logger.py') GROUPCHAT_ORCHESTRATOR_FILE = os.path.join(APP_DIR, 'agent_orchestrator_groupchat.py') @@ -34,13 +38,6 @@ def read_file_text(file_path): return file_handle.read() -def read_config_version(): - for line in read_file_text(CONFIG_FILE).splitlines(): - if line.strip().startswith('VERSION = '): - return line.split('=', 1)[1].strip().strip('"') - raise AssertionError('VERSION assignment not found in config.py') - - def install_appinsights_import_stubs(): azure_module = types.ModuleType('azure') monitor_module = types.ModuleType('azure.monitor') @@ -171,9 +168,9 @@ def test_document_processing_logs_avoid_raw_document_text(): def main(): - expected_version = '0.242.072' - actual_version = read_config_version() - assert actual_version == expected_version, f'Expected version {expected_version}, found {actual_version}' + # The audit must keep running as the app version advances, so assert a floor + # rather than an exact match. See .github/instructions version guidance. + assert_app_version_at_least('0.242.058') tests = [ test_log_event_redaction_helpers_redact_secret_fields, diff --git a/scripts/resolve_multiendpoint_gpt.py b/scripts/resolve_multiendpoint_gpt.py index d6f7a6b6b..4c768570c 100644 --- a/scripts/resolve_multiendpoint_gpt.py +++ b/scripts/resolve_multiendpoint_gpt.py @@ -12,7 +12,7 @@ import os from urllib.parse import urlparse -from azure.cosmos import CosmosClient +import azure.cosmos as azure_cosmos from azure.cosmos.exceptions import CosmosResourceNotFoundError from azure.identity import ClientSecretCredential, DefaultAzureCredential, get_bearer_token_provider from dotenv import load_dotenv @@ -165,7 +165,7 @@ def fetch_settings_from_cosmos(database_name, container_name, settings_id): raise ValueError("AZURE_COSMOS_ENDPOINT and AZURE_COSMOS_KEY must be set in the .env file.") logging.info("Connecting to Cosmos DB endpoint: %s", urlparse(cosmos_endpoint).hostname) - client = CosmosClient(cosmos_endpoint, credential=cosmos_key) + client = azure_cosmos.CosmosClient(cosmos_endpoint, credential=cosmos_key) logging.info("Using Cosmos database=%s container=%s settings_id=%s", database_name, container_name, settings_id) database = client.get_database_client(database_name) try: