From cae226d4b4214ad0a30fe9f43f32f20ee7d0c11d Mon Sep 17 00:00:00 2001 From: Marios Trivyzas Date: Mon, 26 Jun 2017 16:51:58 +0200 Subject: [PATCH] Added support for SSL connection & username auth. --- CHANGES.txt | 4 +++ src/crate/crash/command.py | 62 +++++++++++++++++++++++++++++---- src/crate/crash/test_command.py | 34 +++++++++++++++++- versions.cfg | 2 +- 4 files changed, 94 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 29e8cae6..b34b3490 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,10 @@ Changes for crash Unreleased ========== + - Added support for SSL connection to CrateDB. + + - Added new parameter ``username`` used to authenticate the user in CrateDB. + - Improved queries for ``sysinfo`` command - Fix: Single word statements (such as ``BEGIN;``) that return ``OK`` crashed diff --git a/src/crate/crash/command.py b/src/crate/crash/command.py index 287da4e4..417cda36 100644 --- a/src/crate/crash/command.py +++ b/src/crate/crash/command.py @@ -138,7 +138,8 @@ def _conf_or_default(key, value): dest='autocapitalize', default=False, help='use -a to enable experimental auto-capitalization of SQL keywords') - + parser.add_argument('-U', '--username', type=str, + help='the username in the database') parser.add_argument('--history', type=str, help='the history file to use', default=HISTORY_PATH) parser.add_argument('--config', type=str, @@ -158,6 +159,8 @@ def _conf_or_default(key, value): help='path to client certificate') parser.add_argument('--key-file', type=str, help='path to the key file for the client certificate') + parser.add_argument('--ca-cert-file', type=str, + help='path to the CA certificate file') parser.add_argument('--format', type=str, default=_conf_or_default('format', 'tabular'), choices=output_formats, @@ -186,7 +189,12 @@ def __init__(self, error_trace=False, is_tty=True, autocomplete=True, - autocapitalize=True): + autocapitalize=True, + verify_ssl=True, + cert_file=None, + key_file=None, + ca_cert_file=None, + username=None): self.error_trace = error_trace self.connection = connection or connect(error_trace=error_trace) self.cursor = self.connection.cursor() @@ -207,6 +215,11 @@ def __init__(self, self.logger = ColorPrinter(is_tty) self._autocomplete = autocomplete self._autocapitalize = autocapitalize + self.username = username + self.verify_ssl = verify_ssl + self.cert_file = cert_file + self.key_file = key_file + self.ca_cert_file = ca_cert_file def get_num_columns(self): return 80 @@ -275,7 +288,9 @@ def is_conn_avaliable(self): def _connect(self, server): """ connect to the given server, e.g.: \connect localhost:4200 """ - self.connection = connect(servers=server, error_trace=self.error_trace) + self.connection = connect(servers=server, error_trace=self.error_trace, verify_ssl_cert=self.verify_ssl, + cert_file=self.cert_file, key_file=self.key_file, ca_cert=self.ca_cert_file, + username=self.username) self.cursor = self.connection.cursor() results = [] failed = 0 @@ -295,7 +310,7 @@ def _connect(self, server): else: self.logger.info('CONNECT OK') SysInfoCommand.CLUSTER_INFO['information_schema_query'] = \ - get_information_schema_query(self.connection.lowest_server_version) + get_information_schema_query(self.connection.lowest_server_version) # check for failing node and cluster checks built_in_commands['check'](self, startup=True) @@ -442,13 +457,20 @@ def main(): conn = connect(crate_hosts, verify_ssl_cert=args.verify_ssl, cert_file=args.cert_file, - key_file=args.key_file) + key_file=args.key_file, + ca_cert=args.ca_cert_file, + username=args.username) cmd = CrateCmd(connection=conn, error_trace=error_trace, output_writer=output_writer, is_tty=is_tty, autocomplete=args.autocomplete, - autocapitalize=args.autocapitalize) + autocapitalize=args.autocapitalize, + verify_ssl=args.verify_ssl, + cert_file=args.cert_file, + key_file=args.key_file, + ca_cert_file=args.ca_cert_file, + username=args.username) if error_trace: # log CONNECT command only in verbose mode cmd._connect(crate_hosts) @@ -472,6 +494,34 @@ def main(): cmd.exit() sys.exit(cmd.exit_code) +def test_main(): + is_tty = sys.stdout.isatty() + output_writer = OutputWriter(PrintWrapper(), is_tty) + parser = get_parser(output_writer.formats) + args = parse_args(parser) + output_writer.output_format = args.format + + if args.version: + sys.exit(0) + error_trace = args.verbose > 0 + crate_hosts = [host_and_port(h) for h in args.hosts] + conn = connect(crate_hosts, + verify_ssl_cert=args.verify_ssl, + cert_file=args.cert_file, + key_file=args.key_file, + ca_cert=args.ca_cert_file, + username=args.username) + return CrateCmd(connection=conn, + error_trace=error_trace, + output_writer=output_writer, + is_tty=is_tty, + autocomplete=args.autocomplete, + autocapitalize=args.autocapitalize, + verify_ssl=args.verify_ssl, + cert_file=args.cert_file, + key_file=args.key_file, + ca_cert_file=args.ca_cert_file, + username=args.username) if __name__ == '__main__': main() diff --git a/src/crate/crash/test_command.py b/src/crate/crash/test_command.py index 2ebf323c..1dfee7bf 100644 --- a/src/crate/crash/test_command.py +++ b/src/crate/crash/test_command.py @@ -4,6 +4,7 @@ import sys import os import re +import ssl from unittest import TestCase from six import PY2, StringIO import tempfile @@ -11,7 +12,7 @@ from mock import patch, Mock from crate.client.exceptions import ProgrammingError -from .command import CrateCmd, main, get_stdin, noargs_command, Result, \ +from .command import CrateCmd, main, test_main, get_stdin, noargs_command, Result, \ host_and_port, get_information_schema_query, stmt_type from .outputs import _val_len as val_len, OutputWriter from .printer import ColorPrinter @@ -516,6 +517,37 @@ def my_cmd(self, *args): self.assertEqual(None, text) self.assertEqual('Command does not take any arguments.\n', output.getvalue()) + def test_username_param(self): + sys.argv = ["testcrash", + "--hosts", self.crate_host, + "--username", "testUser" + ] + CrateCmd = test_main() + self.assertEqual(CrateCmd.username, "testUser") + self.assertEqual(CrateCmd.connection.client.username, "testUser") + + def test_ssl_params(self): + sys.argv = ["testcrash", + "--hosts", self.crate_host, + "--verify-ssl", "false", + "--cert-file", "cert_file", + "--key-file", "key_file", + "--ca-cert-file", "ca_cert_file" + ] + CrateCmd = test_main() + self.assertEqual(CrateCmd.verify_ssl, False) + self.assertEqual(CrateCmd.connection.client._pool_kw['cert_reqs'], ssl.CERT_NONE) + + self.assertEqual(CrateCmd.cert_file, 'cert_file') + self.assertEqual(CrateCmd.connection.client._pool_kw['cert_file'], 'cert_file') + + self.assertEqual(CrateCmd.key_file, 'key_file') + self.assertEqual(CrateCmd.connection.client._pool_kw['key_file'], 'key_file') + + self.assertEqual(CrateCmd.ca_cert_file, 'ca_cert_file') + self.assertEqual(CrateCmd.connection.client._pool_kw['ca_certs'], 'ca_cert_file') + + class TestGetInformationSchemaQuery(TestCase): def test_low_version(self): diff --git a/versions.cfg b/versions.cfg index 6c4afac1..cd69cc78 100644 --- a/versions.cfg +++ b/versions.cfg @@ -10,7 +10,7 @@ argparse = 1.1 codecov = 1.6.3 collective.recipe.omelette = 0.16 coverage = 4.0.3 -crate = 0.19.2 +crate = 0.20.0 docutils = 0.12 gp.recipe.tox = 0.4 hexagonit.recipe.download = 1.7.1