This is an automated email from the ASF dual-hosted git repository. smiklosovic pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push: new 917d74b add credentials file support for CQLSH 917d74b is described below commit 917d74bd35712f8374ecefd1d890d02916162daa Author: Bowen Song <bowens...@users.noreply.github.com> AuthorDate: Sat Sep 25 17:16:53 2021 +0100 add credentials file support for CQLSH patch by Bowen Song; reviewed by Brian Houser, Stefan Miklosovic and Brandon Williams for CASSANDRA-16983 --- CHANGES.txt | 1 + bin/cqlsh.py | 89 +++++++++++++++++++++++++++++++++++++++++----- conf/cqlshrc.sample | 4 +-- conf/credentials.sample | 25 +++++++++++++ doc/source/tools/cqlsh.rst | 14 ++++++++ 5 files changed, 122 insertions(+), 11 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 8e06776..f04525b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.1 + * add credentials file support to CQLSH (CASSANDRA-16983) * Skip remaining bytes in the Envelope buffer when a ProtocolException is thrown to avoid double decoding (CASSANDRA-17026) * Allow reverse iteration of resources during permissions checking (CASSANDRA-17016) * Add feature to verify correct ownership of attached locations on disk at startup (CASSANDRA-16879) diff --git a/bin/cqlsh.py b/bin/cqlsh.py index 37f839d..aa005ce 100755 --- a/bin/cqlsh.py +++ b/bin/cqlsh.py @@ -21,11 +21,13 @@ from __future__ import division, unicode_literals, print_function import cmd import codecs import csv +import errno import getpass import optparse import os import platform import re +import stat import sys import traceback import warnings @@ -209,8 +211,9 @@ parser.add_option('--debug', action='store_true', parser.add_option('--coverage', action='store_true', help='Collect coverage data') parser.add_option("--encoding", help="Specify a non-default encoding for output." - + " (Default: %s)" % (UTF8,)) + " (Default: %s)" % (UTF8,)) parser.add_option("--cqlshrc", help="Specify an alternative cqlshrc file location.") +parser.add_option("--credentials", help="Specify an alternative credentials file location.") parser.add_option('--cqlversion', default=None, help='Specify a particular CQL version, ' 'by default the highest version supported by the server will be used.' @@ -226,6 +229,13 @@ parser.add_option("--request-timeout", default=DEFAULT_REQUEST_TIMEOUT_SECONDS, parser.add_option("-t", "--tty", action='store_true', dest='tty', help='Force tty mode (command prompt).') +# This is a hidden option to suppress the warning when the -p/--password command line option is used. +# Power users may use this option if they know no other people has access to the system where cqlsh is run or don't care about security. +# Use of this option in scripting is discouraged. Please use a (temporary) credentials file where possible. +# The Cassandra distributed tests (dtests) also use this option in some tests when a well-known password is supplied via the command line. +parser.add_option("--insecure-password-without-warning", action='store_true', dest='insecure_password_without_warning', + help=optparse.SUPPRESS_HELP) + optvalues = optparse.Values() (options, arguments) = parser.parse_args(sys.argv[1:], values=optvalues) @@ -251,9 +261,9 @@ OLD_CONFIG_FILE = os.path.expanduser(os.path.join('~', '.cqlshrc')) if os.path.exists(OLD_CONFIG_FILE): if os.path.exists(CONFIG_FILE): print('\nWarning: cqlshrc config files were found at both the old location ({0})' - + ' and the new location ({1}), the old config file will not be migrated to the new' - + ' location, and the new location will be used for now. You should manually' - + ' consolidate the config files at the new location and remove the old file.' + ' and the new location ({1}), the old config file will not be migrated to the new' + ' location, and the new location will be used for now. You should manually' + ' consolidate the config files at the new location and remove the old file.' .format(OLD_CONFIG_FILE, CONFIG_FILE)) else: os.rename(OLD_CONFIG_FILE, CONFIG_FILE) @@ -2107,6 +2117,26 @@ def should_use_color(): return True +def is_file_secure(filename): + if is_win: + # We simply cannot tell whether the file is seucre on Windows, + # because os.stat().st_uid is always 0 and os.stat().st_mode is meaningless + return True + + try: + st = os.stat(filename) + except OSError as e: + if e.errno != errno.ENOENT: + raise + return True # the file doesn't exists, the security of it is irrelevant + + uid = os.getuid() + + # Skip enforcing the file owner and UID matching for the root user (uid == 0). + # This is to allow "sudo cqlsh" to work with user owned credentials file. + return (uid == 0 or st.st_uid == uid) and stat.S_IMODE(st.st_mode) & (stat.S_IRGRP | stat.S_IROTH) == 0 + + def read_options(cmdlineargs, environment): configs = configparser.SafeConfigParser() if sys.version_info < (3, 2) else configparser.ConfigParser() configs.read(CONFIG_FILE) @@ -2114,9 +2144,20 @@ def read_options(cmdlineargs, environment): rawconfigs = configparser.RawConfigParser() rawconfigs.read(CONFIG_FILE) + username_from_cqlshrc = option_with_default(configs.get, 'authentication', 'username') + password_from_cqlshrc = option_with_default(rawconfigs.get, 'authentication', 'password') + if username_from_cqlshrc or password_from_cqlshrc: + if password_from_cqlshrc and not is_file_secure(CONFIG_FILE): + print("\nWarning: Password is found in an insecure cqlshrc file. The file is owned or readable by other users on the system.", + end='', file=sys.stderr) + print("\nNotice: Credentials in the cqlshrc file is deprecated and will be ignored in the future." + "\nPlease use a credentials file to specify the username and password.\n", file=sys.stderr) + optvalues = optparse.Values() - optvalues.username = option_with_default(configs.get, 'authentication', 'username') - optvalues.password = option_with_default(rawconfigs.get, 'authentication', 'password') + optvalues.username = None + optvalues.password = None + optvalues.credentials = os.path.expanduser(option_with_default(configs.get, 'authentication', 'credentials', + os.path.join(HISTORY_DIR, 'credentials'))) optvalues.keyspace = option_with_default(configs.get, 'authentication', 'keyspace') optvalues.browser = option_with_default(configs.get, 'ui', 'browser', None) optvalues.completekey = option_with_default(configs.get, 'ui', 'completekey', @@ -2153,8 +2194,38 @@ def read_options(cmdlineargs, environment): optvalues.connect_timeout = option_with_default(configs.getint, 'connection', 'timeout', DEFAULT_CONNECT_TIMEOUT_SECONDS) optvalues.request_timeout = option_with_default(configs.getint, 'connection', 'request_timeout', DEFAULT_REQUEST_TIMEOUT_SECONDS) optvalues.execute = None + optvalues.insecure_password_without_warning = False (options, arguments) = parser.parse_args(cmdlineargs, values=optvalues) + + if not is_file_secure(options.credentials): + print("\nWarning: Credentials file '{0}' exists but is not used, because:" + "\n a. the file owner is not the current user; or" + "\n b. the file is readable by group or other." + "\nPlease ensure the file is owned by the current user and is not readable by group or other." + "\nOn a Linux or UNIX-like system, you often can do this by using the `chown` and `chmod` commands:" + "\n chown YOUR_USERNAME credentials" + "\n chmod 600 credentials\n".format(options.credentials), + file=sys.stderr) + options.credentials = '' # ConfigParser.read() will ignore unreadable files + + if not options.username: + credentials = configparser.SafeConfigParser() if sys.version_info < (3, 2) else configparser.ConfigParser() + credentials.read(options.credentials) + + # use the username from credentials file but fallback to cqlshrc if username is absent from the command line parameters + options.username = option_with_default(credentials.get, 'plain_text_auth', 'username', username_from_cqlshrc) + + if not options.password: + rawcredentials = configparser.RawConfigParser() + rawcredentials.read(options.credentials) + + # handling password in the same way as username, priority cli > credentials > cqlshrc + options.password = option_with_default(rawcredentials.get, 'plain_text_auth', 'password', password_from_cqlshrc) + elif not options.insecure_password_without_warning: + print("\nWarning: Using a password on the command line interface can be insecure." + "\nRecommendation: use the credentials file to securely provide the password.\n", file=sys.stderr) + # Make sure some user values read from the command line are in unicode options.execute = maybe_ensure_text(options.execute) options.username = maybe_ensure_text(options.username) @@ -2295,9 +2366,9 @@ def main(options, hostname, port): # does contain a TZ part) was specified if options.time_format != DEFAULT_TIMESTAMP_FORMAT: sys.stderr.write("Warning: custom timestamp format specified in cqlshrc, " - + "but local timezone could not be detected.\n" - + "Either install Python 'tzlocal' module for auto-detection " - + "or specify client timezone in your cqlshrc.\n\n") + "but local timezone could not be detected.\n" + "Either install Python 'tzlocal' module for auto-detection " + "or specify client timezone in your cqlshrc.\n\n") try: shell = Shell(hostname, diff --git a/conf/cqlshrc.sample b/conf/cqlshrc.sample index 2c00d4a..e79d970 100644 --- a/conf/cqlshrc.sample +++ b/conf/cqlshrc.sample @@ -19,8 +19,8 @@ [authentication] ;; If Cassandra has auth enabled, fill out these options -; username = fred -; password = !!bang!!$ +;; Path to the credentials file, an initial ~ or ~user is expanded to that user's home directory +; credentials = ~/.cassandra/credentials ; keyspace = ks1 diff --git a/conf/credentials.sample b/conf/credentials.sample new file mode 100644 index 0000000..9b5c644 --- /dev/null +++ b/conf/credentials.sample @@ -0,0 +1,25 @@ +; Licensed to the Apache Software Foundation (ASF) under one +; or more contributor license agreements. See the NOTICE file +; distributed with this work for additional information +; regarding copyright ownership. The ASF licenses this file +; to you under the Apache License, Version 2.0 (the +; "License"); you may not use this file except in compliance +; with the License. You may obtain a copy of the License at +; +; http://www.apache.org/licenses/LICENSE-2.0 +; +; Unless required by applicable law or agreed to in writing, +; software distributed under the License is distributed on an +; "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +; KIND, either express or implied. See the License for the +; specific language governing permissions and limitations +; under the License. +; +; Sample ~/.cassandra/credentials file. +; +; Please ensure this file is owned by the user and is not readable by group and other users + +[plain_text_auth] +; username = fred +; password = !!bang!!$ + diff --git a/doc/source/tools/cqlsh.rst b/doc/source/tools/cqlsh.rst index 2f47554..d9dd057 100644 --- a/doc/source/tools/cqlsh.rst +++ b/doc/source/tools/cqlsh.rst @@ -47,6 +47,16 @@ The ``cqlshrc`` file holds configuration options for cqlsh. By default this is Example config values and documentation can be found in the ``conf/cqlshrc.sample`` file of a tarball installation. You can also view the latest version of `cqlshrc online <https://github.com/apache/cassandra/blob/trunk/conf/cqlshrc.sample>`__. +credentials +^^^^^^^^^^^ + +The ``credentials`` file holds authentication credentials for cqlsh. By default this is in the user's home directory at +``~/.cassandra/credentials``, but a custom location can be specified with the ``--credentials`` option. +This file must be owned by the same user running the ``cqlsh``, and it must not be readable by group and other. + +Example config values and documentation can be found in the ``conf/credentials.sample`` file of a tarball installation. You +can also view the latest version of `credentials online <https://github.com/apache/cassandra/blob/trunk/conf/credentials.sample>`__. + Command Line Options ^^^^^^^^^^^^^^^^^^^^ @@ -77,6 +87,7 @@ Options: ``-p`` ``--password`` Password to authenticate against Cassandra with, should be used in conjunction with ``--user`` + Insecure. Use ``credentials`` file if you can. ``-k`` ``--keyspace`` Keyspace to authenticate to, should be used in conjunction @@ -94,6 +105,9 @@ Options: ``--cqlshrc`` Specify a non-default location for the ``cqlshrc`` file +``--credentials`` + Specify a non-default location for the ``credentials`` file + ``-e`` ``--execute`` Execute the given statement, then exit --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org