From 38b85750c952662e5cfe73e85c56b44569ef22dc Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Fri, 23 Dec 2022 21:33:23 +0300 Subject: [PATCH] api: support SSL private key file decryption Support `ssl_password` and `ssl_password_file` options in Connection, MeshConnection and ConnectionPool to decrypt private SSL key file. Tarantool EE supports SSL passwords and password files only in current master since commit e1f47dd4 (after 2.11.0-entrypoint) [1]. Same as in Tarantool, we try `ssl_password`, then each line in `ssl_password_file` and then try to use key without decryption. If all of the above fail, we re-raise errors. 1. https://github.com/tarantool/tarantool-ee/issues/22 Closes #224. --- .github/workflows/testing.yml | 20 +- .gitignore | 12 + CHANGELOG.md | 3 +- tarantool/connection.py | 85 ++++- tarantool/connection_pool.py | 24 +- tarantool/const.py | 4 + tarantool/mesh_connection.py | 22 +- test/data/ca.crt | 32 +- test/data/generate.sh | 14 + test/data/invalidhost.crt | 33 +- test/data/invalidpasswords | 1 + test/data/localhost.crt | 33 +- test/data/localhost.enc.key | 30 ++ test/data/localhost.key | 52 +-- test/data/passwords | 2 + test/suites/lib/skip.py | 75 +++- test/suites/lib/tarantool_server.py | 10 + test/suites/test_ssl.py | 540 +++++++++++++++++++++------- 18 files changed, 735 insertions(+), 257 deletions(-) create mode 100644 test/data/invalidpasswords create mode 100644 test/data/localhost.enc.key create mode 100644 test/data/passwords diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 54f8daec..4214e9ee 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -113,9 +113,14 @@ jobs: fail-fast: false matrix: tarantool: - - '1.10.11-0-gf0b0e7ecf-r470' - - '2.8.3-21-g7d35cd2be-r470' - - '2.10.0-1-gfa775b383-r486-linux-x86_64' + - bundle: 'bundle-1.10.11-0-gf0b0e7ecf-r470' + path: '' + - bundle: 'bundle-2.8.3-21-g7d35cd2be-r470' + path: '' + - bundle: 'bundle-2.10.0-1-gfa775b383-r486-linux-x86_64' + path: '' + - bundle: 'sdk-gc64-2.11.0-entrypoint-107-ga18449d54-r524.linux.x86_64' + path: 'dev/linux/x86_64/master/' python: ['3.6', '3.7', '3.8', '3.9', '3.10'] steps: @@ -131,10 +136,10 @@ jobs: if: github.event_name != 'pull_request_target' uses: actions/checkout@v2 - - name: Install tarantool ${{ matrix.tarantool }} + - name: Install Tarantool EE SDK run: | - ARCHIVE_NAME=tarantool-enterprise-bundle-${{ matrix.tarantool }}.tar.gz - curl -O -L https://${{ secrets.SDK_DOWNLOAD_TOKEN }}@download.tarantool.io/enterprise/${ARCHIVE_NAME} + ARCHIVE_NAME=tarantool-enterprise-${{ matrix.tarantool.bundle }}.tar.gz + curl -O -L https://${{ secrets.SDK_DOWNLOAD_TOKEN }}@download.tarantool.io/enterprise/${{ matrix.tarantool.path }}${ARCHIVE_NAME} tar -xzf ${ARCHIVE_NAME} rm -f ${ARCHIVE_NAME} @@ -163,7 +168,8 @@ jobs: source tarantool-enterprise/env.sh make test env: - TEST_TNT_SSL: ${{ matrix.tarantool == '2.10.0-1-gfa775b383-r486-linux-x86_64' }} + TEST_TNT_SSL: ${{ matrix.tarantool.bundle == 'bundle-2.10.0-1-gfa775b383-r486-linux-x86_64' || + matrix.tarantool.bundle == 'sdk-gc64-2.11.0-entrypoint-107-ga18449d54-r524.linux.x86_64'}} run_tests_pip_branch_install_linux: # We want to run on external PRs, but not on our own internal diff --git a/.gitignore b/.gitignore index e8e7e210..9d3a8412 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,15 @@ debian/files debian/*.substvars deb_dist + +test/data/*.crt +!test/data/ca.crt +!test/data/invalidhost.crt +!test/data/localhost.crt +test/data/*.csr +test/data/*.ext +test/data/*.key +!test/data/localhost.key +!test/data/localhost.enc.key +test/data/*.pem +test/data/*.srl diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e15a1e6..ca32ec6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Support custom packer and unpacker factories (#191). - - Support [crud module](https://github.com/tarantool/crud) native API (#205). +- Support `ssl_password` and `ssl_password_file` options + to decrypt private SSL key file (#224). ### Changed diff --git a/tarantool/connection.py b/tarantool/connection.py index ba23ea74..4d82369e 100644 --- a/tarantool/connection.py +++ b/tarantool/connection.py @@ -56,6 +56,8 @@ DEFAULT_SSL_CERT_FILE, DEFAULT_SSL_CA_FILE, DEFAULT_SSL_CIPHERS, + DEFAULT_SSL_PASSWORD, + DEFAULT_SSL_PASSWORD_FILE, REQUEST_TYPE_OK, REQUEST_TYPE_ERROR, IPROTO_GREETING_SIZE, @@ -569,6 +571,8 @@ def __init__(self, host, port, ssl_cert_file=DEFAULT_SSL_CERT_FILE, ssl_ca_file=DEFAULT_SSL_CA_FILE, ssl_ciphers=DEFAULT_SSL_CIPHERS, + ssl_password=DEFAULT_SSL_PASSWORD, + ssl_password_file=DEFAULT_SSL_PASSWORD_FILE, packer_factory=default_packer_factory, unpacker_factory=default_unpacker_factory): """ @@ -693,6 +697,15 @@ def __init__(self, host, port, suites the connection can use. :type ssl_ciphers: :obj:`str` or :obj:`None`, optional + :param ssl_password: Password for decrypting + :paramref:`~tarantool.Connection.ssl_key_file`. + :type ssl_password: :obj:`str` or :obj:`None`, optional + + :param ssl_password_file: File with password for decrypting + :paramref:`~tarantool.Connection.ssl_key_file`. Connection + tries every line from the file as a password. + :type ssl_password_file: :obj:`str` or :obj:`None`, optional + :param packer_factory: Request MessagePack packer factory. Supersedes :paramref:`~tarantool.Connection.encoding`. See :func:`~tarantool.request.packer_factory` for example of @@ -754,6 +767,8 @@ def __init__(self, host, port, self.ssl_cert_file = ssl_cert_file self.ssl_ca_file = ssl_ca_file self.ssl_ciphers = ssl_ciphers + self.ssl_password = ssl_password + self.ssl_password_file = ssl_password_file self._protocol_version = None self._features = { IPROTO_FEATURE_STREAMS: False, @@ -884,21 +899,7 @@ def wrap_socket_ssl(self): context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) if self.ssl_cert_file: - # If the password argument is not specified and a password is - # required, OpenSSL’s built-in password prompting mechanism - # will be used to interactively prompt the user for a password. - # - # We should disable this behaviour, because a python - # application that uses the connector unlikely assumes - # interaction with a human + a Tarantool implementation does - # not support this at least for now. - def password_raise_error(): - raise SslError("Password for decrypting the private " + - "key is unsupported") - context.load_cert_chain(certfile=self.ssl_cert_file, - keyfile=self.ssl_key_file, - password=password_raise_error) - + self._ssl_load_cert_chain(context) if self.ssl_ca_file: context.load_verify_locations(cafile=self.ssl_ca_file) context.verify_mode = ssl.CERT_REQUIRED @@ -915,6 +916,60 @@ def password_raise_error(): except Exception as e: raise SslError(e) + def _ssl_load_cert_chain(self, context): + """ + Decrypt and load SSL certificate and private key files. + Mimic Tarantool EE approach here: see `SSL commit`_. + + :param context: SSL context. + :type context: :obj:`ssl.SSLContext` + + :raise: :exc:`~tarantool.error.SslError` + + :meta private: + + .. _SSL commit: https://github.com/tarantool/tarantool-ee/commit/e1f47dd4adbc6657159c611298aad225883a536b + """ + + exc_list = [] + + if self.ssl_password is not None: + try: + context.load_cert_chain(certfile=self.ssl_cert_file, + keyfile=self.ssl_key_file, + password=self.ssl_password) + return + except Exception as e: + exc_list.append(e) + + + if self.ssl_password_file is not None: + with open(self.ssl_password_file) as file: + for line in file: + try: + context.load_cert_chain(certfile=self.ssl_cert_file, + keyfile=self.ssl_key_file, + password=line.rstrip()) + return + except Exception as e: + exc_list.append(e) + + + try: + def password_raise_error(): + raise SslError("Password prompt for decrypting the private " + + "key is unsupported, use ssl_password or " + + "ssl_password_file") + context.load_cert_chain(certfile=self.ssl_cert_file, + keyfile=self.ssl_key_file, + password=password_raise_error) + + return + except Exception as e: + exc_list.append(e) + + raise SslError(exc_list) + def handshake(self): """ Process greeting with Tarantool server. diff --git a/tarantool/connection_pool.py b/tarantool/connection_pool.py index 7b3ac250..a57d7284 100644 --- a/tarantool/connection_pool.py +++ b/tarantool/connection_pool.py @@ -17,7 +17,9 @@ POOL_INSTANCE_RECONNECT_DELAY, POOL_INSTANCE_RECONNECT_MAX_ATTEMPTS, POOL_REFRESH_DELAY, - SOCKET_TIMEOUT + SOCKET_TIMEOUT, + DEFAULT_SSL_PASSWORD, + DEFAULT_SSL_PASSWORD_FILE, ) from tarantool.error import ( ClusterConnectWarning, @@ -383,13 +385,15 @@ def __init__(self, .. code-block:: python { - "host': "str" or None, # mandatory - "port": int or "str", # mandatory - "transport": "str", # optional - "ssl_key_file": "str", # optional - "ssl_cert_file": "str", # optional - "ssl_ca_file": "str", # optional - "ssl_ciphers": "str" # optional + "host': "str" or None, # mandatory + "port": int or "str", # mandatory + "transport": "str", # optional + "ssl_key_file": "str", # optional + "ssl_cert_file": "str", # optional + "ssl_ca_file": "str", # optional + "ssl_ciphers": "str" # optional + "ssl_password": "str", # optional + "ssl_password_file": "str" # optional } Refer to corresponding :class:`~tarantool.Connection` @@ -492,7 +496,9 @@ def __init__(self, ssl_key_file=addr['ssl_key_file'], ssl_cert_file=addr['ssl_cert_file'], ssl_ca_file=addr['ssl_ca_file'], - ssl_ciphers=addr['ssl_ciphers']) + ssl_ciphers=addr['ssl_ciphers'], + ssl_password=addr['ssl_password'], + ssl_password_file=addr['ssl_password_file']) ) if connect_now: diff --git a/tarantool/const.py b/tarantool/const.py index 4ea61444..b23e5c68 100644 --- a/tarantool/const.py +++ b/tarantool/const.py @@ -117,6 +117,10 @@ DEFAULT_SSL_CA_FILE = None # Default value for list of SSL ciphers DEFAULT_SSL_CIPHERS = None +# Default value for SSL key file password +DEFAULT_SSL_PASSWORD = None +# Default value for a path to file with SSL key file password +DEFAULT_SSL_PASSWORD_FILE = None # Default cluster nodes list refresh interval (seconds) CLUSTER_DISCOVERY_DELAY = 60 # Default cluster nodes state refresh interval (seconds) diff --git a/tarantool/mesh_connection.py b/tarantool/mesh_connection.py index e8d31b54..214890dd 100644 --- a/tarantool/mesh_connection.py +++ b/tarantool/mesh_connection.py @@ -24,6 +24,8 @@ DEFAULT_SSL_CERT_FILE, DEFAULT_SSL_CA_FILE, DEFAULT_SSL_CIPHERS, + DEFAULT_SSL_PASSWORD, + DEFAULT_SSL_PASSWORD_FILE, CLUSTER_DISCOVERY_DELAY, ) @@ -36,7 +38,9 @@ 'ssl_key_file': DEFAULT_SSL_KEY_FILE, 'ssl_cert_file': DEFAULT_SSL_CERT_FILE, 'ssl_ca_file': DEFAULT_SSL_CA_FILE, - 'ssl_ciphers': DEFAULT_SSL_CIPHERS + 'ssl_ciphers': DEFAULT_SSL_CIPHERS, + 'ssl_password': DEFAULT_SSL_PASSWORD, + 'ssl_password_file': DEFAULT_SSL_PASSWORD_FILE, } @@ -188,6 +192,8 @@ def update_connection(conn, address): conn.ssl_cert_file = address['ssl_cert_file'] conn.ssl_ca_file = address['ssl_ca_file'] conn.ssl_ciphers = address['ssl_ciphers'] + conn.ssl_password = address['ssl_password'] + conn.ssl_password_file = address['ssl_password_file'] class RoundRobinStrategy(object): @@ -269,6 +275,8 @@ def __init__(self, host=None, port=None, ssl_cert_file=DEFAULT_SSL_CERT_FILE, ssl_ca_file=DEFAULT_SSL_CA_FILE, ssl_ciphers=DEFAULT_SSL_CIPHERS, + ssl_password=DEFAULT_SSL_PASSWORD, + ssl_password_file=DEFAULT_SSL_PASSWORD_FILE, addrs=None, strategy_class=RoundRobinStrategy, cluster_discovery_function=None, @@ -427,7 +435,9 @@ def __init__(self, host=None, port=None, 'ssl_key_file': ssl_key_file, 'ssl_cert_file': ssl_cert_file, 'ssl_ca_file': ssl_ca_file, - 'ssl_ciphers': ssl_ciphers}) + 'ssl_ciphers': ssl_ciphers, + 'ssl_password': ssl_password, + 'ssl_password_file': ssl_password_file}) # Verify that at least one address is provided. if not addrs: @@ -467,7 +477,9 @@ def __init__(self, host=None, port=None, ssl_key_file=addr['ssl_key_file'], ssl_cert_file=addr['ssl_cert_file'], ssl_ca_file=addr['ssl_ca_file'], - ssl_ciphers=addr['ssl_ciphers']) + ssl_ciphers=addr['ssl_ciphers'], + ssl_password=addr['ssl_password'], + ssl_password_file=addr['ssl_password_file']) def connect(self): """ @@ -574,7 +586,9 @@ def _opt_refresh_instances(self): 'ssl_key_file': self.ssl_key_file, 'ssl_cert_file': self.ssl_cert_file, 'ssl_ca_file': self.ssl_ca_file, - 'ssl_ciphers': self.ssl_ciphers} + 'ssl_ciphers': self.ssl_ciphers, + 'ssl_password': self.ssl_password, + 'ssl_password_file': self.ssl_password_file} if current_addr not in self.strategy.addrs: self.close() addr = self.strategy.getnext() diff --git a/test/data/ca.crt b/test/data/ca.crt index 013f5483..97721554 100644 --- a/test/data/ca.crt +++ b/test/data/ca.crt @@ -1,20 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDLzCCAhegAwIBAgIUMwa7m6dtjVYPK5iZAMX8YUuHtxEwDQYJKoZIhvcNAQEL +MIIDLzCCAhegAwIBAgIUWsIywvK+tkdt1ew8Hyl+q8AimxswDQYJKoZIhvcNAQEL BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y -MjA2MTYwODQzMThaFw00NDExMTkwODQzMThaMCcxCzAJBgNVBAYTAlVTMRgwFgYD +MjEyMjMxNTM1MjhaFw00NTA1MjgxNTM1MjhaMCcxCzAJBgNVBAYTAlVTMRgwFgYD VQQDDA9FeGFtcGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC923p9pD1ajiAPsM2W6cnjSkexHX2+sJeaLXL6zdFeUjLYRAnfzJ9xVih7 -91yWbuJ9OAswWmz83JrtSm1GqZpFucSz5pFqW2AVrhX5TezlxyH9QwPl+Scu1kCd -+wu7Fgkuw7a0SOpYafPQ6smucCWbxkyZTNgysNuWswykal4VCWyekaY/OojEImoG -smGOXe1Pr2x8XsiWVau1UJ0jj/vh5VzF05mletaUOoQ+iorIHAfnOm2K53jAZlNG -X83VJ1ijSDwiKcnFKcQqlq2Zt88UpxMMv0UyFbDCrOj5qfBbAvzZj5IgUi/NvoZz -M+lzwT+/0mADkAHB6EVa4R29zM+fAgMBAAGjUzBRMB0GA1UdDgQWBBSloRx6dBUI -gJb0yzP2c5zQdQQ+2TAfBgNVHSMEGDAWgBSloRx6dBUIgJb0yzP2c5zQdQQ+2TAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCCUEnzpu8hZAckICLR -5JRDUiHJ3yJ5iv0b9ChNaz/AQBQGRE8bOPC2M/ZG1RuuQ8IbRbzK0fy1ty9KpG2D -JC9iDL6zPOC3e5x2H8Gxbhvjz4QnHPbYTfdJSmX5tJyNIrJ77g4SW5g8eFApTHyY -5KwRD3IDEu4pZNGsM7l0ODBC/4lvR8u7wPJDGyJBpE3uAKC20XqbG8BWm3kPb9+T -wE4Ak/FEXcwARB0fJ6Jni9iK3TeReyB3rpsYJa4N9iY6f1qNy4qQZ8Va6EWPSNnB -FhvCIYt4LdgM9ffUuHPrCX7qdgSNiL4VijgLaEHjFUUlLb6NHgQfYx/JG7wstiKs -Syzb +AoIBAQDHwdnavXTN4Km9bwnRBV+2GW4Z2eFQ5fw2/Ln0BRUp+Wqzc6Cu9sfxH4DV +6+A0H1swcc0kctdPGZvApk6b4A98/Pry09JEMbCzLlHaVGeNKQr+g7Vrb2/v9337 +ofRbhjA+CsD3bBQio5U3ANTvcB2KnUnqA/PmohIpLGyipOpwz2Dr6N4UABZ/wd0o +mYGeRQx7BCgDzK3b+eVmQkWlHuEkAQv7qAVzKGIkw3lUq2Hikq65b+1DXWsENHSC +GRXNRklu/lnsZPfNIcqu0z0OzkFNZ9VWCSQLRmPBzTv5ATSVmZZiiHJSR89q41MK +T2cw9layXRAmtzDX5VUlPvF5GQUtAgMBAAGjUzBRMB0GA1UdDgQWBBSLeibGvWpo +u0/ecImiZxrGfOJgFTAfBgNVHSMEGDAWgBSLeibGvWpou0/ecImiZxrGfOJgFTAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC9pXVMMKiaS3LLA+Vl +oNsLKGtkbLbFYJSvjnNVlcVv64jdEQNeBWdYA4V4FDkkOc8PZ5NNqLiMMq6YEcyz +zPJkvtkIVrGh67TKdcbqyoLXyWcnqne0IN+CDhZuspvY5w8BX18q5rM0vtdp/si0 +z5BM0o6hzUKU8smCOSjDQr7PtbQaPJT0JFLUmNl64TTymg/Wim7i72E5V7wSX1Zp +VkaWDRjRG6H/lIX/88ppEl6aOAEdezgsu68fNjgJBlUK1qkJVvLg/3ddm/od1kwf +5j3289P1myOvTJoWbaUUVI2GON+kPEAniyi08iJTgHUOHJUEUrbb0STmcGF+rCLT +vPdc -----END CERTIFICATE----- diff --git a/test/data/generate.sh b/test/data/generate.sh index 437ecf7f..f78e84db 100755 --- a/test/data/generate.sh +++ b/test/data/generate.sh @@ -33,3 +33,17 @@ openssl x509 -outform pem -in ca.pem -out ca.crt openssl req -new -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.csr -subj "/C=US/ST=YourState/L=YourCity/O=Example-Certificates/CN=localhost" openssl x509 -req -sha256 -days 8192 -in localhost.csr -CA ca.pem -CAkey ca.key -CAcreateserial -extfile domains_localhost.ext -out localhost.crt openssl x509 -req -sha256 -days 8192 -in localhost.csr -CA ca.pem -CAkey ca.key -CAcreateserial -extfile domains_invalidhost.ext -out invalidhost.crt + +password=mysslpassword + +# Tarantool tries every line from the password file. +cat < passwords +unusedpassword +$password +EOF + +cat < invalidpasswords +unusedpassword1 +EOF + +openssl rsa -aes256 -passout "pass:${password}" -in localhost.key -out localhost.enc.key diff --git a/test/data/invalidhost.crt b/test/data/invalidhost.crt index de28671d..393c96d6 100644 --- a/test/data/invalidhost.crt +++ b/test/data/invalidhost.crt @@ -1,22 +1,21 @@ -----BEGIN CERTIFICATE----- -MIIDkjCCAnqgAwIBAgIUV7NbprG6FEvrSP0kZ7pT9s7eN7swDQYJKoZIhvcNAQEL +MIIDczCCAlugAwIBAgIUOT0BKqbGYnCaZSKoTdJjaDJyh1owDQYJKoZIhvcNAQEL BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y -MjA2MTYwODQzMThaFw00NDExMTkwODQzMThaMGcxCzAJBgNVBAYTAlVTMRIwEAYD +MjEyMjMxNTM1MjhaFw00NTA1MjgxNTM1MjhaMGcxCzAJBgNVBAYTAlVTMRIwEAYD VQQIDAlZb3VyU3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MR0wGwYDVQQKDBRFeGFt cGxlLUNlcnRpZmljYXRlczESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqGqKNkOVMGeIClmjLRf02UhtpYcGYVmiblpB -rqbI7eXKKIXMm4ppEEC/1YMVx/iYNYUK0xXxtzZUe1R6L5PYKAm1X+EQ4Sipyj/s -J+qsHxC65mavKB0ylZLZxAjZbiqBBYWwt0uz6ihHAtNXmoBzCE/mTRI3vTOd+CGQ -EI5pLGB85UuyvTfMKFwV9cTfltqGNyAZ670TFxtIwLeGuExfAFTVyofFWb8Kniby -EwKm/1giFl0HrKsHzPljKjlug6lcUeGxooTUJ9sxe6zPYGy2c6EqyV62/UVzgxv9 -LNejeh3vlFmQbeawrwvQSMNi+sVuiaYmq/FIw5e4pUYUTjf+SQIDAQABo3YwdDAf -BgNVHSMEGDAWgBSloRx6dBUIgJb0yzP2c5zQdQQ+2TAJBgNVHRMEAjAAMAsGA1Ud -DwQEAwIE8DAaBgNVHREEEzARgg9pbnZhbGlkaG9zdG5hbWUwHQYDVR0OBBYEFNpJ -/WkoMwKCdo0w0HV8aYm1m7ayMA0GCSqGSIb3DQEBCwUAA4IBAQC2tCfqPF2QrieZ -5632SyuX9oDzBCPQv2vi68QRtL+VxjmJ+IPLHdpZ96jTM7pYIAQ5QVm357JXLixU -NJ0eqgGIFrY4Evx91AGEAX20Ccn8CCXK3LsG1z1UWrvH/txEyOecuLCukaDI5ejq -z1/CKJhxF7bBfukfG2X8qWqqUNRQpkdQObMwZ6Np/GhITIDldxRMIaP05pUGPybR -CrEiC5F5lwgVAwlNhnfJuBcH3XMKWFZuiyur3O6PfSmUByainSnLY94RefofyEct -t20ikQssE6XcX/soTtmwOvIGHHMGcuKBbTwlF0dxv9pLrikpXrv0sf3mT+abUqND -oPmVcDJp +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhuQAPWqsyEFS64fuNJ8WNXRzd1ixxyEPcsj +BQLcvATP8H46K2EEZcLLR4re3ZeSWCR2tG7GOlJXtpivEkPhi/jxbxluIHeghPat +4Q71juZ5y2k+nv1bkP2JT0bjJyflnUY2lno4oCOWswvu0pTTWsJ0/Fenxw0NFD/D +0WjFo1n+J6LOWxAKIcksVyHK2/duLzPqSPSOc6/MxheteTH8bzD7H5/wbNyiPv0L +qb6IAYzbrTEjAw4wb7yyzat928IqYaltRFYx6T7DyxM/IDNI7kZ+hfI41G8aDER8 +9mm6qNIiS/mLXlnkTHVG4SpKx61ALYz87xNYeF/Wyyajf5TkoQIDAQABo1cwVTAf +BgNVHSMEGDAWgBSLeibGvWpou0/ecImiZxrGfOJgFTAJBgNVHRMEAjAAMAsGA1Ud +DwQEAwIE8DAaBgNVHREEEzARgg9pbnZhbGlkaG9zdG5hbWUwDQYJKoZIhvcNAQEL +BQADggEBACLz1JfEphvK2+axxKZbLCaKjEUr2WVAj2MH9fJ8SFINOk7Rd5wILkJP +NV9meItqMfdmclxORH5h/069mHHzzTofoXBdZNbrwSEFWhAACexC+tqvUPvu2yKj +hddhfQW66WrWUQPrlJh9UETt6AoCXN4G75Mkmm1I8o3wzydhWzXpVlqu8gEe45gH +7J/vU+Kzf1+B3HkfNEYbGDKC+MPsQcFH+lM8U42Jqs4UZeynH2iqwjeNh7DvoYV4 +4MjEGG3aqtj3XAxnqqNEnd2ChnsiDAnCMFnTJn4EU35iBDKZM0iECHhIuyewsBhI +vRb9+qM6LNIWDQ0zC7i6Xz2EHeI1EEA= -----END CERTIFICATE----- diff --git a/test/data/invalidpasswords b/test/data/invalidpasswords new file mode 100644 index 00000000..b09d795a --- /dev/null +++ b/test/data/invalidpasswords @@ -0,0 +1 @@ +unusedpassword1 diff --git a/test/data/localhost.crt b/test/data/localhost.crt index 765b843e..bd3458af 100644 --- a/test/data/localhost.crt +++ b/test/data/localhost.crt @@ -1,22 +1,21 @@ -----BEGIN CERTIFICATE----- -MIIDkjCCAnqgAwIBAgIUI7y4bpqOVjvp9aEzUlsSO4pZgjAwDQYJKoZIhvcNAQEL +MIIDczCCAlugAwIBAgIUOT0BKqbGYnCaZSKoTdJjaDJyh1kwDQYJKoZIhvcNAQEL BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y -MjA2MTYwODQzMThaFw00NDExMTkwODQzMThaMGcxCzAJBgNVBAYTAlVTMRIwEAYD +MjEyMjMxNTM1MjhaFw00NTA1MjgxNTM1MjhaMGcxCzAJBgNVBAYTAlVTMRIwEAYD VQQIDAlZb3VyU3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MR0wGwYDVQQKDBRFeGFt cGxlLUNlcnRpZmljYXRlczESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqGqKNkOVMGeIClmjLRf02UhtpYcGYVmiblpB -rqbI7eXKKIXMm4ppEEC/1YMVx/iYNYUK0xXxtzZUe1R6L5PYKAm1X+EQ4Sipyj/s -J+qsHxC65mavKB0ylZLZxAjZbiqBBYWwt0uz6ihHAtNXmoBzCE/mTRI3vTOd+CGQ -EI5pLGB85UuyvTfMKFwV9cTfltqGNyAZ670TFxtIwLeGuExfAFTVyofFWb8Kniby -EwKm/1giFl0HrKsHzPljKjlug6lcUeGxooTUJ9sxe6zPYGy2c6EqyV62/UVzgxv9 -LNejeh3vlFmQbeawrwvQSMNi+sVuiaYmq/FIw5e4pUYUTjf+SQIDAQABo3YwdDAf -BgNVHSMEGDAWgBSloRx6dBUIgJb0yzP2c5zQdQQ+2TAJBgNVHRMEAjAAMAsGA1Ud -DwQEAwIE8DAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwHQYDVR0OBBYEFNpJ -/WkoMwKCdo0w0HV8aYm1m7ayMA0GCSqGSIb3DQEBCwUAA4IBAQC2UFwSoqAMfg1h -xhYauemq13+JXPOnfoR74WzJc8Wva51Bqr8YpVxXU8GCViZKdWi/6sT5h//M4Zrp -wmcUruAQinRUy7RzKoXFHL7g6hQOE440gqaePE/PvjTde8l7FeiGTCSfAqIIFpsz -8YhVajenrzt9ppaHnad/N59uCnIULZrezRq8wJl8Zw82IR/Szcu/4O/tSimYuleY -pNX1h5w2mfpNmKeXkseU8cid1GhCnBg2FK6t6xZ4sSCL2nlpNKsbYvLg5rViRavO -7roUcU4BKK5NnGuYOPKYycSpC500V+shnCq4vTZSsPTOT2dHdMMK5HguxzHxixQv -yPeWBYqy +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhuQAPWqsyEFS64fuNJ8WNXRzd1ixxyEPcsj +BQLcvATP8H46K2EEZcLLR4re3ZeSWCR2tG7GOlJXtpivEkPhi/jxbxluIHeghPat +4Q71juZ5y2k+nv1bkP2JT0bjJyflnUY2lno4oCOWswvu0pTTWsJ0/Fenxw0NFD/D +0WjFo1n+J6LOWxAKIcksVyHK2/duLzPqSPSOc6/MxheteTH8bzD7H5/wbNyiPv0L +qb6IAYzbrTEjAw4wb7yyzat928IqYaltRFYx6T7DyxM/IDNI7kZ+hfI41G8aDER8 +9mm6qNIiS/mLXlnkTHVG4SpKx61ALYz87xNYeF/Wyyajf5TkoQIDAQABo1cwVTAf +BgNVHSMEGDAWgBSLeibGvWpou0/ecImiZxrGfOJgFTAJBgNVHRMEAjAAMAsGA1Ud +DwQEAwIE8DAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEL +BQADggEBAC0cUnap+JQFItZV7moPgcQJuiQ/nRWYifx+cQ2clMP3OypHbxIlYaIM +Te1+IJ+4u4x/7QecqwbFMqnMl6OaitkEV9LuU4lrnYWd30m9TPoPT1pgV759rG2d +lS/sYDQCY5RRpCDg6lUsrCBIMVXG2MT/99rIokFE9kGo8zQaQDBhSl4fIRMDMmA9 +gMgwDoBW/DPDvNc7/VhQY4aUSDy/iudqSfAa24CCOI8hC5eLnnZjSfEqaA50fK2w +TPMlnL2q+QlJjFWhiPyumkGxkUsBRjGMviaXRW8oR8TF+Bub6aTk4qIkoeo8pu38 +BEWjCLicBv68/xEnmetubUX/CgdNts8= -----END CERTIFICATE----- diff --git a/test/data/localhost.enc.key b/test/data/localhost.enc.key new file mode 100644 index 00000000..4bc61379 --- /dev/null +++ b/test/data/localhost.enc.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,A68D3D9FDF7B5ACD2968FB1066D868D5 + +aTdzl4pXl+My3e0NGDmoXiDYaD/pXuUzTd3s9QnUWV/NI5rvAxyHZi7UjU1eEX/V +6C9jk2oMEUg9ceqtO83q0lpMpYuF9zNS1bvCZxjrDfHzc1/zUCNWw97Zbxb6pehR +nCuu4zKIUhxD/K5yS6GmxQMIaELXm9/g8n+H9IrOENcPgH7Bma7cIBaf5gAgDID6 ++Te8oQuu8ZmF2MZt5p1zAyT29Ba2ia/l6nSaJytElaEe9DF6kjL0PHb4+NODBKtI +ouwBQaVzdXvHNbpEDmUWgl2UslEESKZBC4ERTlzOpgEAQkIOjGC78cFXdyZfRddu +vjt/uDyvFisV1CQjulZO3PIgXDiJQEQVIMrTvmHZvrj9jWtbxHDdePozTH/TK1ni +Y898rJiN2/NYg1tD51mzAjg1c/HLgEVee3mS5l5h+OS4qGyQfCq2eLHET6SWijb9 +lARaVqIMKxXFsYe2uYF8Fxjeu5KnhuK2qmx4iZFReA1UKHIUwsA3z5lTZb+bcTV5 +Qj6S/Oq0y+p8eUilPtvdMtzI0ZsmlTq0eY8W3B+8bpXHmQ+3PE7DfpicyA2Yi+Gq +3v7iLvMlEasCeaavmRa9+tNDNaEu7qD8Cjt1HKpp+FnsCoZr2Ft5Mn+r2VM5WAyj +t+0nGQwA13eaL5Ama1gWBYV8TrU1sntK5Fn+qNMB8nGrFxjHiLCPXRdB9cN67MdI +aBAtToxBYd8Ap/MS0kYiNTWIdbJNSt8Ns4NRUmyCEaBZ518sPRbEZAf3v6c5zL0D +7nGGCKHxhmLZfpvW7UikiuDLSt2nsU/uLJ9sc4XeMP+ZvOG1HGoKnoIT26kk+T2V +qSTkmpznJsF5vm+DI7UBuRA61DIm9Eai+zvleF500Slu54nTtXXNO/4FgdVFaygX +caECfgL4eeIGiHYHCYH6mgnnGyZ1klj1d8mOYEUnFxmX8ZCgZD6+HyOOC5QmW7t5 +ggubqRPCcA0QnLn6KOZpK44dKc2tU6Wk57z68Vtano2KXJAmccvJNnhh/A1UAXCV +2kxSE66zyfA84adseWFT6eZR/OhQa/AY/zBGN5u1TBdp+LpWOK52FHfVLEaUW6rk +3bQ7qQglz0SR8ccmyG8uXpJSgSeOblgqb6EobSnhmuWU6hHChm1lTk1AAH589Gjf +7NuCV16XAEA0MSl/IK+nfynjL+8PffUi0RM3tRNIGdkvz0mVC/RMce3AHWArJ4G1 +XF3BXWtVEq3dtR+N6jTB/pZp2yEm+IqZKJ/2D9UetG//LkBpdGj+ypjB+GVTFskf +DltMhBBCGrqUsN8zu2+DrZfuXpnT+z+pN/votQqPFaQ4qOxsE+vToErGela9jWDt +w+5U6d0VQdZbFOq1ptY1+4yxzmxAbIgQDPIxdW0urEwIAedPyfLqGeEzwhf6rDii +ZzRXevbYhwRMRJef57i9h2TUDvcXq3VSTTek6X+YNDBWcsl+nFcG3o8HFHnsb5t6 +CpQ6VMI3469T+gl+6PZ15F/eE+mI9F9Ov/3+pJWII8p/qidu/Y469LrH3qI5PIIj +1VNHMWtDE5I5qR1FsqaZJk7uHlgbVKj5z0Sw6x/iOzksxi1P67hAr0ErY8rrUvsj +-----END RSA PRIVATE KEY----- diff --git a/test/data/localhost.key b/test/data/localhost.key index 5fbcfbaa..ff5d0856 100644 --- a/test/data/localhost.key +++ b/test/data/localhost.key @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoaoo2Q5UwZ4gK -WaMtF/TZSG2lhwZhWaJuWkGupsjt5coohcybimkQQL/VgxXH+Jg1hQrTFfG3NlR7 -VHovk9goCbVf4RDhKKnKP+wn6qwfELrmZq8oHTKVktnECNluKoEFhbC3S7PqKEcC -01eagHMIT+ZNEje9M534IZAQjmksYHzlS7K9N8woXBX1xN+W2oY3IBnrvRMXG0jA -t4a4TF8AVNXKh8VZvwqeJvITAqb/WCIWXQesqwfM+WMqOW6DqVxR4bGihNQn2zF7 -rM9gbLZzoSrJXrb9RXODG/0s16N6He+UWZBt5rCvC9BIw2L6xW6Jpiar8UjDl7il -RhRON/5JAgMBAAECggEAHWxlorbadvcziYlhDIUJsjdo7pkhOHtSOUDcBlEdvBBg -KgW8OjVrhxsk2L7a3JG2N+17N2c3UGi1yEk5QpwsEMynay2VRx0VUuApmEyzzwab -fJrWgaXeO0sJcCoSoKBc47PYbKGVeHSaeWgmfzfvQPXCmNb0tYGx2NK2Smoy/j1B -lXgODPkXHuzj0LOA3OkapgrxqHpN+kPjAfaY8vKYBQ8lbROT3kjgjqEzykC3bCzj -ZNZArGovBRAGr7dvjdh791g3hN2cAgIWhTg4zu8N6gf18G1l4bH8nmRzWT/z7eJi -QvmGjXVPUEpBcWRZuHms5cGcxb7V6smvuJp4v1n+rQKBgQDa1rqNwVlk1Jo0oT5U -KUyJwjaVXa3Foy5oR/T66UDIEBiMEonfI/miMlwXRXdhC1WQTeddk5vX+pn3ISZT -mN6zwz2NGE1i4GmOLIG9a9JkCSPffqDiwYFd2uhbTfKNehIHOC4Xdg/UGz+vOGFZ -MxYiSrytYK6svgHjHlFPp/uP9QKBgQDFA9wVmE76FqVC7crA7Djkyt4cRU5LEILO -qp4AxWE8HU/vlht4PhVA/dgMTNkVLiyrSgTGm15FQKZWe2FMVaAnRcmLy6bRpcAM -fP4HNtwjRWHx1Q4lMRZLrZPO0W8RXUqgMgGd3w1kyJK/C9wnD/01h+3lAnJ1cHlD -5jub6RDkhQKBgQCUciSKFCY3p6ATI23MWVd5+yxblfhSoKbSRj2AFsnC7Gg6XDj6 -DMVBqTee8ZhRVAbupGnVqFOG5o+ae/orqv8mocIW++1CrUftEXPQsls9UJXs/VDV -gL3olJ4ZkX5/SdcA3rMlZwjFsNY6XdxrTaQuDtR+J59Vvm45Sk+N4T1cIQKBgG9d -zSzP2eT4pBZ/QJtpbIe4PXGRo74+6RJV09bvvBU1JJh0K7b+sRj55QSe9B9K6Kky -wBxcex9+eghs2gVCabOJeXJyfiwIG9VzWk1Nr4aok8MWAlb3tni099ZzAOu55pND -cTKCgZm0327rD1ltal62Jb3MclL8by/4lz18s7XZAoGBANSv/AdjlJUQ++9I+P1+ -g7rgrfWKLyQ8FSljO7dAOWsDjrFHSi2f2HCh3URcKqzdjG+/iK+MyKUlaUZDLCzf -QNgI+7n5I/aHfhRWo7ytRPTd78Gyw/lDGW3Pz8MzXJ4pVDgr2UB7KN91/Rx9dJfN -3K04YR/TSpwB0Nug+5a1XuGh +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCG5AA9aqzIQVL +rh+40nxY1dHN3WLHHIQ9yyMFAty8BM/wfjorYQRlwstHit7dl5JYJHa0bsY6Ule2 +mK8SQ+GL+PFvGW4gd6CE9q3hDvWO5nnLaT6e/VuQ/YlPRuMnJ+WdRjaWejigI5az +C+7SlNNawnT8V6fHDQ0UP8PRaMWjWf4nos5bEAohySxXIcrb924vM+pI9I5zr8zG +F615MfxvMPsfn/Bs3KI+/QupvogBjNutMSMDDjBvvLLNq33bwiphqW1EVjHpPsPL +Ez8gM0juRn6F8jjUbxoMRHz2abqo0iJL+YteWeRMdUbhKkrHrUAtjPzvE1h4X9bL +JqN/lOShAgMBAAECggEAUI+f9MYBUtchm4dpIww5D6WurgJ0PK9ZM0xb/HhzpJVa +uDWrbtWVPabbN5YraUy/MFXx7nELVKlYPjCgeLQzqCRqD/I+Arujo4u+HupWgDin +1ASaOOJuXn7bvktte5LTebLqdQnE9rHOmgEZ3cxm7ARJCXJX8AU6qGzOmNsjK+gz +Lf3Yqf2Gmct1Bp+JBea9YB3dAWyph10lExIisxfh7BCwaqaNeA8n8Wv17IGL9B5j +OLC4HAPQJRYvfouFl6eB5TZqLagg+KW3qiBuxafEiuPL7pJiNJnTc1ILbMSwi2x9 +kWTe7RbhsTSoVn5lzHn5Oq9ZmJluSmFTZdwCLGuwAQKBgQD+UsKGygV7y8ihtvJm +jN7T1CzloqqAuHkn3OvWt40nRsBs38FX+rnOavLSljbB+n4GGR3o2wBGaNQN5iOn +RM1RcBSIHBdgZvACtIauybNwmzpRvO57I+ehqC3YDOl+3wKo+fPpN9aKWOEL9KJp +G4dNxdkhVLL2yBtD7M72LD4eKQKBgQDDYywdBuTAts+chrFaUaiI0qOoMfG0sh2H +i0lzUag/vL6X7lXvf7iDG4hYxU4ExQDCsDKxTXBI0FYUJQR8gg1NrjDCe8zWBPSM +iMTzXh4q+OrUqzbId1fPAY1RzG+xsqXTsXjUlDy8qUd9rh+1mS0fFgDEo4ispUdO +IfZSSnlxuQKBgHf/AH2skE3K5w02TsUILpLwB4cJ6zz0zWV7nWMgE9+2SFCWeplS +WZ0FZTDrY0a/M/sYmr4lpsmR6IvuTGA93EpSgb1+06DOsOv11Z5e2OWGuEucw0Ei +vcXOnmLUJM+R1aV42hbuG7IHIZgMgxzoujx932cUmaRK4mJ4N2Z7lYuRAoGAZspP +tN2hjrkeM+ywdSGsln6qVpwf2r4xxtNCSwbUiuOTKX7beuooeeEMNBdo2h2CLupf +YOOqhMQF5Qcseww4T3uqb0aOFrH4rc5uPtJu8JCPil6grYoLif35COWShVvE3b/q +H3v1EBPGZpoqWHFDSa1brheSmbFB+Brp6ZUAmxECgYEAkuJwTsn1GQSH9aDNC3ss +AeNwsRf6nzqAkYt+YSOR6A69VPsNbAjgtG1JFns8hL85r4zj0nmDsTbLXYXw3Meb +96rm8CR1nizHfzfyBcF+aF6TeuDKNHDcotyD9+dgwSQz4MHZ0GK76Ic4rz/wqup8 +ZVvgfzBA6cMoCWnIfYheMho= -----END PRIVATE KEY----- diff --git a/test/data/passwords b/test/data/passwords new file mode 100644 index 00000000..58530047 --- /dev/null +++ b/test/data/passwords @@ -0,0 +1,2 @@ +unusedpassword +mysslpassword diff --git a/test/suites/lib/skip.py b/test/suites/lib/skip.py index e111746e..b7690782 100644 --- a/test/suites/lib/skip.py +++ b/test/suites/lib/skip.py @@ -3,6 +3,39 @@ import re import sys +def fetch_tarantool_version(self): + """Helper to fetch current Tarantool version. + """ + if not hasattr(self, 'tnt_version') or self.tnt_version is None: + srv = None + + if hasattr(self, 'servers') and self.servers is not None: + srv = self.servers[0] + + if hasattr(self, 'srv') and self.srv is not None: + srv = self.srv + + assert srv is not None + + try: + self.tnt_version = srv.admin.tnt_version + except: + self.__class__.tnt_version = srv.admin.tnt_version + +def skip_or_run_test_tarantool_impl(self, REQUIRED_TNT_VERSION, msg): + """Helper to skip or run tests depending on the Tarantool + version. + + Also, it can be used with the 'setUp' method for skipping + the whole test suite. + """ + fetch_tarantool_version(self) + + support_version = pkg_resources.parse_version(REQUIRED_TNT_VERSION) + + if self.tnt_version < support_version: + self.skipTest('Tarantool %s %s' % (self.tnt_version, msg)) + def skip_or_run_test_tarantool(func, REQUIRED_TNT_VERSION, msg): """Decorator to skip or run tests depending on the tarantool @@ -17,28 +50,24 @@ def wrapper(self, *args, **kwargs): if func.__name__ == 'setUp': func(self, *args, **kwargs) - if not hasattr(self, 'tnt_version'): - srv = None - - if hasattr(self, 'servers'): - srv = self.servers[0] - - if hasattr(self, 'srv'): - srv = self.srv + skip_or_run_test_tarantool_impl(self, REQUIRED_TNT_VERSION, msg) - assert srv is not None + if func.__name__ != 'setUp': + func(self, *args, **kwargs) - self.__class__.tnt_version = srv.admin.tnt_version + return wrapper - support_version = pkg_resources.parse_version(REQUIRED_TNT_VERSION) +def skip_or_run_test_tarantool_call(self, REQUIRED_TNT_VERSION, msg): + """Function to skip or run tests depending on the tarantool + version. Useful in cases when in is inconvenient to work + with decorators. - if self.tnt_version < support_version: - self.skipTest('Tarantool %s %s' % (self.tnt_version, msg)) + Also, it can be used with the 'setUp' method for skipping + the whole test suite. + """ - if func.__name__ != 'setUp': - func(self, *args, **kwargs) + skip_or_run_test_tarantool_impl(self, REQUIRED_TNT_VERSION, msg) - return wrapper def skip_or_run_test_pcall_require(func, REQUIRED_TNT_MODULE, msg): """Decorator to skip or run tests depending on tarantool @@ -178,3 +207,17 @@ def skip_or_run_error_ext_type_test(func): return skip_or_run_test_tarantool(func, '2.10.0', 'does not support error extension type') + +def skip_or_run_ssl_password_test_call(self): + """Function to skip or run tests related to SSL password + and SSL password files support. Supported only in Tarantool EE. + Do not check Enterprise prefix since TNT_SSL_TEST already assumes + it. + + Tarantool EE supports SSL passwords and password files only in + current master since commit e1f47dd4 (after 2.11.0-entrypoint). + See https://github.com/tarantool/tarantool-ee/issues/22 + """ + + return skip_or_run_test_tarantool_call(self, '2.11.0', + 'does not support SSL passwords') diff --git a/test/suites/lib/tarantool_server.py b/test/suites/lib/tarantool_server.py index 56c1f06e..6c4c3398 100644 --- a/test/suites/lib/tarantool_server.py +++ b/test/suites/lib/tarantool_server.py @@ -125,6 +125,8 @@ def __new__(cls, ssl_cert_file=None, ssl_ca_file=None, ssl_ciphers=None, + ssl_password=None, + ssl_password_file=None, create_unix_socket=False): if os.name == 'nt': from .remote_tarantool_server import RemoteTarantoolServer @@ -137,6 +139,8 @@ def __init__(self, ssl_cert_file=None, ssl_ca_file=None, ssl_ciphers=None, + ssl_password=None, + ssl_password_file=None, create_unix_socket=False): os.popen('ulimit -c unlimited').close() @@ -162,6 +166,8 @@ def __init__(self, self.ssl_cert_file = ssl_cert_file self.ssl_ca_file = ssl_ca_file self.ssl_ciphers = ssl_ciphers + self.ssl_password = ssl_password + self.ssl_password_file = ssl_password_file def find_exe(self): if 'TARANTOOL_BOX_PATH' in os.environ: @@ -184,6 +190,10 @@ def generate_listen(self, port, port_only): listen += "ssl_ca_file={}&".format(self.ssl_ca_file) if self.ssl_ciphers: listen += "ssl_ciphers={}&".format(self.ssl_ciphers) + if self.ssl_password: + listen += "ssl_password={}&".format(self.ssl_password) + if self.ssl_password_file: + listen += "ssl_password_file={}&".format(self.ssl_password_file) listen = listen[:-1] else: listen = str(port) diff --git a/test/suites/test_ssl.py b/test/suites/test_ssl.py index fd0aa450..65072b59 100644 --- a/test/suites/test_ssl.py +++ b/test/suites/test_ssl.py @@ -11,6 +11,10 @@ ) import tarantool from .lib.tarantool_server import TarantoolServer +from .lib.skip import ( + fetch_tarantool_version, + skip_or_run_ssl_password_test_call, +) def is_test_ssl(): @@ -21,6 +25,39 @@ def is_test_ssl(): return False +class SslTestCase: + def __init__(self, + name="", + ok=False, + server_transport=SSL_TRANSPORT, + server_key_file=None, + server_cert_file=None, + server_ca_file=None, + server_ciphers=None, + server_password=None, + server_password_file=None, + client_cert_file=None, + client_key_file=None, + client_ca_file=None, + client_ciphers=None, + client_password=None, + client_password_file=None): + self.name = name + self.ok = ok + self.server_transport = server_transport + self.server_key_file = server_key_file + self.server_cert_file = server_cert_file + self.server_ca_file = server_ca_file + self.server_ciphers = server_ciphers + self.server_password = server_password + self.server_password_file = server_password_file + self.client_cert_file = client_cert_file + self.client_key_file = client_key_file + self.client_ca_file = client_ca_file + self.client_ciphers = client_ciphers + self.client_password = client_password + self.client_password_file = client_password_file + @unittest.skipIf(not is_test_ssl(), "TEST_TNT_SSL is not set.") class TestSuite_Ssl(unittest.TestCase): @classmethod @@ -34,53 +71,48 @@ def setUpClass(self): self.invalidhost_cert_file = os.path.join(test_data_dir, "invalidhost.crt") self.key_file = os.path.join(test_data_dir, "localhost.key") + self.key_enc_file = os.path.join(test_data_dir, "localhost.enc.key") self.ca_file = os.path.join(test_data_dir, "ca.crt") self.empty_file = os.path.join(test_data_dir, "empty") + self.password = "mysslpassword" + self.invalid_password = "notmysslpassword" + self.password_file = os.path.join(test_data_dir, "passwords") + self.invalid_password_file = os.path.join(test_data_dir, "invalidpasswords") self.invalid_file = "any_invalid_path" + # Extract the version for skips. + self.tnt_version = None + self.srv = TarantoolServer() + self.srv.script = 'test/suites/box.lua' + self.srv.start() + fetch_tarantool_version(self) + self.srv.stop() + self.srv.clean() + self.srv = None + def stop_srv(self, srv): if srv: srv.stop() srv.clean() + def stop_servers(self, servers): + for srv in servers: + srv.stop() + srv.clean() + def stop_con(self, con): if con: con.close() def stop_mesh(self, mesh): if mesh: - mesh.stop() + mesh.close() def stop_pool(self, pool): if pool: pool.close() def test_single(self): - class SslTestCase: - def __init__(self, - name="", - ok=False, - server_transport=SSL_TRANSPORT, - server_key_file=None, - server_cert_file=None, - server_ca_file=None, - server_ciphers=None, - client_cert_file=None, - client_key_file=None, - client_ca_file=None, - client_ciphers=None): - self.name = name - self.ok = ok - self.server_transport = server_transport - self.server_key_file = server_key_file - self.server_cert_file = server_cert_file - self.server_ca_file = server_ca_file - self.server_ciphers = server_ciphers - self.client_cert_file = client_cert_file - self.client_key_file = client_key_file - self.client_ca_file = client_ca_file - self.client_ciphers = client_ciphers - # Requirements from Tarantool Enterprise Edition manual: # https://www.tarantool.io/en/enterprise_doc/security/#configuration # @@ -239,34 +271,170 @@ def __init__(self, client_cert_file=self.cert_file, client_ca_file=self.ca_file, client_ciphers="TLS_AES_128_GCM_SHA256"), + SslTestCase( + name="pass_no_key_encrypt", + ok=True, + server_key_file=self.key_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password=self.password), + SslTestCase( + name="pass_file_no_key_encrypt", + ok=True, + server_key_file=self.key_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password_file=self.password_file), + SslTestCase( + name="pass_key_encrypt", + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password=self.password), + SslTestCase( + name="pass_file_key_encrypt", + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password_file=self.password_file), + SslTestCase( + name="pass_and_pass_file_key_encrypt", + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password=self.password, + client_password_file=self.password_file), + SslTestCase( + name="inv_pass_and_pass_file_key_encrypt", + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password=self.invalid_password, + client_password_file=self.password_file), + SslTestCase( + name="pass_and_inv_pass_file_key_encrypt", + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password=self.password, + client_password_file=self.invalid_password_file), + SslTestCase( + name="inv_pass_and_inv_pass_file_key_encrypt", + ok=False, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password=self.invalid_password, + client_password_file=self.invalid_password_file), + SslTestCase( + name="no_pass_and_inv_pass_file_key_encrypt", + ok=False, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file), + SslTestCase( + name="pass_key_invalidhost_crt_ca_server_and_key_crt_ca_client", + # A Tarantool implementation does not check hostname. It's + # the expected behavior. We don't do that too. + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.invalidhost_cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_ca_file=self.ca_file, + client_password=self.password), + SslTestCase( + name="pass_file_invalid_path", + ok=False, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password_file=self.invalid_file), + SslTestCase( + name="key_pass_crt_ca_ciphers_server_and_client", + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_ciphers="ECDHE-RSA-AES256-GCM-SHA384", + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_ca_file=self.ca_file, + client_ciphers="ECDHE-RSA-AES256-GCM-SHA384", + client_password=self.password), ] for t in testcases: with self.subTest(msg=t.name): - srv = None + if t.server_password is not None \ + or t.server_password_file is not None \ + or t.client_password is not None \ + or t.server_password_file is not None: + skip_or_run_ssl_password_test_call(self) + + srv = TarantoolServer( + transport=t.server_transport, + ssl_key_file=t.server_key_file, + ssl_cert_file=t.server_cert_file, + ssl_ca_file=t.server_ca_file, + ssl_ciphers=t.server_ciphers, + ssl_password=t.server_password, + ssl_password_file=t.server_password_file) + srv.script = 'test/suites/box.lua' + srv.start() + + srv.admin("box.schema.create_space('space_1')") + srv.admin(""" + box.space['space_1']:create_index('primary', { + type = 'tree', + parts = {1, 'num'}, + unique = true}) + """.replace('\n', ' ')) + srv.admin(""" + box.schema.user.create('test', { password = 'test' }) + """.replace('\n', ' ')) + srv.admin(""" + box.schema.user.grant('test', 'execute,read,write', + 'universe') + """.replace('\n', ' ')) + con = None try: - srv = TarantoolServer( - transport=t.server_transport, - ssl_key_file=t.server_key_file, - ssl_cert_file=t.server_cert_file, - ssl_ca_file=t.server_ca_file, - ssl_ciphers=t.server_ciphers) - srv.script = 'test/suites/box.lua' - srv.start() - srv.admin("box.schema.create_space('space_1')") - srv.admin(""" - box.space['space_1']:create_index('primary', { - type = 'tree', - parts = {1, 'num'}, - unique = true}) - """.replace('\n', ' ')) - srv.admin(""" - box.schema.user.create('test', { password = 'test' }) - """.replace('\n', ' ')) - srv.admin(""" - box.schema.user.grant('test', 'execute,read,write', - 'universe') - """.replace('\n', ' ')) con = tarantool.Connection( srv.host, srv.args['primary'], user="test", @@ -276,6 +444,8 @@ def __init__(self, ssl_cert_file=t.client_cert_file, ssl_ca_file=t.client_ca_file, ssl_ciphers=t.client_ciphers, + ssl_password=t.client_password, + ssl_password_file=t.client_password_file, connection_timeout=0.5, socket_timeout=0.5) @@ -291,87 +461,199 @@ def __init__(self, @unittest.skipIf(sys.platform.startswith("win"), 'Pool tests on windows platform are not supported') def test_pool(self): - servers = [] - cnt = 5 - pool = None - try: - addrs = [] - for i in range(cnt): - srv = TarantoolServer( - transport='ssl', - ssl_key_file=self.key_file, - ssl_cert_file=self.cert_file, - ssl_ca_file=self.ca_file) - srv.script = 'test/suites/box.lua' - srv.start() - srv.admin(""" - box.schema.user.create('test', { password = 'test' }) - """.replace('\n', ' ')) - srv.admin(""" - box.schema.user.grant('test', 'execute,read,write', 'universe') - """.replace('\n', ' ')) - servers.append(srv) - addrs.append({ - 'host': srv.host, - 'port': srv.args['primary'], - 'transport': 'ssl', - 'ssl_key_file': self.key_file, - 'ssl_cert_file': self.cert_file, - 'ssl_ca_file': self.ca_file}) + testcases = [ + SslTestCase( + name="key_crt_ca_server_and_client", + ok=True, + server_key_file=self.key_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + client_key_file=self.key_file, + client_cert_file=self.cert_file, + client_ca_file=self.ca_file), + SslTestCase( + name="pass_key_encrypt", + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password=self.password), + SslTestCase( + name="pass_file_key_encrypt", + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password_file=self.password_file), + ] + for t in testcases: + cnt = 5 + with self.subTest(msg=t.name): + if t.server_password is not None \ + or t.server_password_file is not None \ + or t.client_password is not None \ + or t.server_password_file is not None: + skip_or_run_ssl_password_test_call(self) + + addrs = [] + servers = [] + for i in range(cnt): + srv = TarantoolServer( + transport='ssl', + ssl_key_file=t.server_key_file, + ssl_cert_file=t.server_cert_file, + ssl_ca_file=t.server_ca_file, + ssl_ciphers=t.server_ciphers, + ssl_password=t.server_password, + ssl_password_file=t.server_password_file) + srv.script = 'test/suites/box.lua' + srv.start() + srv.admin(""" + box.schema.user.create('test', { password = 'test' }) + """.replace('\n', ' ')) + srv.admin(""" + box.schema.user.grant('test', 'execute,read,write', 'universe') + """.replace('\n', ' ')) + servers.append(srv) + addr = { + 'host': srv.host, + 'port': srv.args['primary'], + 'transport': 'ssl', + } + if t.client_key_file is not None: + addr['ssl_key_file'] = t.client_key_file + if t.client_cert_file is not None: + addr['ssl_cert_file'] = t.client_cert_file + if t.client_ca_file is not None: + addr['ssl_ca_file'] = t.client_ca_file + if t.client_ciphers is not None: + addr['ssl_ciphers'] = t.client_ciphers + if t.client_password is not None: + addr['ssl_password'] = t.client_password + if t.client_password_file is not None: + addr['ssl_password_file'] = t.client_password_file + addrs.append(addr) - pool = tarantool.ConnectionPool( - addrs=addrs, - user='test', - password='test', - connection_timeout=1, - socket_timeout=1) - self.assertSequenceEqual( - pool.eval('return box.info().ro', mode=tarantool.Mode.RW), - [False]) - finally: - self.stop_pool(pool) + pool = None + try: + pool = tarantool.ConnectionPool( + addrs=addrs, + user='test', + password='test', + connection_timeout=1, + socket_timeout=1) + self.assertSequenceEqual( + pool.eval('return box.info().ro', mode=tarantool.Mode.RW), + [False]) + except tarantool.error.SslError: + self.assertFalse(t.ok) + finally: + self.stop_pool(pool) + self.stop_servers(servers) def test_mesh(self): - servers = [] - cnt = 5 - con = None - try: - addrs = [] - for i in range(cnt): - srv = TarantoolServer( - transport='ssl', - ssl_key_file=self.key_file, - ssl_cert_file=self.cert_file, - ssl_ca_file=self.ca_file) - srv.script = 'test/suites/box.lua' - srv.start() - srv.admin(""" - box.schema.user.create('test', { password = 'test' }) - """.replace('\n', ' ')) - srv.admin(""" - box.schema.user.grant('test', 'execute,read,write', 'universe') - """.replace('\n', ' ')) - srv.admin("function srv_id() return %s end" % i) - servers.append(srv) - addrs.append({ - 'host': srv.host, - 'port': srv.args['primary'], - 'transport': 'ssl', - 'ssl_key_file': self.key_file, - 'ssl_cert_file': self.cert_file, - 'ssl_ca_file': self.ca_file}) + testcases = [ + SslTestCase( + name="key_crt_ca_server_and_client", + ok=True, + server_key_file=self.key_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + client_key_file=self.key_file, + client_cert_file=self.cert_file, + client_ca_file=self.ca_file), + SslTestCase( + name="pass_key_encrypt", + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password=self.password), + SslTestCase( + name="pass_file_key_encrypt", + ok=True, + server_key_file=self.key_enc_file, + server_cert_file=self.cert_file, + server_ca_file=self.ca_file, + server_password=self.password, + client_key_file=self.key_enc_file, + client_cert_file=self.cert_file, + client_password_file=self.password_file), + ] + for t in testcases: + cnt = 5 + with self.subTest(msg=t.name): + if t.server_password is not None \ + or t.server_password_file is not None \ + or t.client_password is not None \ + or t.server_password_file is not None: + skip_or_run_ssl_password_test_call(self) + + addrs = [] + servers = [] + for i in range(cnt): + srv = TarantoolServer( + transport='ssl', + ssl_key_file=t.server_key_file, + ssl_cert_file=t.server_cert_file, + ssl_ca_file=t.server_ca_file, + ssl_ciphers=t.server_ciphers, + ssl_password=t.server_password, + ssl_password_file=t.server_password_file) + srv.script = 'test/suites/box.lua' + srv.start() + srv.admin(""" + box.schema.user.create('test', { password = 'test' }) + """.replace('\n', ' ')) + srv.admin(""" + box.schema.user.grant('test', 'execute,read,write', 'universe') + """.replace('\n', ' ')) + srv.admin("function srv_id() return %s end" % i) + servers.append(srv) + addr = { + 'host': srv.host, + 'port': srv.args['primary'], + 'transport': 'ssl', + } + if t.client_key_file is not None: + addr['ssl_key_file'] = t.client_key_file + if t.client_cert_file is not None: + addr['ssl_cert_file'] = t.client_cert_file + if t.client_ca_file is not None: + addr['ssl_ca_file'] = t.client_ca_file + if t.client_ciphers is not None: + addr['ssl_ciphers'] = t.client_ciphers + if t.client_password is not None: + addr['ssl_password'] = t.client_password + if t.client_password_file is not None: + addr['ssl_password_file'] = t.client_password_file + addrs.append(addr) - mesh = tarantool.MeshConnection( - addrs=addrs, - user='test', - password='test', - connection_timeout=0.5, - socket_timeout=0.5) - for i in range(cnt): - with warnings.catch_warnings(): - warnings.simplefilter('ignore', NetworkWarning) - resp = mesh.call('srv_id') - self.assertEqual(resp.data and resp.data[0], i) - servers[i].stop() - finally: - self.stop_mesh(con) + mesh = None + try: + mesh = tarantool.MeshConnection( + addrs=addrs, + user='test', + password='test', + connection_timeout=0.5, + socket_timeout=0.5) + for i in range(cnt): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', NetworkWarning) + resp = mesh.call('srv_id') + self.assertEqual(resp.data and resp.data[0], i) + servers[i].stop() + except tarantool.error.SslError: + self.assertFalse(t.ok) + finally: + self.stop_mesh(mesh) + self.stop_servers(servers)