diff --git a/CHANGES.txt b/CHANGES.txt index 14b7b37818..94dd08eaa8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,7 @@ * ReadCommandController should close fast to avoid deadlock when building secondary index (CASSANDRA-19564) * Redact security-sensitive information in system_views.settings (CASSANDRA-20856) Merged from 4.0: + * Add option to disable cqlsh history (CASSANDRA-21180) * Rate limit password changes (CASSANDRA-21202) * Node does not send multiple inflight echos (CASSANDRA-18866) * Obsolete expired SSTables before compaction starts (CASSANDRA-19776) diff --git a/bin/cqlsh.py b/bin/cqlsh.py index 3cf7219e84..2737b8b71b 100755 --- a/bin/cqlsh.py +++ b/bin/cqlsh.py @@ -153,8 +153,7 @@ from cqlshlib.formatting import (DEFAULT_DATE_FORMAT, DEFAULT_NANOTIME_FORMAT, DEFAULT_TIMESTAMP_FORMAT, CqlType, DateTimeFormat, format_by_type) from cqlshlib.tracing import print_trace, print_trace_session -from cqlshlib.util import get_file_encoding_bomsize -from cqlshlib.util import is_file_secure +from cqlshlib.util import get_file_encoding_bomsize, is_file_secure from cqlshlib.serverversion import version as build_version try: from cqlshlib.serverversion import version as build_version @@ -223,6 +222,8 @@ parser.add_argument("--request-timeout", default=DEFAULT_REQUEST_TIMEOUT_SECONDS help='Specify the default request timeout in seconds (default: %(default)s seconds).') parser.add_argument("-t", "--tty", action='store_true', dest='tty', help='Force tty mode (command prompt).') +parser.add_argument('--disable-history', default=False, action='store_true', + help='Disable saving of history (existing history will still be loaded)') # 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. @@ -274,6 +275,22 @@ else: CQL_DIR = os.path.dirname(CONFIG_FILE) +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.' + .format(OLD_CONFIG_FILE, CONFIG_FILE)) + else: + os.rename(OLD_CONFIG_FILE, CONFIG_FILE) +OLD_HISTORY = os.path.expanduser(os.path.join('~', '.cqlsh_history')) +if os.path.exists(OLD_HISTORY): + os.rename(OLD_HISTORY, HISTORY) + +# END history/config definition + CQL_ERRORS = ( cassandra.AlreadyExists, cassandra.AuthenticationFailed, cassandra.CoordinationFailure, cassandra.InvalidRequest, cassandra.Timeout, cassandra.Unauthorized, cassandra.OperationTimedOut, @@ -449,7 +466,8 @@ class Shell(cmd.Cmd): protocol_version=None, connect_timeout=DEFAULT_CONNECT_TIMEOUT_SECONDS, is_subshell=False, - auth_provider=None): + auth_provider=None, + disable_history=False): cmd.Cmd.__init__(self, completekey=completekey) self.hostname = hostname self.port = port @@ -528,9 +546,18 @@ class Shell(cmd.Cmd): self.check_build_versions() if tty: + # Inform users about history logging if not disabled + if not disable_history and readline is not None: + print() + print("ATTENTION: All commands will be saved to history file: %s" % HISTORY) + print("This may include sensitive information such as passwords.") + print("To disable history, use --disable-history or set 'disabled = true' in the [history] section of cqlshrc.") + print("See https://cassandra.apache.org/doc/latest/tools/cqlsh.html for more information.") + print() self.reset_prompt() self.report_connection() print('Use HELP for help.') + else: self.show_line_nums = True self.stdin = stdin @@ -2137,6 +2164,7 @@ def read_options(cmdlineargs, environment): argvalues.request_timeout = option_with_default(configs.getint, 'connection', 'request_timeout', DEFAULT_REQUEST_TIMEOUT_SECONDS) argvalues.execute = None argvalues.insecure_password_without_warning = False + argvalues.disable_history = option_with_default(configs.getboolean, 'history', 'disabled', False) options, arguments = parser.parse_known_args(cmdlineargs, argvalues) @@ -2258,8 +2286,8 @@ def init_history(): readline.set_completer_delims(delims) -def save_history(): - if readline is not None: +def save_history(history_disabled=False): + if readline is not None and not history_disabled: try: readline.write_history_file(HISTORY) except IOError: @@ -2348,7 +2376,8 @@ def main(options, hostname, port): config_file=CONFIG_FILE, cred_file=options.credentials, username=options.username, - password=options.password)) + password=options.password), + disable_history=options.disable_history) except KeyboardInterrupt: sys.exit('Connection aborted.') except CQL_ERRORS as e: @@ -2368,7 +2397,7 @@ def main(options, hostname, port): signal.signal(signal.SIGHUP, handle_sighup) shell.cmdloop() - save_history() + save_history(options.disable_history) if shell.batch_mode and shell.statement_error: sys.exit(2) diff --git a/conf/cqlshrc.sample b/conf/cqlshrc.sample index 4878b589bc..7d944f580b 100644 --- a/conf/cqlshrc.sample +++ b/conf/cqlshrc.sample @@ -108,6 +108,16 @@ port = 9042 +[history] +;; Controls whether command history is saved to ~/.cassandra/cqlsh_history +;; When disabled, existing history will still be loaded but new commands +;; will not be saved. This can be useful for security purposes to prevent +;; sensitive commands (e.g., those containing passwords) from being persisted. +;; This can also be controlled via the --disable-history command line option. +; disabled = false + + + ;[ssl] ; certfile = ~/keys/cassandra.cert diff --git a/doc/modules/cassandra/pages/tools/cqlsh.adoc b/doc/modules/cassandra/pages/tools/cqlsh.adoc index ba9d36e703..73b4535949 100644 --- a/doc/modules/cassandra/pages/tools/cqlsh.adoc +++ b/doc/modules/cassandra/pages/tools/cqlsh.adoc @@ -38,7 +38,7 @@ modules that are central to the performance of `COPY`. == cqlshrc The `cqlshrc` file holds configuration options for `cqlsh`. -By default, the file is locagted the user's home directory at `~/.cassandra/cqlsh`, but a +By default, the file is located the user's home directory at `~/.cassandra/cqlsh`, but a custom location can be specified with the `--cqlshrc` option. Example config values and documentation can be found in the @@ -59,8 +59,8 @@ Example config values and documentation can be found in the [[cql_history]] == cql history -All CQL commands you execute are written to a history file. By default, CQL history will be written to `~/.cassandra/cql_history`. You can change this default by setting the environment variable `CQL_HISTORY` like `~/some/other/path/to/cqlsh_history` where `cqlsh_history` is a file. All parent directories to history file will be created if they do not exist. If you do not want to persist history, you can do so by setting CQL_HISTORY to /dev/null. -This feature is supported from Cassandra 4.1. +All CQL commands you execute are written to a history file. By default, CQL history will be written to `~/.cassandra/cql_history`. +You can change this default by setting the environment variable `CQL_HISTORY` like `~/some/other/path/to/cqlsh_history` where `cqlsh_history` is a file. All parent directories to history file will be created if they do not exist. If you do not want to persist history, you can do so by setting CQL_HISTORY to `/dev/null`. This can also be controlled via the --disable-history command line option or from the cqlshrc file. Disabling history is supported since Cassandra 4.0. == Command Line Options