diff --git a/.github/workflows/testing-dependency.yaml b/.github/workflows/testing-dependency.yaml index 5ba30106..2074a01a 100644 --- a/.github/workflows/testing-dependency.yaml +++ b/.github/workflows/testing-dependency.yaml @@ -158,3 +158,19 @@ jobs: index_name: '${{ needs.dependency-matrix-setup.outputs.index_name }}' PINECONE_API_KEY: '${{ secrets.PINECONE_API_KEY }}' urllib3_version: '${{ matrix.urllib3_version }}' + + deps-cleanup: + name: Deps cleanup + runs-on: ubuntu-latest + needs: + - dependency-matrix-setup + - dependency-matrix-grpc + - dependency-matrix-grpc-312 + - dependency-matrix-rest + - dependency-matrix-rest-312 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/delete-index + with: + index_name: '${{ needs.dependency-matrix-setup.outputs.index_name }}' + PINECONE_API_KEY: '${{ secrets.PINECONE_API_KEY }}' \ No newline at end of file diff --git a/.github/workflows/testing-integration.yaml b/.github/workflows/testing-integration.yaml index ee07e42f..3334becf 100644 --- a/.github/workflows/testing-integration.yaml +++ b/.github/workflows/testing-integration.yaml @@ -3,6 +3,54 @@ name: "Integration Tests" workflow_call: {} jobs: + # setup-index: + # name: Setup proxyconfig test index + # runs-on: ubuntu-latest + # outputs: + # index_name: ${{ steps.create-index.outputs.index_name }} + # steps: + # - uses: actions/checkout@v4 + # - name: Create index + # id: create-index + # uses: ./.github/actions/create-index + # with: + # PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }} + # NAME_PREFIX: 'proxyconfig-' + # REGION: 'us-west-2' + # CLOUD: 'aws' + # DIMENSION: 1536 + # METRIC: 'cosine' + + # proxy-config: + # name: Proxy config tests + # needs: [setup-index] + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # - uses: actions/setup-python@v5 + # with: + # python-version: 3.9 + # - name: Setup Poetry + # uses: ./.github/actions/setup-poetry + # - name: 'Run integration tests (proxy config)' + # run: | + # poetry run pytest tests/integration/proxy_config -s -v + # env: + # PINECONE_API_KEY: '${{ secrets.PINECONE_API_KEY }}' + # PINECONE_INDEX_NAME: ${{ needs.setup-index.outputs.index_name }} + # - name: Upload logs + # if: always() + # uses: actions/upload-artifact@v4 + # with: + # name: proxy_config_test_logs + # path: tests/integration/proxy_config/logs + # - name: Cleanup index + # if: always() + # uses: ./.github/actions/delete-index + # with: + # PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }} + # INDEX_NAME: ${{ needs.setup-index.outputs.index_name }} + data-plane-serverless: name: Data plane serverless integration tests runs-on: ubuntu-latest @@ -27,7 +75,6 @@ jobs: spec: '${{ matrix.spec }}' PINECONE_API_KEY: '${{ secrets.PINECONE_API_KEY }}' freshness_timeout_seconds: 600 - # data-plane-pod: # name: Data plane pod integration tests # runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 3f605053..4200d51d 100644 --- a/.gitignore +++ b/.gitignore @@ -154,3 +154,5 @@ dmypy.json # Datasets *.hdf5 *~ + +tests/integration/proxy_config/logs \ No newline at end of file diff --git a/README.md b/README.md index d7016ec7..1277555e 100644 --- a/README.md +++ b/README.md @@ -71,14 +71,14 @@ from pinecone import Pinecone pc = Pinecone() # This reads the PINECONE_API_KEY env var ``` -#### Using a configuration object +#### Using configuration keyword params If you prefer to pass configuration in code, for example if you have a complex application that needs to interact with multiple different Pinecone projects, the constructor accepts a keyword argument for `api_key`. If you pass configuration in this way, you can have full control over what name to use for the environment variable, sidestepping any issues that would result from two different client instances both needing to read the same `PINECONE_API_KEY` variable that the client implicitly checks for. -Configuration passed with keyword arguments takes precedent over environment variables. +Configuration passed with keyword arguments takes precedence over environment variables. ```python import os @@ -87,6 +87,81 @@ from pinecone import Pinecone pc = Pinecone(api_key=os.environ.get('CUSTOM_VAR')) ``` +### Proxy configuration + +If your network setup requires you to interact with Pinecone via a proxy, you will need +to pass additional configuration using optional keyword parameters. These optional parameters are forwarded to `urllib3`, which is the underlying library currently used by the Pinecone client to make HTTP requests. You may find it helpful to refer to the [urllib3 documentation on working with proxies](https://urllib3.readthedocs.io/en/stable/advanced-usage.html#http-and-https-proxies) while troubleshooting these settings. + +Here is a basic example: + +```python +from pinecone import Pinecone + +pc = Pinecone( + api_key='YOUR_API_KEY', + proxy_url='https://your-proxy.com' +) + +pc.list_indexes() +``` + +If your proxy requires authentication, you can pass those values in a header dictionary using the `proxy_headers` parameter. + +```python +from pinecone import Pinecone +import urllib3 import make_headers + +pc = Pinecone( + api_key='YOUR_API_KEY', + proxy_url='https://your-proxy.com', + proxy_headers=make_headers(proxy_basic_auth='username:password') +) + +pc.list_indexes() +``` + +### Using proxies with self-signed certificates + +By default the Pinecone Python client will perform SSL certificate verification +using the CA bundle maintained by Mozilla in the [certifi](https://pypi.org/project/certifi/) package. + +If your proxy server is using a self-signed certificate, you will need to pass the path to the certificate in PEM format using the `ssl_ca_certs` parameter. + +```python +from pinecone import Pinecone +import urllib3 import make_headers + +pc = Pinecone( + api_key="YOUR_API_KEY", + proxy_url='https://your-proxy.com', + proxy_headers=make_headers(proxy_basic_auth='username:password'), + ssl_ca_certs='path/to/cert-bundle.pem' +) + +pc.list_indexes() +``` + +### Disabling SSL verification + +If you would like to disable SSL verification, you can pass the `ssl_verify` +parameter with a value of `False`. We do not recommend going to production with SSL verification disabled. + +```python +from pinecone import Pinecone +import urllib3 import make_headers + +pc = Pinecone( + api_key='YOUR_API_KEY', + proxy_url='https://your-proxy.com', + proxy_headers=make_headers(proxy_basic_auth='username:password'), + ssl_ca_certs='path/to/cert-bundle.pem', + ssl_verify=False +) + +pc.list_indexes() + +``` + ### Working with GRPC (for improved performance) If you've followed instructions above to install with optional `grpc` extras, you can unlock some performance improvements by working with an alternative version of the client imported from the `pinecone.grpc` subpackage. diff --git a/pinecone/config/config.py b/pinecone/config/config.py index a73ae87b..2cf50a41 100644 --- a/pinecone/config/config.py +++ b/pinecone/config/config.py @@ -1,6 +1,5 @@ from typing import NamedTuple, Optional, Dict import os -import copy from pinecone.exceptions import PineconeConfigurationError from pinecone.config.openapi import OpenApiConfigFactory @@ -10,7 +9,10 @@ class Config(NamedTuple): api_key: str = "" host: str = "" - openapi_config: Optional[OpenApiConfiguration] = None + proxy_url: Optional[str] = None + proxy_headers: Optional[Dict[str, str]] = None + ssl_ca_certs: Optional[str] = None + ssl_verify: Optional[bool] = None additional_headers: Optional[Dict[str, str]] = {} class ConfigBuilder: @@ -34,7 +36,10 @@ class ConfigBuilder: def build( api_key: Optional[str] = None, host: Optional[str] = None, - openapi_config: Optional[OpenApiConfiguration] = None, + proxy_url: Optional[str] = None, + proxy_headers: Optional[Dict[str, str]] = None, + ssl_ca_certs: Optional[str] = None, + ssl_verify: Optional[bool] = None, additional_headers: Optional[Dict[str, str]] = {}, **kwargs, ) -> Config: @@ -47,11 +52,28 @@ def build( if not host: raise PineconeConfigurationError("You haven't specified a host.") + return Config(api_key, host, proxy_url, proxy_headers, ssl_ca_certs, ssl_verify, additional_headers) + + @staticmethod + def build_openapi_config( + config: Config, openapi_config: Optional[OpenApiConfiguration] = None, **kwargs + ) -> OpenApiConfiguration: if openapi_config: - openapi_config = copy.deepcopy(openapi_config) - openapi_config.host = host - openapi_config.api_key = {"ApiKeyAuth": api_key} - else: - openapi_config = OpenApiConfigFactory.build(api_key=api_key, host=host) + openapi_config = OpenApiConfigFactory.copy(openapi_config=openapi_config, api_key=config.api_key, host=config.host) + elif openapi_config is None: + openapi_config = OpenApiConfigFactory.build(api_key=config.api_key, host=config.host) - return Config(api_key, host, openapi_config, additional_headers) \ No newline at end of file + # Check if value passed before overriding any values present + # in the openapi_config. This means if the user has passed + # an openapi_config object and a kwarg for the same setting, + # the kwarg will take precedence. + if (config.proxy_url): + openapi_config.proxy = config.proxy_url + if (config.proxy_headers): + openapi_config.proxy_headers = config.proxy_headers + if (config.ssl_ca_certs): + openapi_config.ssl_ca_cert = config.ssl_ca_certs + if (config.ssl_verify != None): + openapi_config.verify_ssl = config.ssl_verify + + return openapi_config \ No newline at end of file diff --git a/pinecone/config/openapi.py b/pinecone/config/openapi.py index 6c6c3c05..b6b6f6e6 100644 --- a/pinecone/config/openapi.py +++ b/pinecone/config/openapi.py @@ -3,6 +3,8 @@ import certifi import socket +import copy +import warnings from urllib3.connection import HTTPConnection @@ -17,11 +19,35 @@ class OpenApiConfigFactory: @classmethod def build(cls, api_key: str, host: Optional[str] = None, **kwargs): openapi_config = OpenApiConfiguration() + openapi_config.api_key = {"ApiKeyAuth": api_key} openapi_config.host = host openapi_config.ssl_ca_cert = certifi.where() openapi_config.socket_options = cls._get_socket_options() - openapi_config.api_key = {"ApiKeyAuth": api_key} return openapi_config + + @classmethod + def copy(cls, openapi_config: OpenApiConfiguration, api_key: str, host: str) -> OpenApiConfiguration: + ''' + Copy a user-supplied openapi configuration and update it with the user's api key and host. + If they have not specified other socket configuration, we will use the default values. + We expect these objects are being passed mainly a vehicle for proxy configuration, so + we don't modify those settings. + ''' + copied = copy.deepcopy(openapi_config) + warnings.warn("Passing openapi_config is deprecated and will be removed in a future release. Please pass settings such as proxy_url, proxy_headers, ssl_ca_certs, and ssl_verify directly to the Pinecone constructor as keyword arguments. See the README at https://github.com/pinecone-io/pinecone-python-client for examples.", DeprecationWarning) + + copied.api_key = {"ApiKeyAuth": api_key} + copied.host = host + + # Set sensible defaults if the user hasn't set them + if not copied.socket_options: + copied.socket_options = cls._get_socket_options() + + # We specifically do not modify the user's ssl_ca_cert or proxy settings, as + # they may have set them intentionally. This is the main reason somebody would + # pass an openapi_config in the first place. + + return copied @classmethod def _get_socket_options( diff --git a/pinecone/control/pinecone.py b/pinecone/control/pinecone.py index ca8ebba4..ad25247c 100644 --- a/pinecone/control/pinecone.py +++ b/pinecone/control/pinecone.py @@ -3,7 +3,7 @@ from .index_host_store import IndexHostStore -from pinecone.config import PineconeConfig, Config +from pinecone.config import PineconeConfig, Config, ConfigBuilder from pinecone.core.client.api.manage_indexes_api import ManageIndexesApi from pinecone.utils import normalize_host, setup_openapi_client @@ -24,6 +24,10 @@ def __init__( self, api_key: Optional[str] = None, host: Optional[str] = None, + proxy_url: Optional[str] = None, + proxy_headers: Optional[Dict[str, str]] = None, + ssl_ca_certs: Optional[str] = None, + ssl_verify: Optional[bool] = None, config: Optional[Config] = None, additional_headers: Optional[Dict[str, str]] = {}, pool_threads: Optional[int] = 1, @@ -38,6 +42,14 @@ def __init__( :type api_key: str, optional :param host: The control plane host to connect to. :type host: str, optional + :param proxy_url: The URL of the proxy to use for the connection. Default: `None` + :type proxy_url: str, optional + :param proxy_headers: Additional headers to pass to the proxy. Use this if your proxy setup requires authentication. Default: `{}` + :type proxy_headers: Dict[str, str], optional + :param ssl_ca_certs: The path to the SSL CA certificate bundle to use for the connection. This path should point to a file in PEM format. Default: `None` + :type ssl_ca_certs: str, optional + :param ssl_verify: SSL verification is performed by default, but can be disabled using the boolean flag. Default: `True` + :type ssl_verify: bool, optional :param config: A `pinecone.config.Config` object. If passed, the `api_key` and `host` parameters will be ignored. :type config: pinecone.config.Config, optional :param additional_headers: Additional headers to pass to the API. Default: `{}` @@ -83,6 +95,87 @@ def __init__( request parameters to print out an equivalent curl command that you can run yourself or share with Pinecone support. **Be very careful with this option, as it will print out your API key** which forms part of a required authentication header. Default: `false` + + ### Proxy configuration + + If your network setup requires you to interact with Pinecone via a proxy, you will need + to pass additional configuration using optional keyword parameters. These optional parameters + are forwarded to `urllib3`, which is the underlying library currently used by the Pinecone client to + make HTTP requests. You may find it helpful to refer to the + [urllib3 documentation on working with proxies](https://urllib3.readthedocs.io/en/stable/advanced-usage.html#http-and-https-proxies) + while troubleshooting these settings. + + Here is a basic example: + + ```python + from pinecone import Pinecone + + pc = Pinecone( + api_key='YOUR_API_KEY', + proxy_url='https://your-proxy.com' + ) + + pc.list_indexes() + ``` + + If your proxy requires authentication, you can pass those values in a header dictionary using the `proxy_headers` parameter. + + ```python + from pinecone import Pinecone + import urllib3 import make_headers + + pc = Pinecone( + api_key='YOUR_API_KEY', + proxy_url='https://your-proxy.com', + proxy_headers=make_headers(proxy_basic_auth='username:password') + ) + + pc.list_indexes() + ``` + + ### Using proxies with self-signed certificates + + By default the Pinecone Python client will perform SSL certificate verification + using the CA bundle maintained by Mozilla in the [certifi](https://pypi.org/project/certifi/) package. + If your proxy server is using a self-signed certificate, you will need to pass the path to the certificate + in PEM format using the `ssl_ca_certs` parameter. + + ```python + from pinecone import Pinecone + import urllib3 import make_headers + + pc = Pinecone( + api_key='YOUR_API_KEY', + proxy_url='https://your-proxy.com', + proxy_headers=make_headers(proxy_basic_auth='username:password'), + ssl_ca_certs='path/to/cert-bundle.pem' + ) + + pc.list_indexes() + ``` + + ### Disabling SSL verification + + If you would like to disable SSL verification, you can pass the `ssl_verify` + parameter with a value of `False`. We do not recommend going to production with SSL verification disabled. + + ```python + from pinecone import Pinecone + import urllib3 import make_headers + + pc = Pinecone( + api_key='YOUR_API_KEY', + proxy_url='https://your-proxy.com', + proxy_headers=make_headers(proxy_basic_auth='username:password'), + ssl_ca_certs='path/to/cert-bundle.pem', + ssl_verify=False + ) + + pc.list_indexes() + + ``` + + """ if config: if not isinstance(config, Config): @@ -90,14 +183,24 @@ def __init__( else: self.config = config else: - self.config = PineconeConfig.build(api_key=api_key, host=host, additional_headers=additional_headers, **kwargs) + self.config = PineconeConfig.build( + api_key=api_key, + host=host, + additional_headers=additional_headers, + proxy_url=proxy_url, + proxy_headers=proxy_headers, + ssl_ca_certs=ssl_ca_certs, + ssl_verify=ssl_verify, + **kwargs + ) + self.openapi_config = ConfigBuilder.build_openapi_config(self.config, **kwargs) self.pool_threads = pool_threads if index_api: self.index_api = index_api else: - self.index_api = setup_openapi_client(ManageIndexesApi, self.config, pool_threads) + self.index_api = setup_openapi_client(ManageIndexesApi, self.config, self.openapi_config, pool_threads) self.index_host_store = IndexHostStore() """ @private """ @@ -516,7 +619,7 @@ def Index(self, name: str = '', host: str = '', **kwargs): pt = kwargs.pop('pool_threads', None) or self.pool_threads api_key = self.config.api_key - openapi_config = self.config.openapi_config + openapi_config = self.openapi_config if host != '': # Use host url if it is provided diff --git a/pinecone/data/index.py b/pinecone/data/index.py index 87915e92..8596c7eb 100644 --- a/pinecone/data/index.py +++ b/pinecone/data/index.py @@ -82,10 +82,11 @@ def __init__( api_key=api_key, host=host, additional_headers=additional_headers, - openapi_config=openapi_config, **kwargs ) - self._vector_api = setup_openapi_client(DataPlaneApi, self._config, pool_threads) + openapi_config = ConfigBuilder.build_openapi_config(self._config, openapi_config) + + self._vector_api = setup_openapi_client(DataPlaneApi, self._config, openapi_config, pool_threads) def __enter__(self): return self diff --git a/pinecone/utils/setup_openapi_client.py b/pinecone/utils/setup_openapi_client.py index 2bc9c61f..d1dde061 100644 --- a/pinecone/utils/setup_openapi_client.py +++ b/pinecone/utils/setup_openapi_client.py @@ -1,9 +1,9 @@ from pinecone.core.client.api_client import ApiClient from .user_agent import get_user_agent -def setup_openapi_client(api_klass, config, pool_threads): +def setup_openapi_client(api_klass, config, openapi_config, pool_threads): api_client = ApiClient( - configuration=config.openapi_config, + configuration=openapi_config, pool_threads=pool_threads ) api_client.user_agent = get_user_agent() diff --git a/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca-cert.cer b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca-cert.cer new file mode 100644 index 00000000..5f133756 --- /dev/null +++ b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca-cert.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUG5Ji5NxWD3Q7h8remh7vYloa1UMwDQYJKoZIhvcNAQEL +BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN +MjQwMzE3MDQwNjA2WhcNMzQwMzE3MDQwNjA2WjAoMRIwEAYDVQQDDAltaXRtcHJv +eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAI96RxFM2U3cXyhJre0DbJvZDvrr5IEFJhEO9+7vRFM73cTax2jhUDQx +ZLx5LgWWQmqTfNop5ON1XKqYMxpjTJrHEbIcnybLRmLL+SXVsj547vRH1rps+G4m +3iJWorGju3PieJYj8ppro0mhlynZRHOM8EzkX9TgxdtFpz3hejy9btOwEkRGrjM1 +5prsDubYn0JwGz6N2N/yAf9mviWKnP1xc1CD2xIJwJKX1Tyqi9B93w1YL5JFV7yg +rdlRw4X0a3wav7GiJJkylv8cZrtZ4Kt4TwNMLpqh21LRqJkwyFE8NLXMD/aS4q2U +3K5ml6H9MthNkrheH0RlsiOe5RQJMAcCAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB +/zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FM83YTNU3L2z9vvQvHrGX0U/XAf2MA0GCSqGSIb3DQEBCwUAA4IBAQARURZnD7Nm +d/kN1gIpl+x9aAaMLlvS3hgn6quuVVJzyiHYZKmBq/76VPIyn4dSFQakvS5nob3R +FNzlq3QR6o4jAR6BIEzuKDKExFdYz7hfBA6JgGUxTsofJPBmqC2BvRZlkt/Qb3ea +HDCJUYOXfppABimlVi5gOVf6r80wcuqTK6sIp+V+HVhAf2RbpAFnLWOSzkZ7Qaa9 +jZJ5Jd2nYTx+eOjkNZL2kiV6R9tvuJK0C9nQeJJDTwkmksLJEg+5CS6D51zdRgdc +dCvvesmF6dWQmOxZdm3pqusTkIWNq2RBb2kEqZA84cfVLX4+OOhbieC9XKQjsOcE +h+rsI/lmeuR9 +-----END CERTIFICATE----- diff --git a/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca-cert.p12 b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca-cert.p12 new file mode 100644 index 00000000..10e4d4e6 Binary files /dev/null and b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca-cert.p12 differ diff --git a/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca-cert.pem b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca-cert.pem new file mode 100644 index 00000000..5f133756 --- /dev/null +++ b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUG5Ji5NxWD3Q7h8remh7vYloa1UMwDQYJKoZIhvcNAQEL +BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN +MjQwMzE3MDQwNjA2WhcNMzQwMzE3MDQwNjA2WjAoMRIwEAYDVQQDDAltaXRtcHJv +eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAI96RxFM2U3cXyhJre0DbJvZDvrr5IEFJhEO9+7vRFM73cTax2jhUDQx +ZLx5LgWWQmqTfNop5ON1XKqYMxpjTJrHEbIcnybLRmLL+SXVsj547vRH1rps+G4m +3iJWorGju3PieJYj8ppro0mhlynZRHOM8EzkX9TgxdtFpz3hejy9btOwEkRGrjM1 +5prsDubYn0JwGz6N2N/yAf9mviWKnP1xc1CD2xIJwJKX1Tyqi9B93w1YL5JFV7yg +rdlRw4X0a3wav7GiJJkylv8cZrtZ4Kt4TwNMLpqh21LRqJkwyFE8NLXMD/aS4q2U +3K5ml6H9MthNkrheH0RlsiOe5RQJMAcCAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB +/zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FM83YTNU3L2z9vvQvHrGX0U/XAf2MA0GCSqGSIb3DQEBCwUAA4IBAQARURZnD7Nm +d/kN1gIpl+x9aAaMLlvS3hgn6quuVVJzyiHYZKmBq/76VPIyn4dSFQakvS5nob3R +FNzlq3QR6o4jAR6BIEzuKDKExFdYz7hfBA6JgGUxTsofJPBmqC2BvRZlkt/Qb3ea +HDCJUYOXfppABimlVi5gOVf6r80wcuqTK6sIp+V+HVhAf2RbpAFnLWOSzkZ7Qaa9 +jZJ5Jd2nYTx+eOjkNZL2kiV6R9tvuJK0C9nQeJJDTwkmksLJEg+5CS6D51zdRgdc +dCvvesmF6dWQmOxZdm3pqusTkIWNq2RBb2kEqZA84cfVLX4+OOhbieC9XKQjsOcE +h+rsI/lmeuR9 +-----END CERTIFICATE----- diff --git a/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca.p12 b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca.p12 new file mode 100644 index 00000000..e0177f8a Binary files /dev/null and b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca.p12 differ diff --git a/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca.pem b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca.pem new file mode 100644 index 00000000..b681605c --- /dev/null +++ b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-ca.pem @@ -0,0 +1,47 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAj3pHEUzZTdxfKEmt7QNsm9kO+uvkgQUmEQ737u9EUzvdxNrH +aOFQNDFkvHkuBZZCapN82ink43VcqpgzGmNMmscRshyfJstGYsv5JdWyPnju9EfW +umz4bibeIlaisaO7c+J4liPymmujSaGXKdlEc4zwTORf1ODF20WnPeF6PL1u07AS +REauMzXmmuwO5tifQnAbPo3Y3/IB/2a+JYqc/XFzUIPbEgnAkpfVPKqL0H3fDVgv +kkVXvKCt2VHDhfRrfBq/saIkmTKW/xxmu1ngq3hPA0wumqHbUtGomTDIUTw0tcwP +9pLirZTcrmaXof0y2E2SuF4fRGWyI57lFAkwBwIDAQABAoIBABaZiSY1d6knBCoh +aO8DchEeYJivnX+hIvze4bjWIWoG7Qi7+VsQ2oROH3L8l34zy+RjtO/cV3xomR8Z ++Dq413Et2CC5F2yR6lVXkbge8AOdIu6BflZBIeSf5K99/ASFKNq5GotzwBwIxmCr +vlbOLVUSJyvFcT7j5OaEEzLRGGMGq01Wvn6p4D3W3Fo7Upoj6gG8C+ndISHfCPWZ +pzJYW2iqnlvz3SAWKIhBYYq9OJrdFfi9ZNbKGYMUi2csMjVmDrAyRUi5qqVxM40x +Jumj4+0T8la8j9fms/9lkBzDh05pWGuuRfFj2ztTkIXUA23shNkpRwnuzu9kn786 +NqulHdkCgYEAxcLDgXGTc5n47f/jRf7qV4iau0FjdhRElgB6ld18tVIGbj4dbQjS +NOTVbMgRp4ng+V9M8Na9UyTzrnTTkfvGG9eDHcTNEQSbYCGMzP3TFGV8YnB7jFPa +Q/Cj5eV7O4vns2YrFZOV6QPhzyM4tgV6xuM/YKvHxNtvKA1uBPq7stUCgYEAubsX +99P0pqek0xUaXxPDObkjRv5nilM/1t0bNMFhzlloN4YOnVqM9npNu9n7PGjLJsG5 +qrPrZ6KcWHPnLtjDJwprAdkE54885YPYdRezWQIpeDMePYgP1VQz+PQ+vHX1CH1d +oiKqIZWxEp4jHLV7u0wSbmFBPw0+FL3VRTuOLWsCgYEAiYP5dxWHNyemVblOTOoK +AnxXPEcn5oAJgVUr6PJvOZakKhy/UYaExYsqbc5hmGLkMgP2+LIaTKqxWGqchDLT +e6DM5/JltqPBd4Nc6V7HXLOFXt5gyx+z8vJuxfphSvLqV3GAHCzYXYP5jZQsZ0ZA +LfTvqUVKULVWAj/0dTn1M1ECgYB9gX46zBHgpBxvPy1o3jPoR8Ec8kEJaiQTj6oY +xizPgf84tfAeSNhEnnT04eIx+iZ9dB+AyL/kci/wXbH1KCkHsrSItRvpVhOyjJuy +1GcvWJSpUvG2ZsE8SQAt1O6n75W7POwO6hnJRBw6Fn5nogOQl2FFEZdDgjFXVshN +VmdHLQKBgQCtqBqkyldZDVXxKIZnYKErxN2JeWZHOGCHLJbO+eN/ncQDpQlZV5Lr +Er2mThLRrqApjobQL7bF0IRTfQsOkLYlGd/36JkvRlkpSTixyJRn0PRvR/PdIrbk +LT6c0+82drLGyJHXHUR2P1kDJ03Snh2EMqVLVhm3hmXT9I9lQolRow== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUG5Ji5NxWD3Q7h8remh7vYloa1UMwDQYJKoZIhvcNAQEL +BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN +MjQwMzE3MDQwNjA2WhcNMzQwMzE3MDQwNjA2WjAoMRIwEAYDVQQDDAltaXRtcHJv +eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAI96RxFM2U3cXyhJre0DbJvZDvrr5IEFJhEO9+7vRFM73cTax2jhUDQx +ZLx5LgWWQmqTfNop5ON1XKqYMxpjTJrHEbIcnybLRmLL+SXVsj547vRH1rps+G4m +3iJWorGju3PieJYj8ppro0mhlynZRHOM8EzkX9TgxdtFpz3hejy9btOwEkRGrjM1 +5prsDubYn0JwGz6N2N/yAf9mviWKnP1xc1CD2xIJwJKX1Tyqi9B93w1YL5JFV7yg +rdlRw4X0a3wav7GiJJkylv8cZrtZ4Kt4TwNMLpqh21LRqJkwyFE8NLXMD/aS4q2U +3K5ml6H9MthNkrheH0RlsiOe5RQJMAcCAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB +/zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FM83YTNU3L2z9vvQvHrGX0U/XAf2MA0GCSqGSIb3DQEBCwUAA4IBAQARURZnD7Nm +d/kN1gIpl+x9aAaMLlvS3hgn6quuVVJzyiHYZKmBq/76VPIyn4dSFQakvS5nob3R +FNzlq3QR6o4jAR6BIEzuKDKExFdYz7hfBA6JgGUxTsofJPBmqC2BvRZlkt/Qb3ea +HDCJUYOXfppABimlVi5gOVf6r80wcuqTK6sIp+V+HVhAf2RbpAFnLWOSzkZ7Qaa9 +jZJ5Jd2nYTx+eOjkNZL2kiV6R9tvuJK0C9nQeJJDTwkmksLJEg+5CS6D51zdRgdc +dCvvesmF6dWQmOxZdm3pqusTkIWNq2RBb2kEqZA84cfVLX4+OOhbieC9XKQjsOcE +h+rsI/lmeuR9 +-----END CERTIFICATE----- diff --git a/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-dhparam.pem b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-dhparam.pem new file mode 100644 index 00000000..c10121fb --- /dev/null +++ b/tests/integration/proxy_config/.mitm/proxy1/mitmproxy-dhparam.pem @@ -0,0 +1,14 @@ + +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEAyT6LzpwVFS3gryIo29J5icvgxCnCebcdSe/NHMkD8dKJf8suFCg3 +O2+dguLakSVif/t6dhImxInJk230HmfC8q93hdcg/j8rLGJYDKu3ik6H//BAHKIv +j5O9yjU3rXCfmVJQic2Nne39sg3CreAepEts2TvYHhVv3TEAzEqCtOuTjgDv0ntJ +Gwpj+BJBRQGG9NvprX1YGJ7WOFBP/hWU7d6tgvE6Xa7T/u9QIKpYHMIkcN/l3ZFB +chZEqVlyrcngtSXCROTPcDOQ6Q8QzhaBJS+Z6rcsd7X+haiQqvoFcmaJ08Ks6LQC +ZIL2EtYJw8V8z7C0igVEBIADZBI6OTbuuhDwRw//zU1uq52Oc48CIZlGxTYG/Evq +o9EWAXUYVzWkDSTeBH1r4z/qLPE2cnhtMxbFxuvK53jGB0emy2y1Ei6IhKshJ5qX +IB/aE7SSHyQ3MDHHkCmQJCsOd4Mo26YX61NZ+n501XjqpCBQ2+DfZCBh8Va2wDyv +A2Ryg9SUz8j0AXViRNMJgJrr446yro/FuJZwnQcO3WQnXeqSBnURqKjmqkeFP+d8 +6mk2tqJaY507lRNqtGlLnj7f5RNoBFJDCLBNurVgfvq9TCVWKDIFD4vZRjCrnl6I +rD693XKIHUCWOjMh1if6omGXKHH40QuME2gNa50+YPn1iYDl88uDbbMCAQI= +-----END DH PARAMETERS----- diff --git a/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca-cert.cer b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca-cert.cer new file mode 100644 index 00000000..fb885197 --- /dev/null +++ b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca-cert.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUUo4sMqY4s3aM0RqjLhD1ZzGOhnowDQYJKoZIhvcNAQEL +BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN +MjQwMzE3MDQwNjA2WhcNMzQwMzE3MDQwNjA2WjAoMRIwEAYDVQQDDAltaXRtcHJv +eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAJ/BEbexCoDvIEB2zF8R13tNVqc5eW5kS4Rp0IqKSfWmmrghA0bc6X22 +p6juusl1KSpoWcR1L0iD1Wa2Tlaip0c/DJUwJHwJ70UZyWjwAJPbF282dYqqwygC +hWP1EFKVlctHE6MEMc+o1W7hLC690n0EKtatT5lCHSuUwK69RoNijfPqJrqstQKN +hJZ9bDIHVwi86jUbUcfjb9Uo/AiMjAonuy82wiarHdNmRIIcRcBvXkhx7on/5X5z +/Vq4+lgR91lP+6qYotHI988e4plF0KuzjrTPyki7+OiyJkMxJwJW/E1DU6bvTchN +H9wB27kJ6GtFW21n1YqRWpCR7JyQ4D8CAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB +/zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBNhRsjEjijaA8rS3XezhrtEpVvRMA0GCSqGSIb3DQEBCwUAA4IBAQAc8wSUSk7y +Sz4pQmi6EciZmU9jEnBHld9uYJ4mqRR2oPm+eRPq0yW1VifNEgMLSqNcv8/EH93o +C16jHHQ5TrV0C+wMnnUN3BxliDsi6FdbMa92Df09K9C/LP/v68H4rtMaMskvOrHw +k/r/NsKCxZ1GywLA7s/yVKgtr7ARARf6hHJS6/bxqohdaCFZtxmQIH26sOkTV2Ds +pf1ey+d3xitOl/roLXV91KjGfML4PRCzIPOw0+odSw62e2kikI77OQxOEn4zjyg+ +a0B344gMV7LaNTyqLTx41wU0hk62CeHHS4Gc0XLMfw9NYPTrjyQYK1+lEWDSEHCn +TiBThXoIGeAU +-----END CERTIFICATE----- diff --git a/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca-cert.p12 b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca-cert.p12 new file mode 100644 index 00000000..33125261 Binary files /dev/null and b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca-cert.p12 differ diff --git a/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca-cert.pem b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca-cert.pem new file mode 100644 index 00000000..fb885197 --- /dev/null +++ b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUUo4sMqY4s3aM0RqjLhD1ZzGOhnowDQYJKoZIhvcNAQEL +BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN +MjQwMzE3MDQwNjA2WhcNMzQwMzE3MDQwNjA2WjAoMRIwEAYDVQQDDAltaXRtcHJv +eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAJ/BEbexCoDvIEB2zF8R13tNVqc5eW5kS4Rp0IqKSfWmmrghA0bc6X22 +p6juusl1KSpoWcR1L0iD1Wa2Tlaip0c/DJUwJHwJ70UZyWjwAJPbF282dYqqwygC +hWP1EFKVlctHE6MEMc+o1W7hLC690n0EKtatT5lCHSuUwK69RoNijfPqJrqstQKN +hJZ9bDIHVwi86jUbUcfjb9Uo/AiMjAonuy82wiarHdNmRIIcRcBvXkhx7on/5X5z +/Vq4+lgR91lP+6qYotHI988e4plF0KuzjrTPyki7+OiyJkMxJwJW/E1DU6bvTchN +H9wB27kJ6GtFW21n1YqRWpCR7JyQ4D8CAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB +/zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBNhRsjEjijaA8rS3XezhrtEpVvRMA0GCSqGSIb3DQEBCwUAA4IBAQAc8wSUSk7y +Sz4pQmi6EciZmU9jEnBHld9uYJ4mqRR2oPm+eRPq0yW1VifNEgMLSqNcv8/EH93o +C16jHHQ5TrV0C+wMnnUN3BxliDsi6FdbMa92Df09K9C/LP/v68H4rtMaMskvOrHw +k/r/NsKCxZ1GywLA7s/yVKgtr7ARARf6hHJS6/bxqohdaCFZtxmQIH26sOkTV2Ds +pf1ey+d3xitOl/roLXV91KjGfML4PRCzIPOw0+odSw62e2kikI77OQxOEn4zjyg+ +a0B344gMV7LaNTyqLTx41wU0hk62CeHHS4Gc0XLMfw9NYPTrjyQYK1+lEWDSEHCn +TiBThXoIGeAU +-----END CERTIFICATE----- diff --git a/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca.p12 b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca.p12 new file mode 100644 index 00000000..5f144538 Binary files /dev/null and b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca.p12 differ diff --git a/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca.pem b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca.pem new file mode 100644 index 00000000..103f5f22 --- /dev/null +++ b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-ca.pem @@ -0,0 +1,47 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAn8ERt7EKgO8gQHbMXxHXe01Wpzl5bmRLhGnQiopJ9aaauCED +RtzpfbanqO66yXUpKmhZxHUvSIPVZrZOVqKnRz8MlTAkfAnvRRnJaPAAk9sXbzZ1 +iqrDKAKFY/UQUpWVy0cTowQxz6jVbuEsLr3SfQQq1q1PmUIdK5TArr1Gg2KN8+om +uqy1Ao2Eln1sMgdXCLzqNRtRx+Nv1Sj8CIyMCie7LzbCJqsd02ZEghxFwG9eSHHu +if/lfnP9Wrj6WBH3WU/7qpii0cj3zx7imUXQq7OOtM/KSLv46LImQzEnAlb8TUNT +pu9NyE0f3AHbuQnoa0VbbWfVipFakJHsnJDgPwIDAQABAoIBAA+PjzNxuHCZDW7G +1sVaR/KOTloSv4Daa0vfwRzQt4xVlbOvU4UvHRDOHkQ9Bk8VOggT15qXp4SZHGVy +07kDz7NuF49FYmhkN1aajZz95uOzO/Ps10PFU/KtVcmuVzattCrAWgNPWnxVsuR0 +yzu9gnRqJLOtRTGY2DdXt/HNWFvEfqhM1pCfi/NjpUjZx3d7+P+Vp9eXBnOcrIPN +9fV00sqHgD/Ddm7swAs4Nh3errm3EYsSOBVu0OEMHob7MrgZ2ewG6wFdFDHXB8vp +vc4WmHbqqQ4GW5lkJ/qKwuPxfSS4vZ+eYaZmZkerN3oyeEYvqifbitRcxBnzc/v1 +YMT4+ZECgYEA2yNW3w7kHmgn7+lBknVYjQgRQ5Z7O9H/xyx+2FCt5qyBu7l4463g +KZ7c1zoJg087MkFxIsC2BAenPdA+wxmdou6PwlKMxzvKGtI1Xi0AzcPezrFKcZCI +cp7oh0rUJIrXAz4M6f1R6X+Hg8MYMl/CZthVSxfH5paC0afCdEaZTP0CgYEAuqCB +Gk/1tHdY3X/b5V1Cu52O8bjl4QPtoZ0Yj1ho6Q2bjlXhKuiA8xVkC68nSMlboXmH +tBuHADhocbamSvA/R+jpneTysOE2F18utsAuOhMQmb6JHYF+r7Xf/S7zuGmhBQ9P +AEHXyUKh31EnrG81wD/rzSh8OS3KYPVlbNo0ROsCgYA5sjFCI2KOWvAA65IXJIw+ +/ZvGBs3Fb0H/x8hR3dQbgtnZejjJAVOewbP1etNcXjUAw1gtRT3nC7jNvpF3vrvR +VSxGhoOIRUauDyB7/i9S/bohA27NPbefLhWc4We/g0qfEOxHgynY53nfiDNLuAiw +GU9DqSw5mvEwkBHTmW7tZQKBgDvlESoJqXh+qRWFWGRXNviRi3PGfHhn01b/3Qb8 +P8cz582ZgEdOETxyjAY382qnvPGo2EWUZBJNCDAeh2YbjqOce4WCGeVskfiUQqDC +MtPOlJBTFxxSF/96ZmWSMQPpWpUOIbOabg+Yg+zw1cPAeUa2/Q19xchwCrhtaVyy +9v17AoGAEnWqMtZGCl9XKrRLtowLS2GxI/iEfnPaxtZkbOTso/oFB/fFf+Cez5wQ +RIZ7/QYNYCrSVGMu0vvMiG+u5Am4yDpVmTCY6PIiZXfpXdwh9GZ33CjM8Mwgp5mu +5aOBmmdrxnPmO/rnWHJLnuacmCXiGThj4o7W5pAT87MAIZvWGZ8= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUUo4sMqY4s3aM0RqjLhD1ZzGOhnowDQYJKoZIhvcNAQEL +BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN +MjQwMzE3MDQwNjA2WhcNMzQwMzE3MDQwNjA2WjAoMRIwEAYDVQQDDAltaXRtcHJv +eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAJ/BEbexCoDvIEB2zF8R13tNVqc5eW5kS4Rp0IqKSfWmmrghA0bc6X22 +p6juusl1KSpoWcR1L0iD1Wa2Tlaip0c/DJUwJHwJ70UZyWjwAJPbF282dYqqwygC +hWP1EFKVlctHE6MEMc+o1W7hLC690n0EKtatT5lCHSuUwK69RoNijfPqJrqstQKN +hJZ9bDIHVwi86jUbUcfjb9Uo/AiMjAonuy82wiarHdNmRIIcRcBvXkhx7on/5X5z +/Vq4+lgR91lP+6qYotHI988e4plF0KuzjrTPyki7+OiyJkMxJwJW/E1DU6bvTchN +H9wB27kJ6GtFW21n1YqRWpCR7JyQ4D8CAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB +/zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBNhRsjEjijaA8rS3XezhrtEpVvRMA0GCSqGSIb3DQEBCwUAA4IBAQAc8wSUSk7y +Sz4pQmi6EciZmU9jEnBHld9uYJ4mqRR2oPm+eRPq0yW1VifNEgMLSqNcv8/EH93o +C16jHHQ5TrV0C+wMnnUN3BxliDsi6FdbMa92Df09K9C/LP/v68H4rtMaMskvOrHw +k/r/NsKCxZ1GywLA7s/yVKgtr7ARARf6hHJS6/bxqohdaCFZtxmQIH26sOkTV2Ds +pf1ey+d3xitOl/roLXV91KjGfML4PRCzIPOw0+odSw62e2kikI77OQxOEn4zjyg+ +a0B344gMV7LaNTyqLTx41wU0hk62CeHHS4Gc0XLMfw9NYPTrjyQYK1+lEWDSEHCn +TiBThXoIGeAU +-----END CERTIFICATE----- diff --git a/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-dhparam.pem b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-dhparam.pem new file mode 100644 index 00000000..c10121fb --- /dev/null +++ b/tests/integration/proxy_config/.mitm/proxy2/mitmproxy-dhparam.pem @@ -0,0 +1,14 @@ + +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEAyT6LzpwVFS3gryIo29J5icvgxCnCebcdSe/NHMkD8dKJf8suFCg3 +O2+dguLakSVif/t6dhImxInJk230HmfC8q93hdcg/j8rLGJYDKu3ik6H//BAHKIv +j5O9yjU3rXCfmVJQic2Nne39sg3CreAepEts2TvYHhVv3TEAzEqCtOuTjgDv0ntJ +Gwpj+BJBRQGG9NvprX1YGJ7WOFBP/hWU7d6tgvE6Xa7T/u9QIKpYHMIkcN/l3ZFB +chZEqVlyrcngtSXCROTPcDOQ6Q8QzhaBJS+Z6rcsd7X+haiQqvoFcmaJ08Ks6LQC +ZIL2EtYJw8V8z7C0igVEBIADZBI6OTbuuhDwRw//zU1uq52Oc48CIZlGxTYG/Evq +o9EWAXUYVzWkDSTeBH1r4z/qLPE2cnhtMxbFxuvK53jGB0emy2y1Ei6IhKshJ5qX +IB/aE7SSHyQ3MDHHkCmQJCsOd4Mo26YX61NZ+n501XjqpCBQ2+DfZCBh8Va2wDyv +A2Ryg9SUz8j0AXViRNMJgJrr446yro/FuJZwnQcO3WQnXeqSBnURqKjmqkeFP+d8 +6mk2tqJaY507lRNqtGlLnj7f5RNoBFJDCLBNurVgfvq9TCVWKDIFD4vZRjCrnl6I +rD693XKIHUCWOjMh1if6omGXKHH40QuME2gNa50+YPn1iYDl88uDbbMCAQI= +-----END DH PARAMETERS----- diff --git a/tests/integration/proxy_config/__init__.py b/tests/integration/proxy_config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/proxy_config/conftest.py b/tests/integration/proxy_config/conftest.py new file mode 100644 index 00000000..2c59361f --- /dev/null +++ b/tests/integration/proxy_config/conftest.py @@ -0,0 +1,72 @@ +import time +import os +import pytest +import subprocess +from ..helpers import get_environment_var + +PROXIES = { + 'proxy1': { + 'name': 'proxy1', + 'port': 8080, + 'ssl_ca_certs': os.path.abspath('./tests/integration/proxy_config/.mitm/proxy1'), + 'auth': None + }, + 'proxy2': { + 'name': 'proxy2', + 'port': 8081, + 'ssl_ca_certs': os.path.abspath('./tests/integration/proxy_config/.mitm/proxy2'), + 'auth': ('testuser', 'testpassword') + } +} + +def docker_command(proxy): + cmd = [ + "docker", "run", "-d", # detach to run in background + "--rm", # remove container when stopped + "--name", proxy['name'], # name the container + "-p", f"{proxy['port']}:8080", # map the port + "-v", f"{proxy['ssl_ca_certs']}:/home/mitmproxy/.mitmproxy", # mount config as volume + "mitmproxy/mitmproxy", # docker image name + "mitmdump" # command to run + ] + if proxy['auth']: + cmd.append(f"--set proxyauth={proxy['auth'][0]}:{proxy['auth'][1]}") + print(" ".join(cmd)) + return " ".join(cmd) + +def run_cmd(cmd, output): + output.write("Going to run: " + cmd + "\n") + exit_code = subprocess.call(cmd, shell=True, stdout=output, stderr=output) + if exit_code != 0: + raise Exception(f"Failed to run command: {cmd}") + +@pytest.fixture(scope='session', autouse=True) +def start_docker(): + with open("tests/integration/proxy_config/logs/proxyconfig-docker-start.log", "a") as output: + run_cmd(docker_command(PROXIES['proxy1']), output) + run_cmd(docker_command(PROXIES['proxy2']), output) + + time.sleep(5) + with open("tests/integration/proxy_config/logs/proxyconfig-docker-ps.log", "a") as output: + run_cmd("docker ps --all", output) + + yield + with open("tests/integration/proxy_config/logs/proxyconfig-docker-stop.log", "a") as output: + run_cmd("docker stop proxy1", output) + run_cmd("docker stop proxy2", output) + +@pytest.fixture() +def proxy1(): + return PROXIES['proxy1'] + +@pytest.fixture() +def proxy2(): + return PROXIES['proxy2'] + +@pytest.fixture() +def api_key(): + return get_environment_var('PINECONE_API_KEY') + +@pytest.fixture() +def index_name(): + return get_environment_var('PINECONE_INDEX_NAME') \ No newline at end of file diff --git a/tests/integration/proxy_config/logs/.gitkeep b/tests/integration/proxy_config/logs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/proxy_config/test_proxy_settings.py b/tests/integration/proxy_config/test_proxy_settings.py new file mode 100644 index 00000000..906c1deb --- /dev/null +++ b/tests/integration/proxy_config/test_proxy_settings.py @@ -0,0 +1,82 @@ +import os +import pytest +from pinecone import Pinecone +from urllib3 import make_headers +from urllib3.exceptions import InsecureRequestWarning + +PROXY1_URL_HTTPS = 'https://localhost:8080' +PROXY1_URL_HTTP = 'http://localhost:8080' + +PROXY2_URL = 'https://localhost:8081' + +def exercise_all_apis(client, index_name): + # Control plane + client.list_indexes() + # Data plane + index = client.Index(index_name) + index.describe_index_stats() + +class TestProxyConfig: + def test_https_proxy_with_self_signed_cert(self, api_key, index_name, proxy1): + ssl_ca_certs = os.path.join(proxy1['ssl_ca_certs'], 'mitmproxy-ca-cert.pem') + pc = Pinecone( + api_key=api_key, + proxy_url=PROXY1_URL_HTTPS, + ssl_ca_certs=ssl_ca_certs, + ) + exercise_all_apis(pc, index_name) + + def test_http_proxy_with_self_signed_cert(self, api_key, index_name, proxy1): + ssl_ca_certs = os.path.join(proxy1['ssl_ca_certs'], 'mitmproxy-ca-cert.pem') + pc = Pinecone( + api_key=api_key, + proxy_url=PROXY1_URL_HTTP, + ssl_ca_certs=ssl_ca_certs, + ) + exercise_all_apis(pc, index_name) + + def test_proxy_with_ssl_verification_disabled_emits_warning(self, api_key): + pc = Pinecone( + api_key=api_key, + proxy_url=PROXY1_URL_HTTPS, + ssl_verify=False, + ) + + with pytest.warns(InsecureRequestWarning): + pc.list_indexes() + + def test_proxy_with_incorrect_cert_path(self, api_key): + with pytest.raises(Exception) as e: + pc = Pinecone( + api_key=api_key, + proxy_url=PROXY1_URL_HTTPS, + ssl_ca_certs='~/incorrect/path', + ) + pc.list_indexes() + + assert 'No such file or directory' in str(e.value) + + def test_proxy_with_valid_path_to_incorrect_cert(self, api_key, proxy2): + ssl_ca_certs = os.path.join(proxy2['ssl_ca_certs'], 'mitmproxy-ca-cert.pem') + with pytest.raises(Exception) as e: + pc = Pinecone( + api_key=api_key, + proxy_url=PROXY1_URL_HTTPS, + ssl_ca_certs=ssl_ca_certs, + ) + pc.list_indexes() + + assert 'CERTIFICATE_VERIFY_FAILED' in str(e.value) + + def test_proxy_that_requires_proxyauth(self, api_key, index_name, proxy2): + ssl_ca_certs = os.path.join(proxy2['ssl_ca_certs'], 'mitmproxy-ca-cert.pem') + username = proxy2['auth'][0] + password = proxy2['auth'][1] + pc = Pinecone( + api_key=api_key, + proxy_url=PROXY2_URL, + proxy_headers=make_headers(proxy_basic_auth=f'{username}:{password}'), + ssl_ca_certs=ssl_ca_certs + ) + exercise_all_apis(pc, index_name) + diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index f1d6af7a..cf3996e1 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -51,14 +51,15 @@ def test_init_with_positional_args(self): def test_init_with_kwargs(self): api_key = "my-api-key" controller_host = "my-controller-host" + ssl_ca_cert = 'path/to/cert-bundle.pem' + openapi_config = OpenApiConfiguration() - openapi_config.ssl_ca_cert = 'path/to/cert' - config = PineconeConfig.build(api_key=api_key, host=controller_host, openapi_config=openapi_config) + config = PineconeConfig.build(api_key=api_key, host=controller_host, ssl_ca_certs=ssl_ca_cert, openapi_config=openapi_config) assert config.api_key == api_key assert config.host == 'https://' + controller_host - assert config.openapi_config.ssl_ca_cert == 'path/to/cert' + assert config.ssl_ca_certs == 'path/to/cert-bundle.pem' def test_resolution_order_kwargs_over_env_vars(self): """ @@ -92,7 +93,7 @@ def test_config_pool_threads(self): def test_config_when_openapi_config_is_passed_merges_api_key(self): oai_config = OpenApiConfiguration() pc = Pinecone(api_key='asdf', openapi_config=oai_config) - assert pc.config.openapi_config.api_key == {'ApiKeyAuth': 'asdf'} + assert pc.openapi_config.api_key == {'ApiKeyAuth': 'asdf'} def test_ssl_config_passed_to_index_client(self): oai_config = OpenApiConfiguration() @@ -102,8 +103,8 @@ def test_ssl_config_passed_to_index_client(self): pc = Pinecone(api_key='key', openapi_config=oai_config) - assert pc.config.openapi_config.ssl_ca_cert == 'path/to/cert' - assert pc.config.openapi_config.proxy_headers == proxy_headers + assert pc.openapi_config.ssl_ca_cert == 'path/to/cert' + assert pc.openapi_config.proxy_headers == proxy_headers idx = pc.Index(host='host') assert idx._vector_api.api_client.configuration.ssl_ca_cert == 'path/to/cert' @@ -117,13 +118,30 @@ def test_host_config_not_clobbered_by_index(self): pc = Pinecone(api_key='key', openapi_config=oai_config) - assert pc.config.openapi_config.ssl_ca_cert == 'path/to/cert' - assert pc.config.openapi_config.proxy_headers == proxy_headers - assert pc.config.openapi_config.host == 'https://api.pinecone.io' + assert pc.openapi_config.ssl_ca_cert == 'path/to/cert' + assert pc.openapi_config.proxy_headers == proxy_headers + assert pc.openapi_config.host == 'https://api.pinecone.io' idx = pc.Index(host='host') assert idx._vector_api.api_client.configuration.ssl_ca_cert == 'path/to/cert' assert idx._vector_api.api_client.configuration.proxy_headers == proxy_headers assert idx._vector_api.api_client.configuration.host == 'https://host' - assert pc.config.openapi_config.host == 'https://api.pinecone.io' \ No newline at end of file + assert pc.openapi_config.host == 'https://api.pinecone.io' + + def test_proxy_config(self): + pc = Pinecone( + api_key='asdf', + proxy_url='http://localhost:8080', + ssl_ca_certs='path/to/cert-bundle.pem', + ) + + assert pc.config.proxy_url == 'http://localhost:8080' + assert pc.config.ssl_ca_certs == 'path/to/cert-bundle.pem' + + assert pc.openapi_config.proxy == 'http://localhost:8080' + assert pc.openapi_config.ssl_ca_cert == 'path/to/cert-bundle.pem' + + assert pc.index_api.api_client.configuration.proxy == 'http://localhost:8080' + assert pc.index_api.api_client.configuration.ssl_ca_cert == 'path/to/cert-bundle.pem' + \ No newline at end of file diff --git a/tests/unit/test_config_builder.py b/tests/unit/test_config_builder.py index 7f4d63dc..ce8d6569 100644 --- a/tests/unit/test_config_builder.py +++ b/tests/unit/test_config_builder.py @@ -10,8 +10,6 @@ def test_build_simple(self): assert config.api_key == "my-api-key" assert config.host == "https://my-controller-host" assert config.additional_headers == {} - assert config.openapi_config.host == "https://my-controller-host" - assert config.openapi_config.api_key == {"ApiKeyAuth": "my-api-key"} def test_build_merges_key_and_host_when_openapi_config_provided(self): config = ConfigBuilder.build( @@ -22,8 +20,6 @@ def test_build_merges_key_and_host_when_openapi_config_provided(self): assert config.api_key == "my-api-key" assert config.host == "https://my-controller-host" assert config.additional_headers == {} - assert config.openapi_config.host == "https://my-controller-host" - assert config.openapi_config.api_key == {"ApiKeyAuth": "my-api-key"} def test_build_errors_when_no_api_key_is_present(self): with pytest.raises(PineconeConfigurationError) as e: @@ -33,4 +29,44 @@ def test_build_errors_when_no_api_key_is_present(self): def test_build_errors_when_no_host_is_present(self): with pytest.raises(PineconeConfigurationError) as e: ConfigBuilder.build(api_key='my-api-key') - assert str(e.value) == "You haven't specified a host." \ No newline at end of file + assert str(e.value) == "You haven't specified a host." + + def test_build_openapi_config(self): + config = ConfigBuilder.build(api_key="my-api-key", host="https://my-controller-host") + openapi_config = ConfigBuilder.build_openapi_config(config) + assert openapi_config.host == "https://my-controller-host" + assert openapi_config.api_key == {"ApiKeyAuth": "my-api-key"} + + def test_build_openapi_config_merges_with_existing_config(self): + config = ConfigBuilder.build( + api_key="my-api-key", + host="https://my-controller-host" + ) + openapi_config = OpenApiConfiguration() + openapi_config.ssl_ca_cert = "path/to/bundle" + openapi_config.proxy = 'http://my-proxy:8080' + + openapi_config = ConfigBuilder.build_openapi_config(config, openapi_config) + + assert openapi_config.api_key == {"ApiKeyAuth": "my-api-key"} + assert openapi_config.host == "https://my-controller-host" + assert openapi_config.ssl_ca_cert == "path/to/bundle" + assert openapi_config.proxy == 'http://my-proxy:8080' + + def test_build_openapi_config_does_not_mutate_input(self): + config = ConfigBuilder.build( + api_key="my-api-key", + host="foo", + ssl_ca_certs="path/to/bundle.foo" + ) + + input_openapi_config = OpenApiConfiguration() + input_openapi_config.host = 'bar' + input_openapi_config.ssl_ca_cert = "asdfasdf" + + openapi_config = ConfigBuilder.build_openapi_config(config, input_openapi_config) + assert openapi_config.host == "https://foo" + assert openapi_config.ssl_ca_cert == "path/to/bundle.foo" + + assert input_openapi_config.host == 'bar' + assert input_openapi_config.ssl_ca_cert == "asdfasdf" \ No newline at end of file