Skip to content

Commit

Permalink
Added support for SSL connection & username auth.
Browse files Browse the repository at this point in the history
  • Loading branch information
matriv committed Jun 27, 2017
1 parent a9ece9d commit 628e6c8
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 33 deletions.
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions docs/cli.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ The ``crash`` binary supports several command line arguments.
+---------------------------------------+--------------------------------------------------+
| ``-v``, ``--verbose`` | Print debug information to stdout. |
+---------------------------------------+--------------------------------------------------+
| ``-U``, ``--username`` | The username to authenticate in the database |
+---------------------------------------+--------------------------------------------------+
| ``-c COMMAND``, ``--command COMMAND`` | Execute the SQL statement and exit. |
+---------------------------------------+--------------------------------------------------+
| ``--hosts HOSTS`` | The Crate hosts to connect to. |
Expand Down Expand Up @@ -44,6 +46,16 @@ The ``crash`` binary supports several command line arguments.
| | while typing. This feature is experimental and |
| | may be removed in future versions. |
+---------------------------------------+--------------------------------------------------+
| ``--verify-ssl`` | Force verification of the SSL certificate of the |
| | server. |
+---------------------------------------+--------------------------------------------------+
| ``--cert-file`` | Path to the client certificate file. |
+---------------------------------------+--------------------------------------------------+
| ``--key-file`` | Path to the key file of the client certificate. |
+---------------------------------------+--------------------------------------------------+
| ``--ca-cert-file`` | Path to CA certificate file (used to verify |
| | the certificate sent by the server. |
+---------------------------------------+--------------------------------------------------+

Example Usage
=============
Expand All @@ -55,6 +67,18 @@ Example Usage
-c 'select * from sys.nodes' > output.json


Example Usage with SSL
======================

::

$ crash --hosts st1.crate.io:4200 st2.crate.io st3.crate.io \
--format json \
--verify-ssl true --cert-file /home/.certs/crateClient.crt --key-file /home/.certs/crateClient.key \
--ca-cert-file /home/.certs/rootCA.crt -U sslUser
-c 'select * from sys.nodes' > output.json


.. _commands:

==============
Expand Down
84 changes: 53 additions & 31 deletions src/crate/crash/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,25 @@

from __future__ import print_function

import logging
import os
import sys
import re
import logging

import sys
import urllib3
from appdirs import user_data_dir, user_config_dir
from argparse import ArgumentParser
from collections import namedtuple

from ..crash import __version__ as crash_version
from crate.client import connect
from crate.client.exceptions import ConnectionError, ProgrammingError
from distutils.version import StrictVersion


from .commands import built_in_commands, Command
from .config import Configuration, ConfigurationError
from .printer import ColorPrinter, PrintWrapper
from .outputs import OutputWriter
from .printer import ColorPrinter, PrintWrapper
from .sysinfo import SysInfoCommand
from .commands import built_in_commands, Command

from distutils.version import StrictVersion

from appdirs import user_data_dir, user_config_dir
from ..crash import __version__ as crash_version

import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


Expand Down Expand Up @@ -138,7 +133,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 to authenticate in the database')
parser.add_argument('--history', type=str,
help='the history file to use', default=HISTORY_PATH)
parser.add_argument('--config', type=str,
Expand All @@ -153,11 +149,14 @@ def _conf_or_default(key, value):
parser.add_argument('--hosts', type=str, nargs='*',
default=_conf_or_default('hosts', ['localhost:4200']),
help='the crate hosts to connect to', metavar='HOST')
parser.add_argument('--verify-ssl', type=boolean, default=True)
parser.add_argument('--verify-ssl', type=boolean, default=True,
help='force verification of the SSL certificate of the server')
parser.add_argument('--cert-file', type=str,
help='path to client certificate')
help='path to the client certificate file')
parser.add_argument('--key-file', type=str,
help='path to the key file for the client certificate')
help='path to the key file of 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,
Expand Down Expand Up @@ -186,7 +185,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()
Expand All @@ -207,6 +211,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
Expand Down Expand Up @@ -275,7 +284,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
Expand All @@ -295,7 +306,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)

Expand Down Expand Up @@ -437,18 +448,11 @@ def main():
if args.version:
printer.info(crash_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)
cmd = CrateCmd(connection=conn,
error_trace=error_trace,
output_writer=output_writer,
is_tty=is_tty,
autocomplete=args.autocomplete,
autocapitalize=args.autocapitalize)
error_trace = args.verbose > 0
cmd = _create_cmd(crate_hosts, error_trace, output_writer, is_tty, args)

if error_trace:
# log CONNECT command only in verbose mode
cmd._connect(crate_hosts)
Expand All @@ -472,6 +476,24 @@ def main():
cmd.exit()
sys.exit(cmd.exit_code)

def _create_cmd(crate_hosts, error_trace, output_writer, is_tty, args):
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()
44 changes: 43 additions & 1 deletion src/crate/crash/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import os
import re
import ssl
from unittest import TestCase
from six import PY2, StringIO
import tempfile
Expand All @@ -12,7 +13,8 @@
from crate.client.exceptions import ProgrammingError

from .command import CrateCmd, main, get_stdin, noargs_command, Result, \
host_and_port, get_information_schema_query, stmt_type
host_and_port, get_information_schema_query, stmt_type, _create_cmd, \
get_parser, parse_args
from .outputs import _val_len as val_len, OutputWriter
from .printer import ColorPrinter
from .commands import Command
Expand Down Expand Up @@ -516,6 +518,46 @@ 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"
]
parser = get_parser()
args = parse_args(parser)
crate_hosts = [host_and_port(h) for h in args.hosts]
crateCmd = _create_cmd(crate_hosts, False, None, False, args)

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"
]
parser = get_parser()
args = parse_args(parser)

crate_hosts = [host_and_port(h) for h in args.hosts]
crateCmd = _create_cmd(crate_hosts, False, None, False, args)

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):
Expand Down
2 changes: 1 addition & 1 deletion versions.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 628e6c8

Please sign in to comment.