diff options
author | Patrick Freed <patrick.freed@mongodb.com> | 2018-11-15 18:51:35 -0500 |
---|---|---|
committer | Patrick Freed <patrick.freed@mongodb.com> | 2018-11-20 11:39:08 -0500 |
commit | f137cdd0e7298bae36ff1e7507c783a01b262828 (patch) | |
tree | 737e10ae7c9c37c509cd9177601b13be0bb9b07c | |
parent | d75be58dd0dda5fe1190e8d48d91d6eda8e21acd (diff) | |
download | mongo-f137cdd0e7298bae36ff1e7507c783a01b262828.tar.gz |
SERVER-38155 Make ssl_ECDHE_suites.js use native Python ssl module
-rw-r--r-- | buildscripts/resmokeconfig/suites/ssl.yml | 2 | ||||
-rw-r--r-- | etc/burn_in_tests.yml | 3 | ||||
-rw-r--r-- | etc/pip/components/platform.req | 6 | ||||
-rw-r--r-- | etc/pip/constraints.txt | 6 | ||||
-rw-r--r-- | jstests/ssl/ssl_ECDHE_suites.js | 137 | ||||
-rw-r--r-- | jstests/ssl/sslyze_tester.py | 52 | ||||
-rw-r--r-- | jstests/ssl/tls_enumerator.py | 75 |
7 files changed, 133 insertions, 148 deletions
diff --git a/buildscripts/resmokeconfig/suites/ssl.yml b/buildscripts/resmokeconfig/suites/ssl.yml index 60aa5d163a7..caaf406d4f9 100644 --- a/buildscripts/resmokeconfig/suites/ssl.yml +++ b/buildscripts/resmokeconfig/suites/ssl.yml @@ -4,8 +4,6 @@ selector: roots: - jstests/ssl/*.js exclude_files: - # TODO: Unblacklist after we've removed sslyze dependency in SERVER-38155 - - jstests/ssl/ssl_ECDHE_suites.js # ssl tests start their own mongod's. executor: diff --git a/etc/burn_in_tests.yml b/etc/burn_in_tests.yml index 5827d35c622..c6d63048621 100644 --- a/etc/burn_in_tests.yml +++ b/etc/burn_in_tests.yml @@ -6,5 +6,4 @@ selector: # Exclude list of etc/evergreen.yml task names. exclude_tasks: # Exclude list of jstests file names. - exclude_tests: - - jstests/ssl/ssl_ECDHE_suites.js + exclude_tests:
\ No newline at end of file diff --git a/etc/pip/components/platform.req b/etc/pip/components/platform.req index 324995955fa..26138b57c99 100644 --- a/etc/pip/components/platform.req +++ b/etc/pip/components/platform.req @@ -1,8 +1,4 @@ # Platform-specific components pypiwin32==219; sys_platform == "win32" and python_version < "3" pypiwin32==223; sys_platform == "win32" and python_version > "3" -subprocess32==3.5.2; os_name == "posix" and platform_release != "2.6.18-194.el5xen" and platform_release != "2.6.18-274.el5xen" and python_version < "3" -# Needed for ssl_ECDHE_suites.js test - testing the TLS suites enabled on each platform -sslyze==2.0.1; python_version >= "3.6" and sys_platform == "win32" and (platform_machine == "x86_64" or platform_machine == "AMD64") -sslyze==2.0.1; python_version >= "3.6" and sys_platform == "cygwin" and (platform_machine == "x86_64" or platform_machine == "AMD64") -sslyze==2.0.1; python_version >= "3.6" and sys_platform == "linux" and (platform_machine == "x86_64" or platform_machine == "i686") +subprocess32==3.5.2; os_name == "posix" and platform_release != "2.6.18-194.el5xen" and platform_release != "2.6.18-274.el5xen" and python_version < "3"
\ No newline at end of file diff --git a/etc/pip/constraints.txt b/etc/pip/constraints.txt index 10dabb89676..27662e16b20 100644 --- a/etc/pip/constraints.txt +++ b/etc/pip/constraints.txt @@ -65,8 +65,4 @@ typed-ast==1.1.0; python_version > "3" # Platform-specific components pypiwin32==219; sys_platform == "win32" and python_version < "3" pypiwin32==223; sys_platform == "win32" and python_version > "3" -subprocess32==3.5.2; os_name == "posix" and platform_release != "2.6.18-194.el5xen" and platform_release != "2.6.18-274.el5xen" and python_version < "3" -# Needed for ssl_ECDHE_suites.js test - testing the TLS suites enabled on each platform -sslyze==2.0.1; python_version >= "3.6" and sys_platform == "win32" and (platform_machine == "x86_64" or platform_machine == "AMD64") -sslyze==2.0.1; python_version >= "3.6" and sys_platform == "cygwin" and (platform_machine == "x86_64" or platform_machine == "AMD64") -sslyze==2.0.1; python_version >= "3.6" and sys_platform == "linux" and (platform_machine == "x86_64" or platform_machine == "i686") +subprocess32==3.5.2; os_name == "posix" and platform_release != "2.6.18-194.el5xen" and platform_release != "2.6.18-274.el5xen" and python_version < "3"
\ No newline at end of file diff --git a/jstests/ssl/ssl_ECDHE_suites.js b/jstests/ssl/ssl_ECDHE_suites.js index bd01638f395..0e9839cae56 100644 --- a/jstests/ssl/ssl_ECDHE_suites.js +++ b/jstests/ssl/ssl_ECDHE_suites.js @@ -1,104 +1,77 @@ -// Test that a client can authenticate against the server with roles. -// Also validates RFC2253 +// Test that the server supports at least one ECDHE tls cipher suite. + load('jstests/ssl/libs/ssl_helpers.js'); (function() { "use strict"; + // Need to use toolchain python, which is unsupported on Windows + if (_isWindows()) { + return; + } + // Amazon linux does not currently support ECDHE const EXCLUDED_BUILDS = ['amazon', 'amzn64']; - const suites = [ - "SSLV2 Cipher Suites", - "SSLV3 Cipher Suites", - "TLSV1_0 Cipher Suites", - "TLSV1_1 Cipher Suites", - "TLSV1_2 Cipher Suites", - "TLSV1_3 Cipher Suites" - ]; const SERVER_CERT = "jstests/libs/server.pem"; + const OUTFILE = 'jstests/ssl/ciphers.json'; - function runSSLYze(port) { - let target_os = buildInfo().buildEnvironment.target_os; - let target_arch = buildInfo().buildEnvironment.target_arch; - - if (target_os === "macOS" || target_arch !== "x86_64") { - return null; - } + const suites = [ + 'sslv2', + 'sslv3', + 'tls1', + 'tls1_1', + 'tls1_2', + ]; - let python = "/usr/bin/env python3"; - let sslyze = " jstests/ssl/sslyze_tester.py "; + const x509_options = { + tlsMode: 'requireTLS', + tlsCAFile: CA_CERT, + tlsPEMKeyFile: SERVER_CERT, + ipv6: "", + bind_ip_all: "" + }; - if (_isWindows()) { - const paths = ["c:\\python36\\python.exe", "c:\\python\\python36\\python.exe"]; - for (let p of paths) { - if (fileExists(p)) { - python = p; - } - } - } + const mongod = MongoRunner.runMongod(x509_options); - const python_command = python + sslyze + "--port=" + port; - let ret = 0; - if (_isWindows()) { - ret = runProgram('cmd.exe', '/c', python_command); - } else { - ret = runProgram('/bin/sh', '-c', python_command); - } - assert.eq(ret, 0); + // Run the tls cipher suite enumerator + let python = "/usr/bin/env /opt/mongodbtoolchain/v2/bin/python3"; + let enumerator = " jstests/ssl/tls_enumerator.py "; + const python_command = python + enumerator + "--port=" + mongod.port + " --cafile=" + CA_CERT + + ' --cert=' + CLIENT_CERT + ' --outfile=' + OUTFILE; + assert.eq(runProgram('/bin/sh', '-c', python_command), 0); - try { - let ciphers = cat("jstests/ssl/ciphers.json"); - return JSON.parse(ciphers); - } catch (e) { - jsTestLog("Failed to parse ciphers.json"); - throw e; - } finally { - const python_delete_command = python + sslyze + "--delete"; - if (_isWindows()) { - ret = runProgram('cmd.exe', '/c', python_delete_command); - } else { - ret = runProgram('/bin/sh', '-c', python_delete_command); - } - assert.eq(ret, 0); - } + // Parse its output + let cipherDict = {}; + try { + cipherDict = JSON.parse(cat(OUTFILE)); + } catch (e) { + jsTestLog("Failed to parse ciphers.json"); + throw e; + } finally { + const delete_command = 'rm ' + OUTFILE; + assert.eq(runProgram('/bin/sh', '-c', delete_command), 0); } - function testSSLYzeOutput(cipherDict) { - // Checking that SSL 1.0, 2.0, 3.0 and TLS 1.0 are not accepted - suites.slice(0, 3).forEach(tlsVersion => assert(cipherDict[tlsVersion].length === 0)); + // Checking that SSLv2, SSLv3 and TLS 1.0 are not accepted + suites.slice(0, suites.indexOf('tls1')) + .forEach(tlsVersion => assert(cipherDict[tlsVersion].length === 0)); - // Printing TLS 1.1, 1.2, and 1.3 suites that are accepted - let hasECDHE = false; - suites.slice(3, 6).forEach(tlsVersion => { - print('*************************\n' + tlsVersion + ": "); - cipherDict[tlsVersion].forEach(cipher => { - print(cipher); - if (cipher.includes('ECDHE')) - hasECDHE = true; - }); + // Printing TLS 1.1 and 1.2 suites that are accepted + let hasECDHE = false; + suites.slice(suites.indexOf('tls1_1')).forEach(tlsVersion => { + print('*************************\n' + tlsVersion + ": "); + cipherDict[tlsVersion].forEach(cipher => { + print(cipher); + if (cipher.includes('ECDHE')) + hasECDHE = true; }); + }); - // All platforms except Amazon Linux 1 should support ECDHE - if (!EXCLUDED_BUILDS.includes(buildInfo().buildEnvironment.distmod)) { - assert(hasECDHE, 'Supports at least one ECDHE cipher suite'); - } + // All platforms except Amazon Linux 1 should support ECDHE + if (!EXCLUDED_BUILDS.includes(buildInfo().buildEnvironment.distmod)) { + assert(hasECDHE, 'Supports at least one ECDHE cipher suite'); } - print("1. Testing x.509 auth to mongod"); - { - const x509_options = { - tlsMode: "preferTLS", - tlsCAFile: CA_CERT, - tlsPEMKeyFile: SERVER_CERT, - ipv6: "", - bind_ip_all: "" - }; - let mongod = MongoRunner.runMongod(x509_options); - const cipherDict = runSSLYze(mongod.port); - if (cipherDict !== null) { - testSSLYzeOutput(cipherDict); - } - MongoRunner.stopMongod(mongod); - } + MongoRunner.stopMongod(mongod); }()); diff --git a/jstests/ssl/sslyze_tester.py b/jstests/ssl/sslyze_tester.py deleted file mode 100644 index 70dda5f8c62..00000000000 --- a/jstests/ssl/sslyze_tester.py +++ /dev/null @@ -1,52 +0,0 @@ -#! /usr/bin/env python3 - -# This is a wrapper file around the SSLYze package -# The SSLYze package is used for testing the TLS/SSL -# suites that we allow for connections. -import os, logging, json, argparse, urllib.parse -from sslyze.synchronous_scanner import SynchronousScanner -from sslyze.plugins import openssl_cipher_suites_plugin -from sslyze.plugins.openssl_cipher_suites_plugin import * -from sslyze.server_connectivity_tester import ServerConnectivityTester, ServerConnectivityError - -path = str(os.path.dirname(os.path.abspath(__file__))) -filename = f'{path}/ciphers.json' - -def server_scanner(p): - try: - logger = logging.getLogger(__name__) - logger.info("Opening file") - server_tester = ServerConnectivityTester( - hostname = 'localhost', - port=p - ) - server_info = server_tester.perform() - scanner = SynchronousScanner() - - results = dict() - suite_names = ['SSLV2 Cipher Suites', 'SSLV3 Cipher Suites', 'TLSV1_0 Cipher Suites', 'TLSV1_1 Cipher Suites', 'TLSV1_2 Cipher Suites', 'TLSV1_3 Cipher Suites'] - suites = [Sslv20ScanCommand, Sslv30ScanCommand, Tlsv10ScanCommand, Tlsv11ScanCommand, Tlsv12ScanCommand, Tlsv13ScanCommand] - - logger.info("Scanning SSL/TLS suites, writing to ciphers.json") - for suite_name, suite in zip(suite_names, suites): - scan = scanner.run_scan_command(server_info, suite()) - results[suite_name] = [cipher.name for cipher in scan.accepted_cipher_list] - - with open(filename, 'w') as outfile: - json.dump(results, outfile) - - except ServerConnectivityError as e: - raise RuntimeError(f'Could not connect to {e.server_info.hostname}: {e.error_message}') - -def main(): - parser = argparse.ArgumentParser(description='MongoDB SSL/TLS Suite Validation.') - parser.add_argument('-p', '--port', type=int, default=27017, help="Port to listen on") - parser.add_argument('-d', '--delete', action='store_true', help="Delete the ciphers.json file") - args = parser.parse_args() - if args.delete: - os.remove(filename) - else: - return server_scanner(args.port) - -if __name__ == '__main__': - main() diff --git a/jstests/ssl/tls_enumerator.py b/jstests/ssl/tls_enumerator.py new file mode 100644 index 00000000000..29b0e81190f --- /dev/null +++ b/jstests/ssl/tls_enumerator.py @@ -0,0 +1,75 @@ +import ssl +import socket +import json +import argparse + + +def enumerate_tls_ciphers(protocol_options, host, port, cert, cafile): + root_context = ssl.SSLContext(ssl.PROTOCOL_TLS) + root_context.options |= protocol_options + root_context.set_ciphers('ALL:COMPLEMENTOFALL:-PSK:-SRP') + + ciphers = set([cipher['name'] for cipher in root_context.get_ciphers()]) + + accepted_ciphers = [] + + for cipher_name in ciphers: + context = ssl.SSLContext(root_context.protocol) + context.set_ciphers(cipher_name) + context.options = root_context.options + context.load_verify_locations(cafile=cafile) + context.load_cert_chain(certfile=cert) + + with socket.socket(socket.AF_INET) as sock: + with context.wrap_socket(sock, server_hostname=host) as conn: + try: + conn.connect((host, port)) + except Exception as e: + continue + accepted_ciphers.append(cipher_name) + + return sorted(accepted_ciphers) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='MongoDB TLS Cipher Suite Enumerator') + parser.add_argument('--port', type=int, default=27017, help='Port to connect to') + parser.add_argument('-o', '--outfile', type=str, default='ciphers.json', help='file to write the output to') + parser.add_argument('--host', type=str, default='localhost', help='host to connect to') + parser.add_argument('--cafile', type=str, help='Path to CA certificate') + parser.add_argument('--cert', type=str, help='Path to client certificate') + args = parser.parse_args() + + exclude_ops = { + ssl.OP_NO_SSLv2, + ssl.OP_NO_SSLv3, + ssl.OP_NO_TLSv1, + ssl.OP_NO_TLSv1_1, + ssl.OP_NO_TLSv1_2, + } + + def exclude_except(op): + option = 0 + for other_op in exclude_ops - {op}: + option |= other_op + return option + + suites = { + 'sslv2': exclude_except(ssl.OP_NO_SSLv2), + 'sslv3': exclude_except(ssl.OP_NO_SSLv3), + 'tls1': exclude_except(ssl.OP_NO_TLSv1), + 'tls1_1': exclude_except(ssl.OP_NO_TLSv1_1), + 'tls1_2': exclude_except(ssl.OP_NO_TLSv1_2), + } + + results = { + key: enumerate_tls_ciphers(protocol_options=proto, + host=args.host, + port=args.port, + cafile=args.cafile, + cert=args.cert) + for key, proto in suites.items() + } + + with open(args.outfile, 'w+') as outfile: + json.dump(results, outfile) |