Merge branch 'cassandra-5.0' into trunk

* cassandra-5.0:
  Add option to disable cqlsh history
This commit is contained in:
mck 2026-03-17 23:08:39 +01:00
commit ee273af1d6
No known key found for this signature in database
GPG Key ID: E91335D77E3E87CB
4 changed files with 53 additions and 8 deletions

View File

@ -322,6 +322,7 @@ Merged from 4.1:
* Disk usage guardrail cannot be disabled when failure threshold is reached (CASSANDRA-21057) * Disk usage guardrail cannot be disabled when failure threshold is reached (CASSANDRA-21057)
* ReadCommandController should close fast to avoid deadlock when building secondary index (CASSANDRA-19564) * ReadCommandController should close fast to avoid deadlock when building secondary index (CASSANDRA-19564)
Merged from 4.0: Merged from 4.0:
* Add option to disable cqlsh history (CASSANDRA-21180)
* Rate limit password changes (CASSANDRA-21202) * Rate limit password changes (CASSANDRA-21202)
* Node does not send multiple inflight echos (CASSANDRA-18866) * Node does not send multiple inflight echos (CASSANDRA-18866)
* Obsolete expired SSTables before compaction starts (CASSANDRA-19776) * Obsolete expired SSTables before compaction starts (CASSANDRA-19776)

View File

@ -104,6 +104,15 @@ port = 9042
; max_trace_wait = 10.0 ; max_trace_wait = 10.0
[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] ;[ssl]
; certfile = ~/keys/cassandra.cert ; certfile = ~/keys/cassandra.cert

View File

@ -57,8 +57,8 @@ Example config values and documentation can be found in the
[[cql_history]] [[cql_history]]
== 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. All CQL commands you execute are written to a history file. By default, CQL history will be written to `~/.cassandra/cql_history`.
This feature is supported from Cassandra 4.1. 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 == Command Line Options

View File

@ -25,6 +25,7 @@ import subprocess
import sys import sys
import time import time
import traceback import traceback
from typing import Any
import warnings import warnings
import webbrowser import webbrowser
from contextlib import contextmanager from contextlib import contextmanager
@ -120,6 +121,16 @@ try:
except OSError: except OSError:
print('\nWarning: Cannot create directory at `%s`. Command history will not be saved. Please check what was the environment property CQL_HISTORY set to.\n' % HISTORY_DIR) print('\nWarning: Cannot create directory at `%s`. Command history will not be saved. Please check what was the environment property CQL_HISTORY set to.\n' % HISTORY_DIR)
OLD_HISTORY = os.path.expanduser(os.path.join('~', '.cqlsh_history'))
if os.path.exists(OLD_HISTORY):
if os.path.exists(HISTORY):
print('\nWarning: .cqlsh_history files were found at both the old location ({0})'
+ ' and the new location ({1}), the old file will not be migrated to the new'
+ ' location, and the new location will be used for now. You should manually'
+ ' consolidate these files at the new location, and remove the old file.'
.format(OLD_HISTORY, HISTORY))
else:
os.rename(OLD_HISTORY, HISTORY)
# END history config # END history config
@ -272,7 +283,8 @@ class Shell(cmd.Cmd):
protocol_version=None, protocol_version=None,
connect_timeout=DEFAULT_CONNECT_TIMEOUT_SECONDS, connect_timeout=DEFAULT_CONNECT_TIMEOUT_SECONDS,
is_subshell=False, is_subshell=False,
auth_provider=None): auth_provider=None,
disable_history=False):
cmd.Cmd.__init__(self, completekey=completekey) cmd.Cmd.__init__(self, completekey=completekey)
self.hostname = hostname self.hostname = hostname
self.port = port self.port = port
@ -354,6 +366,14 @@ class Shell(cmd.Cmd):
self.check_build_versions() self.check_build_versions()
if tty: 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.reset_prompt()
self.report_connection() self.report_connection()
print('Use HELP for help.') print('Use HELP for help.')
@ -1896,8 +1916,8 @@ class Shell(cmd.Cmd):
delims += '.' delims += '.'
readline.set_completer_delims(delims) readline.set_completer_delims(delims)
def save_history(self): def save_history(self, history_disabled=False):
if readline is not None: if readline is not None and not history_disabled:
try: try:
readline.write_history_file(HISTORY) readline.write_history_file(HISTORY)
except IOError: except IOError:
@ -2037,7 +2057,7 @@ def read_options(cmdlineargs, parser, config_file, cql_dir, environment=os.envir
argvalues.request_timeout = option_with_default(configs.getint, 'connection', 'request_timeout', Shell.DEFAULT_REQUEST_TIMEOUT_SECONDS) argvalues.request_timeout = option_with_default(configs.getint, 'connection', 'request_timeout', Shell.DEFAULT_REQUEST_TIMEOUT_SECONDS)
argvalues.execute = None argvalues.execute = None
argvalues.insecure_password_without_warning = False 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) options, arguments = parser.parse_known_args(cmdlineargs, argvalues)
# Credentials from cqlshrc will be expanded, # Credentials from cqlshrc will be expanded,
@ -2208,6 +2228,8 @@ def main(cmdline, pkgpath):
help='Specify the default request timeout in seconds (default: %(default)s seconds).') help='Specify the default request timeout in seconds (default: %(default)s seconds).')
parser.add_argument("-t", "--tty", action='store_true', dest='tty', parser.add_argument("-t", "--tty", action='store_true', dest='tty',
help='Force tty mode (command prompt).') 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. # 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. # 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.
@ -2233,6 +2255,18 @@ def main(cmdline, pkgpath):
config_file = default_cqlshrc config_file = default_cqlshrc
cql_dir = os.path.dirname(config_file) 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)
(options, hostname, port) = read_options(cmdline, parser, config_file, cql_dir) (options, hostname, port) = read_options(cmdline, parser, config_file, cql_dir)
docspath = get_docspath(pkgpath) docspath = get_docspath(pkgpath)
@ -2332,7 +2366,8 @@ def main(cmdline, pkgpath):
config_file=config_file, config_file=config_file,
cred_file=options.credentials, cred_file=options.credentials,
username=options.username, username=options.username,
password=options.password)) password=options.password),
disable_history=options.disable_history)
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit('Connection aborted.') sys.exit('Connection aborted.')
except CQL_ERRORS as e: except CQL_ERRORS as e:
@ -2353,7 +2388,7 @@ def main(cmdline, pkgpath):
shell.init_history() shell.init_history()
shell.cmdloop() shell.cmdloop()
shell.save_history() shell.save_history(options.disable_history)
if shell.batch_mode and shell.statement_error: if shell.batch_mode and shell.statement_error:
sys.exit(2) sys.exit(2)