mirror of https://github.com/apache/cassandra
Pre hashed passwords in CQL
patch by Robert Stupp and Berenguer Blasi; reviewed by Andres de la Peña for CASSANDRA-17334 Co-authored-by: Robert Stupp <snazy@snazy.de> Co-authored-by: Berenguer Blasi <berenguerblasi@gmail.com>
This commit is contained in:
parent
94580a272e
commit
30ad754d7e
|
|
@ -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)
|
||||
|
|
|
|||
1
NEWS.txt
1
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1439,7 +1439,7 @@ syntax_rules += r'''
|
|||
;
|
||||
|
||||
<createUserStatement> ::= "CREATE" "USER" ( "IF" "NOT" "EXISTS" )? <username>
|
||||
( "WITH" "PASSWORD" <stringLiteral> )?
|
||||
( "WITH" ("HASHED")? "PASSWORD" <stringLiteral> )?
|
||||
( "SUPERUSER" | "NOSUPERUSER" )?
|
||||
;
|
||||
|
||||
|
|
@ -1469,7 +1469,7 @@ syntax_rules += r'''
|
|||
( "WITH" <roleProperty> ("AND" <roleProperty>)*)?
|
||||
;
|
||||
|
||||
<roleProperty> ::= "PASSWORD" "=" <stringLiteral>
|
||||
<roleProperty> ::= (("HASHED")? "PASSWORD") "=" <stringLiteral>
|
||||
| "OPTIONS" "=" <mapLiteral>
|
||||
| "SUPERUSER" "=" <boolean>
|
||||
| "LOGIN" "=" <boolean>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.<Option>of();
|
||||
}
|
||||
|
||||
|
|
@ -172,20 +172,20 @@ public class CassandraRoleManager implements IRoleManager
|
|||
public void createRole(AuthenticatedUser performer, RoleResource role, RoleOptions options)
|
||||
throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
String insertCql = options.getPassword().isPresent()
|
||||
String insertCql = options.getPassword().isPresent() || options.getHashedPassword().isPresent()
|
||||
? String.format("INSERT INTO %s.%s (role, is_superuser, can_login, salted_hash) VALUES ('%s', %s, %s, '%s')",
|
||||
SchemaConstants.AUTH_KEYSPACE_NAME,
|
||||
AuthKeyspace.ROLES,
|
||||
escape(role.getRoleName()),
|
||||
options.getSuperuser().or(false),
|
||||
options.getLogin().or(false),
|
||||
escape(hashpw(options.getPassword().get())))
|
||||
options.getSuperuser().orElse(false),
|
||||
options.getLogin().orElse(false),
|
||||
options.getHashedPassword().orElseGet(() -> escape(hashpw(options.getPassword().get()))))
|
||||
: String.format("INSERT INTO %s.%s (role, is_superuser, can_login) VALUES ('%s', %s, %s)",
|
||||
SchemaConstants.AUTH_KEYSPACE_NAME,
|
||||
AuthKeyspace.ROLES,
|
||||
escape(role.getRoleName()),
|
||||
options.getSuperuser().or(false),
|
||||
options.getLogin().or(false));
|
||||
options.getSuperuser().orElse(false),
|
||||
options.getLogin().orElse(false));
|
||||
process(insertCql, consistencyForRoleWrite(role.getRoleName()));
|
||||
}
|
||||
|
||||
|
|
@ -511,6 +511,8 @@ public class CassandraRoleManager implements IRoleManager
|
|||
return String.format("is_superuser = %s", entry.getValue());
|
||||
case PASSWORD:
|
||||
return String.format("salted_hash = '%s'", escape(hashpw((String) entry.getValue())));
|
||||
case HASHED_PASSWORD:
|
||||
return String.format("salted_hash = '%s'", (String) entry.getValue());
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public interface IRoleManager extends AuthCache.BulkLoader<RoleResource, Set<Rol
|
|||
*/
|
||||
public enum Option
|
||||
{
|
||||
SUPERUSER, PASSWORD, LOGIN, OPTIONS
|
||||
SUPERUSER, PASSWORD, LOGIN, OPTIONS, HASHED_PASSWORD
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ package org.apache.cassandra.auth;
|
|||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.exceptions.SyntaxException;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
public class RoleOptions
|
||||
{
|
||||
|
|
@ -68,7 +68,7 @@ public class RoleOptions
|
|||
*/
|
||||
public Optional<Boolean> getSuperuser()
|
||||
{
|
||||
return Optional.fromNullable((Boolean)options.get(IRoleManager.Option.SUPERUSER));
|
||||
return Optional.ofNullable((Boolean) options.get(IRoleManager.Option.SUPERUSER));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -77,7 +77,7 @@ public class RoleOptions
|
|||
*/
|
||||
public Optional<Boolean> getLogin()
|
||||
{
|
||||
return Optional.fromNullable((Boolean)options.get(IRoleManager.Option.LOGIN));
|
||||
return Optional.ofNullable((Boolean) options.get(IRoleManager.Option.LOGIN));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -86,7 +86,16 @@ public class RoleOptions
|
|||
*/
|
||||
public Optional<String> getPassword()
|
||||
{
|
||||
return Optional.fromNullable((String)options.get(IRoleManager.Option.PASSWORD));
|
||||
return Optional.ofNullable((String)options.get(IRoleManager.Option.PASSWORD));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the string value of the hashed password option.
|
||||
* @return hashed password option value
|
||||
*/
|
||||
public Optional<String> getHashedPassword()
|
||||
{
|
||||
return Optional.ofNullable((String) options.get(IRoleManager.Option.HASHED_PASSWORD));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -99,7 +108,7 @@ public class RoleOptions
|
|||
@SuppressWarnings("unchecked")
|
||||
public Optional<Map<String, String>> getCustomOptions()
|
||||
{
|
||||
return Optional.fromNullable((Map<String, String>)options.get(IRoleManager.Option.OPTIONS));
|
||||
return Optional.ofNullable((Map<String, String>) options.get(IRoleManager.Option.OPTIONS));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -134,6 +143,26 @@ public class RoleOptions
|
|||
throw new InvalidRequestException(String.format("Invalid value for property '%s'. " +
|
||||
"It must be a string",
|
||||
option.getKey()));
|
||||
if (options.containsKey(IRoleManager.Option.HASHED_PASSWORD))
|
||||
throw new InvalidRequestException(String.format("Properties '%s' and '%s' are mutually exclusive",
|
||||
IRoleManager.Option.PASSWORD, IRoleManager.Option.HASHED_PASSWORD));
|
||||
break;
|
||||
case HASHED_PASSWORD:
|
||||
if (!(option.getValue() instanceof String))
|
||||
throw new InvalidRequestException(String.format("Invalid value for property '%s'. " +
|
||||
"It must be a string",
|
||||
option.getKey()));
|
||||
if (options.containsKey(IRoleManager.Option.PASSWORD))
|
||||
throw new InvalidRequestException(String.format("Properties '%s' and '%s' are mutually exclusive",
|
||||
IRoleManager.Option.PASSWORD, IRoleManager.Option.HASHED_PASSWORD));
|
||||
try
|
||||
{
|
||||
BCrypt.checkpw("dummy", (String) option.getValue());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidRequestException("Invalid hashed password value. Please use jBcrypt.");
|
||||
}
|
||||
break;
|
||||
case OPTIONS:
|
||||
if (!(option.getValue() instanceof Map))
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
package org.apache.cassandra.cql3;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.cassandra.auth.PasswordAuthenticator;
|
||||
import org.apache.cassandra.auth.RoleOptions;
|
||||
|
|
@ -62,10 +62,16 @@ public class PasswordObfuscator
|
|||
return query;
|
||||
|
||||
Optional<String> pass = opts.getPassword();
|
||||
if (!pass.isPresent() || pass.get().isEmpty())
|
||||
pass = opts.getHashedPassword();
|
||||
if (!pass.isPresent() || pass.get().isEmpty())
|
||||
return query;
|
||||
|
||||
// match new line, case insensitive (?si), and PASSWORD_TOKEN up to the actual password greedy. Group that and replace the password
|
||||
return query.replaceAll("((?si)"+ PASSWORD_TOKEN + ".+?)" + pass.get(), "$1" + PasswordObfuscator.OBFUSCATION_TOKEN);
|
||||
// Regular expression:
|
||||
// - Match new line and case insensitive (?si), and PASSWORD_TOKEN with greedy mode up to the start of the actual password and group it.
|
||||
// - Quote the password between \Q and \E so any potential special characters are ignored
|
||||
// - Replace the match with the grouped data + the obfuscated token
|
||||
return query.replaceAll("((?si)"+ PASSWORD_TOKEN + ".+?)\\Q" + pass.get() + "\\E",
|
||||
"$1" + PasswordObfuscator.OBFUSCATION_TOKEN);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.apache.cassandra.tools;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.GnuParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.OptionGroup;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
public class HashPassword
|
||||
{
|
||||
private static final String LOGROUNDS_OPTION = "logrounds";
|
||||
private static final String HELP_OPTION = "help";
|
||||
private static final String ENV_VAR = "environment-var";
|
||||
private static final String PLAIN = "plain";
|
||||
private static final String INPUT = "input";
|
||||
|
||||
private static final int LOGROUNDS_DEFAULT = 10;
|
||||
private static final int MIN_PASS_LENGTH = 4;
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Options options = getOptions();
|
||||
CommandLine cmd = parseCommandLine(args, options);
|
||||
|
||||
String password = null;
|
||||
if (cmd.hasOption(ENV_VAR))
|
||||
{
|
||||
password = System.getenv(cmd.getOptionValue(ENV_VAR));
|
||||
if (password == null)
|
||||
{
|
||||
System.err.println(String.format("Environment variable '%s' is undefined.", cmd.getOptionValue(ENV_VAR)));
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
else if (cmd.hasOption(PLAIN))
|
||||
{
|
||||
password = cmd.getOptionValue(PLAIN);
|
||||
}
|
||||
else if (cmd.hasOption(INPUT))
|
||||
{
|
||||
String input = cmd.getOptionValue(INPUT);
|
||||
byte[] fileInput = null;
|
||||
if ("-".equals(input))
|
||||
{
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
int rd;
|
||||
while ((rd = System.in.read()) != -1)
|
||||
os.write(rd);
|
||||
fileInput = os.toByteArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Path file = Paths.get(input);
|
||||
fileInput = Files.readAllBytes(file);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
System.err.printf("Failed to read from '%s': %s%n", input, e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
password = new String(fileInput, StandardCharsets.UTF_8);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.err.println(String.format("One of the options --%s, --%s or --%s must be used.",
|
||||
ENV_VAR, PLAIN, INPUT));
|
||||
printUsage(options);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (password.chars().anyMatch(i -> i < 32))
|
||||
System.err.println("WARNING: The provided plain text password contains non-printable characters (ASCII<32).");
|
||||
|
||||
if (password.length() < MIN_PASS_LENGTH)
|
||||
System.err.println("WARNING: The provided password is very short, probably too short to be secure.");
|
||||
|
||||
int logRounds = cmd.hasOption(LOGROUNDS_OPTION) ? Integer.parseInt(cmd.getOptionValue(LOGROUNDS_OPTION)) : LOGROUNDS_DEFAULT;
|
||||
if (logRounds < 4 || logRounds > 30)
|
||||
{
|
||||
System.err.println(String.format("Bad value for --%s %d. " +
|
||||
"Please use a value between 4 and 30 inclusively",
|
||||
LOGROUNDS_OPTION, logRounds));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// The number of rounds is in fact = 2^rounds.
|
||||
if (logRounds > 16)
|
||||
System.err.println(String.format("WARNING: Using a high number of hash rounds, as configured using '--%s %d' " +
|
||||
"will consume a lot of CPU and likely cause timeouts. Note that the parameter defines the " +
|
||||
"logarithmic number of rounds: %d becomes 2^%d = %d rounds",
|
||||
LOGROUNDS_OPTION, logRounds,
|
||||
logRounds, logRounds, 1 << logRounds));
|
||||
|
||||
if (password.getBytes().length > 72)
|
||||
System.err.println(String.format("WARNING: The provided password has a length of %d bytes, but the underlying hash/crypt algorithm " +
|
||||
"(bcrypt) can only compare up to 72 bytes. The password will be accepted and work, but only compared up to 72 bytes.",
|
||||
password.getBytes().length));
|
||||
|
||||
String hashed = escape(hashpw(password, logRounds));
|
||||
System.out.print(hashed);
|
||||
System.out.flush();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static CommandLine parseCommandLine(String[] args, Options options) throws ParseException
|
||||
{
|
||||
CommandLineParser parser = new GnuParser();
|
||||
|
||||
CommandLine cmd = parser.parse(options, args, false);
|
||||
|
||||
if (cmd.hasOption(HELP_OPTION))
|
||||
{
|
||||
printUsage(options);
|
||||
System.exit(0);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Options getOptions()
|
||||
{
|
||||
Options options = new Options();
|
||||
options.addOption("h", HELP_OPTION, false, "Display this help message");
|
||||
options.addOption("r", LOGROUNDS_OPTION, true, "Number of hash rounds (default: " + LOGROUNDS_DEFAULT + ").");
|
||||
OptionGroup group = new OptionGroup();
|
||||
group.addOption(new Option("e", ENV_VAR, true,
|
||||
"Use value of the specified environment variable as the password"));
|
||||
group.addOption(new Option("p", PLAIN, true,
|
||||
"Argument is the plain text password"));
|
||||
group.addOption(new Option("i", INPUT, true,
|
||||
"Input is a file (or - for stdin) to read the password from. " +
|
||||
"Make sure that the whole input including newlines is considered. " +
|
||||
"For example, the shell command 'echo -n foobar | hash_password -i -' will " +
|
||||
"work as intended and just hash 'foobar'."));
|
||||
options.addOptionGroup(group);
|
||||
return options;
|
||||
}
|
||||
|
||||
private static String hashpw(String password, int rounds)
|
||||
{
|
||||
return BCrypt.hashpw(password, BCrypt.gensalt(rounds));
|
||||
}
|
||||
|
||||
private static String escape(String name)
|
||||
{
|
||||
return StringUtils.replace(name, "'", "''");
|
||||
}
|
||||
|
||||
public static void printUsage(Options options)
|
||||
{
|
||||
String usage = "hash_password [options]";
|
||||
String header = "--\n" +
|
||||
"Hashes a plain text password and prints the hashed password.\n" +
|
||||
"Options are:";
|
||||
new HelpFormatter().printHelp(usage, header, options, "");
|
||||
}
|
||||
}
|
||||
|
|
@ -61,6 +61,7 @@ public class AuditLoggerAuthTest
|
|||
private static final String TEST_USER = "testuser";
|
||||
private static final String TEST_ROLE = "testrole";
|
||||
private static final String TEST_PW = "testpassword";
|
||||
private static final String TEST_PW_HASH = "$2a$10$1fI9MDCe13ZmEYW4XXZibuASNKyqOY828ELGUtml/t.0Mk/6Kqnsq";
|
||||
private static final String CASS_USER = "cassandra";
|
||||
private static final String CASS_PW = "cassandra";
|
||||
|
||||
|
|
@ -143,6 +144,21 @@ public class AuditLoggerAuthTest
|
|||
assertEquals(0, getInMemAuditLogger().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCqlCreateRoleSyntaxErrorWithHashedPwd()
|
||||
{
|
||||
String createTestRoleCQL = String.format("CREATE ROLE %s WITH LOGIN = %s ANDSUPERUSER = %s AND HASHED PASSWORD",
|
||||
TEST_ROLE, true, false) + TEST_PW_HASH;
|
||||
String createTestRoleCQLExpected = String.format("CREATE ROLE %s WITH LOGIN = %s ANDSUPERUSER = %s AND HASHED PASSWORD ",
|
||||
TEST_ROLE, true, false) + PasswordObfuscator.OBFUSCATION_TOKEN;
|
||||
|
||||
executeWithCredentials(Collections.singletonList(createTestRoleCQL), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS);
|
||||
assertTrue(getInMemAuditLogger().size() > 0);
|
||||
AuditLogEntry logEntry = getInMemAuditLogger().poll();
|
||||
assertLogEntry(logEntry, AuditLogEntryType.REQUEST_FAILURE, createTestRoleCQLExpected, CASS_USER, TEST_PW);
|
||||
assertEquals(0, getInMemAuditLogger().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCqlALTERRoleAuditing()
|
||||
{
|
||||
|
|
@ -151,7 +167,28 @@ public class AuditLoggerAuthTest
|
|||
executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS);
|
||||
assertTrue(getInMemAuditLogger().size() > 0);
|
||||
AuditLogEntry logEntry = getInMemAuditLogger().poll();
|
||||
assertLogEntry(logEntry, AuditLogEntryType.ALTER_ROLE, "ALTER ROLE " + TEST_ROLE + " WITH PASSWORD = '" + PasswordObfuscator.OBFUSCATION_TOKEN + "'", CASS_USER, "foo_bar");
|
||||
assertLogEntry(logEntry,
|
||||
AuditLogEntryType.ALTER_ROLE,
|
||||
"ALTER ROLE " + TEST_ROLE + " WITH PASSWORD = '" + PasswordObfuscator.OBFUSCATION_TOKEN + "'",
|
||||
CASS_USER,
|
||||
"foo_bar");
|
||||
assertEquals(0, getInMemAuditLogger().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCqlALTERRoleAuditingWithHashedPwd()
|
||||
{
|
||||
createTestRole();
|
||||
String cql = "ALTER ROLE " + TEST_ROLE + " WITH HASHED PASSWORD = '" + TEST_PW_HASH + "'";
|
||||
executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS);
|
||||
assertTrue(getInMemAuditLogger().size() > 0);
|
||||
AuditLogEntry logEntry = getInMemAuditLogger().poll();
|
||||
assertLogEntry(logEntry,
|
||||
AuditLogEntryType.ALTER_ROLE,
|
||||
"ALTER ROLE " + TEST_ROLE +
|
||||
" WITH HASHED PASSWORD = '" + PasswordObfuscator.OBFUSCATION_TOKEN + "'",
|
||||
CASS_USER,
|
||||
TEST_PW_HASH);
|
||||
assertEquals(0, getInMemAuditLogger().size());
|
||||
}
|
||||
|
||||
|
|
@ -260,6 +297,45 @@ public class AuditLoggerAuthTest
|
|||
TEST_PW);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCqlUSERCommandsAuditingWithHashedPwd()
|
||||
{
|
||||
//CREATE USER and ALTER USER are supported only for backwards compatibility.
|
||||
|
||||
String user = TEST_ROLE + "userHasedPwd";
|
||||
String cql = "CREATE USER " + user + " WITH HASHED PASSWORD '" + TEST_PW_HASH + "'";
|
||||
executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS);
|
||||
assertTrue(getInMemAuditLogger().size() > 0);
|
||||
AuditLogEntry logEntry = getInMemAuditLogger().poll();
|
||||
assertLogEntry(logEntry,
|
||||
AuditLogEntryType.CREATE_ROLE,
|
||||
"CREATE USER " + user + " WITH HASHED PASSWORD '" + PasswordObfuscator.OBFUSCATION_TOKEN + "'",
|
||||
CASS_USER,
|
||||
TEST_PW_HASH);
|
||||
|
||||
cql = "ALTER USER " + user + " WITH HASHED PASSWORD '" + TEST_PW_HASH + "'";
|
||||
executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS);
|
||||
assertTrue(getInMemAuditLogger().size() > 0);
|
||||
logEntry = getInMemAuditLogger().poll();
|
||||
assertLogEntry(logEntry,
|
||||
AuditLogEntryType.ALTER_ROLE,
|
||||
"ALTER USER " + user + " WITH HASHED PASSWORD '" + PasswordObfuscator.OBFUSCATION_TOKEN + "'",
|
||||
CASS_USER,
|
||||
TEST_PW_HASH);
|
||||
|
||||
cql = "ALTER USER " + user + " WITH HASHED PASSWORD " + TEST_PW_HASH;
|
||||
executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS);
|
||||
assertTrue(getInMemAuditLogger().size() > 0);
|
||||
logEntry = getInMemAuditLogger().poll();
|
||||
assertLogEntry(logEntry,
|
||||
AuditLogEntryType.REQUEST_FAILURE,
|
||||
"ALTER USER " + user
|
||||
+ " WITH HASHED PASSWORD " + PasswordObfuscator.OBFUSCATION_TOKEN
|
||||
+ "; Syntax Exception. Obscured for security reasons.",
|
||||
CASS_USER,
|
||||
TEST_PW_HASH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper methods
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.auth;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.datastax.driver.core.exceptions.AuthenticationException;
|
||||
import org.apache.cassandra.Util;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mindrot.jbcrypt.BCrypt.gensalt;
|
||||
import static org.mindrot.jbcrypt.BCrypt.hashpw;
|
||||
|
||||
public class CreateAndAlterRoleTest extends CQLTester
|
||||
{
|
||||
@BeforeClass
|
||||
public static void setUpClass()
|
||||
{
|
||||
CQLTester.setUpClass();
|
||||
requireAuthentication();
|
||||
requireNetwork();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAlterRoleWithHashedPassword() throws Throwable
|
||||
{
|
||||
String user1 = "hashed_pw_role";
|
||||
String user2 = "pw_role";
|
||||
String plainTextPwd = "super_secret_thing";
|
||||
String plainTextPwd2 = "much_safer_password";
|
||||
String hashedPassword = hashpw(plainTextPwd, gensalt(4));
|
||||
String hashedPassword2 = hashpw(plainTextPwd2, gensalt(4));
|
||||
|
||||
useSuperUser();
|
||||
|
||||
assertInvalidMessage("Invalid hashed password value",
|
||||
String.format("CREATE ROLE %s WITH login=true AND hashed password='%s'",
|
||||
user1, "this_is_an_invalid_hash"));
|
||||
assertInvalidMessage("Options 'password' and 'hashed password' are mutually exclusive",
|
||||
String.format("CREATE ROLE %s WITH login=true AND password='%s' AND hashed password='%s'",
|
||||
user1, plainTextPwd, hashedPassword));
|
||||
executeNet(String.format("CREATE ROLE %s WITH login=true AND hashed password='%s'", user1, hashedPassword));
|
||||
executeNet(String.format("CREATE ROLE %s WITH login=true AND password='%s'", user2, plainTextPwd));
|
||||
|
||||
useUser(user1, plainTextPwd);
|
||||
|
||||
executeNetWithAuthSpin("SELECT key FROM system.local");
|
||||
|
||||
useUser(user2, plainTextPwd);
|
||||
|
||||
executeNetWithAuthSpin("SELECT key FROM system.local");
|
||||
|
||||
useSuperUser();
|
||||
|
||||
assertInvalidMessage("Options 'password' and 'hashed password' are mutually exclusive",
|
||||
String.format("ALTER ROLE %s WITH password='%s' AND hashed password='%s'",
|
||||
user1, plainTextPwd2, hashedPassword2));
|
||||
executeNet(String.format("ALTER ROLE %s WITH password='%s'", user1, plainTextPwd2));
|
||||
executeNet(String.format("ALTER ROLE %s WITH hashed password='%s'", user2, hashedPassword2));
|
||||
|
||||
useUser(user1, plainTextPwd2);
|
||||
|
||||
executeNetWithAuthSpin("SELECT key FROM system.local");
|
||||
|
||||
useUser(user2, plainTextPwd2);
|
||||
|
||||
executeNetWithAuthSpin("SELECT key FROM system.local");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAlterUserWithHashedPassword() throws Throwable
|
||||
{
|
||||
String user1 = "hashed_pw_user";
|
||||
String user2 = "pw_user";
|
||||
String plainTextPwd = "super_secret_thing";
|
||||
String plainTextPwd2 = "much_safer_password";
|
||||
String hashedPassword = hashpw(plainTextPwd, gensalt(4));
|
||||
String hashedPassword2 = hashpw(plainTextPwd2, gensalt(4));
|
||||
|
||||
useSuperUser();
|
||||
|
||||
assertInvalidMessage("Invalid hashed password value",
|
||||
String.format("CREATE USER %s WITH hashed password '%s'",
|
||||
user1, "this_is_an_invalid_hash"));
|
||||
executeNet(String.format("CREATE USER %s WITH hashed password '%s'", user1, hashedPassword));
|
||||
executeNet(String.format("CREATE USER %s WITH password '%s'", user2, plainTextPwd));
|
||||
|
||||
useUser(user1, plainTextPwd);
|
||||
|
||||
executeNetWithAuthSpin("SELECT key FROM system.local");
|
||||
|
||||
useUser(user2, plainTextPwd);
|
||||
|
||||
executeNetWithAuthSpin("SELECT key FROM system.local");
|
||||
|
||||
useSuperUser();
|
||||
|
||||
executeNet(String.format("ALTER USER %s WITH password '%s'", user1, plainTextPwd2));
|
||||
executeNet(String.format("ALTER USER %s WITH hashed password '%s'", user2, hashedPassword2));
|
||||
|
||||
useUser(user1, plainTextPwd2);
|
||||
|
||||
executeNetWithAuthSpin("SELECT key FROM system.local");
|
||||
|
||||
useUser(user2, plainTextPwd2);
|
||||
|
||||
executeNetWithAuthSpin("SELECT key FROM system.local");
|
||||
}
|
||||
|
||||
/**
|
||||
* Altering or creating auth may take some time to be effective
|
||||
*
|
||||
* @param query
|
||||
*/
|
||||
void executeNetWithAuthSpin(String query)
|
||||
{
|
||||
Util.spinAssertEquals(true, () -> {
|
||||
try
|
||||
{
|
||||
executeNet(query);
|
||||
return true;
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
assertTrue("Unexpected exception: " + e, e instanceof AuthenticationException);
|
||||
reinitializeNetwork();
|
||||
return false;
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
|
@ -51,10 +51,23 @@ public class RoleOptionsTest
|
|||
opts.setOption(IRoleManager.Option.SUPERUSER, new HashSet<>());
|
||||
assertInvalidOptions(opts, "Invalid value for property 'SUPERUSER'. It must be a boolean");
|
||||
|
||||
opts = new RoleOptions();
|
||||
opts.setOption(IRoleManager.Option.HASHED_PASSWORD, 99);
|
||||
assertInvalidOptions(opts, "Invalid value for property 'HASHED_PASSWORD'. It must be a string");
|
||||
|
||||
opts = new RoleOptions();
|
||||
opts.setOption(IRoleManager.Option.HASHED_PASSWORD, "invalid_hash");
|
||||
assertInvalidOptions(opts, "Invalid hashed password value. Please use jBcrypt.");
|
||||
|
||||
opts = new RoleOptions();
|
||||
opts.setOption(IRoleManager.Option.OPTIONS, false);
|
||||
assertInvalidOptions(opts, "Invalid value for property 'OPTIONS'. It must be a map");
|
||||
|
||||
opts = new RoleOptions();
|
||||
opts.setOption(IRoleManager.Option.PASSWORD, "abc");
|
||||
opts.setOption(IRoleManager.Option.HASHED_PASSWORD, "$2a$10$JSJEMFm6GeaW9XxT5JIheuEtPvat6i7uKbnTcxX3c1wshIIsGyUtG");
|
||||
assertInvalidOptions(opts, "Properties 'PASSWORD' and 'HASHED_PASSWORD' are mutually exclusive");
|
||||
|
||||
opts = new RoleOptions();
|
||||
opts.setOption(IRoleManager.Option.LOGIN, true);
|
||||
opts.setOption(IRoleManager.Option.SUPERUSER, false);
|
||||
|
|
@ -111,7 +124,7 @@ public class RoleOptionsTest
|
|||
}
|
||||
catch (InvalidRequestException e)
|
||||
{
|
||||
assertTrue(e.getMessage().equals(message));
|
||||
assertEquals(message, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,13 +29,23 @@ import static org.junit.Assert.assertEquals;
|
|||
|
||||
public class PasswordObfuscatorTest
|
||||
{
|
||||
private static final RoleOptions opts = new RoleOptions();
|
||||
private static final String optsPassword = "testpassword";
|
||||
private static final RoleOptions plainPwdOpts = new RoleOptions();
|
||||
private static final RoleOptions hashedPwdOpts = new RoleOptions();
|
||||
private static final String plainPwd = "testpassword";
|
||||
private static final String hashedPwd = "$2a$10$1fI9MDCe13ZmEYW4XXZibuASNKyqOY828ELGUtml/t.0Mk/6Kqnsq";
|
||||
|
||||
@BeforeClass
|
||||
public static void startup()
|
||||
{
|
||||
opts.setOption(org.apache.cassandra.auth.IRoleManager.Option.PASSWORD, "testpassword");
|
||||
plainPwdOpts.setOption(org.apache.cassandra.auth.IRoleManager.Option.PASSWORD, plainPwd);
|
||||
hashedPwdOpts.setOption(org.apache.cassandra.auth.IRoleManager.Option.HASHED_PASSWORD, hashedPwd);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpecialCharsObfuscation()
|
||||
{
|
||||
assertEquals("ALTER ROLE testrole WITH HASHED PASSWORD = '" + OBFUSCATION_TOKEN + "'",
|
||||
obfuscate(format("ALTER ROLE testrole WITH HASHED PASSWORD = '%s'",hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -45,7 +55,13 @@ public class PasswordObfuscatorTest
|
|||
obfuscate("CREATE ROLE role1 WITH LOGIN = true AND PASSWORD = '123'"));
|
||||
|
||||
assertEquals(format("CREATE ROLE role1 WITH LOGIN = true AND PASSWORD = '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE ROLE role1 WITH LOGIN = true AND PASSWORD = '%s'", optsPassword), opts));
|
||||
obfuscate(format("CREATE ROLE role1 WITH LOGIN = true AND PASSWORD = '%s'", plainPwd), plainPwdOpts));
|
||||
|
||||
assertEquals(format("CREATE ROLE role1 WITH LOGIN = true AND HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate("CREATE ROLE role1 WITH LOGIN = true AND HASHED PASSWORD = '" + hashedPwd + "'"));
|
||||
|
||||
assertEquals(format("CREATE ROLE role1 WITH LOGIN = true AND HASHED PASSWORD = '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE ROLE role1 WITH LOGIN = true AND HASHED PASSWORD = '%s'", hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -55,14 +71,20 @@ public class PasswordObfuscatorTest
|
|||
obfuscate("CREATE ROLE role1 WITH password = '123' AND LOGIN = true"));
|
||||
|
||||
assertEquals(format("CREATE ROLE role1 WITH password = '%s' AND LOGIN = true", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE ROLE role1 WITH password = '%s' AND LOGIN = true", optsPassword), opts));
|
||||
obfuscate(format("CREATE ROLE role1 WITH password = '%s' AND LOGIN = true", plainPwd), plainPwdOpts));
|
||||
|
||||
assertEquals(format("CREATE ROLE role1 WITH HASHED password %s", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE ROLE role1 WITH HASHED password = '%s' AND LOGIN = true", hashedPwd)));
|
||||
|
||||
assertEquals(format("CREATE ROLE role1 WITH HASHED password = '%s' AND LOGIN = true", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE ROLE role1 WITH HASHED password = '%s' AND LOGIN = true", hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateRoleWithoutPassword()
|
||||
{
|
||||
assertEquals("CREATE ROLE role1", obfuscate("CREATE ROLE role1"));
|
||||
assertEquals("CREATE ROLE role1", obfuscate("CREATE ROLE role1", opts));
|
||||
assertEquals("CREATE ROLE role1", obfuscate("CREATE ROLE role1", plainPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -72,11 +94,19 @@ public class PasswordObfuscatorTest
|
|||
obfuscate("CREATE ROLE role1 WITH LOGIN = true AND PASSWORD = '123';" +
|
||||
"CREATE ROLE role2 WITH LOGIN = true AND PASSWORD = '123'"));
|
||||
|
||||
assertEquals(format("CREATE ROLE role1 WITH LOGIN = true AND PASSWORD = '%s';"
|
||||
+ "CREATE ROLE role2 WITH LOGIN = true AND PASSWORD = '%s'", OBFUSCATION_TOKEN, OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE ROLE role1 WITH LOGIN = true AND PASSWORD = '%s';"
|
||||
+ "CREATE ROLE role2 WITH LOGIN = true AND PASSWORD = '%s'", optsPassword, optsPassword),
|
||||
opts));
|
||||
assertEquals(format("CREATE ROLE role1 WITH LOGIN = true AND PASSWORD = '%s';" +
|
||||
"CREATE ROLE role2 WITH LOGIN = true AND PASSWORD = '%s'", OBFUSCATION_TOKEN, OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE ROLE role1 WITH LOGIN = true AND PASSWORD = '%s';" +
|
||||
"CREATE ROLE role2 WITH LOGIN = true AND PASSWORD = '%s'", plainPwd, plainPwd), plainPwdOpts));
|
||||
|
||||
assertEquals(format("CREATE ROLE role1 WITH LOGIN = true AND HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE ROLE role1 WITH LOGIN = true AND HASHED PASSWORD = '%s';" +
|
||||
"CREATE ROLE role2 WITH LOGIN = true AND HASHED PASSWORD = '%s'", hashedPwd, hashedPwd)));
|
||||
|
||||
assertEquals(format("CREATE ROLE role1 WITH LOGIN = true AND HASHED PASSWORD = '%s';" +
|
||||
"CREATE ROLE role2 WITH LOGIN = true AND HASHED PASSWORD = '%s'", OBFUSCATION_TOKEN, OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE ROLE role1 WITH LOGIN = true AND HASHED PASSWORD = '%s';" +
|
||||
"CREATE ROLE role2 WITH LOGIN = true AND HASHED PASSWORD = '%s'", hashedPwd, hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -86,7 +116,13 @@ public class PasswordObfuscatorTest
|
|||
obfuscate("ALTER ROLE role1 with PASSWORD = '123'"));
|
||||
|
||||
assertEquals(format("ALTER ROLE role1 with PASSWORD = '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER ROLE role1 with PASSWORD = '%s'", optsPassword), opts));
|
||||
obfuscate(format("ALTER ROLE role1 with PASSWORD = '%s'", plainPwd), plainPwdOpts));
|
||||
|
||||
assertEquals(format("ALTER ROLE role1 with HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER ROLE role1 with HASHED PASSWORD = '%s'", hashedPwd)));
|
||||
|
||||
assertEquals(format("ALTER ROLE role1 with HASHED PASSWORD = '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER ROLE role1 with HASHED PASSWORD = '%s'", hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -96,7 +132,13 @@ public class PasswordObfuscatorTest
|
|||
obfuscate("ALTER ROLE role1 with PASSWORD='123'"));
|
||||
|
||||
assertEquals(format("ALTER ROLE role1 with PASSWORD='%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER ROLE role1 with PASSWORD='%s'", optsPassword), opts));
|
||||
obfuscate(format("ALTER ROLE role1 with PASSWORD='%s'", plainPwd), plainPwdOpts));
|
||||
|
||||
assertEquals(format("ALTER ROLE role1 with HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER ROLE role1 with HASHED PASSWORD='%s'", hashedPwd)));
|
||||
|
||||
assertEquals(format("ALTER ROLE role1 with HASHED PASSWORD='%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER ROLE role1 with HASHED PASSWORD='%s'", hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -106,7 +148,13 @@ public class PasswordObfuscatorTest
|
|||
obfuscate("ALTER ROLE role1 with PASSWORD= '123'"));
|
||||
|
||||
assertEquals(format("ALTER ROLE role1 with PASSWORD= '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER ROLE role1 with PASSWORD= '%s'", optsPassword), opts));
|
||||
obfuscate(format("ALTER ROLE role1 with PASSWORD= '%s'", plainPwd), plainPwdOpts));
|
||||
|
||||
assertEquals(format("ALTER ROLE role1 with HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER ROLE role1 with HASHED PASSWORD= '%s'", hashedPwd)));
|
||||
|
||||
assertEquals(format("ALTER ROLE role1 with HASHED PASSWORD= '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER ROLE role1 with HASHED PASSWORD= '%s'", hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -114,7 +162,9 @@ public class PasswordObfuscatorTest
|
|||
{
|
||||
assertEquals("ALTER ROLE role1", obfuscate("ALTER ROLE role1"));
|
||||
|
||||
assertEquals("ALTER ROLE role1", obfuscate("ALTER ROLE role1", opts));
|
||||
assertEquals("ALTER ROLE role1", obfuscate("ALTER ROLE role1", plainPwdOpts));
|
||||
|
||||
assertEquals("ALTER ROLE role1", obfuscate("ALTER ROLE role1", hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -124,7 +174,13 @@ public class PasswordObfuscatorTest
|
|||
obfuscate("CREATE USER user1 with PASSWORD '123'"));
|
||||
|
||||
assertEquals(format("CREATE USER user1 with PASSWORD '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE USER user1 with PASSWORD '%s'", optsPassword), opts));
|
||||
obfuscate(format("CREATE USER user1 with PASSWORD '%s'", plainPwd), plainPwdOpts));
|
||||
|
||||
assertEquals(format("CREATE USER user1 with HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE USER user1 with HASHED PASSWORD '%s'", hashedPwd)));
|
||||
|
||||
assertEquals(format("CREATE USER user1 with HASHED PASSWORD '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE USER user1 with HASHED PASSWORD '%s'", hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -132,7 +188,9 @@ public class PasswordObfuscatorTest
|
|||
{
|
||||
assertEquals("CREATE USER user1", obfuscate("CREATE USER user1"));
|
||||
|
||||
assertEquals("CREATE USER user1", obfuscate("CREATE USER user1", opts));
|
||||
assertEquals("CREATE USER user1", obfuscate("CREATE USER user1", plainPwdOpts));
|
||||
|
||||
assertEquals("CREATE USER user1", obfuscate("CREATE USER user1", hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -142,7 +200,13 @@ public class PasswordObfuscatorTest
|
|||
obfuscate("ALTER USER user1 with PASSWORD '123'"));
|
||||
|
||||
assertEquals(format("ALTER USER user1 with PASSWORD '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER USER user1 with PASSWORD '%s'", optsPassword), opts));
|
||||
obfuscate(format("ALTER USER user1 with PASSWORD '%s'", plainPwd), plainPwdOpts));
|
||||
|
||||
assertEquals(format("ALTER USER user1 with HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER USER user1 with HASHED PASSWORD '%s'", hashedPwd)));
|
||||
|
||||
assertEquals(format("ALTER USER user1 with HASHED PASSWORD '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER USER user1 with HASHED PASSWORD '%s'", hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -152,7 +216,13 @@ public class PasswordObfuscatorTest
|
|||
obfuscate("ALTER USER user1 with paSSwoRd '123'"));
|
||||
|
||||
assertEquals(format("ALTER USER user1 with paSSwoRd '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER USER user1 with paSSwoRd '%s'", optsPassword), opts));
|
||||
obfuscate(format("ALTER USER user1 with paSSwoRd '%s'", plainPwd), plainPwdOpts));
|
||||
|
||||
assertEquals(format("ALTER USER user1 with HASHED paSSwoRd %s", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER USER user1 with HASHED paSSwoRd '%s'", hashedPwd)));
|
||||
|
||||
assertEquals(format("ALTER USER user1 with HASHED paSSwoRd '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER USER user1 with HASHED paSSwoRd '%s'", hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -162,7 +232,13 @@ public class PasswordObfuscatorTest
|
|||
obfuscate("ALTER USER user1 with PASSWORD\n'123'"));
|
||||
|
||||
assertEquals(format("ALTER USER user1 with PASSWORD\n'%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER USER user1 with PASSWORD\n'%s'", optsPassword), opts));
|
||||
obfuscate(format("ALTER USER user1 with PASSWORD\n'%s'", plainPwd), plainPwdOpts));
|
||||
|
||||
assertEquals(format("ALTER USER user1 with HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER USER user1 with HASHED PASSWORD\n'%s'", hashedPwd)));
|
||||
|
||||
assertEquals(format("ALTER USER user1 with HASHED PASSWORD\n'%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("ALTER USER user1 with HASHED PASSWORD\n'%s'", hashedPwd), hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -175,6 +251,12 @@ public class PasswordObfuscatorTest
|
|||
newLinePassOpts.setOption(org.apache.cassandra.auth.IRoleManager.Option.PASSWORD, "test\npassword");
|
||||
assertEquals(String.format("CREATE USER user1 with PASSWORD '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE USER user1 with PASSWORD '%s'", "test\npassword"), newLinePassOpts));
|
||||
|
||||
assertEquals(String.format("CREATE USER user1 with HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate("CREATE USER user1 with HASHED PASSWORD 'a\nb'"));
|
||||
|
||||
assertEquals(String.format("CREATE USER user1 with HASHED PASSWORD '%s'", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("CREATE USER user1 with HASHED PASSWORD '%s'", "test\npassword"), newLinePassOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -187,6 +269,12 @@ public class PasswordObfuscatorTest
|
|||
emptyPassOpts.setOption(org.apache.cassandra.auth.IRoleManager.Option.PASSWORD, "");
|
||||
assertEquals("CREATE USER user1 with PASSWORD ''",
|
||||
obfuscate("CREATE USER user1 with PASSWORD ''", emptyPassOpts));
|
||||
|
||||
assertEquals(String.format("CREATE USER user1 with HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate("CREATE USER user1 with HASHED PASSWORD ''"));
|
||||
|
||||
assertEquals("CREATE USER user1 with HASHED PASSWORD ''",
|
||||
obfuscate("CREATE USER user1 with HASHED PASSWORD ''", emptyPassOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -194,6 +282,9 @@ public class PasswordObfuscatorTest
|
|||
{
|
||||
assertEquals(String.format("CREATE USER user1 with PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate("CREATE USER user1 with PASSWORD 'p a ss wor d'"));
|
||||
|
||||
assertEquals(String.format("CREATE USER user1 with HASHED PASSWORD %s", OBFUSCATION_TOKEN),
|
||||
obfuscate("CREATE USER user1 with HASHED PASSWORD 'p a ss wor d'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -211,8 +302,23 @@ public class PasswordObfuscatorTest
|
|||
"APPLY BATCH;", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("BEGIN BATCH \n" +
|
||||
" CREATE ROLE alice1 WITH PASSWORD = '%s' and LOGIN = true; \n" +
|
||||
"APPLY BATCH;", optsPassword),
|
||||
opts));
|
||||
"APPLY BATCH;", plainPwd),
|
||||
plainPwdOpts));
|
||||
|
||||
assertEquals(format("BEGIN BATCH \n" +
|
||||
" CREATE ROLE alice1 WITH HASHED PASSWORD %s",
|
||||
OBFUSCATION_TOKEN),
|
||||
obfuscate("BEGIN BATCH \n" +
|
||||
" CREATE ROLE alice1 WITH HASHED PASSWORD = '" + hashedPwd + "' and LOGIN = true; \n" +
|
||||
"APPLY BATCH;"));
|
||||
|
||||
assertEquals(format("BEGIN BATCH \n" +
|
||||
" CREATE ROLE alice1 WITH HASHED PASSWORD = '%s' and LOGIN = true; \n" +
|
||||
"APPLY BATCH;", OBFUSCATION_TOKEN),
|
||||
obfuscate(format("BEGIN BATCH \n" +
|
||||
" CREATE ROLE alice1 WITH HASHED PASSWORD = '%s' and LOGIN = true; \n" +
|
||||
"APPLY BATCH;", hashedPwd),
|
||||
hashedPwdOpts));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -234,7 +340,26 @@ public class PasswordObfuscatorTest
|
|||
obfuscate(format("BEGIN BATCH \n" +
|
||||
" CREATE ROLE alice1 WITH PASSWORD = '%s' and LOGIN = true; \n" +
|
||||
" CREATE ROLE alice2 WITH PASSWORD = '%s' and LOGIN = true; \n" +
|
||||
"APPLY BATCH;", optsPassword, optsPassword),
|
||||
opts));
|
||||
"APPLY BATCH;", plainPwd, plainPwd),
|
||||
plainPwdOpts));
|
||||
|
||||
assertEquals(format("BEGIN BATCH \n" +
|
||||
" CREATE ROLE alice1 WITH HASHED PASSWORD %s",
|
||||
OBFUSCATION_TOKEN),
|
||||
obfuscate("BEGIN BATCH \n" +
|
||||
" CREATE ROLE alice1 WITH HASHED PASSWORD = '" + hashedPwd + "' and LOGIN = true; \n" +
|
||||
" CREATE ROLE alice2 WITH HASHED PASSWORD = '" + hashedPwd + "' and LOGIN = true; \n" +
|
||||
"APPLY BATCH;"));
|
||||
|
||||
assertEquals(format("BEGIN BATCH \n" +
|
||||
" CREATE ROLE alice1 WITH HASHED PASSWORD = '%s' and LOGIN = true; \n" +
|
||||
" CREATE ROLE alice2 WITH HASHED PASSWORD = '%s' and LOGIN = true; \n" +
|
||||
"APPLY BATCH;"
|
||||
, OBFUSCATION_TOKEN, OBFUSCATION_TOKEN),
|
||||
obfuscate(format("BEGIN BATCH \n" +
|
||||
" CREATE ROLE alice1 WITH HASHED PASSWORD = '%s' and LOGIN = true; \n" +
|
||||
" CREATE ROLE alice2 WITH HASHED PASSWORD = '%s' and LOGIN = true; \n" +
|
||||
"APPLY BATCH;", hashedPwd, hashedPwd),
|
||||
hashedPwdOpts));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.apache.cassandra.tools;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.tools.ToolRunner.ToolResult;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class HashPasswordTest extends CQLTester
|
||||
{
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
private static final String plaintextPassword = "foobar";
|
||||
private static final String hashPasswordTool = "tools/bin/hash_password";
|
||||
|
||||
/* If help changed you also need to change the docs*/
|
||||
@Test
|
||||
public void testHelpAndShouldChangeDocs()
|
||||
{
|
||||
ToolResult tool = ToolRunner.invoke(hashPasswordTool, "-h");
|
||||
tool.assertOnCleanExit();
|
||||
String help = "usage: hash_password [options]\n" +
|
||||
"--\n" +
|
||||
"Hashes a plain text password and prints the hashed password.\n" +
|
||||
"Options are:\n" +
|
||||
" -e,--environment-var <arg> Use value of the specified environment\n" +
|
||||
" variable as the password\n" +
|
||||
" -h,--help Display this help message\n" +
|
||||
" -i,--input <arg> Input is a file (or - for stdin) to read the\n" +
|
||||
" password from. Make sure that the whole input including newlines is\n" +
|
||||
" considered. For example, the shell command 'echo -n foobar | hash_password\n" +
|
||||
" -i -' will work as intended and just hash 'foobar'.\n" +
|
||||
" -p,--plain <arg> Argument is the plain text password\n" +
|
||||
" -r,--logrounds <arg> Number of hash rounds (default: 10).\n";
|
||||
assertEquals(help, tool.getStdout());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlain()
|
||||
{
|
||||
ToolResult tool = ToolRunner.invoke(hashPasswordTool, "--plain", plaintextPassword);
|
||||
tool.assertOnCleanExit();
|
||||
String hashed = tool.getStdout();
|
||||
assertTrue("Hashed password does not validate: " + hashed, BCrypt.checkpw(plaintextPassword, hashed));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStdIn()
|
||||
{
|
||||
ToolResult tool = ToolRunner.invoke(Collections.emptyMap(),
|
||||
new ByteArrayInputStream(plaintextPassword.getBytes()),
|
||||
Arrays.asList(hashPasswordTool, "--input", "-"));
|
||||
tool.assertOnCleanExit();
|
||||
String hashed = tool.getStdout();
|
||||
assertTrue("Hashed password does not validate: " + hashed, BCrypt.checkpw(plaintextPassword, hashed));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFile() throws IOException
|
||||
{
|
||||
File file = temporaryFolder.newFile();
|
||||
Files.write(file.toPath(), plaintextPassword.getBytes());
|
||||
|
||||
ToolResult tool = ToolRunner.invoke(hashPasswordTool, "--input", file.getAbsolutePath());
|
||||
tool.assertOnCleanExit();
|
||||
String hashed = tool.getStdout();
|
||||
assertTrue("Hashed password does not validate: " + hashed, BCrypt.checkpw(plaintextPassword, hashed));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnvVar()
|
||||
{
|
||||
ToolResult tool = ToolRunner.invoke(Collections.singletonMap("THE_PASSWORD", plaintextPassword),
|
||||
null,
|
||||
Arrays.asList(hashPasswordTool, "--environment-var", "THE_PASSWORD"));
|
||||
tool.assertOnCleanExit();
|
||||
String hashed = tool.getStdout();
|
||||
assertTrue("Hashed password does not validate: " + hashed, BCrypt.checkpw(plaintextPassword, hashed));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogRounds()
|
||||
{
|
||||
ToolResult tool = ToolRunner.invoke(hashPasswordTool, "--plain", plaintextPassword, "-r", "10");
|
||||
tool.assertOnCleanExit();
|
||||
String hashed = tool.getStdout();
|
||||
assertTrue("Hashed password does not validate: " + hashed, BCrypt.checkpw(plaintextPassword, hashed));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortPass()
|
||||
{
|
||||
ToolResult tool = ToolRunner.invoke(hashPasswordTool, "--plain", "A");
|
||||
tool.assertOnExitCode();
|
||||
assertThat(tool.getStderr(), containsString("password is very short"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorMessages()
|
||||
{
|
||||
assertToolError("One of the options --environment-var, --plain or --input must be used.", hashPasswordTool);
|
||||
assertToolError("Environment variable 'non_existing_environment_variable_name' is undefined.",
|
||||
hashPasswordTool,
|
||||
"--environment-var",
|
||||
"non_existing_environment_variable_name");
|
||||
assertToolError("Failed to read from '/foo/bar/baz/blah/yadda': ",
|
||||
hashPasswordTool,
|
||||
"--input",
|
||||
"/foo/bar/baz/blah/yadda");
|
||||
}
|
||||
|
||||
private static void assertToolError(String expectedMessage, String... args)
|
||||
{
|
||||
ToolResult tool = ToolRunner.invoke(args);
|
||||
assertEquals(1, tool.getExitCode());
|
||||
assertThat(tool.getStderr(), containsString(expectedMessage));
|
||||
}
|
||||
}
|
||||
|
|
@ -119,11 +119,13 @@ public class ToolRunner
|
|||
|
||||
private final InputStream input;
|
||||
private final T out;
|
||||
private final boolean autoCloseOut;
|
||||
|
||||
private StreamGobbler(InputStream input, T out)
|
||||
private StreamGobbler(InputStream input, T out, boolean autoCloseOut)
|
||||
{
|
||||
this.input = input;
|
||||
this.out = out;
|
||||
this.autoCloseOut = autoCloseOut;
|
||||
}
|
||||
|
||||
public void run()
|
||||
|
|
@ -136,6 +138,8 @@ public class ToolRunner
|
|||
int read = input.read(buffer);
|
||||
if (read == -1)
|
||||
{
|
||||
if (autoCloseOut)
|
||||
out.close();
|
||||
return;
|
||||
}
|
||||
out.write(buffer, 0, read);
|
||||
|
|
@ -536,19 +540,19 @@ public class ToolRunner
|
|||
if (includeStdinWatcher)
|
||||
numWatchers = 3;
|
||||
ioWatchers = new Thread[numWatchers];
|
||||
ioWatchers[0] = new Thread(new StreamGobbler<>(process.getErrorStream(), err));
|
||||
ioWatchers[0] = new Thread(new StreamGobbler<>(process.getErrorStream(), err, false));
|
||||
ioWatchers[0].setDaemon(true);
|
||||
ioWatchers[0].setName("IO Watcher stderr");
|
||||
ioWatchers[0].start();
|
||||
|
||||
ioWatchers[1] = new Thread(new StreamGobbler<>(process.getInputStream(), out));
|
||||
ioWatchers[1] = new Thread(new StreamGobbler<>(process.getInputStream(), out, false));
|
||||
ioWatchers[1].setDaemon(true);
|
||||
ioWatchers[1].setName("IO Watcher stdout");
|
||||
ioWatchers[1].start();
|
||||
|
||||
if (includeStdinWatcher)
|
||||
{
|
||||
ioWatchers[2] = new Thread(new StreamGobbler<>(stdin, process.getOutputStream()));
|
||||
ioWatchers[2] = new Thread(new StreamGobbler<>(stdin, process.getOutputStream(), true));
|
||||
ioWatchers[2].setDaemon(true);
|
||||
ioWatchers[2].setName("IO Watcher stdin");
|
||||
ioWatchers[2].start();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
#!/bin/sh
|
||||
|
||||
# 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.
|
||||
|
||||
if [ "x$CASSANDRA_INCLUDE" = "x" ]; then
|
||||
# Locations (in order) to use when searching for an include file.
|
||||
for include in "`dirname "$0"`/cassandra.in.sh" \
|
||||
"$HOME/.cassandra.in.sh" \
|
||||
/usr/share/cassandra/cassandra.in.sh \
|
||||
/usr/local/share/cassandra/cassandra.in.sh \
|
||||
/opt/cassandra/cassandra.in.sh; do
|
||||
if [ -r "$include" ]; then
|
||||
. "$include"
|
||||
break
|
||||
fi
|
||||
done
|
||||
elif [ -r "$CASSANDRA_INCLUDE" ]; then
|
||||
. "$CASSANDRA_INCLUDE"
|
||||
fi
|
||||
|
||||
if [ -z "$CLASSPATH" ]; then
|
||||
echo "You must set the CLASSPATH var" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "x${MAX_HEAP_SIZE}" = "x" ]; then
|
||||
MAX_HEAP_SIZE="256M"
|
||||
fi
|
||||
|
||||
if [ "x${MAX_DIRECT_MEMORY}" = "x" ]; then
|
||||
MAX_DIRECT_MEMORY="2G"
|
||||
fi
|
||||
|
||||
JVM_OPTS="${JVM_OPTS} -Xmx${MAX_HEAP_SIZE} -XX:MaxDirectMemorySize=${MAX_DIRECT_MEMORY}"
|
||||
|
||||
"${JAVA}" ${JAVA_AGENT} -ea -cp "${CLASSPATH}" ${JVM_OPTS} \
|
||||
-Dcassandra.storagedir="${cassandra_storagedir}" \
|
||||
-Dlogback.configurationFile=logback-tools.xml \
|
||||
org.apache.cassandra.tools.HashPassword "$@"
|
||||
Loading…
Reference in New Issue