This is an automated email from the ASF dual-hosted git repository.

mpetrov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 2332899b1a3 IGNITE-23459 Added support for interactive input of 
sensitive command arguments in control utility (#11604)
2332899b1a3 is described below

commit 2332899b1a36b10cd6c769611d1f76ed1089d82e
Author: Denis <[email protected]>
AuthorDate: Thu Feb 6 20:34:31 2025 +1000

    IGNITE-23459 Added support for interactive input of sensitive command 
arguments in control utility (#11604)
---
 .../internal/commandline/ArgumentParser.java       | 156 ++++++++--------
 .../internal/commandline/CommandHandler.java       |  42 +----
 .../commandline/ConnectionAndSslParameters.java    |  13 +-
 .../commandline/argument/parser/CLIArgument.java   | 199 ++++++++++++++++-----
 .../argument/parser/CLIArgumentParser.java         |  82 ++++++---
 .../commandline/indexreader/IgniteIndexReader.java |  41 +++--
 .../commandline/CommandHandlerParsingTest.java     |   6 +-
 .../GridCommandHandlerSslWithSecurityTest.java     | 122 +++++++++++++
 .../ignite/util/CacheMetricsCommandTest.java       |   2 +-
 .../org/apache/ignite/util/CdcCommandTest.java     |   4 +-
 .../util/GridCommandHandlerClusterByClassTest.java |   9 +-
 .../GridCommandHandlerFactoryAbstractTest.java     |   2 +-
 .../apache/ignite/util/GridCommandHandlerTest.java |   2 +-
 .../ignite/internal/management/api/Argument.java   |   6 +
 14 files changed, 477 insertions(+), 209 deletions(-)

diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java
index 5392ae5db51..1f09aeb3b4b 100644
--- 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java
@@ -15,16 +15,15 @@
  * 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.ListIterator;
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
@@ -42,15 +41,17 @@ import org.apache.ignite.internal.management.api.Command;
 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.commandline.argument.parser.CLIArgumentParser.readNextValueToken;
 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;
@@ -62,7 +63,9 @@ import static 
org.apache.ignite.internal.management.api.CommandUtils.isBoolean;
 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.
@@ -131,9 +134,6 @@ public class ArgumentParser {
     /** */
     static final String CMD_SSL_FACTORY = "--ssl-factory";
 
-    /** Set of sensitive arguments */
-    private static final Set<String> SENSITIVE_ARGUMENTS = new HashSet<>();
-
     /** */
     private static final BiConsumer<String, Integer> PORT_VALIDATOR = (name, 
val) -> {
         if (val <= 0 || val > 65535)
@@ -143,64 +143,46 @@ 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);
-    }
-
-    /**
-     * @param arg To check.
-     * @return True if provided argument is among sensitive one and not should 
be displayed.
-     */
-    public static boolean isSensitiveArgument(String arg) {
-        return SENSITIVE_ARGUMENTS.contains(arg);
-    }
+    /** Console instance. */
+    protected final GridConsole console;
 
     /**
      * @param log Logger.
      * @param registry Supported commands.
+     * @param console Console.
      */
-    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.
      *
@@ -236,7 +218,9 @@ public class ArgumentParser {
 
         CLIArgumentParser parser = createArgumentParser();
 
-        parser.parse(args.iterator());
+        parser.parse(args.listIterator());
+
+        String safeCmd = buildSafeCommandString(raw.listIterator(), parser);
 
         A arg = (A)argument(
             cmdPath.peek().argClass(),
@@ -253,7 +237,7 @@ public class ArgumentParser {
             throw new IllegalArgumentException("Experimental commands 
disabled");
         }
 
-        return new ConnectionAndSslParameters<>(cmdPath, arg, parser);
+        return new ConnectionAndSslParameters<>(cmdPath, arg, parser, safeCmd);
     }
 
     /**
@@ -323,14 +307,11 @@ public class ArgumentParser {
         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());
 
@@ -339,14 +320,10 @@ public class ArgumentParser {
                 || 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))
@@ -359,6 +336,37 @@ public class ArgumentParser {
 
         namedArgs.addAll(common);
 
-        return new CLIArgumentParser(positionalArgs, namedArgs);
+        return new CLIArgumentParser(positionalArgs, namedArgs, console);
+    }
+
+    /** @return String representation of command with hidden values of 
sensitive arguments. */
+    private String buildSafeCommandString(ListIterator<String> rawIter, 
CLIArgumentParser parser) {
+        SB res = new SB();
+
+        while (rawIter.hasNext()) {
+            String arg = rawIter.next();
+
+            CLIArgument<?> argDesc = 
parser.getArgumentDescriptor(arg.toLowerCase());
+
+            res.a(arg).a(' ');
+
+            if (argDesc == null || argDesc.isFlag())
+                continue;
+
+            String argVal = readNextValueToken(rawIter);
+
+            if (argVal != null) {
+                if (!argDesc.isSensitive())
+                    res.a(argVal).a(' ');
+                else {
+                    res.a("***** ");
+
+                    log.info(String.format("Warning: %s is insecure. Whenever 
possible, use interactive prompt for " +
+                            "password (just omit the argument value).", 
argDesc.name()));
+                }
+            }
+        }
+
+        return res.toString();
     }
 }
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java
index 4c733407f62..2b45198eeb7 100644
--- 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java
@@ -60,7 +60,6 @@ import org.apache.ignite.internal.util.IgniteUtils;
 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;
@@ -247,7 +246,7 @@ public class CommandHandler {
 
             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();
 
@@ -272,7 +271,7 @@ public class CommandHandler {
                     }
 
                     logger.info("Command [" + cmdName + "] started");
-                    logger.info("Arguments: " + argumentsToString(rawArgs));
+                    logger.info("Arguments: " + args.safeCommandString());
                     logger.info(U.DELIM);
 
                     String deprecationMsg = 
args.command().deprecationMessage(args.commandArg());
@@ -445,36 +444,6 @@ public class CommandHandler {
         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.
@@ -651,7 +620,8 @@ public class CommandHandler {
             "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("");
@@ -717,8 +687,8 @@ public class CommandHandler {
         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" +
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ConnectionAndSslParameters.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ConnectionAndSslParameters.java
index 1d001a4c19e..d1a5bc28248 100644
--- 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ConnectionAndSslParameters.java
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ConnectionAndSslParameters.java
@@ -65,19 +65,25 @@ public class ConnectionAndSslParameters<A extends 
IgniteDataTransferObject> {
     /** */
     private final CLIArgumentParser parser;
 
+    /** String representation of command with hidden values of sensitive 
arguments. */
+    private final String safeCmd;
+
     /**
      * @param cmdPath Path to the command in {@link CommandsRegistry} 
hierarchy.
      * @param arg Command argument.
      * @param parser CLI arguments parser.
+     * @param safeCmd String safe representation of command.
      */
     public ConnectionAndSslParameters(
         Deque<Command<?, ?>> cmdPath,
         A arg,
-        CLIArgumentParser parser
+        CLIArgumentParser parser,
+        String safeCmd
     ) {
         this.cmdPath = cmdPath;
         this.arg = arg;
         this.parser = parser;
+        this.safeCmd = safeCmd;
 
         this.user = parser.get(CMD_USER);
         this.pwd = parser.get(CMD_PASSWORD);
@@ -249,4 +255,9 @@ public class ConnectionAndSslParameters<A extends 
IgniteDataTransferObject> {
     public boolean verbose() {
         return parser.get(CMD_VERBOSE);
     }
+
+    /** @return String representation of command with hidden values of 
sensitive arguments. */
+    public String safeCommandString() {
+        return safeCmd;
+    }
 }
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/argument/parser/CLIArgument.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/argument/parser/CLIArgument.java
index 94ce189a7a7..60064256b2a 100644
--- 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/argument/parser/CLIArgument.java
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/argument/parser/CLIArgument.java
@@ -19,27 +19,21 @@ package 
org.apache.ignite.internal.commandline.argument.parser;
 
 import java.util.function.BiConsumer;
 import java.util.function.Function;
-import java.util.function.Supplier;
+import org.apache.ignite.internal.util.typedef.internal.A;
 
 /**
  * Command line argument.
  * @param <T> Value type.
  */
 public class CLIArgument<T> {
-    /** */
-    private final BiConsumer<String, T> EMPTY = (name, val) -> {};
-
     /** */
     private final String name;
 
     /** */
-    private final String usage;
-
-    /** */
-    private final boolean isOptional;
+    private final Class<T> type;
 
     /** */
-    private final Class<T> type;
+    private final String usage;
 
     /** */
     private final Function<CLIArgumentParser, T> dfltValSupplier;
@@ -48,48 +42,28 @@ public class CLIArgument<T> {
     private final BiConsumer<String, T> validator;
 
     /** */
-    public static <T> CLIArgument<T> optionalArg(String name, String usage, 
Class<T> type) {
-        return new CLIArgument<>(name, usage, true, type, null, null);
-    }
+    private final boolean isOptional;
 
     /** */
-    public static <T> CLIArgument<T> optionalArg(String name, String usage, 
Class<T> type, Supplier<T> dfltValSupplier) {
-        return new CLIArgument<>(name, usage, true, type, p -> 
dfltValSupplier.get(), null);
-    }
+    private final boolean isSensitive;
 
     /** */
-    public static <T> CLIArgument<T> optionalArg(
+    private CLIArgument(
         String name,
-        String usage,
         Class<T> type,
-        Function<CLIArgumentParser, T> dfltValSupplier,
-        BiConsumer<String, T> validator
-    ) {
-        return new CLIArgument<>(name, usage, true, type, dfltValSupplier, 
validator);
-    }
-
-    /** */
-    public static <T> CLIArgument<T> mandatoryArg(String name, String usage, 
Class<T> type) {
-        return new CLIArgument<>(name, usage, false, type, null, null);
-    }
-
-    /** */
-    public CLIArgument(
-        String name,
         String usage,
-        boolean isOptional,
-        Class<T> type,
         Function<CLIArgumentParser, T> dfltValSupplier,
-        BiConsumer<String, T> validator
+        BiConsumer<String, T> validator,
+        boolean isOptional,
+        boolean isSensitive
     ) {
         this.name = name;
-        this.usage = usage;
-        this.isOptional = isOptional;
         this.type = type;
-        this.dfltValSupplier = dfltValSupplier == null
-            ? (type.equals(Boolean.class) ? p -> (T)Boolean.FALSE : p -> null)
-            : dfltValSupplier;
+        this.usage = usage;
+        this.dfltValSupplier = dfltValSupplier;
         this.validator = validator;
+        this.isOptional = isOptional;
+        this.isSensitive = isSensitive;
     }
 
     /** */
@@ -97,28 +71,161 @@ public class CLIArgument<T> {
         return name;
     }
 
+    /** */
+    public Class<T> type() {
+        return type;
+    }
+
     /** */
     public String usage() {
         return usage;
     }
 
+    /** */
+    public Function<CLIArgumentParser, T> defaultValueSupplier() {
+        return dfltValSupplier;
+    }
+
+    /** */
+    public BiConsumer<String, T> validator() {
+        return validator;
+    }
+
     /** */
     public boolean optional() {
         return isOptional;
     }
 
     /** */
-    public Class<T> type() {
-        return type;
+    public boolean isSensitive() {
+        return isSensitive;
     }
 
     /** */
-    public Function<CLIArgumentParser, T> defaultValueSupplier() {
-        return dfltValSupplier;
+    public boolean isFlag() {
+        return type.equals(Boolean.class) || type.equals(boolean.class);
     }
 
-    /** */
-    public BiConsumer<String, T> validator() {
-        return validator == null ? EMPTY : validator;
+    /**
+     * Command line argument builder.
+     * @param <T> Value type.
+     */
+    public static class CLIArgumentBuilder<T> {
+        /** */
+        private final String name;
+
+        /** */
+        private final Class<T> type;
+
+        /** */
+        private String usage;
+
+        /** */
+        private Function<CLIArgumentParser, T> dfltValSupplier;
+
+        /** */
+        private BiConsumer<String, T> validator;
+
+        /** */
+        private boolean isOptional;
+
+        /** */
+        private boolean isSensitive;
+
+        /** */
+        private CLIArgumentBuilder(String name, Class<T> type, boolean 
isOptional) {
+            this.name = name;
+            this.type = type;
+            this.isOptional = isOptional;
+        }
+
+        /** */
+        public CLIArgumentBuilder<T> withUsage(String usage) {
+            this.usage = usage;
+
+            return this;
+        }
+
+        /** */
+        public CLIArgumentBuilder<T> withDefault(T dflt) {
+            dfltValSupplier = t -> dflt;
+
+            return this;
+        }
+
+        /** */
+        public CLIArgumentBuilder<T> withDefault(Function<CLIArgumentParser, 
T> dfltValSupplier) {
+            this.dfltValSupplier = dfltValSupplier;
+
+            return this;
+        }
+
+        /** */
+        public CLIArgumentBuilder<T> withValidator(BiConsumer<String, T> 
validator) {
+            this.validator = validator;
+
+            return this;
+        }
+
+        /** */
+        public CLIArgumentBuilder<T> markOptional() {
+            isOptional = true;
+
+            return this;
+        }
+
+        /** */
+        public CLIArgumentBuilder<T> withOptional(boolean optional) {
+            isOptional = optional;
+
+            return this;
+        }
+
+        /** */
+        public CLIArgumentBuilder<T> markSensitive() {
+            isSensitive = true;
+
+            return this;
+        }
+
+        /** */
+        public CLIArgumentBuilder<T> withSensitive(boolean sensitive) {
+            isSensitive = sensitive;
+
+            return this;
+        }
+
+        /**  */
+        public static <T> CLIArgumentBuilder<T> argument(String name, Class<T> 
type) {
+            return new CLIArgumentBuilder<>(name, type, false);
+        }
+
+        /** */
+        public static <T> CLIArgumentBuilder<T> optionalArgument(String name, 
Class<T> type) {
+            return new CLIArgumentBuilder<>(name, type, true);
+        }
+
+        /** */
+        public CLIArgument<T> build() {
+            A.notNull(name, "name");
+            A.notNull(type, "type");
+
+            boolean isFlag = type.equals(Boolean.class) || 
type.equals(boolean.class);
+
+            if (isFlag && isSensitive)
+                throw new IllegalArgumentException("Flag argument can't be 
sensitive");
+
+            Function<CLIArgumentParser, T> dfltValSupplier = 
this.dfltValSupplier;
+
+            if (dfltValSupplier == null)
+                dfltValSupplier = isFlag ? p -> (T)Boolean.FALSE : p -> null;
+
+            BiConsumer<String, T> validator = this.validator;
+
+            if (validator == null)
+                validator = (name, val) -> {};
+
+            return new CLIArgument<>(name, type, usage, dfltValSupplier, 
validator, isOptional, isSensitive);
+        }
     }
 }
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/argument/parser/CLIArgumentParser.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/argument/parser/CLIArgumentParser.java
index ff7f2f84a79..9d7769cac80 100644
--- 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/argument/parser/CLIArgumentParser.java
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/argument/parser/CLIArgumentParser.java
@@ -19,14 +19,14 @@ package 
org.apache.ignite.internal.commandline.argument.parser;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.commandline.GridConsole;
 import org.apache.ignite.internal.util.GridStringBuilder;
 
 import static java.util.stream.Collectors.toSet;
@@ -49,20 +49,21 @@ public class CLIArgumentParser {
     /** */
     private final Map<String, Object> parsedArgs = new HashMap<>();
 
-    /** */
-    public CLIArgumentParser(List<CLIArgument<?>> argConfiguration) {
-        this(Collections.emptyList(), argConfiguration);
-    }
+    /** Console instance. */
+    protected final GridConsole console;
 
     /** */
     public CLIArgumentParser(
-        List<CLIArgument<?>> positionalArgConfig,
-        List<CLIArgument<?>> argConfiguration
+        List<CLIArgument<?>> positionalArgCfg,
+        List<CLIArgument<?>> argConfiguration,
+        GridConsole console
     ) {
-        this.positionalArgCfg = positionalArgConfig;
+        this.positionalArgCfg = positionalArgCfg;
 
         for (CLIArgument<?> cliArg : argConfiguration)
             this.argConfiguration.put(cliArg.name(), cliArg);
+
+        this.console = console;
     }
 
     /**
@@ -71,7 +72,7 @@ public class CLIArgumentParser {
      *
      * @param argsIter Iterator.
      */
-    public void parse(Iterator<String> argsIter) {
+    public void parse(ListIterator<String> argsIter) {
         Set<String> obligatoryArgs =
             argConfiguration.values().stream().filter(a -> 
!a.optional()).map(CLIArgument::name).collect(toSet());
 
@@ -102,18 +103,10 @@ public class CLIArgumentParser {
             else if (parsedArgs.get(cliArg.name()) != null)
                 throw new IllegalArgumentException(cliArg.name() + " argument 
specified twice");
 
-            boolean bool = cliArg.type().equals(Boolean.class) || 
cliArg.type().equals(boolean.class);
-
-            if (!bool && !argsIter.hasNext())
-                throw new IllegalArgumentException("Please specify a value for 
argument: " + arg);
-
-            String strVal = bool ? "true" : argsIter.next();
-
-            if (strVal != null && strVal.startsWith(NAME_PREFIX))
-                throw new IllegalArgumentException("Unexpected value: " + 
strVal);
+            String argVal = readArgumentValue(cliArg, argsIter);
 
             try {
-                Object val = parseVal(strVal, cliArg.type());
+                Object val = parseVal(argVal, cliArg.type());
 
                 
((CLIArgument<Object>)cliArg).validator().accept(cliArg.name(), val);
 
@@ -177,6 +170,14 @@ public class CLIArgumentParser {
         return (T)parsedPositionalArgs.get(position);
     }
 
+    /**
+     * @param name Argument name.
+     * @return Argument descriptor.
+     */
+    public CLIArgument<?> getArgumentDescriptor(String name) {
+        return argConfiguration.get(name);
+    }
+
     /**
      * Returns usage description.
      *
@@ -214,4 +215,45 @@ public class CLIArgumentParser {
         else
             return arg.name();
     }
+
+    /** */
+    private String readArgumentValue(CLIArgument<?> arg, ListIterator<String> 
argsIter) {
+        if (arg.isFlag())
+            return "true";
+
+        String argVal = readNextValueToken(argsIter);
+
+        if (argVal == null) {
+            if (console != null && arg.isSensitive())
+                argVal = new 
String(requestPasswordFromConsole(arg.name().substring(NAME_PREFIX.length()) + 
": "));
+            else
+                throw new IllegalArgumentException("Please specify a value for 
argument: " + arg.name());
+        }
+
+        return argVal;
+    }
+
+    /** */
+    public static String readNextValueToken(ListIterator<String> argsIter) {
+        if (!argsIter.hasNext())
+            return null;
+
+        String val = argsIter.next();
+
+        if (val.startsWith(NAME_PREFIX)) {
+            argsIter.previous();
+
+            return null;
+        }
+
+        return val;
+    }
+
+    /**
+     * @param msg Message.
+     * @return Password.
+     */
+    private char[] requestPasswordFromConsole(String msg) {
+        return console.readPassword(msg);
+    }
 }
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/indexreader/IgniteIndexReader.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/indexreader/IgniteIndexReader.java
index 94af0f28227..4ffd28a6c6d 100644
--- 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/indexreader/IgniteIndexReader.java
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/indexreader/IgniteIndexReader.java
@@ -65,7 +65,6 @@ import 
org.apache.ignite.internal.processors.cache.persistence.StorageException;
 import 
org.apache.ignite.internal.processors.cache.persistence.file.AsyncFileIOFactory;
 import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStore;
 import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
-import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreV2;
 import 
org.apache.ignite.internal.processors.cache.persistence.file.FileVersionCheckingFactory;
 import 
org.apache.ignite.internal.processors.cache.persistence.freelist.io.PagesListMetaIO;
 import 
org.apache.ignite.internal.processors.cache.persistence.freelist.io.PagesListNodeIO;
@@ -104,8 +103,8 @@ import static java.util.Objects.nonNull;
 import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toList;
 import static 
org.apache.ignite.configuration.DataStorageConfiguration.DFLT_PAGE_SIZE;
-import static 
org.apache.ignite.internal.commandline.argument.parser.CLIArgument.mandatoryArg;
-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.SystemViewTask.SimpleType.NUMBER;
 import static 
org.apache.ignite.internal.management.SystemViewTask.SimpleType.STRING;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_DATA;
@@ -118,6 +117,7 @@ import static 
org.apache.ignite.internal.pagemem.PageIdUtils.pageIndex;
 import static org.apache.ignite.internal.pagemem.PageIdUtils.partId;
 import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.INDEX_FILE_NAME;
 import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.PART_FILE_TEMPLATE;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreV2.VERSION;
 import static org.apache.ignite.internal.util.GridUnsafe.allocateBuffer;
 import static org.apache.ignite.internal.util.GridUnsafe.bufferAddress;
 import static org.apache.ignite.internal.util.GridUnsafe.freeBuffer;
@@ -300,20 +300,27 @@ public class IgniteIndexReader implements AutoCloseable {
     public static void main(String[] args) {
         System.out.println("THIS UTILITY MUST BE LAUNCHED ON PERSISTENT STORE 
WHICH IS NOT UNDER RUNNING GRID!");
 
-        CLIArgumentParser p = new CLIArgumentParser(asList(
-            mandatoryArg(
-                DIR_ARG,
-                "partition directory, where " + INDEX_FILE_NAME + " and 
(optionally) partition files are located.",
-                String.class
+        CLIArgumentParser p = new CLIArgumentParser(
+            Collections.emptyList(),
+            asList(
+                argument(DIR_ARG, String.class)
+                    .withUsage("partition directory, where " + INDEX_FILE_NAME 
+ " and (optionally) partition files are located.")
+                    .build(),
+                optionalArgument(PART_CNT_ARG, Integer.class).withUsage("full 
partitions count in cache group.").withDefault(0).build(),
+                optionalArgument(PAGE_SIZE_ARG, Integer.class).withUsage("page 
size.").withDefault(DFLT_PAGE_SIZE).build(),
+                optionalArgument(PAGE_STORE_VER_ARG, 
Integer.class).withUsage("page store version.").withDefault(VERSION).build(),
+                optionalArgument(INDEXES_ARG, String[].class)
+                    .withUsage("you can specify index tree names that will be 
processed, separated by comma without " +
+                        "spaces, other index trees will be skipped.")
+                    .withDefault(U.EMPTY_STRS)
+                    .build(),
+                optionalArgument(CHECK_PARTS_ARG, Boolean.class)
+                    .withUsage("check cache data tree in partition files and 
it's consistency with indexes.")
+                    .withDefault(false)
+                    .build()
             ),
-            optionalArg(PART_CNT_ARG, "full partitions count in cache group.", 
Integer.class, () -> 0),
-            optionalArg(PAGE_SIZE_ARG, "page size.", Integer.class, () -> 
DFLT_PAGE_SIZE),
-            optionalArg(PAGE_STORE_VER_ARG, "page store version.", 
Integer.class, () -> FilePageStoreV2.VERSION),
-            optionalArg(INDEXES_ARG, "you can specify index tree names that 
will be processed, separated by comma " +
-                "without spaces, other index trees will be skipped.", 
String[].class, () -> U.EMPTY_STRS),
-            optionalArg(CHECK_PARTS_ARG,
-                "check cache data tree in partition files and it's consistency 
with indexes.", Boolean.class, () -> false)
-        ));
+            null
+        );
 
         if (args.length == 0) {
             System.out.println(p.usage());
@@ -321,7 +328,7 @@ public class IgniteIndexReader implements AutoCloseable {
             return;
         }
 
-        p.parse(asList(args).iterator());
+        p.parse(asList(args).listIterator());
 
         Set<String> idxs = new HashSet<>(asList(p.get(INDEXES_ARG)));
 
diff --git 
a/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
 
b/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
index 8a816611c24..ada5db81577 100644
--- 
a/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
+++ 
b/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
@@ -968,7 +968,7 @@ public class CommandHandlerParsingTest {
                 "--group-names", "--some-other-arg"
             )),
             IllegalArgumentException.class,
-            "Unexpected value: --some-other-arg"
+            "Please specify a value for argument: --group-names"
         );
 
         GridTestUtils.assertThrows(
@@ -979,7 +979,7 @@ public class CommandHandlerParsingTest {
                 "--cache-names", "--some-other-arg"
             )),
             IllegalArgumentException.class,
-            "Unexpected value: --some-other-arg"
+            "Please specify a value for argument: --cache-names"
         );
 
         GridTestUtils.assertThrows(
@@ -1339,7 +1339,7 @@ public class CommandHandlerParsingTest {
      * @return Common parameters container object.
      */
     private <A extends IgniteDataTransferObject> ConnectionAndSslParameters<A> 
parseArgs(List<String> args) {
-        return new ArgumentParser(setupTestLogger(), new 
IgniteCommandRegistry()).parseAndValidate(args);
+        return new ArgumentParser(setupTestLogger(), new 
IgniteCommandRegistry(), null).parseAndValidate(args);
     }
 
     /**
diff --git 
a/modules/control-utility/src/test/java/org/apache/ignite/internal/processors/security/GridCommandHandlerSslWithSecurityTest.java
 
b/modules/control-utility/src/test/java/org/apache/ignite/internal/processors/security/GridCommandHandlerSslWithSecurityTest.java
index b3fe7c8e38a..2a5ac1cd27f 100644
--- 
a/modules/control-utility/src/test/java/org/apache/ignite/internal/processors/security/GridCommandHandlerSslWithSecurityTest.java
+++ 
b/modules/control-utility/src/test/java/org/apache/ignite/internal/processors/security/GridCommandHandlerSslWithSecurityTest.java
@@ -200,4 +200,126 @@ public class GridCommandHandlerSslWithSecurityTest 
extends GridCommandHandlerFac
         assertContains(log, testOutput, "--keystore-password *****");
         assertContains(log, testOutput, "--truststore-password *****");
     }
+
+    /**
+     * Verify that the command work correctly when request starts with the 
--password argument
+     * without value that invoke console password input for user, and that it 
is requested only once.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testInputKeyUserPwdOnlyOncePwdArgStart() throws Exception {
+        doPasswordInteractiveInputTest(Arrays.asList(
+            "--password",
+            "--state",
+            "--user", login,
+            "--keystore", keyStorePath(CLI_CMD_HND.equals(commandHandler) ? 
"thinClient" : "connectorServer"),
+            "--keystore-password", keyStorePassword(),
+            "--truststore", keyStorePath(CLI_CMD_HND.equals(commandHandler) ? 
"trusttwo" : "trustthree"),
+            "--truststore-password", keyStorePassword()));
+    }
+
+    /**
+     * Verify that the command work correctly when request contains the 
--password argument inside
+     * without value that invoke console password input for user, and that it 
is requested only once.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testInputKeyUserPwdOnlyOncePwdArgMiddle() throws Exception {
+        doPasswordInteractiveInputTest(Arrays.asList(
+            "--state",
+            "--user", login,
+            "--password",
+            "--keystore", keyStorePath(CLI_CMD_HND.equals(commandHandler) ? 
"thinClient" : "connectorServer"),
+            "--keystore-password", keyStorePassword(),
+            "--truststore", keyStorePath(CLI_CMD_HND.equals(commandHandler) ? 
"trusttwo" : "trustthree"),
+            "--truststore-password", keyStorePassword()));
+    }
+
+    /**
+     * Verify that the command work correctly when request ends with the 
--password argument
+     * without value that invoke console password input for user, and that it 
is requested only once.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testInputKeyUserPwdOnlyOncePwdArgEnd() throws Exception {
+        doPasswordInteractiveInputTest(Arrays.asList(
+            "--state",
+            "--user", login,
+            "--keystore", keyStorePath(CLI_CMD_HND.equals(commandHandler) ? 
"thinClient" : "connectorServer"),
+            "--keystore-password", keyStorePassword(),
+            "--truststore", keyStorePath(CLI_CMD_HND.equals(commandHandler) ? 
"trusttwo" : "trustthree"),
+            "--truststore-password", keyStorePassword(),
+            "--password"));
+    }
+
+    /**
+     * Verify that the command work correctly when request few arguments
+     * without value that invoke console input.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testInputKeyForFewRequestedArguments() throws Exception {
+        IgniteEx crd = startGrid();
+
+        crd.cluster().state(ACTIVE);
+
+        TestCommandHandler hnd = newCommandHandler();
+
+        AtomicInteger reqCnt = new AtomicInteger();
+
+        ((CommandHandler)GridTestUtils.getFieldValue(hnd, "hnd")).console = 
new NoopConsole() {
+            @Override public char[] readPassword(String fmt, Object... args) {
+                if (reqCnt.incrementAndGet() == 1)
+                    return keyStorePassword().toCharArray();
+                else
+                    return pwd.toCharArray();
+            }
+        };
+
+        int exitCode = hnd.execute(Arrays.asList(
+            "--state",
+            "--user", login,
+            "--verbose",
+            "--keystore", keyStorePath(CLI_CMD_HND.equals(commandHandler) ? 
"thinClient" : "connectorServer"),
+            "--keystore-password",
+            "--truststore", keyStorePath(CLI_CMD_HND.equals(commandHandler) ? 
"trusttwo" : "trustthree"),
+            "--truststore-password", keyStorePassword(),
+            "--password"));
+
+        assertEquals(EXIT_CODE_OK, exitCode);
+        assertEquals(2, reqCnt.get());
+    }
+
+    /**
+     * Perform the test with prepared List arguments.
+     *
+     * @param args List of query arguments.
+     * @throws Exception If failed.
+     */
+    private void doPasswordInteractiveInputTest(List<String> args) throws 
Exception {
+        IgniteEx crd = startGrid();
+
+        crd.cluster().state(ACTIVE);
+
+        TestCommandHandler hnd = newCommandHandler();
+
+        AtomicInteger pwdCnt = new AtomicInteger();
+
+        ((CommandHandler)GridTestUtils.getFieldValue(hnd, "hnd")).console = 
new NoopConsole() {
+            @Override public char[] readPassword(String fmt, Object... args) {
+                pwdCnt.incrementAndGet();
+
+                return pwd.toCharArray();
+            }
+        };
+
+        int exitCode = hnd.execute(args);
+
+        assertEquals(EXIT_CODE_OK, exitCode);
+        assertEquals(1, pwdCnt.get());
+    }
 }
diff --git 
a/modules/control-utility/src/test/java/org/apache/ignite/util/CacheMetricsCommandTest.java
 
b/modules/control-utility/src/test/java/org/apache/ignite/util/CacheMetricsCommandTest.java
index 5f36276a957..ab5f6581c00 100644
--- 
a/modules/control-utility/src/test/java/org/apache/ignite/util/CacheMetricsCommandTest.java
+++ 
b/modules/control-utility/src/test/java/org/apache/ignite/util/CacheMetricsCommandTest.java
@@ -184,7 +184,7 @@ public class CacheMetricsCommandTest extends 
GridCommandHandlerAbstractTest {
             CACHE_ONE);
 
         // Check when after --caches argument extra argument is passed instead 
of list of caches
-        checkInvalidArguments("Unexpected value: --all-caches", ENABLE, 
CACHES_ARGUMENT, ALL_CACHES_ARGUMENT);
+        checkInvalidArguments("Please specify a value for argument: --caches", 
ENABLE, CACHES_ARGUMENT, ALL_CACHES_ARGUMENT);
     }
 
     /**
diff --git 
a/modules/control-utility/src/test/java/org/apache/ignite/util/CdcCommandTest.java
 
b/modules/control-utility/src/test/java/org/apache/ignite/util/CdcCommandTest.java
index 95870fc3f2c..2b3f8f5a0e6 100644
--- 
a/modules/control-utility/src/test/java/org/apache/ignite/util/CdcCommandTest.java
+++ 
b/modules/control-utility/src/test/java/org/apache/ignite/util/CdcCommandTest.java
@@ -177,7 +177,7 @@ public class CdcCommandTest extends 
GridCommandHandlerAbstractTest {
 
         assertContains(log, executeCommand(EXIT_CODE_INVALID_ARGUMENTS,
                 CDC, DELETE_LOST_SEGMENT_LINKS, NODE_ID),
-            "Unexpected value: --yes");
+            "Please specify a value for argument: --node-id");
 
         assertContains(log, executeCommand(EXIT_CODE_INVALID_ARGUMENTS,
                 CDC, DELETE_LOST_SEGMENT_LINKS, NODE_ID, "10"),
@@ -298,7 +298,7 @@ public class CdcCommandTest extends 
GridCommandHandlerAbstractTest {
 
         assertContains(log, executeCommand(EXIT_CODE_INVALID_ARGUMENTS,
                 CDC, RESEND, CACHES),
-            "Unexpected value: --yes");
+            "Please specify a value for argument: --caches");
     }
 
     /** */
diff --git 
a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
 
b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
index c6dceefd994..efc11f3cd5b 100644
--- 
a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
+++ 
b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
@@ -1362,9 +1362,7 @@ public class GridCommandHandlerClusterByClassTest extends 
GridCommandHandlerClus
         assertContains(
             log,
             executeCommand(EXIT_CODE_INVALID_ARGUMENTS, "--cache", CREATE, 
SPRING_XML_CONFIG),
-            !sslEnabled()
-                ? "Please specify a value for argument: --springXmlConfig"
-                : "Unexpected value: --keystore"
+                "Please specify a value for argument: --springxmlconfig"
         );
 
         autoConfirmation = true;
@@ -1651,10 +1649,7 @@ public class GridCommandHandlerClusterByClassTest 
extends GridCommandHandlerClus
 
         assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute("--cache", SCAN, 
"cache", "--limit"));
 
-        if (sslEnabled()) // Extra arguments at the end added.
-            assertContains(log, testOut.toString(), "Unexpected value: 
--keystore");
-        else
-            assertContains(log, testOut.toString(), "Please specify a value 
for argument: --limit");
+        assertContains(log, testOut.toString(), "Please specify a value for 
argument: --limit");
 
         assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute("--cache", SCAN, 
"cache", "--limit", "test"));
 
diff --git 
a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerFactoryAbstractTest.java
 
b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerFactoryAbstractTest.java
index 5c84a7030e9..07a98d279ec 100644
--- 
a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerFactoryAbstractTest.java
+++ 
b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerFactoryAbstractTest.java
@@ -206,7 +206,7 @@ public class GridCommandHandlerFactoryAbstractTest extends 
GridCommonAbstractTes
             String cmdName = null;
 
             try {
-                ArgumentParser parser = new ArgumentParser(log, new 
IgniteCommandRegistry());
+                ArgumentParser parser = new ArgumentParser(log, new 
IgniteCommandRegistry(), null);
 
                 ConnectionAndSslParameters<IgniteDataTransferObject> p = 
parser.parseAndValidate(value);
 
diff --git 
a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
 
b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
index e79a6e0704c..8371fa7b0cf 100644
--- 
a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
+++ 
b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
@@ -3787,7 +3787,7 @@ public class GridCommandHandlerTest extends 
GridCommandHandlerClusterPerMethodAb
             assertContains(
                 log,
                 testOut.toString(),
-                !sslEnabled() ? "Please specify a value for argument: 
--increment" : "Unexpected value: "
+                "Please specify a value for argument: --increment"
             );
 
             // Wrong params.
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/management/api/Argument.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/Argument.java
index 3ca12071ba1..67e424d9621 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/management/api/Argument.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/management/api/Argument.java
@@ -52,4 +52,10 @@ public @interface Argument {
      * @return {@code True} if parameter name expected without "--" prefix.
      */
     public boolean withoutPrefix() default false;
+
+    /**
+     * @return {@code True} if the argument value is sensitive and needs value 
to be requested from console,
+     * in case this value is not specified, {@code false} if not needed.
+     */
+    public boolean sensitive() default false;
 }

Reply via email to