From 30ad754d7e95501ffa916bf986e4cfda1aa5e441 Mon Sep 17 00:00:00 2001 From: Bereng Date: Tue, 1 Mar 2022 07:44:19 +0100 Subject: [PATCH] Pre hashed passwords in CQL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit patch by Robert Stupp and Berenguer Blasi; reviewed by Andres de la Peña for CASSANDRA-17334 Co-authored-by: Robert Stupp Co-authored-by: Berenguer Blasi --- CHANGES.txt | 1 + NEWS.txt | 1 + debian/cassandra.install | 1 + pylib/cqlshlib/cql3handling.py | 4 +- redhat/cassandra.spec | 1 + src/antlr/Lexer.g | 1 + src/antlr/Parser.g | 27 ++- .../cassandra/auth/CassandraRoleManager.java | 18 +- .../apache/cassandra/auth/IRoleManager.java | 2 +- .../apache/cassandra/auth/RoleOptions.java | 41 +++- .../cassandra/cql3/PasswordObfuscator.java | 12 +- .../apache/cassandra/tools/HashPassword.java | 195 ++++++++++++++++++ .../cassandra/audit/AuditLoggerAuthTest.java | 80 ++++++- .../auth/CreateAndAlterRoleTest.java | 149 +++++++++++++ .../cassandra/auth/RoleOptionsTest.java | 15 +- .../cql3/PasswordObfuscatorTest.java | 175 +++++++++++++--- .../cassandra/tools/HashPasswordTest.java | 150 ++++++++++++++ .../apache/cassandra/tools/ToolRunner.java | 12 +- tools/bin/hash_password | 53 +++++ 19 files changed, 884 insertions(+), 54 deletions(-) create mode 100644 src/java/org/apache/cassandra/tools/HashPassword.java create mode 100644 test/unit/org/apache/cassandra/auth/CreateAndAlterRoleTest.java create mode 100644 test/unit/org/apache/cassandra/tools/HashPasswordTest.java create mode 100755 tools/bin/hash_password diff --git a/CHANGES.txt b/CHANGES.txt index d54a5a70f8..3ba98e9c41 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,7 @@ * Log largest memtable flush at info instead of debug (CASSANDRA-17472) * Add native transport rate limiter options to example cassandra.yaml, and expose metric for dispatch rate (CASSANDRA-17423) * Add diagnostic events for guardrails (CASSANDRA-17197) + * Pre hashed passwords in CQL (CASSANDRA-17334) * Increase cqlsh version (CASSANDRA-17432) * Update SUPPORTED_UPGRADE_PATHS to include 3.0 and 3.x to 4.1 paths and remove obsolete tests (CASSANDRA-17362) * Support DELETE in CQLSSTableWriter (CASSANDRA-14797) diff --git a/NEWS.txt b/NEWS.txt index 5cd9fa224b..1755c732d2 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -58,6 +58,7 @@ New features ------------ - Support for native transport rate limiting via native_transport_rate_limiting_enabled and native_transport_max_requests_per_second in cassandra.yaml. + - Support for pre hashing passwords on CQL DCL commands - Expose all client options via system_views.clients and nodetool clientstats. - Support for String concatenation has been added through the + operator. - New configuration max_hints_size_per_host to limit the size of local hints files per host in mebibytes. Setting to diff --git a/debian/cassandra.install b/debian/cassandra.install index df45d88eba..f54d1ad835 100644 --- a/debian/cassandra.install +++ b/debian/cassandra.install @@ -24,6 +24,7 @@ tools/bin/cassandra-stress usr/bin tools/bin/fqltool usr/bin tools/bin/auditlogviewer usr/bin tools/bin/jmxtool usr/bin +tools/bin/hash_password usr/bin lib/*.jar usr/share/cassandra/lib lib/*.zip usr/share/cassandra/lib lib/sigar-bin/* usr/share/cassandra/lib/sigar-bin diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index 5a9e4981f6..762a666c71 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -1439,7 +1439,7 @@ syntax_rules += r''' ; ::= "CREATE" "USER" ( "IF" "NOT" "EXISTS" )? - ( "WITH" "PASSWORD" )? + ( "WITH" ("HASHED")? "PASSWORD" )? ( "SUPERUSER" | "NOSUPERUSER" )? ; @@ -1469,7 +1469,7 @@ syntax_rules += r''' ( "WITH" ("AND" )*)? ; - ::= "PASSWORD" "=" + ::= (("HASHED")? "PASSWORD") "=" | "OPTIONS" "=" | "SUPERUSER" "=" | "LOGIN" "=" diff --git a/redhat/cassandra.spec b/redhat/cassandra.spec index ddb73e49c9..15be0b0478 100644 --- a/redhat/cassandra.spec +++ b/redhat/cassandra.spec @@ -202,6 +202,7 @@ This package contains extra tools for working with Cassandra clusters. %attr(755,root,root) %{_bindir}/jmxtool %attr(755,root,root) %{_bindir}/fqltool %attr(755,root,root) %{_bindir}/generatetokens +%attr(755,root,root) %{_bindir}/hash_password %changelog diff --git a/src/antlr/Lexer.g b/src/antlr/Lexer.g index d89097e55d..34c7e2ed2f 100644 --- a/src/antlr/Lexer.g +++ b/src/antlr/Lexer.g @@ -150,6 +150,7 @@ K_ROLES: R O L E S; K_SUPERUSER: S U P E R U S E R; K_NOSUPERUSER: N O S U P E R U S E R; K_PASSWORD: P A S S W O R D; +K_HASHED: H A S H E D; K_LOGIN: L O G I N; K_NOLOGIN: N O L O G I N; K_OPTIONS: O P T I O N S; diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g index efc86648ed..fd74c2a3f9 100644 --- a/src/antlr/Parser.g +++ b/src/antlr/Parser.g @@ -1169,6 +1169,10 @@ createUserStatement returns [CreateRoleStatement stmt] ( K_WITH userPassword[opts] )? ( K_SUPERUSER { superuser = true; } | K_NOSUPERUSER { superuser = false; } )? { opts.setOption(IRoleManager.Option.SUPERUSER, superuser); + if (opts.getPassword().isPresent() && opts.getHashedPassword().isPresent()) + { + throw new SyntaxException("Options 'password' and 'hashed password' are mutually exclusive"); + } $stmt = new CreateRoleStatement(name, opts, DCPermissions.all(), ifNotExists); } ; @@ -1184,7 +1188,13 @@ alterUserStatement returns [AlterRoleStatement stmt] ( K_WITH userPassword[opts] )? ( K_SUPERUSER { opts.setOption(IRoleManager.Option.SUPERUSER, true); } | K_NOSUPERUSER { opts.setOption(IRoleManager.Option.SUPERUSER, false); } ) ? - { $stmt = new AlterRoleStatement(name, opts, null); } + { + if (opts.getPassword().isPresent() && opts.getHashedPassword().isPresent()) + { + throw new SyntaxException("Options 'password' and 'hashed password' are mutually exclusive"); + } + $stmt = new AlterRoleStatement(name, opts, null); + } ; /** @@ -1232,6 +1242,10 @@ createRoleStatement returns [CreateRoleStatement stmt] { opts.setOption(IRoleManager.Option.SUPERUSER, false); } + if (opts.getPassword().isPresent() && opts.getHashedPassword().isPresent()) + { + throw new SyntaxException("Options 'password' and 'hashed password' are mutually exclusive"); + } $stmt = new CreateRoleStatement(name, opts, dcperms.build(), ifNotExists); } ; @@ -1252,7 +1266,13 @@ alterRoleStatement returns [AlterRoleStatement stmt] } : K_ALTER K_ROLE name=userOrRoleName ( K_WITH roleOptions[opts, dcperms] )? - { $stmt = new AlterRoleStatement(name, opts, dcperms.isModified() ? dcperms.build() : null); } + { + if (opts.getPassword().isPresent() && opts.getHashedPassword().isPresent()) + { + throw new SyntaxException("Options 'password' and 'hashed password' are mutually exclusive"); + } + $stmt = new AlterRoleStatement(name, opts, dcperms.isModified() ? dcperms.build() : null); + } ; /** @@ -1286,6 +1306,7 @@ roleOptions[RoleOptions opts, DCPermissions.Builder dcperms] roleOption[RoleOptions opts, DCPermissions.Builder dcperms] : K_PASSWORD '=' v=STRING_LITERAL { opts.setOption(IRoleManager.Option.PASSWORD, $v.text); } + | K_HASHED K_PASSWORD '=' v=STRING_LITERAL { opts.setOption(IRoleManager.Option.HASHED_PASSWORD, $v.text); } | K_OPTIONS '=' m=fullMapLiteral { opts.setOption(IRoleManager.Option.OPTIONS, convertPropertyMap(m)); } | K_SUPERUSER '=' b=BOOLEAN { opts.setOption(IRoleManager.Option.SUPERUSER, Boolean.valueOf($b.text)); } | K_LOGIN '=' b=BOOLEAN { opts.setOption(IRoleManager.Option.LOGIN, Boolean.valueOf($b.text)); } @@ -1300,6 +1321,7 @@ dcPermission[DCPermissions.Builder builder] // for backwards compatibility in CREATE/ALTER USER, this has no '=' userPassword[RoleOptions opts] : K_PASSWORD v=STRING_LITERAL { opts.setOption(IRoleManager.Option.PASSWORD, $v.text); } + | K_HASHED K_PASSWORD v=STRING_LITERAL { opts.setOption(IRoleManager.Option.HASHED_PASSWORD, $v.text); } ; /** @@ -1869,6 +1891,7 @@ basic_unreserved_keyword returns [String str] | K_NOLOGIN | K_OPTIONS | K_PASSWORD + | K_HASHED | K_EXISTS | K_CUSTOM | K_TRIGGER diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java index 6e8f7d8337..0344de921d 100644 --- a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java +++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java @@ -135,10 +135,10 @@ public class CassandraRoleManager implements IRoleManager public CassandraRoleManager() { supportedOptions = DatabaseDescriptor.getAuthenticator() instanceof PasswordAuthenticator - ? ImmutableSet.of(Option.LOGIN, Option.SUPERUSER, Option.PASSWORD) + ? ImmutableSet.of(Option.LOGIN, Option.SUPERUSER, Option.PASSWORD, Option.HASHED_PASSWORD) : ImmutableSet.of(Option.LOGIN, Option.SUPERUSER); alterableOptions = DatabaseDescriptor.getAuthenticator() instanceof PasswordAuthenticator - ? ImmutableSet.of(Option.PASSWORD) + ? ImmutableSet.of(Option.PASSWORD, Option.HASHED_PASSWORD) : ImmutableSet.