Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
java.nio.file.Paths @ Use org.opensearch.common.io.PathUtils.get() instead.
java.nio.file.FileSystems#getDefault() @ use org.opensearch.common.io.PathUtils.getDefaultFileSystem() instead.

joptsimple.internal.Strings @ use org.opensearch.core.common.Strings instead.
org.apache.logging.log4j.util.Strings @ use org.opensearch.core.common.Strings instead.

java.nio.file.Files#getFileStore(java.nio.file.Path) @ Use org.opensearch.env.Environment.getFileStore() instead, impacted by JDK-8034057
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@

package org.opensearch.tools.cli.keystore;

import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.opensearch.cli.ExitCodes;
import org.opensearch.cli.Terminal;
import org.opensearch.cli.UserException;
Expand All @@ -44,51 +42,52 @@

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;

import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

/**
* A subcommand for the keystore cli which adds a file setting.
* A subcommand for the keystore CLI which adds a file setting.
*/
@Command(name = "add-file", description = "Add a file setting to the keystore (provide pairs: <setting> <path> ...)", mixinStandardHelpOptions = true, usageHelpAutoWidth = true)
class AddFileKeyStoreCommand extends BaseKeyStoreCommand {

private final OptionSpec<String> arguments;
/**
* Expect positional arguments in pairs: setting path [setting path]...
* We collect them raw and validate the pairing ourselves to preserve original behavior/messages.
*/
@Parameters(arity = "0..*", paramLabel = "setting path", description = "Pairs of <setting> <path> to add")
private List<String> argPairs = new ArrayList<>();

AddFileKeyStoreCommand() {
// BaseKeyStoreCommand already exposes --force/-f and handles keystore creation/prompting.
super("Add a file setting to the keystore", false);
this.forceOption = parser.acceptsAll(
Arrays.asList("f", "force"),
"Overwrite existing setting without prompting, creating keystore if necessary"
);
// jopt simple has issue with multiple non options, so we just get one set of them here
// and convert to File when necessary
// see https://github.com/jopt-simple/jopt-simple/issues/103
this.arguments = parser.nonOptions("(setting path)+");
}

@Override
protected void executeCommand(Terminal terminal, OptionSet options, Environment env) throws Exception {
final List<String> argumentValues = arguments.values(options);
if (argumentValues.size() == 0) {
protected void executeCommand(Terminal terminal, Environment env) throws Exception {
if (argPairs.isEmpty()) {
throw new UserException(ExitCodes.USAGE, "Missing setting name");
}
if (argumentValues.size() % 2 != 0) {
if (argPairs.size() % 2 != 0) {
throw new UserException(ExitCodes.USAGE, "settings and filenames must come in pairs");
}

final KeyStoreWrapper keyStore = getKeyStore();

for (int i = 0; i < argumentValues.size(); i += 2) {
final String setting = argumentValues.get(i);
for (int i = 0; i < argPairs.size(); i += 2) {
final String setting = argPairs.get(i);

if (keyStore.getSettingNames().contains(setting) && options.has(forceOption) == false) {
if (keyStore.getSettingNames().contains(setting) && !force) {
if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) {
terminal.println("Exiting without modifying keystore.");
return;
}
}

final Path file = getPath(argumentValues.get(i + 1));
final Path file = getPath(argPairs.get(i + 1));
if (Files.exists(file) == false) {
throw new UserException(ExitCodes.IO_ERROR, "File [" + file.toString() + "] does not exist");
}
Expand All @@ -103,5 +102,4 @@ protected void executeCommand(Terminal terminal, OptionSet options, Environment
private Path getPath(String file) {
return PathUtils.get(file);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@

package org.opensearch.tools.cli.keystore;

import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.opensearch.cli.ExitCodes;
import org.opensearch.cli.Terminal;
import org.opensearch.cli.UserException;
Expand All @@ -48,25 +46,28 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;

import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

/**
* A subcommand for the keystore cli which adds a string setting.
* A subcommand for the keystore CLI which adds a string setting.
*/
@Command(name = "add", description = "Add one or more string settings to the keystore", mixinStandardHelpOptions = true, usageHelpAutoWidth = true)
class AddStringKeyStoreCommand extends BaseKeyStoreCommand {

private final OptionSpec<Void> stdinOption;
private final OptionSpec<String> arguments;
@Option(names = { "-x", "--stdin" }, description = "Read setting values from stdin")
boolean readFromStdin;

@Parameters(arity = "0..*", paramLabel = "setting", description = "Setting names to add")
List<String> settings = new ArrayList<>();

AddStringKeyStoreCommand() {
super("Add a string settings to the keystore", false);
this.stdinOption = parser.acceptsAll(Arrays.asList("x", "stdin"), "Read setting values from stdin");
this.forceOption = parser.acceptsAll(
Arrays.asList("f", "force"),
"Overwrite existing setting without prompting, creating keystore if necessary"
);
this.arguments = parser.nonOptions("setting names");
// --force/-f option is inherited from BaseKeyStoreCommand
}

// pkg private so tests can manipulate
Expand All @@ -75,8 +76,7 @@ InputStream getStdin() {
}

@Override
protected void executeCommand(Terminal terminal, OptionSet options, Environment env) throws Exception {
final List<String> settings = arguments.values(options);
protected void executeCommand(Terminal terminal, Environment env) throws Exception {
if (settings.isEmpty()) {
throw new UserException(ExitCodes.USAGE, "the setting names can not be empty");
}
Expand All @@ -85,7 +85,8 @@ protected void executeCommand(Terminal terminal, OptionSet options, Environment

final Closeable closeable;
final CheckedFunction<String, char[], IOException> valueSupplier;
if (options.has(stdinOption)) {

if (readFromStdin) {
final BufferedReader stdinReader = new BufferedReader(new InputStreamReader(getStdin(), StandardCharsets.UTF_8));
valueSupplier = s -> {
try (CharArrayWriter writer = new CharArrayWriter()) {
Expand All @@ -107,7 +108,7 @@ protected void executeCommand(Terminal terminal, OptionSet options, Environment

try (Closeable ignored = closeable) {
for (final String setting : settings) {
if (keyStore.getSettingNames().contains(setting) && options.has(forceOption) == false) {
if (keyStore.getSettingNames().contains(setting) && !force) {
if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) {
terminal.println("Exiting without modifying keystore.");
return;
Expand All @@ -124,5 +125,4 @@ protected void executeCommand(Terminal terminal, OptionSet options, Environment

keyStore.save(env.configDir(), getKeyStorePassword().getChars());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@

package org.opensearch.tools.cli.keystore;

import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.opensearch.cli.ExitCodes;
import org.opensearch.cli.Terminal;
import org.opensearch.cli.UserException;
Expand All @@ -43,6 +41,8 @@

import java.nio.file.Path;

import picocli.CommandLine.Option;

/**
* Base settings class for key store commands.
*
Expand All @@ -58,14 +58,15 @@ public abstract class BaseKeyStoreCommand extends KeyStoreAwareCommand {
* Option to force operations without prompting for confirmation.
* When specified, operations proceed without asking for user input.
*/
protected OptionSpec<Void> forceOption;
@Option(names = { "-f", "--force" }, description = "Force operation without prompting for confirmation")
protected boolean force;

/**
* Creates a new BaseKeyStoreCommand with the specified description and existence requirement.
*
* @param description The description of the command
* @param description The description of the command
* @param keyStoreMustExist If true, the keystore must exist before executing the command.
* If false, a new keystore may be created if none exists.
* If false, a new keystore may be created if none exists.
*/
public BaseKeyStoreCommand(String description, boolean keyStoreMustExist) {
super(description);
Expand All @@ -75,19 +76,18 @@ public BaseKeyStoreCommand(String description, boolean keyStoreMustExist) {
/**
* Executes the keystore command by loading/creating the keystore and handling password management.
* If the keystore doesn't exist and keyStoreMustExist is false, prompts to create a new one
* unless the force option is specified.
* unless the {@code --force} option is specified.
*
* @param terminal The terminal to use for user interaction
* @param options The command-line options provided
* @param env The environment settings
* @throws Exception if there are errors during keystore operations
* @param env The environment settings
* @throws Exception if there are errors during keystore operations
* @throws UserException if the keystore is required but doesn't exist
*/
@Override
protected final void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
protected final void execute(Terminal terminal, Environment env) throws Exception {
try {
final Path configFile = env.configDir();
keyStore = KeyStoreWrapper.load(configFile);
final Path configDir = env.configDir();
keyStore = KeyStoreWrapper.load(configDir);
if (keyStore == null) {
if (keyStoreMustExist) {
throw new UserException(
Expand All @@ -96,20 +96,20 @@ protected final void execute(Terminal terminal, OptionSet options, Environment e
+ KeyStoreWrapper.keystorePath(env.configDir())
+ "]. Use 'create' command to create one."
);
} else if (options.has(forceOption) == false) {
} else if (force == false) {
if (terminal.promptYesNo("The opensearch keystore does not exist. Do you want to create it?", false) == false) {
terminal.println("Exiting without creating keystore.");
return;
}
}
keyStorePassword = new SecureString(new char[0]);
keyStore = KeyStoreWrapper.create();
keyStore.save(configFile, keyStorePassword.getChars());
keyStore.save(configDir, keyStorePassword.getChars());
} else {
keyStorePassword = keyStore.hasPassword() ? readPassword(terminal, false) : new SecureString(new char[0]);
keyStore.decrypt(keyStorePassword.getChars());
}
executeCommand(terminal, options, env);
executeCommand(terminal, env);
} catch (SecurityException e) {
throw new UserException(ExitCodes.DATA_ERROR, e.getMessage());
} finally {
Expand Down Expand Up @@ -144,9 +144,8 @@ protected SecureString getKeyStorePassword() {
* {@link #getKeyStore()} and {@link #getKeyStorePassword()} respectively.
*
* @param terminal The terminal to use for user interaction
* @param options The command line options that were specified
* @param env The environment configuration
* @param env The environment configuration
* @throws Exception if there is an error executing the command
*/
protected abstract void executeCommand(Terminal terminal, OptionSet options, Environment env) throws Exception;
protected abstract void executeCommand(Terminal terminal, Environment env) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,47 @@

package org.opensearch.tools.cli.keystore;

import joptsimple.OptionSet;
import org.opensearch.cli.ExitCodes;
import org.opensearch.cli.Terminal;
import org.opensearch.cli.UserException;
import org.opensearch.common.settings.KeyStoreWrapper;
import org.opensearch.core.common.settings.SecureString;
import org.opensearch.env.Environment;

import picocli.CommandLine.Command;

/**
* A sub-command for the keystore cli which changes the password.
* A sub-command for the keystore CLI which changes the password.
*
* @opensearch.internal
*/
@Command(name = "passwd", description = "Changes the password of a keystore", mixinStandardHelpOptions = true, usageHelpAutoWidth = true)
public class ChangeKeyStorePasswordCommand extends BaseKeyStoreCommand {

ChangeKeyStorePasswordCommand() {
/**
* Constructs the change password command.
*/
public ChangeKeyStorePasswordCommand() {
super("Changes the password of a keystore", true);
}

/**
* Executes the password change command by prompting for a new password
* and saving the keystore with the updated password.
*
* <p>
* This implementation will:
* 1. Prompt for a new password with verification
* 2. Save the keystore with the new password
* 3. Display a success message upon completion
*
* @param terminal The terminal to use for user interaction
* @param options The command-line options provided
* @param env The environment settings containing configuration directory
* @throws Exception if there are errors during password change
* @throws UserException if there are security-related errors
*/
@Override
protected void executeCommand(Terminal terminal, OptionSet options, Environment env) throws Exception {
protected void executeCommand(Terminal terminal, Environment env) throws Exception {
try (SecureString newPassword = readPassword(terminal, true)) {
final KeyStoreWrapper keyStore = getKeyStore();
keyStore.save(env.configDir(), newPassword.getChars());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@

package org.opensearch.tools.cli.keystore;

import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.opensearch.cli.ExitCodes;
import org.opensearch.cli.Terminal;
import org.opensearch.cli.UserException;
Expand All @@ -43,23 +41,26 @@

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;

import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

/**
* A sub-command for the keystore cli to create a new keystore.
* A sub-command for the keystore CLI to create a new keystore.
*/
@Command(name = "create", description = "Creates a new OpenSearch keystore", mixinStandardHelpOptions = true, usageHelpAutoWidth = true)
class CreateKeyStoreCommand extends KeyStoreAwareCommand {

private final OptionSpec<Void> passwordOption;
@Option(names = { "-p", "--password" }, description = "Prompt for password to encrypt the keystore")
boolean promptForPassword;

CreateKeyStoreCommand() {
super("Creates a new opensearch keystore");
this.passwordOption = parser.acceptsAll(Arrays.asList("p", "password"), "Prompt for password to encrypt the keystore");
}

@Override
protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
try (SecureString password = options.has(passwordOption) ? readPassword(terminal, true) : new SecureString(new char[0])) {
protected void execute(Terminal terminal, Environment env) throws Exception {
try (SecureString password = promptForPassword ? readPassword(terminal, true) : new SecureString(new char[0])) {
Path keystoreFile = KeyStoreWrapper.keystorePath(env.configDir());
if (Files.exists(keystoreFile)) {
if (terminal.promptYesNo("An opensearch keystore already exists. Overwrite?", false) == false) {
Expand Down
Loading
Loading