diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d53d98e..e0424e59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## 0.22.0 [unreleased] +### Features + +1. [#254](https://github.com/InfluxCommunity/influxdb3-python/pull/254): + - Add support for mTLS in query api. + - Add examples for config mTLS. + ### Breaking Changes 1. [#238](https://github.com/InfluxCommunity/influxdb3-python/pull/238): Drop support for Python 3.9. Python 3.10 or newer is now required. diff --git a/examples/core/basic_ssl.py b/examples/core/basic_ssl.py index 3ef86b60..cc3d8af3 100644 --- a/examples/core/basic_ssl.py +++ b/examples/core/basic_ssl.py @@ -78,6 +78,9 @@ def main() -> None: query_with_verify_ssl_off(temp_cert_file) remove_cert(temp_cert_file) + # This is not a completed examples, please change the paths to your real certificates path. + # config_mtls() + def write_and_query_with_explicit_sys_cert(): print("\nwrite and query with typical linux system cert\n") @@ -105,11 +108,46 @@ def query_with_verify_ssl_off(cert): database=DATABASE, ssl_ca_cert=cert, verify_ssl=False) as _client: - query = "SELECT * FROM \"escooter\" ORDER BY time DESC" reader: pyarrow.Table = _client.query(query, mode="") print_results(reader.to_pylist()) +# Please change these paths with your real path to your certificates. +path_client_cert_file = "path/to/cert_file.crt" +path_client_key_file = "path/to/cert_file.key" +path_client_cert_file_pem = "path/to/cert_file.pem" +path_server_cert = "path/to/server_cert.crt" +key_file_password = "password" + + +def config_mtls(): + print("\nConfig tls/mtls for client\n") + + # Config mTLS with client send certificate to the server with separated .crt file and .key file. + # `ssl_ca_cert` is optional. + with InfluxDBClient3(token=TOKEN, + host=HOST, + database=DATABASE, + ssl_ca_cert=path_server_cert, + cert_file=path_client_cert_file, + cert_key_file=path_client_key_file, + cert_key_file_password=key_file_password, # Optional + verify_ssl=True) as _client: + result = _client.get_server_version() + print(result) + + # Config mTLS with client send a certificate to the server with both certificate and key bundled inside a .pem file. + # `ssl_ca_cert` is optional. + with InfluxDBClient3(token=TOKEN, + host=HOST, + database=DATABASE, + ssl_ca_cert=path_server_cert, + cert_file=path_client_cert_file_pem, + verify_ssl=True) as _client: + result = _client.get_server_version() + print(result) + + if __name__ == "__main__": main() diff --git a/influxdb_client_3/__init__.py b/influxdb_client_3/__init__.py index 25c7896c..96d1acea 100644 --- a/influxdb_client_3/__init__.py +++ b/influxdb_client_3/__init__.py @@ -373,6 +373,12 @@ def __init__( q_opts_builder.disable_grpc_compression(True) if kw_keys.__contains__('ssl_ca_cert'): q_opts_builder.root_certs(kwargs.get('ssl_ca_cert', None)) + if kw_keys.__contains__('cert_file'): + q_opts_builder.cert_chain(kwargs.get('cert_file', None)) + if kw_keys.__contains__('cert_key_file'): + q_opts_builder.private_key(kwargs.get('cert_key_file', None)) + if kw_keys.__contains__('cert_key_password'): + q_opts_builder.private_key_password(kwargs.get('cert_key_password', None)) if kw_keys.__contains__('verify_ssl'): q_opts_builder.tls_verify(kwargs.get('verify_ssl', True)) if kw_keys.__contains__('proxy'): diff --git a/influxdb_client_3/query/query_api.py b/influxdb_client_3/query/query_api.py index d2c4d17b..0377a430 100644 --- a/influxdb_client_3/query/query_api.py +++ b/influxdb_client_3/query/query_api.py @@ -6,6 +6,7 @@ from pyarrow.flight import FlightClient, Ticket, FlightCallOptions, FlightStreamReader from influxdb_client_3.version import USER_AGENT +from influxdb_client_3.write_client.client.util.helpers import read_certs class QueryApiOptions(object): @@ -14,7 +15,9 @@ class QueryApiOptions(object): Attributes ---------- - tls_root_certs (bytes): contents of an SSL root certificate or chain read as bytes + tls_root_certs (bytes): contents of an SSL root certificate or chain read as bytes + cert_chain (bytes): client certificate if using mutual TLS + private_key (bytes): client private key for cert_chain is using mutual TLS tls_verify (bool): whether to verify SSL certificates or not proxy (str): URL to a proxy server flight_client_options (dict): base set of flight client options passed to internal pyarrow.flight.FlightClient @@ -24,6 +27,9 @@ class QueryApiOptions(object): """ _DEFAULT_TIMEOUT = 300.0 tls_root_certs: bytes = None + cert_chain: bytes = None + private_key: bytes = None + private_key_password: str = None tls_verify: bool = None proxy: str = None flight_client_options: dict = None @@ -37,11 +43,17 @@ def __init__(self, root_certs_path: str, flight_client_options: dict, timeout: float = _DEFAULT_TIMEOUT, disable_grpc_compression: bool = False, - middleware: list = None): + middleware: list = None, + cert_chain: str = None, + private_key: str = None, + private_key_password: str = None): """ Initialize a set of QueryApiOptions :param root_certs_path: path to a certificate .pem file. + :param cert_chain: client certificate if using mutual TLS + :param private_key: client private key for cert_chain is using mutual TLS + :param private_key_password: password for private_key if using mutual TLS :param verify: whether to verify SSL certificates or not. :param proxy: URL of a proxy server, if required. :param flight_client_options: set of flight_client_options @@ -51,7 +63,14 @@ def __init__(self, root_certs_path: str, :param middleware: list of middleware functions to be applied to Flight calls. """ if root_certs_path: - self.tls_root_certs = self._read_certs(root_certs_path) + self.tls_root_certs = read_certs(root_certs_path) + if cert_chain: + self.cert_chain = read_certs(cert_chain) + if private_key: + if private_key_password: + self.private_key = read_certs(private_key, private_key_password) + else: + self.private_key = read_certs(private_key) self.tls_verify = verify self.proxy = proxy self.flight_client_options = flight_client_options @@ -59,12 +78,6 @@ def __init__(self, root_certs_path: str, self.disable_grpc_compression = disable_grpc_compression self.middleware = middleware - def _read_certs(self, path: str) -> bytes: - with open(path, "rb") as certs_file: - certs = certs_file.read() - # Normalize PEM line endings so behavior is stable across platforms. - return certs.replace(b"\r\n", b"\n").replace(b"\r", b"\n") - class QueryApiOptionsBuilder(object): """ @@ -77,12 +90,18 @@ class QueryApiOptionsBuilder(object): options = QueryApiOptionsBuilder()\ .proxy("http://internal.tunnel.proxy:8080") \ .root_certs("/home/fred/.etc/ssl/alt_certs.pem") \ + .cert_chain("/home/fred/.etc/ssl/client.crt") \ + .private_key("/home/fred/.etc/ssl/client.key") \ + .private_key_password("password") \ .tls_verify(True) \ .build() client = QueryApi(connection, token, None, None, options) """ _root_certs_path: str = None + _cert_chain: str = None + _private_key: str = None + _private_key_password: str = None _tls_verify: bool = True _proxy: str = None _flight_client_options: dict = None @@ -94,6 +113,18 @@ def root_certs(self, path: str): self._root_certs_path = path return self + def cert_chain(self, path: str): + self._cert_chain = path + return self + + def private_key(self, path: str): + self._private_key = path + return self + + def private_key_password(self, password: str): + self._private_key_password = password + return self + def tls_verify(self, verify: bool): self._tls_verify = verify return self @@ -123,6 +154,9 @@ def build(self) -> QueryApiOptions: """Build a QueryApiOptions object with previously set values""" return QueryApiOptions( root_certs_path=self._root_certs_path, + cert_chain=self._cert_chain, + private_key=self._private_key, + private_key_password=self._private_key_password, verify=self._tls_verify, proxy=self._proxy, flight_client_options=self._flight_client_options, @@ -180,8 +214,35 @@ def __init__(self, if ('generic_options' in options.flight_client_options and 'grpc.secondary_user_agent' in dict(options.flight_client_options["generic_options"]).keys()): self._flight_client_options['generic_options'].remove(default_user_agent) + if options.tls_root_certs: self._flight_client_options["tls_root_certs"] = options.tls_root_certs + if options.cert_chain: + self._flight_client_options["cert_chain"] = options.cert_chain + if options.private_key: + self._flight_client_options["private_key"] = options.private_key + elif (options.private_key is None and options.cert_chain and + not self._flight_client_options.get("private_key")): + # Only fall back to using cert_chain as private_key if the provided PEM + # content clearly includes a private key block (common in combined PEM files). + pem = options.cert_chain + if isinstance(pem, (bytes, bytearray)): + pem_text = pem.decode("utf-8", errors="ignore") + else: + pem_text = str(pem) + has_private_key = ("BEGIN PRIVATE KEY" in pem_text or + "BEGIN RSA PRIVATE KEY" in pem_text or + "BEGIN EC PRIVATE KEY" in pem_text or + "BEGIN ENCRYPTED PRIVATE KEY" in pem_text + ) + if has_private_key: + self._flight_client_options["private_key"] = options.cert_chain + else: + raise ValueError( + "mTLS misconfiguration: 'cert_chain' was provided without a " + "'private_key'. Provide 'private_key' explicitly, or pass a " + "combined PEM that includes a private key block." + ) if options.proxy: self._proxy = options.proxy if options.tls_verify is not None: diff --git a/influxdb_client_3/write_client/client/util/helpers.py b/influxdb_client_3/write_client/client/util/helpers.py index ada5be43..984608d6 100644 --- a/influxdb_client_3/write_client/client/util/helpers.py +++ b/influxdb_client_3/write_client/client/util/helpers.py @@ -1,4 +1,7 @@ """Functions to share utility across client classes.""" +from typing import Optional, Union + +from cryptography.hazmat.primitives import serialization def _is_id(value): @@ -47,3 +50,41 @@ def get_org_query_param(org, client, required_id=False): raise e return _org + + +def read_certs(path: str, password: Optional[Union[str, bytes]] = None) -> bytes: + """ + Read PEM certificate or key data from a file and produce a stable byte + representation suitable for downstream cryptographic operations. Encrypted + private key material is decrypted when credentials are provided, and PEM line + endings are normalized so the resulting bytes are consistent across operating + systems. + + :param path: Filesystem path to the PEM certificate or private key file. + :param password: Optional password used to decrypt encrypted PEM private key + material. + :return: PEM data as bytes with normalized line endings. + :raises OSError: If the file cannot be opened or read. + :raises ValueError: If encrypted key material cannot be decrypted or the PEM + data is invalid. + :raises TypeError: If the provided password is not accepted by the + cryptographic loader. + :raises cryptography.exceptions.UnsupportedAlgorithm: If the PEM data uses an + unsupported key type or encryption algorithm. + """ + with open(path, "rb") as certs_file: + data = certs_file.read() + if password is not None: + password_bytes = password.encode() if isinstance(password, str) else password + private_key_obj = serialization.load_pem_private_key( + data, + password=password_bytes, + ) + data = private_key_obj.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + + # Normalize PEM line endings so behavior is stable across platforms. + return data.replace(b"\r\n", b"\n").replace(b"\r", b"\n") diff --git a/scripts/generate-self-signed-cert.sh b/scripts/generate-self-signed-cert.sh new file mode 100644 index 00000000..50d75954 --- /dev/null +++ b/scripts/generate-self-signed-cert.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# +# The MIT License +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + + set -euo pipefail + +KEY_PASSWORD="changeit" +INFLUXDB_SERVER_NAME="influxdb" +OTHER_SERVER_NAME="other-server" +CERTS_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../tests/data/certificates" && pwd)" + +for SERVER_NAME in "$INFLUXDB_SERVER_NAME" "$OTHER_SERVER_NAME"; do + echo "### Generate server key and certificate for $SERVER_NAME..." + openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 | openssl pkcs8 -topk8 -nocrypt -out "$CERTS_DIR/${SERVER_NAME}.key" + + openssl req -new -x509 -key "$CERTS_DIR/${SERVER_NAME}.key" -out "$CERTS_DIR/${SERVER_NAME}.crt" -days 3650 \ + -subj "/CN=localhost/O=Development/C=US" \ + -addext "subjectAltName = DNS:localhost,IP:127.0.0.1" + + cat "$CERTS_DIR/${SERVER_NAME}.crt" "$CERTS_DIR/${SERVER_NAME}.key" > "$CERTS_DIR/${SERVER_NAME}.pem" + +# openssl pkcs12 -export -in "$CERTS_DIR/${SERVER_NAME}.crt" -inkey "$CERTS_DIR/${SERVER_NAME}.key" \ +# -out "$CERTS_DIR/${SERVER_NAME}.p12" -name "myalias" -password "pass:$KEY_PASSWORD" +done + +echo "### Generate client key..." +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 | openssl pkcs8 -topk8 -nocrypt -out "$CERTS_DIR/client.key" +openssl req -new -x509 -key "$CERTS_DIR/client.key" -out "$CERTS_DIR/client.crt" -days 3650 \ + -subj "/CN=test-client/O=Development/C=US" +openssl rsa -in "$CERTS_DIR/client.key" -aes256 -passout "pass:$KEY_PASSWORD" -out "$CERTS_DIR/client-encrypted.key" + +cat "$CERTS_DIR/client.crt" "$CERTS_DIR/client.key" > "$CERTS_DIR/client.pem" + +#openssl pkcs12 -export -in "$CERTS_DIR/client.crt" -inkey "$CERTS_DIR/client.key" \ +# -out "$CERTS_DIR/client.p12" -name "myalias" -password "pass:$KEY_PASSWORD" diff --git a/tests/data/certificates/client-encrypted.key b/tests/data/certificates/client-encrypted.key new file mode 100644 index 00000000..097489db --- /dev/null +++ b/tests/data/certificates/client-encrypted.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQUd5dtLfUMWQE3Ihc +4e1KDAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEPHP2ds2/jQ1AWI4 +FKeI3uEEggTQ5aG2Z0LeB4Gd6939NiSd5KYt7e3gtrn4wSWTGlLzBqRX/OrbxiYi +HnowTMleYrTxG7sLTfSqueyw5XARYIkhgcauM6M3XZU5fb5vL+8229hNO5POlxHG +7F5cW7qF2py+8izvqyMI8KNGHXgGpI3xe5/C2GYWLTzcjcIHi6iAk/riKR8Yqmg0 +8esxIS4biPEYSkPYgMIuwGqBpd0xdJpeukS64xKi3LMM46R7HJl1kTofrXI7XwiT +0bp5EPb9Q61eu0VduPkGuIeywYXTTBDqMfzH3eC6m7M0KJRzJ7FdYgONVEf9ESvd +XrShZZB11TrJctB6anilwzHCpLGy1pHd6YlwA+pDS/dJQUtEqCaMyuLICfEfjebs +CJ1sQN8fIzIHaBwBQblT4ZkqHuUWMooxTBZ0EVLaIZVXuNllN4VWSDMmNgoNQx1L +4NsS1Ghx3cyLKGTtcUp+hT7OirZH89vOQYpWHz+t2w4HN79g1KVZRPC/ucTZFlkE +Ezy/sAfU3PWlOh9FE5YIlhejWRHdsAb+DK1Wf4HrP0qI+cWFyfNoxHcSIv3OESjh +QkxyZMW1lehVNDhlNLPknIONGkFZw7cGcVCvq2HJurPYUaHbZuz+UnPgvUicNTEB +VCkpR7ehS5MyQnOp9TW1vlNWS+BSRCAK/iWfZct1s5e1zZzglLRPgeZETvwTXTJS +EdF3srUz8of6mLwKT8j+3yRF2VtPknmIfoncKSAEKd3SZ3h2owNGheBlh75YvzlU +kA8n0ktkOaNan8+4h8jG9D/6Fn9dIpzzi3oFkzC4p5PWmeZEEiQqbJ6kt2iWqbuJ +bgthH22wNZnzjzQxAP1WDDYTNwIxR2ekmvZvIZvmvs973xsrSW6n89r7QYR4DgWX +D7A5O5TMe467keKjtadOrDWUTd3fQmKwHjpW1XhYmBbgzU6dwMbS6u37xQnweREA +PMfUztsG6mKUg5a4FFruqNAu0cnFTRTW3Fw71PuFmJtgG6ELZ+bsep5E2eDQzWL1 +e6Su1fk822SGjqqzukx0fKFdVW6Mq0x7yz6Q1D7v0gPusVnnBViQW0h+onWJcdsF +UAPjBLRw627VvUc0+8PKBszz3yRn/ycIxWTryOzRtRFOPBrSsk0kBWHKh9kWWAgb +SqLAiaXPMif3vIsobJEUo6B7WE+Ktfu6JwPF7StKKPW6vFbs+Eo1RVVsuMaPkh/4 +47SzMYPceq9iGta/gnjG/NaLNqbjfdvwUAjPET6khMvTlEFAk4T5KvF8EhWyuTur +fEU4s+HmBcg85x1R4k70bVK6lfZWr3RfPpsP0+v38BjFuSU9X0f7biGDQvYJTg3r +Hnf/EaZn+YmEyhoGsRt30I9/HiCiropJbczfAPQrvsrZc0y8ik7pF1kTlJT1YrSw +67N0sLnCM53ovUlQRpbkUIIdaJcKgRGAKKC/LSqfRibiZjm+tGR4iQmnVVG+0Nhi +Fd2SRaMZ0OgVXG0K8RVfrR78GtEy2SHWDfOIsF/qpk5s1ilGYC0V3SzjbB/tV0Lf +1ZsHBJ2V8wCz/KV7O8b4DehO3X1fmhLnmhnoO8o2oJH6huoJOY1f39iB19PIIG7/ +Qs+HBtm1ieVCG9xdnfE5zGUtvCrPaDdwWP5fpfBs/vrJMtU23Y0xEv8= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/data/certificates/client.crt b/tests/data/certificates/client.crt new file mode 100644 index 00000000..352088d0 --- /dev/null +++ b/tests/data/certificates/client.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIUJNOhJC4NihFwc8h7exHlGpFH5GQwDQYJKoZIhvcNAQEL +BQAwOTEUMBIGA1UEAwwLdGVzdC1jbGllbnQxFDASBgNVBAoMC0RldmVsb3BtZW50 +MQswCQYDVQQGEwJVUzAeFw0yNjExMTAwODMyMThaFw0zNjExMDcwODMyMThaMDkx +FDASBgNVBAMMC3Rlc3QtY2xpZW50MRQwEgYDVQQKDAtEZXZlbG9wbWVudDELMAkG +A1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIi8aBkPgy +Bj/JcDqT8SIvmaAtHL4qpXUSt+Qfj9s0Q1BuHqwR8Q/w+tUYRNOuWZ2lyOAOLCPm +QadTtkoxZxtLVIPuulafYVDtRrBFz+OeiAE7VBkZY6vqprDnDsGxDLjpYZgYRonu +9IsnuKb34E3B5bq6sXGFOKJmcCF3a6k74uBwUE35b3WVGjrxLHmmAFqfiAI16ZV9 ++l0x5AVsTdQdYDHGZv8rkOCYYpncjJbE4DxVNxfKei3tDkT84LOt28NyeWp9/Z1R +nJfnKJW0XWm/x3HEBwgYyMoKDwJyVH/CDWIaafnIj9dygLOyO4qVsXvfmriH32YL +py/ZZGWxx0spAgMBAAGjUzBRMB0GA1UdDgQWBBSXDN+6XHo1/aOkfbd9o3rCg+bL +7zAfBgNVHSMEGDAWgBSXDN+6XHo1/aOkfbd9o3rCg+bL7zAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA3Lxgfdrs2glZsC2mJf7DF3pYEMUpKSeK+ +dzV8gheSXs5DWLaFaEsLkJ7KEdDxUeVdloz8u562l0vMaT0vzG0RZbZJFnWl3Rig +DCxv+iQuopTy726ssyE+vLmNNJpvphiwl+C3u79kSITiXZ52cG1AWmbOi0FeEc5d +4pvcN13E1xVaNTykgQFzwc/11/fk1t0d09RAgUirxbBb42fpZ+GbrM3p2wOPiVm6 +dK38OKvwJDwNxg+71qlKFTek46jhIVGKinNzDllYA/7GvDuGbiep7NMarYMrdKgL +we7OX2YlEXOAz+okP6hXH8xPXXAGg37lzBrFVXSQ4PMiXaEczYvi +-----END CERTIFICATE----- diff --git a/tests/data/certificates/client.key b/tests/data/certificates/client.key new file mode 100644 index 00000000..ffe736f3 --- /dev/null +++ b/tests/data/certificates/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDIi8aBkPgyBj/J +cDqT8SIvmaAtHL4qpXUSt+Qfj9s0Q1BuHqwR8Q/w+tUYRNOuWZ2lyOAOLCPmQadT +tkoxZxtLVIPuulafYVDtRrBFz+OeiAE7VBkZY6vqprDnDsGxDLjpYZgYRonu9Isn +uKb34E3B5bq6sXGFOKJmcCF3a6k74uBwUE35b3WVGjrxLHmmAFqfiAI16ZV9+l0x +5AVsTdQdYDHGZv8rkOCYYpncjJbE4DxVNxfKei3tDkT84LOt28NyeWp9/Z1RnJfn +KJW0XWm/x3HEBwgYyMoKDwJyVH/CDWIaafnIj9dygLOyO4qVsXvfmriH32YLpy/Z +ZGWxx0spAgMBAAECggEABeGjhrU7sivgvwWdki0mNYMp4WjQYuvLTSs7c3mCkjvP +JgucPEJ3lzej/Dw4gQp7xxrnB0NgIiaWw6u67Uv6zvDBlcS9OZtfBjZmgQWhxUpz +zsuemNIFfrI4ye3y3EBlQ+Lq1PtsnCdr1VRjAS4A2cjk+witLZ0Flj9/m8L6iZkG +aVJUJBuU3Rmtv7uX4xR4EqTj0hnmmYrbrTU0vOTuPhJOhHmltL6cN63mRbUbNssO +j0dsB8XoULO+35WsEbQ9dFdFQtQvcFCUHeDEIEHBwvIzgrSLUgEq56Sa1/bSZFYI +0Rw3ME4ugnd44ODHEhoKOYTHJsvdyI2WL5uNzO0CAQKBgQD2yJVu1YEwi7WffCEp +ihV9DI6/muMS8tcXdAuuUQY3JR+P1Teqd9gFxrLb8ct3pbjEhqhTjdYUdlncAdOT ++d/pzGQHYGMJJDo+xhlZUVP+HpTEuu2aGh5usF3hJK0SqJh9pNUQvQS7+D0PdeKO +0+CPJcJXAu1BwtREP8C6r71AIQKBgQDQCSE8lbdDnSqwDCyphrEsArqYyJ11kImr +flOviM9R+xwPDrttbI3WhK83LSsFj8z7W78TMf5kxUgL1WrwXm6Cdk2l91FJQVxu +HPIy7U7yEAVUNQvXJToV+zBmOOuTCfMB4EQnk8CLAiAIWfZtHyu4Tlta6pIqFtOe +/MVP1sbKCQKBgQDeO2leIsQw/E71LOoMJz4MmyddYlSENsz9YNI4CtznAfY3J1H2 +n++X3AQrTsXknAcUaxLgE0Uqrh1jov1uL0G1oAqgHetxSv9skiY3qaZSnCVqN89J +y6WGFiqkWcvFlq+jzbDVXvIJd5U8+/KmniSFpAIrKwaPBqzyPNsJAsMRIQKBgEu/ +t0lvHk7RUHTPygdkUui6iaUODz96hJZcJi6ZOxwXugzFiXt7dl3w7DfEC5E33iR8 +MKPVpVKd/vDIIesqB0UbvIQ8/G0XDofNpGbkv8UmDjquIgSRuoe8cdAW07gzfDdn +qIlcpFQ5Tt6bmz36hpnSdjMxjE0lCKMwWMwMYU+5AoGBAIfaaquxj/32/Tban2Mh +NhZN1GKhNC0M3llmohEyRGcNQg1lSyXvX537ZLM/AkljuyarTFgxoTdee8F5IrNH +vDtFGK0VcCprVKD8jBIfHn6PlJzGgoEi161al3tv7XKSRClaQM8TwvH0+aKokN7V +4KSjXEHzRH0T2YxbIqPQi017 +-----END PRIVATE KEY----- diff --git a/tests/data/certificates/client.pem b/tests/data/certificates/client.pem new file mode 100644 index 00000000..4d5f6bc0 --- /dev/null +++ b/tests/data/certificates/client.pem @@ -0,0 +1,48 @@ +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIUJNOhJC4NihFwc8h7exHlGpFH5GQwDQYJKoZIhvcNAQEL +BQAwOTEUMBIGA1UEAwwLdGVzdC1jbGllbnQxFDASBgNVBAoMC0RldmVsb3BtZW50 +MQswCQYDVQQGEwJVUzAeFw0yNjExMTAwODMyMThaFw0zNjExMDcwODMyMThaMDkx +FDASBgNVBAMMC3Rlc3QtY2xpZW50MRQwEgYDVQQKDAtEZXZlbG9wbWVudDELMAkG +A1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIi8aBkPgy +Bj/JcDqT8SIvmaAtHL4qpXUSt+Qfj9s0Q1BuHqwR8Q/w+tUYRNOuWZ2lyOAOLCPm +QadTtkoxZxtLVIPuulafYVDtRrBFz+OeiAE7VBkZY6vqprDnDsGxDLjpYZgYRonu +9IsnuKb34E3B5bq6sXGFOKJmcCF3a6k74uBwUE35b3WVGjrxLHmmAFqfiAI16ZV9 ++l0x5AVsTdQdYDHGZv8rkOCYYpncjJbE4DxVNxfKei3tDkT84LOt28NyeWp9/Z1R +nJfnKJW0XWm/x3HEBwgYyMoKDwJyVH/CDWIaafnIj9dygLOyO4qVsXvfmriH32YL +py/ZZGWxx0spAgMBAAGjUzBRMB0GA1UdDgQWBBSXDN+6XHo1/aOkfbd9o3rCg+bL +7zAfBgNVHSMEGDAWgBSXDN+6XHo1/aOkfbd9o3rCg+bL7zAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA3Lxgfdrs2glZsC2mJf7DF3pYEMUpKSeK+ +dzV8gheSXs5DWLaFaEsLkJ7KEdDxUeVdloz8u562l0vMaT0vzG0RZbZJFnWl3Rig +DCxv+iQuopTy726ssyE+vLmNNJpvphiwl+C3u79kSITiXZ52cG1AWmbOi0FeEc5d +4pvcN13E1xVaNTykgQFzwc/11/fk1t0d09RAgUirxbBb42fpZ+GbrM3p2wOPiVm6 +dK38OKvwJDwNxg+71qlKFTek46jhIVGKinNzDllYA/7GvDuGbiep7NMarYMrdKgL +we7OX2YlEXOAz+okP6hXH8xPXXAGg37lzBrFVXSQ4PMiXaEczYvi +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDIi8aBkPgyBj/J +cDqT8SIvmaAtHL4qpXUSt+Qfj9s0Q1BuHqwR8Q/w+tUYRNOuWZ2lyOAOLCPmQadT +tkoxZxtLVIPuulafYVDtRrBFz+OeiAE7VBkZY6vqprDnDsGxDLjpYZgYRonu9Isn +uKb34E3B5bq6sXGFOKJmcCF3a6k74uBwUE35b3WVGjrxLHmmAFqfiAI16ZV9+l0x +5AVsTdQdYDHGZv8rkOCYYpncjJbE4DxVNxfKei3tDkT84LOt28NyeWp9/Z1RnJfn +KJW0XWm/x3HEBwgYyMoKDwJyVH/CDWIaafnIj9dygLOyO4qVsXvfmriH32YLpy/Z +ZGWxx0spAgMBAAECggEABeGjhrU7sivgvwWdki0mNYMp4WjQYuvLTSs7c3mCkjvP +JgucPEJ3lzej/Dw4gQp7xxrnB0NgIiaWw6u67Uv6zvDBlcS9OZtfBjZmgQWhxUpz +zsuemNIFfrI4ye3y3EBlQ+Lq1PtsnCdr1VRjAS4A2cjk+witLZ0Flj9/m8L6iZkG +aVJUJBuU3Rmtv7uX4xR4EqTj0hnmmYrbrTU0vOTuPhJOhHmltL6cN63mRbUbNssO +j0dsB8XoULO+35WsEbQ9dFdFQtQvcFCUHeDEIEHBwvIzgrSLUgEq56Sa1/bSZFYI +0Rw3ME4ugnd44ODHEhoKOYTHJsvdyI2WL5uNzO0CAQKBgQD2yJVu1YEwi7WffCEp +ihV9DI6/muMS8tcXdAuuUQY3JR+P1Teqd9gFxrLb8ct3pbjEhqhTjdYUdlncAdOT ++d/pzGQHYGMJJDo+xhlZUVP+HpTEuu2aGh5usF3hJK0SqJh9pNUQvQS7+D0PdeKO +0+CPJcJXAu1BwtREP8C6r71AIQKBgQDQCSE8lbdDnSqwDCyphrEsArqYyJ11kImr +flOviM9R+xwPDrttbI3WhK83LSsFj8z7W78TMf5kxUgL1WrwXm6Cdk2l91FJQVxu +HPIy7U7yEAVUNQvXJToV+zBmOOuTCfMB4EQnk8CLAiAIWfZtHyu4Tlta6pIqFtOe +/MVP1sbKCQKBgQDeO2leIsQw/E71LOoMJz4MmyddYlSENsz9YNI4CtznAfY3J1H2 +n++X3AQrTsXknAcUaxLgE0Uqrh1jov1uL0G1oAqgHetxSv9skiY3qaZSnCVqN89J +y6WGFiqkWcvFlq+jzbDVXvIJd5U8+/KmniSFpAIrKwaPBqzyPNsJAsMRIQKBgEu/ +t0lvHk7RUHTPygdkUui6iaUODz96hJZcJi6ZOxwXugzFiXt7dl3w7DfEC5E33iR8 +MKPVpVKd/vDIIesqB0UbvIQ8/G0XDofNpGbkv8UmDjquIgSRuoe8cdAW07gzfDdn +qIlcpFQ5Tt6bmz36hpnSdjMxjE0lCKMwWMwMYU+5AoGBAIfaaquxj/32/Tban2Mh +NhZN1GKhNC0M3llmohEyRGcNQg1lSyXvX537ZLM/AkljuyarTFgxoTdee8F5IrNH +vDtFGK0VcCprVKD8jBIfHn6PlJzGgoEi161al3tv7XKSRClaQM8TwvH0+aKokN7V +4KSjXEHzRH0T2YxbIqPQi017 +-----END PRIVATE KEY----- diff --git a/tests/data/certificates/influxdb.crt b/tests/data/certificates/influxdb.crt new file mode 100644 index 00000000..fcda68ff --- /dev/null +++ b/tests/data/certificates/influxdb.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUI9ghegnLaOoQfadXkr3Y9T5K0FQwDQYJKoZIhvcNAQEL +BQAwNzESMBAGA1UEAwwJbG9jYWxob3N0MRQwEgYDVQQKDAtEZXZlbG9wbWVudDEL +MAkGA1UEBhMCVVMwHhcNMjYxMTEwMDgzMjE4WhcNMzYxMTA3MDgzMjE4WjA3MRIw +EAYDVQQDDAlsb2NhbGhvc3QxFDASBgNVBAoMC0RldmVsb3BtZW50MQswCQYDVQQG +EwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALcMSHs/GA32aio4 +hKVwfkJIMyYikAMlMDEyMo2bLK4bBe+Gf1Q7qRdwUcFGyHOwXg2Bhwrk+2CmLEZt +fuXr5my+u2jAqa+H2ttXB1pDBjjcvm9UT0Sj9kd0cOW87DE/8URa3/V/rv9vz0rI +vtN/aa5JDcRLc3bs2y1on6dS6aa2PwYqoiUEDrLjnFQ15/rQc3HjRFCiVbu03+ru +XD06rUNe9K8QIV/nIqTz+XZc4OXqoq8ZtMWV0kk5RcmxNls07JdgCxseDES0iuqS +lz2YsCEvqQrhcCiBI22FQaXTtf30bZqsQltd3ZJI7KoiR73p+c4NK8MUyBrBcl0I +/7IxKW8CAwEAAaNvMG0wHQYDVR0OBBYEFIaoMvDsVa8YIrS1TVK1S2DNdyimMB8G +A1UdIwQYMBaAFIaoMvDsVa8YIrS1TVK1S2DNdyimMA8GA1UdEwEB/wQFMAMBAf8w +GgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQAL +5oXm7y9Eyi0/b9qAQKsMflHFw5kwCugyV3kEwAp9+bB6oXDr0xPEAy4aZkp1Cg2s +RwHPVBnzs3ea96FRcQkFBBkqvKdy80uuXwZXB80iZ0bv6PyKHZ8oMSGyQc6lPqmr +OjqhWDzw7WD6ez5lgins5FBhu5YpLWYwC5xQQ9/PpeFgQ/uM92gWtEbnDGTFVx83 +mB5pUwmxgErl9ZYNleN2kNE8JEsHAbYFGZsU1/4OEaBt1Ss/NB+RlM9/J9ZvQgJ/ +f3y8AZoamQNarxeZ/oF8iacrXOosOJ7y2pkwSEgdHQMS8raBL8/x4K7jODKL5vsj +jwd6OLW5jL+hDXOC5IhL +-----END CERTIFICATE----- diff --git a/tests/data/certificates/influxdb.key b/tests/data/certificates/influxdb.key new file mode 100644 index 00000000..fe5780e3 --- /dev/null +++ b/tests/data/certificates/influxdb.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC3DEh7PxgN9moq +OISlcH5CSDMmIpADJTAxMjKNmyyuGwXvhn9UO6kXcFHBRshzsF4NgYcK5PtgpixG +bX7l6+ZsvrtowKmvh9rbVwdaQwY43L5vVE9Eo/ZHdHDlvOwxP/FEWt/1f67/b89K +yL7Tf2muSQ3ES3N27NstaJ+nUummtj8GKqIlBA6y45xUNef60HNx40RQolW7tN/q +7lw9Oq1DXvSvECFf5yKk8/l2XODl6qKvGbTFldJJOUXJsTZbNOyXYAsbHgxEtIrq +kpc9mLAhL6kK4XAogSNthUGl07X99G2arEJbXd2SSOyqIke96fnODSvDFMgawXJd +CP+yMSlvAgMBAAECggEARYxcs7suxAtWnhEYURWTW1K8YGZsak2EaITGkWgei05I +LJBhK5t5bSbnYTSoL9CYg7Ato5WJ71hj5SRwPSKqy7GSWkQtlLxdCs6fxAtIP22b +y4fwkEdt6QtUY+2qnjHJ1StkAY6QaoxMagEpBAnamPY3zVfPmKV4x2GDm5sUnJrn +h0C1Ay0pb0tuWA51dJEtWkoo5MZivioa9rsbJXQw1OPuTd41mO8Bt8t8Cn7F8TyF +u/0W5xVKIizuI8TCWSi30JuaffJJMn7FwOt0R2WA1MJjxAz+LXdhqdhjwd6ZdEHz +659mr2IayabYWatCMbMqy+4d1gm6Ar3BaTVDSefFwQKBgQDx4Mu58DK9FRjxYaPC +6CTIqH7t1UTbK5hOX4zSRYx4lr00CqyrM6fOxg7U5VbbXfx1f6cpxByrSZ4EKH8+ +pOxnxfBc03YvAA4MfWgAtWnBqR5yiHJYTP3AIDhfht7xLcwRdWLMK4YhrRZKbM2p +XGrgb3aMfI+lDoHAgi0lFNo7rwKBgQDBvDB7GJwziOw7yQXHgYs0ueIPsCuTM10l +lMGpYVbTRvivmoTJnc8v29Gq1eMT0PNyHmibi8UqztKm38VibxemFCPCpAg0kNEd +sPjWLCFWenXeNRKRVlQW1dXkEQWVxjTpPs5YMcxZ6l90EVzyl58uRQP2TpcGc6lt +YmaQa66eQQKBgHeHAOX3vvsVa+gMuiJdGaBXM7JQC+DK4E8KhMp8N/h3dTQ8eit4 +MVrLIRUv6AYrodBmrrLDCO/UR1QCty67zV9Sv37/uI1tV4PwjO6ct5BOBI/QwcDE ++zhwM6txOMayPP/7pEEE2RnXMQbCUbd5xg72xfySqWGFk7UQH0MN1PJdAoGAd7nK +KDiER6DlhOjtRmb7VWFOoC1aMIT8mn381KVQGA9zHmqah1KcONJbF31I8PUfjbXX +JdFRzuuz6Oa/P96xubQWiXH/lCSu5u94SDtLevsBHoqj5+d23gnWPjR6xB2Sk1Z6 +BKz5wdOCm+MjEtS6zche83eMA7O0z+GQys2RQ0ECgYAk04FIpxS/uITQRGb1qjmq +tv5Nn/80HwcTB4r0ohDgCnlTDFAjGFvF7SdRolDVVbVGerV3q5YwkXKuZgj5dzMJ +RRz5uPsJhRcqCPzjAX5BVRsu9F7LiMZJTxem96gLlDdo4+enzcz9vJQvp1lSxSmG +3ROZpxNS/PCTsrwpVF61jw== +-----END PRIVATE KEY----- diff --git a/tests/data/certificates/influxdb.pem b/tests/data/certificates/influxdb.pem new file mode 100644 index 00000000..bf98d856 --- /dev/null +++ b/tests/data/certificates/influxdb.pem @@ -0,0 +1,49 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUI9ghegnLaOoQfadXkr3Y9T5K0FQwDQYJKoZIhvcNAQEL +BQAwNzESMBAGA1UEAwwJbG9jYWxob3N0MRQwEgYDVQQKDAtEZXZlbG9wbWVudDEL +MAkGA1UEBhMCVVMwHhcNMjYxMTEwMDgzMjE4WhcNMzYxMTA3MDgzMjE4WjA3MRIw +EAYDVQQDDAlsb2NhbGhvc3QxFDASBgNVBAoMC0RldmVsb3BtZW50MQswCQYDVQQG +EwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALcMSHs/GA32aio4 +hKVwfkJIMyYikAMlMDEyMo2bLK4bBe+Gf1Q7qRdwUcFGyHOwXg2Bhwrk+2CmLEZt +fuXr5my+u2jAqa+H2ttXB1pDBjjcvm9UT0Sj9kd0cOW87DE/8URa3/V/rv9vz0rI +vtN/aa5JDcRLc3bs2y1on6dS6aa2PwYqoiUEDrLjnFQ15/rQc3HjRFCiVbu03+ru +XD06rUNe9K8QIV/nIqTz+XZc4OXqoq8ZtMWV0kk5RcmxNls07JdgCxseDES0iuqS +lz2YsCEvqQrhcCiBI22FQaXTtf30bZqsQltd3ZJI7KoiR73p+c4NK8MUyBrBcl0I +/7IxKW8CAwEAAaNvMG0wHQYDVR0OBBYEFIaoMvDsVa8YIrS1TVK1S2DNdyimMB8G +A1UdIwQYMBaAFIaoMvDsVa8YIrS1TVK1S2DNdyimMA8GA1UdEwEB/wQFMAMBAf8w +GgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQAL +5oXm7y9Eyi0/b9qAQKsMflHFw5kwCugyV3kEwAp9+bB6oXDr0xPEAy4aZkp1Cg2s +RwHPVBnzs3ea96FRcQkFBBkqvKdy80uuXwZXB80iZ0bv6PyKHZ8oMSGyQc6lPqmr +OjqhWDzw7WD6ez5lgins5FBhu5YpLWYwC5xQQ9/PpeFgQ/uM92gWtEbnDGTFVx83 +mB5pUwmxgErl9ZYNleN2kNE8JEsHAbYFGZsU1/4OEaBt1Ss/NB+RlM9/J9ZvQgJ/ +f3y8AZoamQNarxeZ/oF8iacrXOosOJ7y2pkwSEgdHQMS8raBL8/x4K7jODKL5vsj +jwd6OLW5jL+hDXOC5IhL +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC3DEh7PxgN9moq +OISlcH5CSDMmIpADJTAxMjKNmyyuGwXvhn9UO6kXcFHBRshzsF4NgYcK5PtgpixG +bX7l6+ZsvrtowKmvh9rbVwdaQwY43L5vVE9Eo/ZHdHDlvOwxP/FEWt/1f67/b89K +yL7Tf2muSQ3ES3N27NstaJ+nUummtj8GKqIlBA6y45xUNef60HNx40RQolW7tN/q +7lw9Oq1DXvSvECFf5yKk8/l2XODl6qKvGbTFldJJOUXJsTZbNOyXYAsbHgxEtIrq +kpc9mLAhL6kK4XAogSNthUGl07X99G2arEJbXd2SSOyqIke96fnODSvDFMgawXJd +CP+yMSlvAgMBAAECggEARYxcs7suxAtWnhEYURWTW1K8YGZsak2EaITGkWgei05I +LJBhK5t5bSbnYTSoL9CYg7Ato5WJ71hj5SRwPSKqy7GSWkQtlLxdCs6fxAtIP22b +y4fwkEdt6QtUY+2qnjHJ1StkAY6QaoxMagEpBAnamPY3zVfPmKV4x2GDm5sUnJrn +h0C1Ay0pb0tuWA51dJEtWkoo5MZivioa9rsbJXQw1OPuTd41mO8Bt8t8Cn7F8TyF +u/0W5xVKIizuI8TCWSi30JuaffJJMn7FwOt0R2WA1MJjxAz+LXdhqdhjwd6ZdEHz +659mr2IayabYWatCMbMqy+4d1gm6Ar3BaTVDSefFwQKBgQDx4Mu58DK9FRjxYaPC +6CTIqH7t1UTbK5hOX4zSRYx4lr00CqyrM6fOxg7U5VbbXfx1f6cpxByrSZ4EKH8+ +pOxnxfBc03YvAA4MfWgAtWnBqR5yiHJYTP3AIDhfht7xLcwRdWLMK4YhrRZKbM2p +XGrgb3aMfI+lDoHAgi0lFNo7rwKBgQDBvDB7GJwziOw7yQXHgYs0ueIPsCuTM10l +lMGpYVbTRvivmoTJnc8v29Gq1eMT0PNyHmibi8UqztKm38VibxemFCPCpAg0kNEd +sPjWLCFWenXeNRKRVlQW1dXkEQWVxjTpPs5YMcxZ6l90EVzyl58uRQP2TpcGc6lt +YmaQa66eQQKBgHeHAOX3vvsVa+gMuiJdGaBXM7JQC+DK4E8KhMp8N/h3dTQ8eit4 +MVrLIRUv6AYrodBmrrLDCO/UR1QCty67zV9Sv37/uI1tV4PwjO6ct5BOBI/QwcDE ++zhwM6txOMayPP/7pEEE2RnXMQbCUbd5xg72xfySqWGFk7UQH0MN1PJdAoGAd7nK +KDiER6DlhOjtRmb7VWFOoC1aMIT8mn381KVQGA9zHmqah1KcONJbF31I8PUfjbXX +JdFRzuuz6Oa/P96xubQWiXH/lCSu5u94SDtLevsBHoqj5+d23gnWPjR6xB2Sk1Z6 +BKz5wdOCm+MjEtS6zche83eMA7O0z+GQys2RQ0ECgYAk04FIpxS/uITQRGb1qjmq +tv5Nn/80HwcTB4r0ohDgCnlTDFAjGFvF7SdRolDVVbVGerV3q5YwkXKuZgj5dzMJ +RRz5uPsJhRcqCPzjAX5BVRsu9F7LiMZJTxem96gLlDdo4+enzcz9vJQvp1lSxSmG +3ROZpxNS/PCTsrwpVF61jw== +-----END PRIVATE KEY----- diff --git a/tests/data/certificates/other-server.crt b/tests/data/certificates/other-server.crt new file mode 100644 index 00000000..960d156a --- /dev/null +++ b/tests/data/certificates/other-server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUTPyrj+g53LEMapukn/vmJH6dwJswDQYJKoZIhvcNAQEL +BQAwNzESMBAGA1UEAwwJbG9jYWxob3N0MRQwEgYDVQQKDAtEZXZlbG9wbWVudDEL +MAkGA1UEBhMCVVMwHhcNMjYxMTEwMDgzMjE4WhcNMzYxMTA3MDgzMjE4WjA3MRIw +EAYDVQQDDAlsb2NhbGhvc3QxFDASBgNVBAoMC0RldmVsb3BtZW50MQswCQYDVQQG +EwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdzrj/1zx5zpL12 +YjP2eQlFRg9GpYzj/bemajqcmx4dLhXQOhsUFh6sQRvxugAJJhQ0XrO3fkyyCEyT +M5kF1U9y6Dr1xzZ+7X9Hd4myu5Vin9T4IKJfndMsfvHNVcPq4pRgV9O7QN9JuPs0 +d5hkg9a2O2yMSkpgEOX3GZkvRGjx8LMIE3hRBv/8BeY1zJ0bj9bny7pIdvXeay0x +Na8nY7c5o6EQPEKUVF0iaa/9cSocqmz9CCWXXIhIW7gkBRSQ5Ha7oT7W9eAQScsL +hyysYjZ5wrADRbGVczl8M+MJq3jtcDU60WLvA/6x8kzOxvBAKHUVUWuyBKYzZlHI +7xNos3MCAwEAAaNvMG0wHQYDVR0OBBYEFHRU0oYhleEJeYwX7esm8cm1SoHMMB8G +A1UdIwQYMBaAFHRU0oYhleEJeYwX7esm8cm1SoHMMA8GA1UdEwEB/wQFMAMBAf8w +GgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBv +9Ax2boJMrql3VBJUYXgsWG49J8lGIs7o6VGsjahbyOH5WyWj6QztuE89N/cxOMv8 +zphiEEEI8BDt8lLt/1yqDjNTVKyKlz1N8ax8d25W+tU7kotgURCFyro2NWXzWRdr +bap0XIwAOiQDVEIvSesPbGJrHg/TXsz4BI4iTfW6RH6It1laMgbwK6jVliuIcavu +ajvxT6+QvokGr5+NPppfogDkI52msxU0Xud5BrwKypJZv1ld6408w/uCkrk3Uifr +NMEA9NW3i5yyHzXZ4IIFPozMvKbsY3V/qxV6eFqzVyMnsO16TE3VtrYjop+pcBSP +DRROb0WEy3e5KEnbhT66 +-----END CERTIFICATE----- diff --git a/tests/data/certificates/other-server.key b/tests/data/certificates/other-server.key new file mode 100644 index 00000000..7688c8c7 --- /dev/null +++ b/tests/data/certificates/other-server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXc64/9c8ec6S9 +dmIz9nkJRUYPRqWM4/23pmo6nJseHS4V0DobFBYerEEb8boACSYUNF6zt35MsghM +kzOZBdVPcug69cc2fu1/R3eJsruVYp/U+CCiX53TLH7xzVXD6uKUYFfTu0DfSbj7 +NHeYZIPWtjtsjEpKYBDl9xmZL0Ro8fCzCBN4UQb//AXmNcydG4/W58u6SHb13mst +MTWvJ2O3OaOhEDxClFRdImmv/XEqHKps/Qgll1yISFu4JAUUkOR2u6E+1vXgEEnL +C4csrGI2ecKwA0WxlXM5fDPjCat47XA1OtFi7wP+sfJMzsbwQCh1FVFrsgSmM2ZR +yO8TaLNzAgMBAAECggEABGNyuiG6RayeKmtCvzS2HyGlvQ2UAHgewvKMg3REPmQk +ELe+50f2uB7PqCqh9BxPwSgfUl/PxhMmkGl3hkiX/LxcpFC43Ahjg1ujyBaovtRj +wz0JXQsePJ8+cm07vfiRZjFAej8jyxSMyqKjkIPhGhifsi6CfJM6Ch/V2w12kv/c +Q1uGQNo8wef99AF6aEWdnYQKSC2B/X7xvomWYcwq0gNllx1r8I7YpiXLOpDZksax +0VaiRw1w37FeiNBoyl9EZJ9QzZPQ77HpVcH+BTZ8JL9TETbn7XTPKwCPAYhJf4Rr +LiWL6dL2yepay1U0j8CX105kUxbz3bilc7B6qxBlcQKBgQDUCO2FSYpr3n1QYspL +oemKOY7LjXA0flu/xH/56BUI9udA+Xso6znpKTHJ9bH5dkCXfX0zjp4BXlmOdUCP +gBt2podq/F8Z0o2o+XHGfaPVHo7jS/NwPzcmjfa5GPYzN34Onm6VaG+OapXf6ply +wGRu44Swj7/0S0mK41uLemzLyQKBgQC22u7LmrDR1+x2J2xBykHPRPXPNee9aTrI +3e4tb6RnIRWj96gTepEfcRU9rIimzQhYfe4nrHJJ4+9Pdfz9w+c9BxwoqJGsZY9u +T3eKmSBdNJGFIb1ycvFs6hvsjgwNHBrDP50to5A6R4T9zNdhzukgK8DwdzTvPIM+ +vUyVIjWrWwKBgC+vjlOClYnq8L3qadqWoGJAzZeDyK9Vl6bKN4npowlwFdQsB4HN +XSnN5O+pvQwAh0hKpaS5t1lRccyzHADp68NEcoIiHfU9wDQ3XVopmXXtuG7v/uDI +psuZ89nu/jfIjCYXhYE+ipA58lj4dugy0qOJsabPvXjgkbqVpVn5/4vZAoGBAJ+E +zS/YKotWOFY3PC0E2naMbXiwSROnen4DhiYjMCQ765oYkoszsOjiCSq9mpURZ0dz +zNDVkvq9G2eRwfxL7C4ERt99d2ATVZZhQyMpRN+q3YxsC86NUeq3LVcn8IF7VCmj +FGbdOxRskiuriPYtwJitMutUw1w1yTVxhR7RMkrjAoGAG+p/m/lZqV/HyifiSjbX +SlSHPnQM8ZSqk2DUznAnybARcF/UynJXFF/5OpUr5bw30FaXcGval1ixW/w1a6k5 +Tlfjg29OdO8uWib/O9feHZIddT++EfuGsPTMwuNuwauDZBJbpvQQPHjv6MMFCkf7 +aBSOavov3h+oUwG2iEFk0mk= +-----END PRIVATE KEY----- diff --git a/tests/data/certificates/other-server.pem b/tests/data/certificates/other-server.pem new file mode 100644 index 00000000..6738352f --- /dev/null +++ b/tests/data/certificates/other-server.pem @@ -0,0 +1,49 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUTPyrj+g53LEMapukn/vmJH6dwJswDQYJKoZIhvcNAQEL +BQAwNzESMBAGA1UEAwwJbG9jYWxob3N0MRQwEgYDVQQKDAtEZXZlbG9wbWVudDEL +MAkGA1UEBhMCVVMwHhcNMjYxMTEwMDgzMjE4WhcNMzYxMTA3MDgzMjE4WjA3MRIw +EAYDVQQDDAlsb2NhbGhvc3QxFDASBgNVBAoMC0RldmVsb3BtZW50MQswCQYDVQQG +EwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdzrj/1zx5zpL12 +YjP2eQlFRg9GpYzj/bemajqcmx4dLhXQOhsUFh6sQRvxugAJJhQ0XrO3fkyyCEyT +M5kF1U9y6Dr1xzZ+7X9Hd4myu5Vin9T4IKJfndMsfvHNVcPq4pRgV9O7QN9JuPs0 +d5hkg9a2O2yMSkpgEOX3GZkvRGjx8LMIE3hRBv/8BeY1zJ0bj9bny7pIdvXeay0x +Na8nY7c5o6EQPEKUVF0iaa/9cSocqmz9CCWXXIhIW7gkBRSQ5Ha7oT7W9eAQScsL +hyysYjZ5wrADRbGVczl8M+MJq3jtcDU60WLvA/6x8kzOxvBAKHUVUWuyBKYzZlHI +7xNos3MCAwEAAaNvMG0wHQYDVR0OBBYEFHRU0oYhleEJeYwX7esm8cm1SoHMMB8G +A1UdIwQYMBaAFHRU0oYhleEJeYwX7esm8cm1SoHMMA8GA1UdEwEB/wQFMAMBAf8w +GgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBv +9Ax2boJMrql3VBJUYXgsWG49J8lGIs7o6VGsjahbyOH5WyWj6QztuE89N/cxOMv8 +zphiEEEI8BDt8lLt/1yqDjNTVKyKlz1N8ax8d25W+tU7kotgURCFyro2NWXzWRdr +bap0XIwAOiQDVEIvSesPbGJrHg/TXsz4BI4iTfW6RH6It1laMgbwK6jVliuIcavu +ajvxT6+QvokGr5+NPppfogDkI52msxU0Xud5BrwKypJZv1ld6408w/uCkrk3Uifr +NMEA9NW3i5yyHzXZ4IIFPozMvKbsY3V/qxV6eFqzVyMnsO16TE3VtrYjop+pcBSP +DRROb0WEy3e5KEnbhT66 +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXc64/9c8ec6S9 +dmIz9nkJRUYPRqWM4/23pmo6nJseHS4V0DobFBYerEEb8boACSYUNF6zt35MsghM +kzOZBdVPcug69cc2fu1/R3eJsruVYp/U+CCiX53TLH7xzVXD6uKUYFfTu0DfSbj7 +NHeYZIPWtjtsjEpKYBDl9xmZL0Ro8fCzCBN4UQb//AXmNcydG4/W58u6SHb13mst +MTWvJ2O3OaOhEDxClFRdImmv/XEqHKps/Qgll1yISFu4JAUUkOR2u6E+1vXgEEnL +C4csrGI2ecKwA0WxlXM5fDPjCat47XA1OtFi7wP+sfJMzsbwQCh1FVFrsgSmM2ZR +yO8TaLNzAgMBAAECggEABGNyuiG6RayeKmtCvzS2HyGlvQ2UAHgewvKMg3REPmQk +ELe+50f2uB7PqCqh9BxPwSgfUl/PxhMmkGl3hkiX/LxcpFC43Ahjg1ujyBaovtRj +wz0JXQsePJ8+cm07vfiRZjFAej8jyxSMyqKjkIPhGhifsi6CfJM6Ch/V2w12kv/c +Q1uGQNo8wef99AF6aEWdnYQKSC2B/X7xvomWYcwq0gNllx1r8I7YpiXLOpDZksax +0VaiRw1w37FeiNBoyl9EZJ9QzZPQ77HpVcH+BTZ8JL9TETbn7XTPKwCPAYhJf4Rr +LiWL6dL2yepay1U0j8CX105kUxbz3bilc7B6qxBlcQKBgQDUCO2FSYpr3n1QYspL +oemKOY7LjXA0flu/xH/56BUI9udA+Xso6znpKTHJ9bH5dkCXfX0zjp4BXlmOdUCP +gBt2podq/F8Z0o2o+XHGfaPVHo7jS/NwPzcmjfa5GPYzN34Onm6VaG+OapXf6ply +wGRu44Swj7/0S0mK41uLemzLyQKBgQC22u7LmrDR1+x2J2xBykHPRPXPNee9aTrI +3e4tb6RnIRWj96gTepEfcRU9rIimzQhYfe4nrHJJ4+9Pdfz9w+c9BxwoqJGsZY9u +T3eKmSBdNJGFIb1ycvFs6hvsjgwNHBrDP50to5A6R4T9zNdhzukgK8DwdzTvPIM+ +vUyVIjWrWwKBgC+vjlOClYnq8L3qadqWoGJAzZeDyK9Vl6bKN4npowlwFdQsB4HN +XSnN5O+pvQwAh0hKpaS5t1lRccyzHADp68NEcoIiHfU9wDQ3XVopmXXtuG7v/uDI +psuZ89nu/jfIjCYXhYE+ipA58lj4dugy0qOJsabPvXjgkbqVpVn5/4vZAoGBAJ+E +zS/YKotWOFY3PC0E2naMbXiwSROnen4DhiYjMCQ765oYkoszsOjiCSq9mpURZ0dz +zNDVkvq9G2eRwfxL7C4ERt99d2ATVZZhQyMpRN+q3YxsC86NUeq3LVcn8IF7VCmj +FGbdOxRskiuriPYtwJitMutUw1w1yTVxhR7RMkrjAoGAG+p/m/lZqV/HyifiSjbX +SlSHPnQM8ZSqk2DUznAnybARcF/UynJXFF/5OpUr5bw30FaXcGval1ixW/w1a6k5 +Tlfjg29OdO8uWib/O9feHZIddT++EfuGsPTMwuNuwauDZBJbpvQQPHjv6MMFCkf7 +aBSOavov3h+oUwG2iEFk0mk= +-----END PRIVATE KEY----- diff --git a/tests/test_influxdb_client_3.py b/tests/test_influxdb_client_3.py index a8660640..6fd49cf8 100644 --- a/tests/test_influxdb_client_3.py +++ b/tests/test_influxdb_client_3.py @@ -1,6 +1,10 @@ +import os import re +import ssl import unittest from collections import defaultdict +from dataclasses import dataclass +from typing import Optional from unittest.mock import patch import pandas as pd @@ -603,6 +607,189 @@ def test_get_version_fail(self): ).get_server_version() +class TestTlsInfluxDBClient3(unittest.TestCase): + dir_path = os.path.dirname(os.path.realpath(__file__)) + certs_dir = os.path.join(dir_path, "data", "certificates") + + server_cert = os.path.join(certs_dir, "influxdb.crt") + server_key = os.path.join(certs_dir, "influxdb.key") + server_cert_pem = os.path.join(certs_dir, "influxdb.pem") + other_cert = os.path.join(certs_dir, "other-server.crt") + other_cert_key = os.path.join(certs_dir, "other-server.key") + other_cert_pem = os.path.join(certs_dir, "other-server.pem") + client_cert = os.path.join(certs_dir, "client.crt") + client_cert_key = os.path.join(certs_dir, "client.key") + client_cert_key_encrypted = os.path.join(certs_dir, "client-encrypted.key") + client_cert_pem = os.path.join(certs_dir, "client.pem") + + @dataclass + class TlsTestCase: + __test__ = False + name: str + server_cert: str + client_cert: Optional[str] = None + client_key: Optional[str] = None + client_key_password: Optional[str] = None + expect_success: bool = True + + def __str__(self): + return self.name + + TEST_CASES = [ + TlsTestCase( + name="Tls success with correct server certificate", + server_cert=server_cert, + ), + TlsTestCase( + name="Tls success with correct server certificate pem", + server_cert=server_cert_pem, + ), + TlsTestCase( + name="Tls fail with incorrect server certificate", + server_cert=other_cert, + expect_success=False, + ), + TlsTestCase( + name="mtls success with correct client certificate and client key files", + server_cert=server_cert, + client_cert=client_cert, + client_key=client_cert_key, + ), + TlsTestCase( + name="mtls success with correct client certificate and encrypted client key files", + server_cert=server_cert, + client_cert=client_cert, + client_key=client_cert_key_encrypted, + client_key_password="changeit", + ), + TlsTestCase( + name="mtls fail with incorrect password for encrypted client key file", + server_cert=server_cert, + client_cert=client_cert, + client_key=client_cert_key_encrypted, + client_key_password="wrongpassword", + expect_success=False, + ), + TlsTestCase( + name="mtls success with correct client certificate and client key bundled in pem file", + server_cert=server_cert, + client_cert=client_cert_pem, + ), + TlsTestCase( + name="mtls fail with incorrect client certificate and client key bundled in a pem file", + server_cert=server_cert, + client_cert=other_cert_pem, + expect_success=False, + ), + TlsTestCase( + name="mtls fail with incorrect client certificate and client key files", + server_cert=server_cert, + client_cert=other_cert, + client_key=other_cert_key, + expect_success=False, + ), + TlsTestCase( + name="mtls fail with incorrect server certificate file", + server_cert=other_cert, + client_cert=client_cert, + client_key=client_cert_key, + expect_success=False, + ), + ] + + def create_write_secure_server(self, is_mtls): + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ssl_context.load_cert_chain(certfile=self.server_cert, keyfile=self.server_key) + if is_mtls: + ssl_context.verify_mode = ssl.CERT_REQUIRED + ssl_context.load_verify_locations(cafile=self.client_cert) + server = HTTPServer(ssl_context=ssl_context) + server.expect_request("/api/v2/write").respond_with_json( + response_json={"data": "success"}, + status=204 + ) + return server + + def create_query_secure_server(self, is_mtls): + verify_client = False + tls_ca_cert = None + if is_mtls: + verify_client = True + tls_ca_cert = self.client_cert + return ConstantFlightServer( + root_certificates=tls_ca_cert, + server_cert=self.server_cert, + server_cert_key=self.server_key, + verify_client=verify_client, + ) + + def _create_client(self, host: str, test_case: TlsTestCase) -> InfluxDBClient3: + return InfluxDBClient3( + host=host, + org="my_org", + database="my_db", + token="my_token", + auth_scheme="my_scheme", + ssl_ca_cert=test_case.server_cert, + cert_file=test_case.client_cert, + cert_key_file=test_case.client_key, + cert_key_password=test_case.client_key_password, + ) + + def test_write_with_secure_server(self): + for test_case in self.TEST_CASES: + with self.subTest(test_case=test_case.name): + server = None + client = None + try: + server = self.create_write_secure_server(test_case.client_cert is not None) + server.start() + if not test_case.expect_success: + with self.assertRaises(Exception) as err: + client = self._create_client(f"https://{server.host}:{server.port}", test_case) + client.write(record="data,foo=bar val=3.14") + if isinstance(err.exception, ValueError): + self.assertIn("Incorrect password, could not decrypt key", str(err.exception)) + else: + error_msg = str(err.exception).lower() + self.assertTrue("ssl" in error_msg or "grpc_status:14" in error_msg) + else: + client = self._create_client(f"https://{server.host}:{server.port}", test_case) + client.write(record="data,foo=bar val=3.14") + finally: + if server is not None: + server.stop() + if client is not None: + client.close() + + def test_query_with_secure_server(self): + for test_case in self.TEST_CASES: + with self.subTest(test_case=test_case.name): + server = None + client = None + try: + server = self.create_query_secure_server(test_case.client_cert is not None) + if not test_case.expect_success: + with self.assertRaises(Exception) as err: + client = self._create_client(f"https://localhost:{server.port}", test_case) + client.query("SELECT * FROM test") + exc = err.exception + if isinstance(exc, ValueError): + self.assertIn("Incorrect password, could not decrypt key", str(exc)) + else: + error_msg = str(exc).lower() + self.assertTrue("ssl" in error_msg or "server never sent a data message" in error_msg) + else: + client = self._create_client(f"https://localhost:{server.port}", test_case) + result = client.query("SELECT * FROM test") + self.assertIsNotNone(result) + finally: + if server is not None: + server.shutdown() + if client is not None: + client.close() + + class TestWriteDataFrame(unittest.TestCase): """Tests for the write_dataframe() method.""" diff --git a/tests/util/mocks.py b/tests/util/mocks.py index 088fcb87..8f4d4604 100644 --- a/tests/util/mocks.py +++ b/tests/util/mocks.py @@ -18,6 +18,8 @@ ServerAuthHandler ) +from influxdb_client_3.write_client.client.util.helpers import read_certs + class NoopAuthHandler(ServerAuthHandler): """A no-op auth handler - as seen in pyarrow tests""" @@ -85,8 +87,41 @@ def to_list(self): class ConstantFlightServer(FlightServerBase): - def __init__(self, location=None, options=None, **kwargs): - super().__init__(location, **kwargs) + def __init__(self, + location=None, + options=None, + server_cert=None, + server_cert_key=None, + verify_client=None, + root_certificates=None, + **kwargs): + server_cert_bytes = None + if server_cert is not None: + server_cert_bytes = read_certs(server_cert) + + server_cert_key_bytes = None + if server_cert_key is not None: + server_cert_key_bytes = read_certs(server_cert_key) + + tls_certificates = None + if (server_cert_bytes is None) != (server_cert_key_bytes is None): + raise ValueError( + "Both server_cert and server_cert_key must be provided together to enable TLS." + ) + if server_cert_bytes is not None and server_cert_key_bytes is not None: + tls_certificates = [(server_cert_bytes, server_cert_key_bytes)] + + root_cert_bytes = None + if root_certificates is not None: + root_cert_bytes = read_certs(root_certificates) + + super().__init__( + location=location, + verify_client=verify_client, + tls_certificates=tls_certificates, + root_certificates=root_cert_bytes, + **kwargs + ) self.cd = ConstantData() self.options = options