snowflake-connector-python 4.4.0__cp314-cp314-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/connector/__init__.py +118 -0
- snowflake/connector/_query_context_cache.py +276 -0
- snowflake/connector/_sql_util.py +48 -0
- snowflake/connector/_utils.py +353 -0
- snowflake/connector/aio/README.md +88 -0
- snowflake/connector/aio/__init__.py +163 -0
- snowflake/connector/aio/_azure_storage_client.py +227 -0
- snowflake/connector/aio/_bind_upload_agent.py +69 -0
- snowflake/connector/aio/_connection.py +1216 -0
- snowflake/connector/aio/_cursor.py +1402 -0
- snowflake/connector/aio/_description.py +5 -0
- snowflake/connector/aio/_direct_file_operation_utils.py +88 -0
- snowflake/connector/aio/_file_transfer_agent.py +316 -0
- snowflake/connector/aio/_gcs_storage_client.py +360 -0
- snowflake/connector/aio/_network.py +860 -0
- snowflake/connector/aio/_ocsp_asn1crypto.py +51 -0
- snowflake/connector/aio/_ocsp_snowflake.py +608 -0
- snowflake/connector/aio/_pandas_tools.py +561 -0
- snowflake/connector/aio/_result_batch.py +442 -0
- snowflake/connector/aio/_result_set.py +309 -0
- snowflake/connector/aio/_s3_storage_client.py +460 -0
- snowflake/connector/aio/_session_manager.py +588 -0
- snowflake/connector/aio/_storage_client.py +331 -0
- snowflake/connector/aio/_telemetry.py +97 -0
- snowflake/connector/aio/_time_util.py +57 -0
- snowflake/connector/aio/_wif_util.py +324 -0
- snowflake/connector/aio/auth/__init__.py +52 -0
- snowflake/connector/aio/auth/_auth.py +406 -0
- snowflake/connector/aio/auth/_by_plugin.py +131 -0
- snowflake/connector/aio/auth/_default.py +28 -0
- snowflake/connector/aio/auth/_idtoken.py +54 -0
- snowflake/connector/aio/auth/_keypair.py +60 -0
- snowflake/connector/aio/auth/_no_auth.py +31 -0
- snowflake/connector/aio/auth/_oauth.py +27 -0
- snowflake/connector/aio/auth/_oauth_code.py +117 -0
- snowflake/connector/aio/auth/_oauth_credentials.py +100 -0
- snowflake/connector/aio/auth/_okta.py +247 -0
- snowflake/connector/aio/auth/_pat.py +27 -0
- snowflake/connector/aio/auth/_usrpwdmfa.py +30 -0
- snowflake/connector/aio/auth/_webbrowser.py +405 -0
- snowflake/connector/aio/auth/_workload_identity.py +60 -0
- snowflake/connector/arrow_context.py +211 -0
- snowflake/connector/auth/__init__.py +53 -0
- snowflake/connector/auth/_auth.py +669 -0
- snowflake/connector/auth/_http_server.py +257 -0
- snowflake/connector/auth/_oauth_base.py +472 -0
- snowflake/connector/auth/by_plugin.py +219 -0
- snowflake/connector/auth/default.py +36 -0
- snowflake/connector/auth/idtoken.py +72 -0
- snowflake/connector/auth/keypair.py +225 -0
- snowflake/connector/auth/no_auth.py +39 -0
- snowflake/connector/auth/oauth.py +49 -0
- snowflake/connector/auth/oauth_code.py +490 -0
- snowflake/connector/auth/oauth_credentials.py +71 -0
- snowflake/connector/auth/okta.py +339 -0
- snowflake/connector/auth/pat.py +40 -0
- snowflake/connector/auth/usrpwdmfa.py +69 -0
- snowflake/connector/auth/webbrowser.py +514 -0
- snowflake/connector/auth/workload_identity.py +110 -0
- snowflake/connector/azure_storage_client.py +278 -0
- snowflake/connector/backoff_policies.py +141 -0
- snowflake/connector/bind_upload_agent.py +89 -0
- snowflake/connector/cache.py +696 -0
- snowflake/connector/compat.py +127 -0
- snowflake/connector/config_manager.py +553 -0
- snowflake/connector/connection.py +2671 -0
- snowflake/connector/connection_diagnostic.py +793 -0
- snowflake/connector/constants.py +460 -0
- snowflake/connector/converter.py +809 -0
- snowflake/connector/converter_issue23517.py +87 -0
- snowflake/connector/converter_null.py +14 -0
- snowflake/connector/converter_snowsql.py +205 -0
- snowflake/connector/crl.py +919 -0
- snowflake/connector/crl_cache.py +734 -0
- snowflake/connector/cursor.py +2099 -0
- snowflake/connector/dbapi.py +53 -0
- snowflake/connector/description.py +21 -0
- snowflake/connector/direct_file_operation_utils.py +88 -0
- snowflake/connector/encryption_util.py +220 -0
- snowflake/connector/errorcode.py +94 -0
- snowflake/connector/errors.py +666 -0
- snowflake/connector/externals_utils/__init__.py +0 -0
- snowflake/connector/externals_utils/externals_setup.py +30 -0
- snowflake/connector/feature.py +4 -0
- snowflake/connector/file_compression_type.py +118 -0
- snowflake/connector/file_lock.py +72 -0
- snowflake/connector/file_transfer_agent.py +1244 -0
- snowflake/connector/file_util.py +153 -0
- snowflake/connector/gcs_storage_client.py +476 -0
- snowflake/connector/gzip_decoder.py +85 -0
- snowflake/connector/interval_util.py +26 -0
- snowflake/connector/local_storage_client.py +90 -0
- snowflake/connector/log_configuration.py +62 -0
- snowflake/connector/logging_utils/__init__.py +0 -0
- snowflake/connector/logging_utils/filters.py +72 -0
- snowflake/connector/minicore/__init__.py +0 -0
- snowflake/connector/minicore/aix_ppc64/libsf_mini_core.a +0 -0
- snowflake/connector/minicore/aix_ppc64/libsf_mini_core_static.a +0 -0
- snowflake/connector/minicore/linux_aarch64_glibc/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/linux_aarch64_musl/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/linux_x86_64_glibc/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/linux_x86_64_musl/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/macos_aarch64/libsf_mini_core.dylib +0 -0
- snowflake/connector/minicore/macos_x86_64/libsf_mini_core.dylib +0 -0
- snowflake/connector/minicore/windows_x86_64/sf_mini_core.dll +0 -0
- snowflake/connector/minicore/windows_x86_64/sf_mini_core_static.lib +0 -0
- snowflake/connector/nanoarrow_arrow_iterator.cp314-win_amd64.pyd +0 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +60 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp +570 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.hpp +98 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +115 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp +1140 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +138 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +47 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +83 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +72 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +73 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +57 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +95 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +57 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +34 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +346 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +145 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.cpp +67 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +101 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1908 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +215 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +55 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +148 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +239 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +210 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +206 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +118 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +898 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +190 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc.c +3204 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3217 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3618 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +379 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_arrow_iterator.pyx +262 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.c +512 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.h +350 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +33273 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +397 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.cpp +100 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +49 -0
- snowflake/connector/network.py +1227 -0
- snowflake/connector/ocsp_asn1crypto.py +447 -0
- snowflake/connector/ocsp_snowflake.py +1939 -0
- snowflake/connector/options.py +179 -0
- snowflake/connector/os_details.py +88 -0
- snowflake/connector/pandas_tools.py +734 -0
- snowflake/connector/platform_detection.py +555 -0
- snowflake/connector/proxy.py +28 -0
- snowflake/connector/py.typed +0 -0
- snowflake/connector/result_batch.py +917 -0
- snowflake/connector/result_set.py +340 -0
- snowflake/connector/s3_storage_client.py +614 -0
- snowflake/connector/secret_detector.py +181 -0
- snowflake/connector/session_manager.py +650 -0
- snowflake/connector/sf_dirs.py +64 -0
- snowflake/connector/sfbinaryformat.py +35 -0
- snowflake/connector/sfdatetime.py +345 -0
- snowflake/connector/snow_logging.py +144 -0
- snowflake/connector/sqlstate.py +9 -0
- snowflake/connector/ssd_internal_keys.py +30 -0
- snowflake/connector/ssl_wrap_socket.py +281 -0
- snowflake/connector/storage_client.py +486 -0
- snowflake/connector/telemetry.py +254 -0
- snowflake/connector/telemetry_oob.py +544 -0
- snowflake/connector/test_util.py +30 -0
- snowflake/connector/time_util.py +159 -0
- snowflake/connector/token_cache.py +457 -0
- snowflake/connector/tool/__init__.py +0 -0
- snowflake/connector/tool/dump_certs.py +57 -0
- snowflake/connector/tool/dump_ocsp_response.py +139 -0
- snowflake/connector/tool/dump_ocsp_response_cache.py +194 -0
- snowflake/connector/tool/probe_connection.py +69 -0
- snowflake/connector/url_util.py +55 -0
- snowflake/connector/util_text.py +320 -0
- snowflake/connector/vendored/__init__.py +3 -0
- snowflake/connector/vendored/requests/LICENSE +175 -0
- snowflake/connector/vendored/requests/__init__.py +184 -0
- snowflake/connector/vendored/requests/__version__.py +14 -0
- snowflake/connector/vendored/requests/_internal_utils.py +50 -0
- snowflake/connector/vendored/requests/adapters.py +696 -0
- snowflake/connector/vendored/requests/api.py +157 -0
- snowflake/connector/vendored/requests/auth.py +314 -0
- snowflake/connector/vendored/requests/certs.py +17 -0
- snowflake/connector/vendored/requests/compat.py +106 -0
- snowflake/connector/vendored/requests/cookies.py +561 -0
- snowflake/connector/vendored/requests/exceptions.py +151 -0
- snowflake/connector/vendored/requests/help.py +134 -0
- snowflake/connector/vendored/requests/hooks.py +33 -0
- snowflake/connector/vendored/requests/models.py +1039 -0
- snowflake/connector/vendored/requests/packages.py +23 -0
- snowflake/connector/vendored/requests/sessions.py +831 -0
- snowflake/connector/vendored/requests/status_codes.py +128 -0
- snowflake/connector/vendored/requests/structures.py +99 -0
- snowflake/connector/vendored/requests/utils.py +1086 -0
- snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
- snowflake/connector/vendored/urllib3/__init__.py +211 -0
- snowflake/connector/vendored/urllib3/_base_connection.py +165 -0
- snowflake/connector/vendored/urllib3/_collections.py +487 -0
- snowflake/connector/vendored/urllib3/_request_methods.py +278 -0
- snowflake/connector/vendored/urllib3/_version.py +34 -0
- snowflake/connector/vendored/urllib3/connection.py +1108 -0
- snowflake/connector/vendored/urllib3/connectionpool.py +1178 -0
- snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/__init__.py +17 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/connection.py +260 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/emscripten_fetch_worker.js +110 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/fetch.py +726 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/request.py +22 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/response.py +277 -0
- snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +564 -0
- snowflake/connector/vendored/urllib3/contrib/socks.py +228 -0
- snowflake/connector/vendored/urllib3/exceptions.py +335 -0
- snowflake/connector/vendored/urllib3/fields.py +341 -0
- snowflake/connector/vendored/urllib3/filepost.py +89 -0
- snowflake/connector/vendored/urllib3/http2/__init__.py +53 -0
- snowflake/connector/vendored/urllib3/http2/connection.py +356 -0
- snowflake/connector/vendored/urllib3/http2/probe.py +87 -0
- snowflake/connector/vendored/urllib3/poolmanager.py +651 -0
- snowflake/connector/vendored/urllib3/py.typed +2 -0
- snowflake/connector/vendored/urllib3/response.py +1480 -0
- snowflake/connector/vendored/urllib3/util/__init__.py +42 -0
- snowflake/connector/vendored/urllib3/util/connection.py +137 -0
- snowflake/connector/vendored/urllib3/util/proxy.py +43 -0
- snowflake/connector/vendored/urllib3/util/request.py +263 -0
- snowflake/connector/vendored/urllib3/util/response.py +101 -0
- snowflake/connector/vendored/urllib3/util/retry.py +549 -0
- snowflake/connector/vendored/urllib3/util/ssl_.py +527 -0
- snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +159 -0
- snowflake/connector/vendored/urllib3/util/ssltransport.py +271 -0
- snowflake/connector/vendored/urllib3/util/timeout.py +275 -0
- snowflake/connector/vendored/urllib3/util/url.py +469 -0
- snowflake/connector/vendored/urllib3/util/util.py +42 -0
- snowflake/connector/vendored/urllib3/util/wait.py +124 -0
- snowflake/connector/version.py +3 -0
- snowflake/connector/wif_util.py +434 -0
- snowflake_connector_python-4.4.0.dist-info/DELVEWHEEL +2 -0
- snowflake_connector_python-4.4.0.dist-info/METADATA +1591 -0
- snowflake_connector_python-4.4.0.dist-info/RECORD +302 -0
- snowflake_connector_python-4.4.0.dist-info/WHEEL +5 -0
- snowflake_connector_python-4.4.0.dist-info/entry_points.txt +4 -0
- snowflake_connector_python-4.4.0.dist-info/licenses/LICENSE.txt +202 -0
- snowflake_connector_python-4.4.0.dist-info/licenses/NOTICE +8 -0
- snowflake_connector_python-4.4.0.dist-info/top_level.txt +1 -0
- snowflake_connector_python.libs/msvcp140-0f885b509a685d2bbfa652fed26b5fb3.dll +0 -0
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import ipaddress
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import socket
|
|
9
|
+
import ssl
|
|
10
|
+
import tempfile
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from logging import getLogger
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, AnyStr
|
|
15
|
+
from urllib.request import getproxies
|
|
16
|
+
|
|
17
|
+
import certifi
|
|
18
|
+
import OpenSSL
|
|
19
|
+
|
|
20
|
+
from .compat import IS_WINDOWS, urlparse
|
|
21
|
+
from .cursor import SnowflakeCursor
|
|
22
|
+
from .session_manager import SessionManager, SessionManagerFactory
|
|
23
|
+
from .url_util import extract_top_level_domain_from_hostname
|
|
24
|
+
from .vendored import urllib3
|
|
25
|
+
|
|
26
|
+
logger = getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
if IS_WINDOWS:
|
|
29
|
+
import winreg
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _decode_dict(d: dict[str, dict[str, Any]]):
|
|
33
|
+
result: dict[str, dict[str, Any]] = {}
|
|
34
|
+
for key, value in d.items():
|
|
35
|
+
if isinstance(key, bytes):
|
|
36
|
+
key = key.decode()
|
|
37
|
+
if isinstance(value, bytes):
|
|
38
|
+
value = value.decode()
|
|
39
|
+
elif isinstance(value, dict):
|
|
40
|
+
value = _decode_dict(value)
|
|
41
|
+
result.update({key: value})
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _is_list_of_json_objects(allowlist: list[dict[str, Any]]):
|
|
46
|
+
if isinstance(allowlist, list) and all(
|
|
47
|
+
isinstance(item, dict) for item in allowlist
|
|
48
|
+
):
|
|
49
|
+
try:
|
|
50
|
+
json.dumps(allowlist)
|
|
51
|
+
return True
|
|
52
|
+
except TypeError:
|
|
53
|
+
return False
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ConnectionDiagnostic:
|
|
58
|
+
"""Implementation of a connection test utility for Snowflake connector
|
|
59
|
+
|
|
60
|
+
Use new ConnectionTest() to get the object.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
account: str,
|
|
66
|
+
host: str,
|
|
67
|
+
connection_diag_log_path: str | None = None,
|
|
68
|
+
connection_diag_allowlist_path: str | None = None,
|
|
69
|
+
proxy_host: str | None = None,
|
|
70
|
+
proxy_port: str | None = None,
|
|
71
|
+
proxy_user: str | None = None,
|
|
72
|
+
proxy_password: str | None = None,
|
|
73
|
+
session_manager: SessionManager | None = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
self.account = account
|
|
76
|
+
self.host = host
|
|
77
|
+
self.test_results: dict[str, list[str]] = {
|
|
78
|
+
"INITIAL": [],
|
|
79
|
+
"PROXY": [],
|
|
80
|
+
"SNOWFLAKE_URL": [],
|
|
81
|
+
"STAGE": [],
|
|
82
|
+
"OCSP_RESPONDER": [],
|
|
83
|
+
"OUT_OF_BAND_TELEMETRY": [],
|
|
84
|
+
"IGNORE": [],
|
|
85
|
+
}
|
|
86
|
+
host_type = "INITIAL"
|
|
87
|
+
self.__append_message(host_type, f"Specified snowflake account: {self.account}")
|
|
88
|
+
self.__append_message(
|
|
89
|
+
host_type, f"Host based on specified account: {self.host}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
top_level_domain = extract_top_level_domain_from_hostname(host)
|
|
93
|
+
if (
|
|
94
|
+
f".{top_level_domain}.snowflakecomputing.{top_level_domain}" in self.host
|
|
95
|
+
): # repeated domain name pattern
|
|
96
|
+
self.host = (
|
|
97
|
+
host.split(f".{top_level_domain}.snow", 1)[0] + f".{top_level_domain}"
|
|
98
|
+
)
|
|
99
|
+
logger.warning(
|
|
100
|
+
f"Account should not have snowflakecomputing.{top_level_domain} in it. You provided {host}. "
|
|
101
|
+
f"Continuing with fixed host."
|
|
102
|
+
)
|
|
103
|
+
self.__append_message(
|
|
104
|
+
host_type,
|
|
105
|
+
f"We removed extra .snowflakecomputing.{top_level_domain} and will continue with host: "
|
|
106
|
+
f"{self.host}",
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
self.host = host
|
|
110
|
+
|
|
111
|
+
self.ocsp_urls: list[str] = []
|
|
112
|
+
self.crl_urls: list[str] = []
|
|
113
|
+
self.cert_info: dict[str, dict[str, Any]] = {}
|
|
114
|
+
self.proxy_type: str = "params"
|
|
115
|
+
self.proxy_host = proxy_host
|
|
116
|
+
self.proxy_port = proxy_port
|
|
117
|
+
self.proxy_user = proxy_user
|
|
118
|
+
self.proxy_password = proxy_password
|
|
119
|
+
if self.proxy_host is None:
|
|
120
|
+
proxy_url = os.getenv("HTTPS_PROXY")
|
|
121
|
+
self.proxy_type = "environment"
|
|
122
|
+
else:
|
|
123
|
+
proxy_url = getproxies()["https"]
|
|
124
|
+
self.proxy_type = "system"
|
|
125
|
+
|
|
126
|
+
(
|
|
127
|
+
self.proxy_host,
|
|
128
|
+
self.proxy_port,
|
|
129
|
+
self.proxy_user,
|
|
130
|
+
self.proxy_password,
|
|
131
|
+
) = self.__parse_proxy(proxy_url)
|
|
132
|
+
self.__https_host_report(self.host)
|
|
133
|
+
self.full_connection_diag_log_path: Path | None = (
|
|
134
|
+
Path(connection_diag_log_path)
|
|
135
|
+
if connection_diag_log_path is not None
|
|
136
|
+
else None
|
|
137
|
+
)
|
|
138
|
+
self.full_connection_diag_allowlist_path: Path | None = (
|
|
139
|
+
Path(connection_diag_allowlist_path)
|
|
140
|
+
if connection_diag_allowlist_path is not None
|
|
141
|
+
else None
|
|
142
|
+
)
|
|
143
|
+
self.tmpdir: str = tempfile.gettempdir()
|
|
144
|
+
if self.full_connection_diag_log_path is None:
|
|
145
|
+
self.full_connection_diag_log_path = Path(self.tmpdir)
|
|
146
|
+
else:
|
|
147
|
+
if not self.full_connection_diag_log_path.is_absolute():
|
|
148
|
+
logger.warning(
|
|
149
|
+
f"Path {self.full_connection_diag_log_path} for connection test is not absolute."
|
|
150
|
+
)
|
|
151
|
+
self.full_connection_diag_log_path = Path(self.tmpdir)
|
|
152
|
+
elif not self.full_connection_diag_log_path.exists():
|
|
153
|
+
logger.warning(
|
|
154
|
+
f"Path {self.full_connection_diag_log_path} for connection test does not exist."
|
|
155
|
+
)
|
|
156
|
+
self.full_connection_diag_log_path = Path(self.tmpdir)
|
|
157
|
+
|
|
158
|
+
self.report_file: Path = (
|
|
159
|
+
self.full_connection_diag_log_path / "SnowflakeConnectionTestReport.txt"
|
|
160
|
+
)
|
|
161
|
+
logger.info(f"Reporting to file {self.report_file}")
|
|
162
|
+
|
|
163
|
+
if self.full_connection_diag_allowlist_path is not None:
|
|
164
|
+
if not self.full_connection_diag_allowlist_path.is_absolute():
|
|
165
|
+
logger.warning(
|
|
166
|
+
f"Path '{self.full_connection_diag_allowlist_path}' for connection test allowlist is not absolute."
|
|
167
|
+
)
|
|
168
|
+
logger.warning(
|
|
169
|
+
"Will connect to Snowflake for allowlist json instead. If you did not provide a valid "
|
|
170
|
+
"password, please make sure to update and run again."
|
|
171
|
+
)
|
|
172
|
+
self.full_connection_diag_allowlist_path = None
|
|
173
|
+
elif not self.full_connection_diag_allowlist_path.exists():
|
|
174
|
+
logger.warning(
|
|
175
|
+
f"File '{self.full_connection_diag_allowlist_path}' for connection test allowlist does not exist."
|
|
176
|
+
)
|
|
177
|
+
logger.warning(
|
|
178
|
+
"Will connect to Snowflake for allowlist json instead. If you did not provide a valid "
|
|
179
|
+
"password, please make sure to update and run again."
|
|
180
|
+
)
|
|
181
|
+
self.full_connection_diag_allowlist_path = None
|
|
182
|
+
|
|
183
|
+
self.allowlist_sql: str = (
|
|
184
|
+
"select /* snowflake-connector-python:connection_diagnostics */ system$allowlist();"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if self.__is_privatelink():
|
|
188
|
+
self.ocsp_urls.append(f"ocsp.{self.host}")
|
|
189
|
+
self.allowlist_sql = "select system$allowlist_privatelink();"
|
|
190
|
+
else:
|
|
191
|
+
self.ocsp_urls.append(f"ocsp.snowflakecomputing.{top_level_domain}")
|
|
192
|
+
|
|
193
|
+
self.allowlist_retrieval_success: bool = False
|
|
194
|
+
self.cursor: SnowflakeCursor | None = None
|
|
195
|
+
|
|
196
|
+
# Use a non-pooled SessionManager—clone the given one or create a fresh instance if not supplied (should only happen in tests).
|
|
197
|
+
self._session_manager = (
|
|
198
|
+
session_manager.clone(use_pooling=False)
|
|
199
|
+
if session_manager
|
|
200
|
+
else SessionManagerFactory.get_manager(use_pooling=False)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def __parse_proxy(self, proxy_url: str) -> tuple[str, str, str, str]:
|
|
204
|
+
parsed = urlparse(proxy_url)
|
|
205
|
+
proxy_host = parsed.hostname
|
|
206
|
+
proxy_port = parsed.port
|
|
207
|
+
proxy_user = parsed.username
|
|
208
|
+
proxy_password = parsed.password
|
|
209
|
+
return proxy_host, proxy_port, proxy_user, proxy_password
|
|
210
|
+
|
|
211
|
+
def __test_socket_get_cert(
|
|
212
|
+
self,
|
|
213
|
+
host: str,
|
|
214
|
+
port: int = 443,
|
|
215
|
+
timeout: int = 10,
|
|
216
|
+
host_type: str = "SNOWFLAKE_URL",
|
|
217
|
+
) -> str:
|
|
218
|
+
try:
|
|
219
|
+
self.__list_ips(host, host_type=host_type)
|
|
220
|
+
connect_creds: str = ""
|
|
221
|
+
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
222
|
+
conn.settimeout(timeout)
|
|
223
|
+
if self.proxy_host is not None:
|
|
224
|
+
proxy_addr = (self.proxy_host, self.proxy_port)
|
|
225
|
+
if self.proxy_user is not None:
|
|
226
|
+
proxy_auth = f"{self.proxy_user}:{self.proxy_password}"
|
|
227
|
+
proxy_auth = proxy_auth.encode("utf-8")
|
|
228
|
+
credentials = base64.b64encode(proxy_auth).decode().strip("\n")
|
|
229
|
+
connect_creds = f"Proxy-Authorization: Basic {credentials}\r\n"
|
|
230
|
+
conn.connect(proxy_addr)
|
|
231
|
+
else:
|
|
232
|
+
conn.connect((host, int(port)))
|
|
233
|
+
|
|
234
|
+
if port == 443:
|
|
235
|
+
if self.proxy_host is not None:
|
|
236
|
+
connect = f"CONNECT {host}:{port} HTTP/1.1\r\n{connect_creds}"
|
|
237
|
+
connect = f"{connect}Host: {host}\r\n\r\n"
|
|
238
|
+
conn.send(str.encode(connect))
|
|
239
|
+
conn.recv(4096).decode("utf-8")
|
|
240
|
+
|
|
241
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
242
|
+
context.load_verify_locations(certifi.where())
|
|
243
|
+
# Best-effort: enable partial-chain when supported
|
|
244
|
+
_partial_flag = getattr(ssl, "VERIFY_X509_PARTIAL_CHAIN", 0)
|
|
245
|
+
if _partial_flag and hasattr(context, "verify_flags"):
|
|
246
|
+
context.verify_flags |= _partial_flag
|
|
247
|
+
sock = context.wrap_socket(conn, server_hostname=host)
|
|
248
|
+
certificate = ssl.DER_cert_to_PEM_cert(sock.getpeercert(True))
|
|
249
|
+
http_request = f"""GET / {host}:{port} HTTP/1.1\r\n
|
|
250
|
+
Host: {host}\r\n
|
|
251
|
+
User-Agent: snowflake-connector-python-diagnostic
|
|
252
|
+
\r\n\r\n"""
|
|
253
|
+
try:
|
|
254
|
+
sock.send(str.encode(http_request))
|
|
255
|
+
except Exception as e:
|
|
256
|
+
self.__append_message(
|
|
257
|
+
host_type,
|
|
258
|
+
f"{host}:{port}: URL Check: Failed: Unknown Exception: {e}",
|
|
259
|
+
)
|
|
260
|
+
conn.close()
|
|
261
|
+
return certificate
|
|
262
|
+
else:
|
|
263
|
+
if self.proxy_host is not None:
|
|
264
|
+
connect = (
|
|
265
|
+
f"CONNECT {host}:{port} HTTP/1.1\r\n{connect_creds}\r\n\r\n"
|
|
266
|
+
)
|
|
267
|
+
else:
|
|
268
|
+
connect = (
|
|
269
|
+
f"GET / HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
conn.send(str.encode(connect))
|
|
273
|
+
response = conn.recv(4096).decode("utf-8")
|
|
274
|
+
conn.close()
|
|
275
|
+
|
|
276
|
+
if response is not None:
|
|
277
|
+
good_responses = "(200|301|cloudfront)"
|
|
278
|
+
if not re.search(good_responses, response):
|
|
279
|
+
self.__append_message(
|
|
280
|
+
host_type, f"{host}:{port}: URL Check: Failed: {response}"
|
|
281
|
+
)
|
|
282
|
+
return "FAILED"
|
|
283
|
+
self.__append_message(
|
|
284
|
+
host_type, f"{host}:{port}: URL Check: Connected Successfully"
|
|
285
|
+
)
|
|
286
|
+
return "SUCCESS"
|
|
287
|
+
except ssl.SSLError as e:
|
|
288
|
+
if "WRONG_VERSION_NUMBER" in str(e):
|
|
289
|
+
self.__append_message(
|
|
290
|
+
host_type,
|
|
291
|
+
f"{host}:{port}: URL Check: Failed: Proxy Auth Error: {e}",
|
|
292
|
+
)
|
|
293
|
+
return "FAILED"
|
|
294
|
+
except Exception as e:
|
|
295
|
+
self.__append_message(
|
|
296
|
+
host_type, f"{host}:{port}: URL Check: Failed: Unknown Exception: {e}"
|
|
297
|
+
)
|
|
298
|
+
return "FAILED"
|
|
299
|
+
|
|
300
|
+
self.__append_message(
|
|
301
|
+
host_type, f"{host}:{port}: URL Check: Connected Successfully"
|
|
302
|
+
)
|
|
303
|
+
return "SUCCESS"
|
|
304
|
+
|
|
305
|
+
def run_post_test(self) -> None:
|
|
306
|
+
results: list[str] = None
|
|
307
|
+
if self.full_connection_diag_allowlist_path is None:
|
|
308
|
+
if self.cursor is not None:
|
|
309
|
+
try:
|
|
310
|
+
results = self.cursor.execute(
|
|
311
|
+
self.allowlist_sql, _is_internal=True
|
|
312
|
+
).fetchall()[0][0]
|
|
313
|
+
results = json.loads(str(results))
|
|
314
|
+
self.allowlist_retrieval_success = True
|
|
315
|
+
except Exception as e:
|
|
316
|
+
logger.warning(f"Unable to do allowlist checks: exception: {e}")
|
|
317
|
+
else:
|
|
318
|
+
results_file = open(self.full_connection_diag_allowlist_path)
|
|
319
|
+
try:
|
|
320
|
+
results = json.load(results_file)
|
|
321
|
+
self.allowlist_retrieval_success = True
|
|
322
|
+
except Exception as e:
|
|
323
|
+
self.__append_message(
|
|
324
|
+
"INITIAL",
|
|
325
|
+
f"Allowlist was not valid json: '{e}'. Please run 'select system$allowlist();' and validate the file {self.full_connection_diag_allowlist_path} is correct.",
|
|
326
|
+
)
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
if _is_list_of_json_objects(results):
|
|
330
|
+
for result in results:
|
|
331
|
+
host_type = result["type"]
|
|
332
|
+
host = result["host"]
|
|
333
|
+
host_port = result["port"]
|
|
334
|
+
|
|
335
|
+
if host_type in ("OCSP_RESPONDER"):
|
|
336
|
+
if host not in self.ocsp_urls:
|
|
337
|
+
self.__test_socket_get_cert(
|
|
338
|
+
host, port=host_port, host_type=host_type
|
|
339
|
+
)
|
|
340
|
+
elif host_type in ("STAGE", "OUT_OF_BAND_TELEMETRY"):
|
|
341
|
+
try:
|
|
342
|
+
self.__https_host_report(
|
|
343
|
+
host, port=host_port, host_type=host_type
|
|
344
|
+
)
|
|
345
|
+
except Exception:
|
|
346
|
+
pass
|
|
347
|
+
else:
|
|
348
|
+
self.__append_message(
|
|
349
|
+
"INITIAL",
|
|
350
|
+
"Allowlist is not a valid list of json objects. Please run 'select system$allowlist();' and provide as a json file using the connection_diag_allowlist_path option.",
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def __is_privatelink(self) -> bool:
|
|
354
|
+
return "privatelink" in self.host
|
|
355
|
+
|
|
356
|
+
def __list_ips(self, host: str, host_type: str = "SNOWFLAKE_URL") -> None:
|
|
357
|
+
try:
|
|
358
|
+
ips = socket.gethostbyname_ex(host)[2]
|
|
359
|
+
base_message = f"{host}: nslookup results"
|
|
360
|
+
if "snowflakecomputing" in host:
|
|
361
|
+
for ip in ips:
|
|
362
|
+
if ipaddress.ip_address(ip).is_private:
|
|
363
|
+
if not self.__is_privatelink():
|
|
364
|
+
self.__append_message(
|
|
365
|
+
host_type,
|
|
366
|
+
f"{base_message}: private ip: {ip}: WARNING: this is not "
|
|
367
|
+
f"typical for a non-privatelink account",
|
|
368
|
+
)
|
|
369
|
+
else:
|
|
370
|
+
if self.__is_privatelink():
|
|
371
|
+
self.__append_message(
|
|
372
|
+
host_type,
|
|
373
|
+
f"{base_message}: public ip: {ip}: WARNING: privatelink accounts "
|
|
374
|
+
f"must have a private ip.",
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
self.__append_message(
|
|
378
|
+
host_type, f"{base_message}: public ip: {ip}"
|
|
379
|
+
)
|
|
380
|
+
else:
|
|
381
|
+
self.__append_message(host_type, f"{base_message}: {ips}")
|
|
382
|
+
except Exception as e:
|
|
383
|
+
logger.warning(f"Connectivity Test Exception in list_ips: {e}")
|
|
384
|
+
|
|
385
|
+
def __https_host_report(
|
|
386
|
+
self, host: str, port: int = 443, host_type: str = "SNOWFLAKE_URL"
|
|
387
|
+
) -> None:
|
|
388
|
+
try:
|
|
389
|
+
certificate = self.__test_socket_get_cert(
|
|
390
|
+
host, port=port, host_type=host_type
|
|
391
|
+
)
|
|
392
|
+
if "BEGIN CERTIFICATE" in certificate:
|
|
393
|
+
x509 = OpenSSL.crypto.load_certificate(
|
|
394
|
+
OpenSSL.crypto.FILETYPE_PEM, certificate
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
result = {
|
|
398
|
+
"subject": dict(x509.get_subject().get_components()),
|
|
399
|
+
"issuer": dict(x509.get_issuer().get_components()),
|
|
400
|
+
"serialNumber": x509.get_serial_number(),
|
|
401
|
+
"version": x509.get_version(),
|
|
402
|
+
"notBefore": datetime.strptime(
|
|
403
|
+
str(x509.get_notBefore().decode("utf-8")), "%Y%m%d%H%M%SZ"
|
|
404
|
+
),
|
|
405
|
+
"notAfter": datetime.strptime(
|
|
406
|
+
str(x509.get_notAfter().decode("utf-8")), "%Y%m%d%H%M%SZ"
|
|
407
|
+
),
|
|
408
|
+
}
|
|
409
|
+
self.cert_info[host] = result
|
|
410
|
+
extensions = (
|
|
411
|
+
x509.get_extension(i) for i in range(x509.get_extension_count())
|
|
412
|
+
)
|
|
413
|
+
extension_data = {}
|
|
414
|
+
for e in extensions:
|
|
415
|
+
extension_data[e.get_short_name().decode("utf-8")] = str(e)
|
|
416
|
+
|
|
417
|
+
_, _, host_suffix = host.partition(".")
|
|
418
|
+
if host_suffix in str(result["subject"]):
|
|
419
|
+
self.__append_message(
|
|
420
|
+
host_type, f"{host}:{port}: URL Check: Connected Successfully"
|
|
421
|
+
)
|
|
422
|
+
elif "subjectAltName" in extension_data:
|
|
423
|
+
if host_suffix in str(extension_data["subjectAltName"]):
|
|
424
|
+
self.__append_message(
|
|
425
|
+
host_type,
|
|
426
|
+
f"{host}:{port}: URL Check: Connected Successfully",
|
|
427
|
+
)
|
|
428
|
+
else:
|
|
429
|
+
self.__append_message(
|
|
430
|
+
host_type,
|
|
431
|
+
f"{host}:{port}: URL Check: Failed: Certificate mismatch: Host not in subject or alt names",
|
|
432
|
+
)
|
|
433
|
+
self.__append_message(host_type, f"{host}: Cert info:")
|
|
434
|
+
|
|
435
|
+
subject_str = _decode_dict(result["subject"])
|
|
436
|
+
self.__append_message(host_type, f"{host}: subject: {subject_str}")
|
|
437
|
+
|
|
438
|
+
issuer_str = _decode_dict(result["issuer"])
|
|
439
|
+
self.__append_message(host_type, f"{host}: issuer: {issuer_str}")
|
|
440
|
+
self.__append_message(
|
|
441
|
+
host_type, f"{host}: serialNumber: {result['serialNumber']}"
|
|
442
|
+
)
|
|
443
|
+
self.__append_message(
|
|
444
|
+
host_type, f"{host}: version: {result['version']}"
|
|
445
|
+
)
|
|
446
|
+
self.__append_message(
|
|
447
|
+
host_type, f"{host}: notBefore: {result['notBefore']}"
|
|
448
|
+
)
|
|
449
|
+
self.__append_message(
|
|
450
|
+
host_type, f"{host}: notAfter: {result['notAfter']}"
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
if host_type == "SNOWFLAKE_URL":
|
|
454
|
+
if "authorityInfoAccess" in extension_data:
|
|
455
|
+
ocsp_urls_orig = re.findall(
|
|
456
|
+
r"(https?://\S+)", extension_data["authorityInfoAccess"]
|
|
457
|
+
)
|
|
458
|
+
for url in ocsp_urls_orig:
|
|
459
|
+
self.ocsp_urls.append(url.split("/")[2])
|
|
460
|
+
else:
|
|
461
|
+
self.__append_message(
|
|
462
|
+
"INITIAL", "Unable to find ocsp URLs in certificate."
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
if "crlDistributionPoints" in extension_data:
|
|
466
|
+
crl_urls_orig = re.findall(
|
|
467
|
+
r"(https?://\S+)", extension_data["crlDistributionPoints"]
|
|
468
|
+
)
|
|
469
|
+
for url in crl_urls_orig:
|
|
470
|
+
self.crl_urls.append(url.split("/")[2])
|
|
471
|
+
else:
|
|
472
|
+
self.__append_message(
|
|
473
|
+
"IGNORE", "Unable to find crl URLs in certificate."
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
if "subjectAltName" in extension_data:
|
|
477
|
+
self.__append_message(
|
|
478
|
+
host_type,
|
|
479
|
+
f"{host}: subjectAltName: {extension_data['subjectAltName']}",
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
self.__append_message(host_type, f"{host}: crlUrls: {self.crl_urls}")
|
|
483
|
+
self.__append_message(host_type, f"{host}: ocspURLs: {self.ocsp_urls}")
|
|
484
|
+
|
|
485
|
+
except Exception as e:
|
|
486
|
+
logger.warning(f"Connectivity Test Exception in https_host_report: {e}")
|
|
487
|
+
|
|
488
|
+
def __get_issuer_string(self, issuer: dict[bytes, bytes]) -> str:
|
|
489
|
+
issuer_str: str = (
|
|
490
|
+
re.sub('[{}"]', "", json.dumps(_decode_dict(issuer)))
|
|
491
|
+
.replace(": ", "=")
|
|
492
|
+
.replace(",", ";")
|
|
493
|
+
)
|
|
494
|
+
return issuer_str
|
|
495
|
+
|
|
496
|
+
def __append_message(self, host_type: str, message: str) -> None:
|
|
497
|
+
self.test_results[host_type].append(f"{host_type}: {message}")
|
|
498
|
+
|
|
499
|
+
def __check_for_proxies(self) -> None:
|
|
500
|
+
# TODO: See if we need to do anything for noproxy
|
|
501
|
+
# If we need more proxy checks, this site might work
|
|
502
|
+
# curl -k -v https://amibehindaproxy.com 2>&1 | tee | grep alert
|
|
503
|
+
env_proxy_backup: dict[str, str] = {}
|
|
504
|
+
proxy_keys = ("HTTP_PROXY", "HTTPS_PROXY", "https_proxy", "http_proxy")
|
|
505
|
+
restore_keys = []
|
|
506
|
+
|
|
507
|
+
for proxy_key in proxy_keys:
|
|
508
|
+
if proxy_key in os.environ.keys():
|
|
509
|
+
env_proxy_backup[proxy_key] = os.environ.get(proxy_key)
|
|
510
|
+
del os.environ[proxy_key]
|
|
511
|
+
restore_keys.append(proxy_key)
|
|
512
|
+
|
|
513
|
+
host_type = "PROXY"
|
|
514
|
+
system_proxies = getproxies()
|
|
515
|
+
self.__append_message(
|
|
516
|
+
host_type,
|
|
517
|
+
f"Proxies with Env vars removed(SYSTEM PROXIES): {system_proxies}",
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
if "https" in system_proxies.keys():
|
|
521
|
+
proxy_host, proxy_port, proxy_user, proxy_password = self.__parse_proxy(
|
|
522
|
+
getproxies()["https"]
|
|
523
|
+
)
|
|
524
|
+
if proxy_user is not None:
|
|
525
|
+
proxy_url_example = (
|
|
526
|
+
f"http://{proxy_user}:{proxy_password}@{proxy_host}:{proxy_port}"
|
|
527
|
+
)
|
|
528
|
+
else:
|
|
529
|
+
proxy_url_example = f"http://{proxy_host}:{proxy_port}"
|
|
530
|
+
self.__append_message(
|
|
531
|
+
host_type,
|
|
532
|
+
f"""If there are failures, try using the SYSTEM PROXY: On Windows, do
|
|
533
|
+
"set HTTPS_PROXY='{proxy_url_example}'". On Linux/Mac, do
|
|
534
|
+
"export HTTPS_PROXY='{proxy_url_example}'" """,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
for restore_key in restore_keys:
|
|
538
|
+
os.environ[restore_key] = env_proxy_backup[restore_key]
|
|
539
|
+
|
|
540
|
+
self.__append_message(
|
|
541
|
+
host_type, f"Proxies with Env vars restored(ENV PROXIES): {getproxies()}"
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
cert_authorities = (
|
|
545
|
+
"C=US; O=Google Trust Services LLC",
|
|
546
|
+
"C=US; O=Amazon",
|
|
547
|
+
"C=US; O=DigiCert Inc",
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
check_pattern = f"(^{'|^'.join(cert_authorities)})"
|
|
551
|
+
if self.host in self.cert_info.keys():
|
|
552
|
+
issuer = self.__get_issuer_string(self.cert_info[self.host]["issuer"])
|
|
553
|
+
if not re.search(check_pattern, issuer):
|
|
554
|
+
self.__append_message(
|
|
555
|
+
host_type,
|
|
556
|
+
f"There is likely a proxy because the issuer for {self.host} is "
|
|
557
|
+
f"not correct. Got {issuer} and expected one of {cert_authorities}",
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
test_host = "www.google.com"
|
|
561
|
+
self.__https_host_report(test_host, port=443, host_type="IGNORE")
|
|
562
|
+
if test_host in self.cert_info.keys():
|
|
563
|
+
issuer = self.__get_issuer_string(self.cert_info[test_host]["issuer"])
|
|
564
|
+
if not re.search(check_pattern, issuer):
|
|
565
|
+
self.__append_message(
|
|
566
|
+
host_type,
|
|
567
|
+
f"There is likely a proxy because the issuer for {test_host} is "
|
|
568
|
+
f"not correct. Got {issuer} and expected one of {cert_authorities}",
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Get Windows proxy info from Registry just in case:
|
|
572
|
+
if IS_WINDOWS:
|
|
573
|
+
registry_start_key = "Software\\Microsoft\\Windows\\CurrentVersion"
|
|
574
|
+
hkey_strings = ["HKEY_CURRENT_USER", "HKEY_LOCAL_MACHINE"]
|
|
575
|
+
for hkey_str in hkey_strings:
|
|
576
|
+
self.__walk_win_registry(host_type, hkey_str, registry_start_key)
|
|
577
|
+
|
|
578
|
+
try:
|
|
579
|
+
# Using a URL that does not exist is a check for a transparent proxy
|
|
580
|
+
urllib3.disable_warnings()
|
|
581
|
+
|
|
582
|
+
request_kwargs = {
|
|
583
|
+
"timeout": 10,
|
|
584
|
+
"verify": False, # skip cert validation – same as cert_reqs=CERT_NONE
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
# If an explicit proxy was specified via constructor params, pass it
|
|
588
|
+
# explicitly so that the request goes through the same path as the
|
|
589
|
+
# legacy ProxyManager code (inc. basic-auth header).
|
|
590
|
+
if self.proxy_host is not None:
|
|
591
|
+
if self.proxy_user is not None:
|
|
592
|
+
proxy_url = f"http://{self.proxy_user}:{self.proxy_password}@{self.proxy_host}:{self.proxy_port}"
|
|
593
|
+
else:
|
|
594
|
+
proxy_url = f"http://{self.proxy_host}:{self.proxy_port}"
|
|
595
|
+
|
|
596
|
+
request_kwargs["proxies"] = {"http": proxy_url, "https": proxy_url}
|
|
597
|
+
|
|
598
|
+
resp = self._session_manager.get(
|
|
599
|
+
"https://nonexistentdomain.invalid", use_pooling=False, **request_kwargs
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
# squid does not throw exception. Check response body
|
|
603
|
+
if "does not exist" in resp.text:
|
|
604
|
+
self.__append_message(
|
|
605
|
+
host_type,
|
|
606
|
+
"It is likely there is a proxy based on HTTP response.",
|
|
607
|
+
)
|
|
608
|
+
except Exception as e:
|
|
609
|
+
if "NewConnectionError" in str(e):
|
|
610
|
+
self.__append_message(
|
|
611
|
+
host_type,
|
|
612
|
+
f"Proxy check using invalid URL did not show proxy: Review result, "
|
|
613
|
+
f"but you can probably ignore: Result: {e}",
|
|
614
|
+
)
|
|
615
|
+
elif "ProxyError" in str(e):
|
|
616
|
+
self.__append_message(
|
|
617
|
+
host_type, f"It is likely there is a proxy based on Exception: {e}"
|
|
618
|
+
)
|
|
619
|
+
else:
|
|
620
|
+
self.__append_message(
|
|
621
|
+
host_type,
|
|
622
|
+
f"Could not determine if a proxy does or does not exist based on Exception: {e}",
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
def run_test(self) -> None:
|
|
626
|
+
self.__check_for_proxies()
|
|
627
|
+
self.ocsp_urls = list(set(self.ocsp_urls))
|
|
628
|
+
for url in self.ocsp_urls:
|
|
629
|
+
self.__test_socket_get_cert(url, port=80, host_type="OCSP_RESPONDER")
|
|
630
|
+
|
|
631
|
+
def generate_report(self) -> None:
|
|
632
|
+
message = (
|
|
633
|
+
"=========Connectivity diagnostic report================================"
|
|
634
|
+
)
|
|
635
|
+
initial_joined_results = "\n".join(self.test_results["INITIAL"])
|
|
636
|
+
message = f"{message}\n" f"{initial_joined_results}\n"
|
|
637
|
+
|
|
638
|
+
proxy_joined_results = "\n".join(self.test_results["PROXY"])
|
|
639
|
+
message = (
|
|
640
|
+
f"{message}\n"
|
|
641
|
+
"=========Proxy information - These are best guesses, not guarantees====\n"
|
|
642
|
+
f"{proxy_joined_results}\n"
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
snowflake_url_joined_results = "\n".join(self.test_results["SNOWFLAKE_URL"])
|
|
646
|
+
message = (
|
|
647
|
+
f"{message}\n"
|
|
648
|
+
"=========Snowflake URL information=====================================\n"
|
|
649
|
+
f"{snowflake_url_joined_results}\n"
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
if self.allowlist_retrieval_success:
|
|
653
|
+
snowflake_stage_joined_results = "\n".join(self.test_results["STAGE"])
|
|
654
|
+
message = (
|
|
655
|
+
f"{message}\n"
|
|
656
|
+
"=========Snowflake Stage information===================================\n"
|
|
657
|
+
"We retrieved stage info from the allowlist\n"
|
|
658
|
+
f"{snowflake_stage_joined_results}\n"
|
|
659
|
+
)
|
|
660
|
+
else:
|
|
661
|
+
message = (
|
|
662
|
+
f"{message}\n"
|
|
663
|
+
"=========Snowflake Stage information - Unavailable=====================\n"
|
|
664
|
+
"We could not connect to Snowflake to get allowlist, so we do not have stage\n"
|
|
665
|
+
f"diagnostic info\n"
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
message = (
|
|
669
|
+
f"{message}\n"
|
|
670
|
+
"=========Snowflake OCSP information===================================="
|
|
671
|
+
)
|
|
672
|
+
snowflake_ocsp_joined_results = "\n".join(self.test_results["OCSP_RESPONDER"])
|
|
673
|
+
if self.allowlist_retrieval_success:
|
|
674
|
+
message = (
|
|
675
|
+
f"{message}\n"
|
|
676
|
+
"We were able to retrieve system allowlist.\n"
|
|
677
|
+
"These OCSP hosts came from the certificate and the allowlist."
|
|
678
|
+
)
|
|
679
|
+
else:
|
|
680
|
+
message = (
|
|
681
|
+
f"{message}\n"
|
|
682
|
+
"We were unable to retrieve system allowlist.\n"
|
|
683
|
+
"These OCSP hosts only came from the certificate."
|
|
684
|
+
)
|
|
685
|
+
message = f"{message}\n" f"{snowflake_ocsp_joined_results}\n"
|
|
686
|
+
|
|
687
|
+
if self.allowlist_retrieval_success:
|
|
688
|
+
snowflake_telemetry_joined_results = "\n".join(
|
|
689
|
+
self.test_results["OUT_OF_BAND_TELEMETRY"]
|
|
690
|
+
)
|
|
691
|
+
message = (
|
|
692
|
+
f"{message}\n"
|
|
693
|
+
"=========Snowflake Out of bound telemetry check========================\n"
|
|
694
|
+
f"{snowflake_telemetry_joined_results}\n"
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
logger.debug(message)
|
|
698
|
+
self.report_file.write_text(message)
|
|
699
|
+
|
|
700
|
+
def __get_win_registry_values(self, registry_key: AnyStr) -> dict[str, str]:
|
|
701
|
+
"""Gets values from windows registry key"""
|
|
702
|
+
registry_key_values: dict = {}
|
|
703
|
+
i = 0
|
|
704
|
+
while True:
|
|
705
|
+
try:
|
|
706
|
+
registry_key_value = winreg.EnumValue(registry_key, i)
|
|
707
|
+
except OSError:
|
|
708
|
+
break
|
|
709
|
+
registry_key_values[registry_key_value[0]] = registry_key_value[1:]
|
|
710
|
+
i = i + 1
|
|
711
|
+
return registry_key_values
|
|
712
|
+
|
|
713
|
+
def __walk_win_registry(
|
|
714
|
+
self, host_type: str, hkey_str: str, registry_key_str: str
|
|
715
|
+
) -> None:
|
|
716
|
+
"""Walks the windows registry to search for key relating to proxies"""
|
|
717
|
+
if hkey_str == "HKEY_CURRENT_USER":
|
|
718
|
+
hkey = winreg.HKEY_CURRENT_USER
|
|
719
|
+
elif hkey_str == "HKEY_LOCAL_MACHINE":
|
|
720
|
+
hkey = winreg.HKEY_LOCAL_MACHINE
|
|
721
|
+
else:
|
|
722
|
+
hkey = None
|
|
723
|
+
|
|
724
|
+
registry = winreg.OpenKey(hkey, registry_key_str)
|
|
725
|
+
i = 0
|
|
726
|
+
if hkey is not None:
|
|
727
|
+
try:
|
|
728
|
+
while True:
|
|
729
|
+
registry_key = winreg.EnumKey(registry, i)
|
|
730
|
+
i = i + 1
|
|
731
|
+
if registry_key:
|
|
732
|
+
new_registry_key_str = os.path.join(
|
|
733
|
+
registry_key_str, registry_key
|
|
734
|
+
)
|
|
735
|
+
if (
|
|
736
|
+
"internet settings" in str(registry_key).lower()
|
|
737
|
+
or "wpad" in str(registry_key).lower()
|
|
738
|
+
):
|
|
739
|
+
new_registry_key = winreg.OpenKey(
|
|
740
|
+
hkey, new_registry_key_str, 0, winreg.KEY_READ
|
|
741
|
+
)
|
|
742
|
+
values = self.__get_win_registry_values(new_registry_key)
|
|
743
|
+
if "AutoConfigURL" in values.keys():
|
|
744
|
+
wpad = values["AutoConfigURL"][0]
|
|
745
|
+
self.__append_message(
|
|
746
|
+
host_type,
|
|
747
|
+
f"There may be a proxy: Found a Wpad in Windows "
|
|
748
|
+
f"registry: Check proxy config for auto detect "
|
|
749
|
+
f"script: hkey: {hkey_str} : {new_registry_key_str} : "
|
|
750
|
+
f"wpad: {wpad}",
|
|
751
|
+
)
|
|
752
|
+
# Let's see if we can get the wpad proxy info
|
|
753
|
+
url = f"http://{wpad}/wpad.dat"
|
|
754
|
+
try:
|
|
755
|
+
resp = self._session_manager.get(url, timeout=10)
|
|
756
|
+
proxy_info = resp.text
|
|
757
|
+
self.__append_message(
|
|
758
|
+
host_type,
|
|
759
|
+
f"Wpad request returned possible proxy: {proxy_info}",
|
|
760
|
+
)
|
|
761
|
+
except Exception:
|
|
762
|
+
pass
|
|
763
|
+
|
|
764
|
+
elif "ProxyServer" in values.keys():
|
|
765
|
+
proxy = values["ProxyServer"][0]
|
|
766
|
+
self.__append_message(
|
|
767
|
+
host_type,
|
|
768
|
+
f"There may be a proxy: Found a proxy server in "
|
|
769
|
+
f"Windows registry: hkey: {hkey} :"
|
|
770
|
+
f" {new_registry_key_str} : ProxyServer: {proxy}",
|
|
771
|
+
)
|
|
772
|
+
elif "ProxyEnable" in values.keys():
|
|
773
|
+
proxy_enable = values["ProxyEnable"][0]
|
|
774
|
+
if proxy_enable == 1:
|
|
775
|
+
self.__append_message(
|
|
776
|
+
host_type,
|
|
777
|
+
f"There may be a proxy: Proxy is enabled per the "
|
|
778
|
+
f"registry: hkey: {hkey} : {new_registry_key_str}"
|
|
779
|
+
f" : ProxyEnable: {proxy_enable}",
|
|
780
|
+
)
|
|
781
|
+
else:
|
|
782
|
+
self.__append_message(
|
|
783
|
+
host_type,
|
|
784
|
+
f"Found Wpad key in registry: Most likely nothing, "
|
|
785
|
+
f"but review: hkey: {hkey_str} : "
|
|
786
|
+
f"{new_registry_key_str}: value: {values}",
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
self.__walk_win_registry(
|
|
790
|
+
host_type, hkey_str, new_registry_key_str
|
|
791
|
+
)
|
|
792
|
+
except OSError:
|
|
793
|
+
pass
|