Skip to content

Commit

Permalink
IGNITE-23459 Add console input if password argument presented without…
Browse files Browse the repository at this point in the history
… value for ./control.sh
  • Loading branch information
Положаев Денис Александрович committed Feb 1, 2025
1 parent f47b67c commit 7c81ece
Show file tree
Hide file tree
Showing 13 changed files with 476 additions and 203 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@
* limitations under the License.
*/


package org.apache.ignite.internal.commandline;

import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
Expand All @@ -42,15 +40,16 @@
import org.apache.ignite.internal.management.api.CommandUtils;
import org.apache.ignite.internal.management.api.CommandsRegistry;
import org.apache.ignite.internal.management.api.Positional;
import org.apache.ignite.internal.util.typedef.internal.SB;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteExperimental;
import org.apache.ignite.ssl.SslContextFactory;

import static org.apache.ignite.IgniteSystemProperties.IGNITE_ENABLE_EXPERIMENTAL_COMMAND;
import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_HOST;
import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_PORT;
import static org.apache.ignite.internal.commandline.CommandHandler.UTILITY_NAME;
import static org.apache.ignite.internal.commandline.argument.parser.CLIArgument.optionalArg;
import static org.apache.ignite.internal.commandline.argument.parser.CLIArgument.CLIArgumentBuilder.argument;
import static org.apache.ignite.internal.commandline.argument.parser.CLIArgument.CLIArgumentBuilder.optionalArgument;
import static org.apache.ignite.internal.management.api.CommandUtils.CMD_WORDS_DELIM;
import static org.apache.ignite.internal.management.api.CommandUtils.NAME_PREFIX;
import static org.apache.ignite.internal.management.api.CommandUtils.PARAM_WORDS_DELIM;
Expand All @@ -62,7 +61,9 @@
import static org.apache.ignite.internal.management.api.CommandUtils.toFormattedCommandName;
import static org.apache.ignite.internal.management.api.CommandUtils.toFormattedFieldName;
import static org.apache.ignite.internal.management.api.CommandUtils.visitCommandParams;
import static org.apache.ignite.ssl.SslContextFactory.DFLT_KEY_ALGORITHM;
import static org.apache.ignite.ssl.SslContextFactory.DFLT_SSL_PROTOCOL;
import static org.apache.ignite.ssl.SslContextFactory.DFLT_STORE_TYPE;

/**
* Argument parser.
Expand Down Expand Up @@ -131,8 +132,8 @@ public class ArgumentParser {
/** */
static final String CMD_SSL_FACTORY = "--ssl-factory";

/** Set of sensitive arguments */
private static final Set<String> SENSITIVE_ARGUMENTS = new HashSet<>();
/**String builder for logging arguments with hidden confidential values*/
private static final SB argumentsToString = new SB();

/** */
private static final BiConsumer<String, Integer> PORT_VALIDATOR = (name, val) -> {
Expand All @@ -143,64 +144,64 @@ public class ArgumentParser {
/** */
private final List<CLIArgument<?>> common = new ArrayList<>();

static {
SENSITIVE_ARGUMENTS.add(CMD_PASSWORD);
SENSITIVE_ARGUMENTS.add(CMD_KEYSTORE_PASSWORD);
SENSITIVE_ARGUMENTS.add(CMD_TRUSTSTORE_PASSWORD);
}
/** Console instance */
protected final GridConsole console;

/**
* @param arg To check.
* @return True if provided argument is among sensitive one and not should be displayed.
* @return a string of logging arguments with hidden confidential values
* and clearning argumentsToString
*/
public static boolean isSensitiveArgument(String arg) {
return SENSITIVE_ARGUMENTS.contains(arg);
public static String argumentsToString() {
String args = argumentsToString.toString();

argumentsToString.setLength(0);

return args;
}

/**
* @param log Logger.
* @param registry Supported commands.
* @param console Supported commands.
*/
public ArgumentParser(IgniteLogger log, IgniteCommandRegistry registry) {
public ArgumentParser(IgniteLogger log, IgniteCommandRegistry registry, GridConsole console) {
this.log = log;
this.registry = registry;

BiConsumer<String, ?> securityWarn = (name, val) -> log.info(String.format("Warning: %s is insecure. " +
"Whenever possible, use interactive prompt for password (just discard %s option).", name, name));

arg(CMD_HOST, "HOST_OR_IP", String.class, DFLT_HOST);
arg(CMD_PORT, "PORT", Integer.class, DFLT_PORT, PORT_VALIDATOR);
arg(CMD_USER, "USER", String.class, null);
arg(CMD_PASSWORD, "PASSWORD", String.class, null, (BiConsumer<String, String>)securityWarn);
arg(CMD_VERBOSE, CMD_VERBOSE, boolean.class, false);
arg(CMD_SSL_PROTOCOL, "SSL_PROTOCOL[, SSL_PROTOCOL_2, ..., SSL_PROTOCOL_N]", String[].class, new String[] {DFLT_SSL_PROTOCOL});
arg(CMD_SSL_CIPHER_SUITES, "SSL_CIPHER_1[, SSL_CIPHER_2, ..., SSL_CIPHER_N]", String[].class, null);
arg(CMD_SSL_KEY_ALGORITHM, "SSL_KEY_ALGORITHM", String.class, SslContextFactory.DFLT_KEY_ALGORITHM);
arg(CMD_SSL_FACTORY, "SSL_FACTORY_PATH", String.class, null);
arg(CMD_KEYSTORE_TYPE, "KEYSTORE_TYPE", String.class, SslContextFactory.DFLT_STORE_TYPE);
arg(CMD_KEYSTORE, "KEYSTORE_PATH", String.class, null);
arg(CMD_KEYSTORE_PASSWORD, "KEYSTORE_PASSWORD", char[].class, null, (BiConsumer<String, char[]>)securityWarn);
arg(CMD_TRUSTSTORE_TYPE, "TRUSTSTORE_TYPE", String.class, SslContextFactory.DFLT_STORE_TYPE);
arg(CMD_TRUSTSTORE, "TRUSTSTORE_PATH", String.class, null);
arg(CMD_TRUSTSTORE_PASSWORD, "TRUSTSTORE_PASSWORD", char[].class, null, (BiConsumer<String, char[]>)securityWarn);
arg(CMD_AUTO_CONFIRMATION, CMD_AUTO_CONFIRMATION, boolean.class, false);
arg(
CMD_ENABLE_EXPERIMENTAL,
CMD_ENABLE_EXPERIMENTAL, Boolean.class,
IgniteSystemProperties.getBoolean(IGNITE_ENABLE_EXPERIMENTAL_COMMAND)
this.console = console;

common.addAll(List.of(
optionalArgument(CMD_HOST, String.class).withUsage("HOST_OR_IP").withDefault(DFLT_HOST).build(),
optionalArgument(CMD_PORT, Integer.class).withUsage("PORT").withDefault(DFLT_PORT).withValidator(PORT_VALIDATOR).build(),
optionalArgument(CMD_USER, String.class).withUsage("USER").build(),
optionalArgument(CMD_PASSWORD, String.class).withUsage("PASSWORD").markSensitive().build(),
optionalArgument(CMD_VERBOSE, boolean.class).withUsage(CMD_VERBOSE).withDefault(false).build(),
optionalArgument(CMD_SSL_PROTOCOL, String[].class)
.withUsage("SSL_PROTOCOL[, SSL_PROTOCOL_2, ..., SSL_PROTOCOL_N]")
.withDefault(t -> new String[] {DFLT_SSL_PROTOCOL})
.build(),
optionalArgument(CMD_SSL_CIPHER_SUITES, String[].class).withUsage("SSL_CIPHER_1[, SSL_CIPHER_2, ..., SSL_CIPHER_N]").build(),
optionalArgument(CMD_SSL_KEY_ALGORITHM, String.class).withUsage("SSL_KEY_ALGORITHM").withDefault(DFLT_KEY_ALGORITHM).build(),
optionalArgument(CMD_SSL_FACTORY, String.class).withUsage("SSL_FACTORY_PATH").build(),
optionalArgument(CMD_KEYSTORE_TYPE, String.class).withUsage("KEYSTORE_TYPE").withDefault(DFLT_STORE_TYPE).build(),
optionalArgument(CMD_KEYSTORE, String.class).withUsage("KEYSTORE_PATH").build(),
optionalArgument(CMD_KEYSTORE_PASSWORD, char[].class)
.withUsage("KEYSTORE_PASSWORD")
.markSensitive()
.build(),
optionalArgument(CMD_TRUSTSTORE_TYPE, String.class).withUsage("TRUSTSTORE_TYPE").withDefault(DFLT_STORE_TYPE).build(),
optionalArgument(CMD_TRUSTSTORE, String.class).withUsage("TRUSTSTORE_PATH").build(),
optionalArgument(CMD_TRUSTSTORE_PASSWORD, char[].class)
.withUsage("TRUSTSTORE_PASSWORD")
.markSensitive()
.build(),
optionalArgument(CMD_AUTO_CONFIRMATION, boolean.class).withUsage(CMD_AUTO_CONFIRMATION).withDefault(false).build(),
optionalArgument(CMD_ENABLE_EXPERIMENTAL, Boolean.class)
.withUsage(CMD_ENABLE_EXPERIMENTAL)
.withDefault(t -> IgniteSystemProperties.getBoolean(IGNITE_ENABLE_EXPERIMENTAL_COMMAND))
.build())
);
}

/** */
private <T> void arg(String name, String usage, Class<T> type, T dflt, BiConsumer<String, T> validator) {
common.add(optionalArg(name, usage, type, t -> dflt, validator));
}

/** */
private <T> void arg(String name, String usage, Class<T> type, T dflt) {
common.add(optionalArg(name, usage, type, () -> dflt));
}

/**
* Creates list of common utility options.
*
Expand Down Expand Up @@ -232,11 +233,11 @@ public String[] getCommonOptions() {
public <A extends IgniteDataTransferObject> ConnectionAndSslParameters<A> parseAndValidate(List<String> raw) {
List<String> args = new ArrayList<>(raw);

findCommand(args.iterator());
argumentsToString.a(findCommand(args.iterator()));

CLIArgumentParser parser = createArgumentParser();

parser.parse(args.iterator());
argumentsToString.a(parser.parse(args.listIterator()));

A arg = (A)argument(
cmdPath.peek().argClass(),
Expand All @@ -261,9 +262,11 @@ public <A extends IgniteDataTransferObject> ConnectionAndSslParameters<A> parseA
*
* @param iter Iterator of CLI arguments.
*/
protected void findCommand(Iterator<String> iter) {
protected SB findCommand(Iterator<String> iter) {
assert cmdPath.isEmpty();

SB cmdsToStr = new SB();

while (iter.hasNext() && cmdPath.isEmpty()) {
String cmdName = iter.next();

Expand All @@ -276,6 +279,8 @@ protected void findCommand(Iterator<String> iter) {
continue;

cmdPath.push(cmd);

cmdsToStr.a(cmdName).a(' ');
}

if (cmdPath.isEmpty())
Expand Down Expand Up @@ -305,6 +310,8 @@ protected void findCommand(Iterator<String> iter) {

cmdPath.push(cmd1);

cmdsToStr.a(cmd1).a(' ');

// Remove command name parameter to exclude it from ongoing parsing.
iter.remove();
}
Expand All @@ -314,6 +321,8 @@ protected void findCommand(Iterator<String> iter) {
"Command " + toFormattedCommandName(cmdPath.peek().getClass()) + " can't be executed"
);
}

return cmdsToStr;
}

/** */
Expand All @@ -323,14 +332,11 @@ private CLIArgumentParser createArgumentParser() {
List<CLIArgument<?>> positionalArgs = new ArrayList<>();
List<CLIArgument<?>> namedArgs = new ArrayList<>();

BiFunction<Field, Boolean, CLIArgument<?>> toArg = (fld, optional) -> new CLIArgument<>(
toFormattedFieldName(fld).toLowerCase(),
null,
optional,
fld.getType(),
null,
(name, val) -> {}
);
BiFunction<Field, Boolean, CLIArgument<?>> toArg =
(fld, optional) -> argument(toFormattedFieldName(fld).toLowerCase(), fld.getType())
.withOptional(optional)
.withSensitive(fld.getAnnotation(Argument.class).sensitive())
.build();

List<Set<String>> grpdFlds = CommandUtils.argumentGroupsValues(cmdPath.peek().argClass());

Expand All @@ -339,14 +345,10 @@ private CLIArgumentParser createArgumentParser() {
|| fld.getAnnotation(Argument.class).optional())
);

Consumer<Field> positionalArgCb = fld -> positionalArgs.add(new CLIArgument<>(
fld.getName().toLowerCase(),
null,
fld.getAnnotation(Argument.class).optional(),
fld.getType(),
null,
(name, val) -> {}
));
Consumer<Field> positionalArgCb = fld -> positionalArgs.add(argument(fld.getName().toLowerCase(), fld.getType())
.withOptional(fld.getAnnotation(Argument.class).optional())
.build()
);

BiConsumer<ArgumentGroup, List<Field>> argGrpCb = (argGrp0, flds) -> flds.forEach(fld -> {
if (fld.isAnnotationPresent(Positional.class))
Expand All @@ -359,6 +361,6 @@ private CLIArgumentParser createArgumentParser() {

namedArgs.addAll(common);

return new CLIArgumentParser(positionalArgs, namedArgs);
return new CLIArgumentParser(positionalArgs, namedArgs, console, log);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
import org.apache.ignite.internal.util.spring.IgniteSpringHelperImpl;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.SB;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteExperimental;
import org.apache.ignite.ssl.SslContextFactory;
Expand All @@ -76,6 +75,7 @@
import static org.apache.ignite.internal.commandline.ArgumentParser.CMD_AUTO_CONFIRMATION;
import static org.apache.ignite.internal.commandline.ArgumentParser.CMD_ENABLE_EXPERIMENTAL;
import static org.apache.ignite.internal.commandline.ArgumentParser.CMD_VERBOSE;
import static org.apache.ignite.internal.commandline.ArgumentParser.argumentsToString;
import static org.apache.ignite.internal.commandline.CommandLogger.errorMessage;
import static org.apache.ignite.internal.management.api.CommandUtils.CMD_WORDS_DELIM;
import static org.apache.ignite.internal.management.api.CommandUtils.DOUBLE_INDENT;
Expand Down Expand Up @@ -247,7 +247,7 @@ public <A extends IgniteDataTransferObject> int execute(List<String> rawArgs) {

verbose = F.exist(rawArgs, CMD_VERBOSE::equalsIgnoreCase);

ConnectionAndSslParameters<A> args = new ArgumentParser(logger, registry).parseAndValidate(rawArgs);
ConnectionAndSslParameters<A> args = new ArgumentParser(logger, registry, console).parseAndValidate(rawArgs);

cmdName = toFormattedCommandName(args.cmdPath().peekLast().getClass()).toUpperCase();

Expand All @@ -272,7 +272,7 @@ public <A extends IgniteDataTransferObject> int execute(List<String> rawArgs) {
}

logger.info("Command [" + cmdName + "] started");
logger.info("Arguments: " + argumentsToString(rawArgs));
logger.info("Arguments: " + argumentsToString());
logger.info(U.DELIM);

String deprecationMsg = args.command().deprecationMessage(args.commandArg());
Expand Down Expand Up @@ -445,36 +445,6 @@ private boolean isConnectionClosedSilentlyException(Throwable e) {
return e instanceof ClientConnectionException && e.getMessage().startsWith("Channel is closed");
}

/**
* Joins user's arguments and hides sensitive information.
*
* @param rawArgs Arguments which user has provided.
* @return String which could be shown in console and pritned to log.
*/
private String argumentsToString(List<String> rawArgs) {
boolean hide = false;

SB sb = new SB();

for (int i = 0; i < rawArgs.size(); i++) {
if (hide) {
sb.a("***** ");

hide = false;

continue;
}

String arg = rawArgs.get(i);

sb.a(arg).a(' ');

hide = ArgumentParser.isSensitiveArgument(arg);
}

return sb.toString();
}

/**
* @param args Common arguments.
* @return Thin client configuration to connect to cluster.
Expand Down Expand Up @@ -651,7 +621,8 @@ private void printHelp(List<String> rawArgs) {
"The command has the following syntax:");
logger.info("");

logger.info(INDENT + join(" ", join(" ", UTILITY_NAME, join(" ", new ArgumentParser(logger, registry).getCommonOptions())),
logger.info(INDENT + join(" ",
join(" ", UTILITY_NAME, join(" ", new ArgumentParser(logger, registry, null).getCommonOptions())),
asOptional("command", true), "<command_parameters>"));
logger.info("");
logger.info("");
Expand Down Expand Up @@ -717,8 +688,8 @@ private void printCacheHelpHeader(IgniteLogger logger) {
logger.info(INDENT + "The '--cache subcommand' is used to get information about and perform actions" +
" with caches. The command has the following syntax:");
logger.info("");
logger.info(INDENT + join(" ", UTILITY_NAME, join(" ", new ArgumentParser(logger, null).getCommonOptions())) + " " +
"--cache [subcommand] <subcommand_parameters>");
logger.info(INDENT + join(" ", UTILITY_NAME, join(" ", new ArgumentParser(logger, null, null).getCommonOptions())) +
" --cache [subcommand] <subcommand_parameters>");
logger.info("");
logger.info(INDENT + "The subcommands that take [nodeId] as an argument ('list', 'find_garbage', " +
"'contention' and 'validate_indexes') will be executed on the given node or on all server nodes" +
Expand Down
Loading

0 comments on commit 7c81ece

Please sign in to comment.