Added more unit tests, re-implemented & added tests for ESUsersTool
- Added CliTool infrastructure (should eventually be moved to core and removed from this repo) Original commit: elastic/x-pack-elasticsearch@ba498163f5
This commit is contained in:
parent
f727e29066
commit
9b3160b7ac
2
pom.xml
2
pom.xml
|
@ -301,7 +301,7 @@
|
|||
<excludes>
|
||||
<exclude>jsr166e/**</exclude>
|
||||
<!-- start excludes for valid system-out -->
|
||||
<exclude>org/elasticsearch/shield/support/CmdLineTool*</exclude>
|
||||
<exclude>org/elasticsearch/shield/support/cli/Terminal*</exclude>
|
||||
<exclude>org/elasticsearch/common/logging/log4j/ConsoleAppender*</exclude>
|
||||
<exclude>org/elasticsearch/plugins/PluginManager.class</exclude>
|
||||
<exclude>org/elasticsearch/bootstrap/Bootstrap.class</exclude>
|
||||
|
|
|
@ -51,7 +51,7 @@ public class FileUserPasswdStore extends AbstractComponent implements UserPasswd
|
|||
|
||||
FileUserPasswdStore(Settings settings, Environment env, ResourceWatcherService watcherService, Listener listener) {
|
||||
super(settings);
|
||||
file = resolveFile(componentSettings, env);
|
||||
file = resolveFile(settings, env);
|
||||
esUsers = ImmutableMap.copyOf(parseFile(file, logger));
|
||||
watcher = new FileWatcher(file.getParent().toFile());
|
||||
watcher.addListener(new FileListener());
|
||||
|
@ -72,7 +72,7 @@ public class FileUserPasswdStore extends AbstractComponent implements UserPasswd
|
|||
}
|
||||
|
||||
public static Path resolveFile(Settings settings, Environment env) {
|
||||
String location = settings.get("file.users");
|
||||
String location = settings.get("shield.authc.esusers.files.users");
|
||||
if (location == null) {
|
||||
return env.configFile().toPath().resolve(".users");
|
||||
}
|
||||
|
@ -110,9 +110,9 @@ public class FileUserPasswdStore extends AbstractComponent implements UserPasswd
|
|||
}
|
||||
|
||||
public static void writeFile(Map<String, char[]> esUsers, Path path) {
|
||||
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path, Charsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.WRITE))) {
|
||||
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path, Charsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE))) {
|
||||
for (Map.Entry<String, char[]> entry : esUsers.entrySet()) {
|
||||
writer.printf(Locale.ROOT, "{}\t{}", entry.getKey(), new String(entry.getValue()));
|
||||
writer.printf(Locale.ROOT, "%s:%s%s", entry.getKey(), new String(entry.getValue()), System.lineSeparator());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new ElasticsearchException("Could not write users file [" + path.toAbsolutePath() + "], please check file permissions", ioe);
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.shield.authc.esusers;
|
|||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.collect.Lists;
|
||||
import org.elasticsearch.common.inject.internal.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
|
@ -52,7 +53,7 @@ public class FileUserRolesStore extends AbstractComponent implements UserRolesSt
|
|||
|
||||
FileUserRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, Listener listener) {
|
||||
super(settings);
|
||||
file = resolveFile(componentSettings, env);
|
||||
file = resolveFile(settings, env);
|
||||
userRoles = ImmutableMap.copyOf(parseFile(file, logger));
|
||||
FileWatcher watcher = new FileWatcher(file.getParent().toFile());
|
||||
watcher.addListener(new FileListener());
|
||||
|
@ -65,7 +66,7 @@ public class FileUserRolesStore extends AbstractComponent implements UserRolesSt
|
|||
}
|
||||
|
||||
public static Path resolveFile(Settings settings, Environment env) {
|
||||
String location = settings.get("file.users_roles");
|
||||
String location = settings.get("shield.authc.esusers.files.users_roles");
|
||||
if (location == null) {
|
||||
return env.configFile().toPath().resolve(".users_roles");
|
||||
}
|
||||
|
@ -104,9 +105,9 @@ public class FileUserRolesStore extends AbstractComponent implements UserRolesSt
|
|||
}
|
||||
|
||||
public static void writeFile(Map<String, String[]> userRoles, Path path) {
|
||||
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path, Charsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.WRITE))) {
|
||||
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path, Charsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE, StandardOpenOption.WRITE))) {
|
||||
for (Map.Entry<String, String[]> entry : userRoles.entrySet()) {
|
||||
writer.printf(Locale.ROOT, "{}\t{}", entry.getKey(), Strings.arrayToCommaDelimitedString(entry.getValue()));
|
||||
writer.printf(Locale.ROOT, "%s:%s%s", entry.getKey(), Strings.arrayToCommaDelimitedString(entry.getValue()), System.lineSeparator());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new ElasticsearchException("Could not write users file [" + path.toAbsolutePath() + "], please check file permissions");
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.authc.esusers.tool;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.shield.authc.esusers.FileUserPasswdStore;
|
||||
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
|
||||
import org.elasticsearch.shield.authc.support.Hasher;
|
||||
import org.elasticsearch.shield.support.cli.CliTool;
|
||||
import org.elasticsearch.shield.support.cli.CliToolConfig;
|
||||
import org.elasticsearch.shield.support.cli.Terminal;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.shield.support.cli.CliToolConfig.Builder.cmd;
|
||||
import static org.elasticsearch.shield.support.cli.CliToolConfig.Builder.option;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ESUsersTool extends CliTool {
|
||||
|
||||
private static final CliToolConfig CONFIG = CliToolConfig.config("esusers", ESUsersTool.class)
|
||||
.cmds(Useradd.CMD, Userdel.CMD, Passwd.CMD)
|
||||
.build();
|
||||
|
||||
public ESUsersTool() {
|
||||
super(CONFIG);
|
||||
}
|
||||
|
||||
public ESUsersTool(Terminal terminal) {
|
||||
super(CONFIG, terminal);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Command parse(String cmdName, CommandLine cli) throws Exception {
|
||||
switch (cmdName.toLowerCase(Locale.ROOT)) {
|
||||
case Useradd.NAME: return Useradd.parse(terminal, cli);
|
||||
case Userdel.NAME: return Userdel.parse(terminal, cli);
|
||||
case Passwd.NAME: return Passwd.parse(terminal, cli);
|
||||
default:
|
||||
assert false : "should never get here, if the user enters an unknown command, an error message should be shown before parse is called";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class Useradd extends CliTool.Command {
|
||||
|
||||
private static final String NAME = "useradd";
|
||||
|
||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, Useradd.class)
|
||||
.options(
|
||||
option("p", "password").hasArg(false).required(false),
|
||||
option("r", "roles").hasArg(false).required(false))
|
||||
.build();
|
||||
|
||||
public static Command parse(Terminal terminal, CommandLine cli) {
|
||||
if (cli.getArgs().length == 0) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, "username is missing");
|
||||
}
|
||||
|
||||
String username = cli.getArgs()[0];
|
||||
|
||||
char[] password;
|
||||
String passwordStr = cli.getOptionValue("password");
|
||||
if (passwordStr != null) {
|
||||
password = passwordStr.toCharArray();
|
||||
} else {
|
||||
password = terminal.readSecret("Enter new password: ");
|
||||
char[] retyped = terminal.readSecret("Retype new password: ");
|
||||
if (!Arrays.equals(password, retyped)) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, "Password mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
String rolesCsv = cli.getOptionValue("roles");
|
||||
String[] roles = (rolesCsv != null) ? rolesCsv.split(",") : Strings.EMPTY_ARRAY;
|
||||
return new Useradd(terminal, username, password, roles);
|
||||
}
|
||||
|
||||
final String username;
|
||||
final char[] passwd;
|
||||
final String[] roles;
|
||||
|
||||
Useradd(Terminal terminal, String username, char[] passwd, String... roles) {
|
||||
super(terminal);
|
||||
this.username = username;
|
||||
this.passwd = passwd;
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
Path file = FileUserPasswdStore.resolveFile(settings, env);
|
||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(file, null));
|
||||
if (users == null) {
|
||||
// file doesn't exist so we just create a new file
|
||||
users = new HashMap<>();
|
||||
}
|
||||
if (users.containsKey(username)) {
|
||||
terminal.println("User [{}] already exists", username);
|
||||
return ExitStatus.CODE_ERROR;
|
||||
}
|
||||
Hasher hasher = Hasher.HTPASSWD;
|
||||
users.put(username, hasher.hash(passwd));
|
||||
FileUserPasswdStore.writeFile(users, file);
|
||||
|
||||
|
||||
file = FileUserRolesStore.resolveFile(settings, env);
|
||||
Map<String, String[]> userRoles = new HashMap<>(FileUserRolesStore.parseFile(file, null));
|
||||
if (userRoles == null) {
|
||||
// file doesn't exist, so we just create a new file
|
||||
userRoles = new HashMap<>();
|
||||
}
|
||||
userRoles.put(username, roles);
|
||||
FileUserRolesStore.writeFile(userRoles, file);
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
}
|
||||
|
||||
static class Userdel extends CliTool.Command {
|
||||
|
||||
private static final String NAME = "userdel";
|
||||
|
||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, Userdel.class).build();
|
||||
|
||||
public static Command parse(Terminal terminal, CommandLine cli) {
|
||||
if (cli.getArgs().length == 0) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, "username is missing");
|
||||
}
|
||||
|
||||
String username = cli.getArgs()[0];
|
||||
return new Userdel(terminal, username);
|
||||
}
|
||||
|
||||
final String username;
|
||||
|
||||
Userdel(Terminal terminal, String username) {
|
||||
super(terminal);
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
Path file = FileUserPasswdStore.resolveFile(settings, env);
|
||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(file, null));
|
||||
if (users != null) {
|
||||
char[] passwd = users.remove(username);
|
||||
if (passwd != null) {
|
||||
FileUserPasswdStore.writeFile(users, file);
|
||||
} else {
|
||||
terminal.println("Warning: users file [%s] did not contain password entry for user [%s]", file.toAbsolutePath(), username);
|
||||
}
|
||||
}
|
||||
|
||||
file = FileUserRolesStore.resolveFile(settings, env);
|
||||
Map<String, String[]> userRoles = new HashMap<>(FileUserRolesStore.parseFile(file, null));
|
||||
if (userRoles != null) {
|
||||
String[] roles = userRoles.remove(username);
|
||||
if (roles != null) {
|
||||
FileUserRolesStore.writeFile(userRoles, file);
|
||||
} else {
|
||||
terminal.println("Warning: users_roles file [%s] did not contain roles entry for user [%s]", file.toAbsolutePath(), username);
|
||||
}
|
||||
}
|
||||
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
}
|
||||
|
||||
static class Passwd extends CliTool.Command {
|
||||
|
||||
private static final String NAME = "passwd";
|
||||
|
||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, Passwd.class)
|
||||
.options(option("p", "password").hasArg(false).required(false))
|
||||
.build();
|
||||
|
||||
public static Command parse(Terminal terminal, CommandLine cli) {
|
||||
if (cli.getArgs().length == 0) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, "username is missing");
|
||||
}
|
||||
|
||||
String username = cli.getArgs()[0];
|
||||
|
||||
char[] password;
|
||||
String passwordStr = cli.getOptionValue("password");
|
||||
if (passwordStr != null) {
|
||||
password = passwordStr.toCharArray();
|
||||
} else {
|
||||
password = terminal.readSecret("Enter new password: ");
|
||||
char[] retyped = terminal.readSecret("Retype new password: ");
|
||||
if (!Arrays.equals(password, retyped)) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, "Password mismatch");
|
||||
}
|
||||
}
|
||||
return new Passwd(terminal, username, password);
|
||||
}
|
||||
|
||||
final String username;
|
||||
final char[] passwd;
|
||||
|
||||
Passwd(Terminal terminal, String username, char[] passwd) {
|
||||
super(terminal);
|
||||
this.username = username;
|
||||
this.passwd = passwd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
Path file = FileUserPasswdStore.resolveFile(settings, env);
|
||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(file, null));
|
||||
if (users == null) {
|
||||
// file doesn't exist so we just create a new file
|
||||
users = new HashMap<>();
|
||||
}
|
||||
if (!users.containsKey(username)) {
|
||||
terminal.println("User [{}] doesn't exist", username);
|
||||
return ExitStatus.NO_USER;
|
||||
}
|
||||
Hasher hasher = Hasher.HTPASSWD;
|
||||
users.put(username, hasher.hash(passwd));
|
||||
FileUserPasswdStore.writeFile(users, file);
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.authc.esusers.tool;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.elasticsearch.shield.authc.esusers.FileUserPasswdStore;
|
||||
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
|
||||
import org.elasticsearch.shield.authc.support.Hasher;
|
||||
import org.elasticsearch.shield.support.CmdLineTool;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class UserAdd extends CmdLineTool {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new UserAdd().execute(args);
|
||||
}
|
||||
|
||||
public UserAdd() {
|
||||
super("useradd",
|
||||
option("p", "password", "The user password").hasArg(true).required(true),
|
||||
option("r", "roles", "Comma-separated list of the roles of the user").hasArg(true).required(true),
|
||||
option("h", "help", "Prints usage help").hasArg(false).required(false)
|
||||
);
|
||||
}
|
||||
|
||||
public void run(CommandLine cli) throws Exception {
|
||||
|
||||
if (cli.getArgs().length == 0) {
|
||||
terminal.println("username is missing");
|
||||
printUsage();
|
||||
exit(ExitStatus.USAGE);
|
||||
}
|
||||
|
||||
String username = cli.getArgs()[0];
|
||||
|
||||
|
||||
char[] password;
|
||||
String passwordStr = cli.getOptionValue("password");
|
||||
if (passwordStr != null) {
|
||||
password = passwordStr.toCharArray();
|
||||
} else {
|
||||
password = terminal.readPassword("Enter new password: ");
|
||||
char[] retyped = terminal.readPassword("Retype new password: ");
|
||||
if (!Arrays.equals(password, retyped)) {
|
||||
terminal.print("Password mismatch");
|
||||
exit(ExitStatus.USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
String[] roles = null;
|
||||
String rolesCsv = cli.getOptionValue("roles");
|
||||
if (rolesCsv != null) {
|
||||
roles = rolesCsv.split(",");
|
||||
}
|
||||
addUser(username, password, roles);
|
||||
}
|
||||
|
||||
private void addUser(String username, char[] passwd, String[] roles) {
|
||||
Path file = FileUserPasswdStore.resolveFile(settings, env);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(file, null);
|
||||
if (users == null) {
|
||||
// file doesn't exist so we just create a new file
|
||||
users = new HashMap<>();
|
||||
}
|
||||
if (users.containsKey(username)) {
|
||||
terminal.println("User [{}] already exists", username);
|
||||
exit(ExitStatus.CODE_ERROR);
|
||||
}
|
||||
Hasher hasher = Hasher.HTPASSWD;
|
||||
users.put(username, hasher.hash(passwd));
|
||||
FileUserPasswdStore.writeFile(users, file);
|
||||
|
||||
file = FileUserRolesStore.resolveFile(settings, env);
|
||||
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(file, null);
|
||||
if (userRoles == null) {
|
||||
// file doesn't exist, so we just create a new file
|
||||
userRoles = new HashMap<>();
|
||||
}
|
||||
userRoles.put(username, roles);
|
||||
FileUserRolesStore.writeFile(userRoles, file);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.support;
|
||||
|
||||
import org.apache.commons.cli.*;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.node.internal.InternalSettingsPreparer;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.elasticsearch.common.settings.ImmutableSettings.Builder.EMPTY_SETTINGS;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class CmdLineTool {
|
||||
|
||||
protected enum ExitStatus {
|
||||
OK(0),
|
||||
USAGE(64),
|
||||
IO_ERROR(74),
|
||||
CODE_ERROR(70);
|
||||
|
||||
private final int status;
|
||||
|
||||
private ExitStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
protected static final Terminal terminal = ConsoleTerminal.supported() ? new ConsoleTerminal() : new SystemTerminal();
|
||||
protected static final Environment env;
|
||||
protected static final Settings settings;
|
||||
|
||||
static {
|
||||
Tuple<Settings, Environment> tuple = InternalSettingsPreparer.prepareSettings(EMPTY_SETTINGS, true);
|
||||
settings = tuple.v1();
|
||||
env = tuple.v2();
|
||||
}
|
||||
|
||||
private final Options options;
|
||||
private final String cmd;
|
||||
private final HelpFormatter helpFormatter = new HelpFormatter();
|
||||
|
||||
protected CmdLineTool(String cmd, OptionBuilder... options) {
|
||||
this.cmd = cmd;
|
||||
this.options = new Options();
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
this.options.addOption(options[i].option);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void execute(String[] args) {
|
||||
CommandLineParser parser = new GnuParser();
|
||||
try {
|
||||
CommandLine cli = parser.parse(options, args);
|
||||
run(cli);
|
||||
} catch (ParseException pe) {
|
||||
printUsage();
|
||||
} catch (Exception e) {
|
||||
terminal.println("Error: %s", e.getMessage());
|
||||
printUsage();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void printUsage() {
|
||||
helpFormatter.printUsage(terminal.printWriter(), HelpFormatter.DEFAULT_WIDTH, cmd, options);
|
||||
}
|
||||
|
||||
protected void exit(ExitStatus status) {
|
||||
System.exit(status.status);
|
||||
}
|
||||
|
||||
protected abstract void run(CommandLine cli) throws Exception;
|
||||
|
||||
protected static OptionBuilder option(String shortName, String longName, String description) {
|
||||
return new OptionBuilder(shortName, longName, description);
|
||||
}
|
||||
|
||||
protected static class OptionBuilder {
|
||||
|
||||
private final Option option;
|
||||
|
||||
private OptionBuilder(String shortName, String longName, String description) {
|
||||
option = new Option(shortName, description);
|
||||
option.setLongOpt(longName);
|
||||
}
|
||||
|
||||
public OptionBuilder required(boolean required) {
|
||||
option.setRequired(required);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionBuilder hasArg(boolean hasArg) {
|
||||
if (hasArg) {
|
||||
option.setArgs(1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static abstract class Terminal {
|
||||
|
||||
public abstract void print(String msg, Object... args);
|
||||
|
||||
public void println(String msg, Object... args) {
|
||||
print(msg + System.lineSeparator(), args);
|
||||
}
|
||||
|
||||
public abstract void print(Throwable t);
|
||||
|
||||
public void newLine() {
|
||||
println("");
|
||||
}
|
||||
|
||||
public abstract String readString(String msg, Object... args);
|
||||
|
||||
public abstract char[] readPassword(String msg, Object... args);
|
||||
|
||||
public abstract PrintWriter printWriter();
|
||||
}
|
||||
|
||||
private static class ConsoleTerminal extends Terminal {
|
||||
|
||||
final Console console = System.console();
|
||||
|
||||
static boolean supported() {
|
||||
return System.console() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String msg, Object... args) {
|
||||
console.printf(msg, args);
|
||||
console.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(Throwable t) {
|
||||
t.printStackTrace(console.writer());
|
||||
console.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readString(String msg, Object... args) {
|
||||
return console.readLine(msg, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] readPassword(String msg, Object... args) {
|
||||
return console.readPassword(msg, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter printWriter() {
|
||||
return console.writer();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SystemTerminal extends Terminal {
|
||||
|
||||
private final PrintWriter printWriter = new PrintWriter(System.out);
|
||||
|
||||
@Override
|
||||
public void print(String msg, Object... args) {
|
||||
System.out.print(String.format(Locale.ROOT, msg, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(Throwable t) {
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readString(String msg, Object... args) {
|
||||
print(msg, args);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
try {
|
||||
return reader.readLine();
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Could not read input");
|
||||
ioe.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] readPassword(String msg, Object... args) {
|
||||
return readString(msg, args).toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter printWriter() {
|
||||
return printWriter;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.support.cli;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.GnuParser;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.node.internal.InternalSettingsPreparer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.elasticsearch.common.settings.ImmutableSettings.Builder.EMPTY_SETTINGS;
|
||||
|
||||
/**
|
||||
* A base class for command-line interface tool.
|
||||
*
|
||||
* Two modes are supported:
|
||||
*
|
||||
* - Singe command mode. The tool exposes a single command that can potentially accept arguments (eg. CLI options).
|
||||
* - Multi command mode. The tool support multiple command, each for different tasks, each potentially accepts arguments.
|
||||
*
|
||||
* In a multi-command mode. The first argument must be the command name. For example, the plugin manager
|
||||
* can be seen as a multi-command tool with two possible commands: install and uninstall
|
||||
*
|
||||
* The tool is configured using a {@link CliToolConfig} which encapsulates the tool's commands and their
|
||||
* potential options. The tool also comes with out of the box simple help support (the -h/--help option is
|
||||
* automatically handled) where the help text is configured in a dedicated *.help files located in the same package
|
||||
* as the tool.
|
||||
*/
|
||||
public abstract class CliTool {
|
||||
|
||||
// based on sysexits.h
|
||||
public static enum ExitStatus {
|
||||
OK(0),
|
||||
USAGE(64), /* command line usage error */
|
||||
DATA_ERROR(65), /* data format error */
|
||||
NO_INPUT(66), /* cannot open input */
|
||||
NO_USER(67), /* addressee unknown */
|
||||
NO_HOST(68), /* host name unknown */
|
||||
UNAVAILABLE(69), /* service unavailable */
|
||||
CODE_ERROR(70), /* internal software error */
|
||||
CANT_CREATE(73), /* can't create (user) output file */
|
||||
IO_ERROR(74), /* input/output error */
|
||||
TEMP_FAILURE(75), /* temp failure; user is invited to retry */
|
||||
PROTOCOL(76), /* remote error in protocol */
|
||||
NOPERM(77), /* permission denied */
|
||||
CONFIG(78); /* configuration error */
|
||||
|
||||
final int status;
|
||||
|
||||
private ExitStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int status() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
protected final Terminal terminal;
|
||||
protected final Environment env;
|
||||
protected final Settings settings;
|
||||
|
||||
private final CliToolConfig config;
|
||||
|
||||
protected CliTool(CliToolConfig config) {
|
||||
this(config, Terminal.INSTANCE);
|
||||
}
|
||||
|
||||
protected CliTool(CliToolConfig config, Terminal terminal) {
|
||||
Preconditions.checkArgument(config.cmds().size() != 0, "At least one command must be configured");
|
||||
this.config = config;
|
||||
this.terminal = terminal;
|
||||
Tuple<Settings, Environment> tuple = InternalSettingsPreparer.prepareSettings(EMPTY_SETTINGS, true);
|
||||
settings = tuple.v1();
|
||||
env = tuple.v2();
|
||||
}
|
||||
|
||||
public final int execute(String... args) {
|
||||
|
||||
// first lets see if the user requests tool help. We're doing it only if
|
||||
// this is a multi-command tool. If it's a single command tool, the -h/--help
|
||||
// option will be taken care of on the command level
|
||||
if (!config.isSingle() && args.length > 0 && (args[0].equals("-h") || args[0].equals("--help"))) {
|
||||
config.printUsage(terminal);
|
||||
return ExitStatus.OK.status;
|
||||
}
|
||||
|
||||
CliToolConfig.Cmd cmd = null;
|
||||
if (config.isSingle()) {
|
||||
cmd = config.single();
|
||||
} else {
|
||||
|
||||
if (args.length == 0) {
|
||||
terminal.println("Error: command not specified");
|
||||
config.printUsage(terminal);
|
||||
return ExitStatus.USAGE.status;
|
||||
}
|
||||
|
||||
String cmdName = args[0];
|
||||
cmd = config.cmd(cmdName);
|
||||
if (cmd == null) {
|
||||
terminal.println("Error: unknown command [%s]. Use [-h] option to list available commands", cmdName);
|
||||
return ExitStatus.USAGE.status;
|
||||
}
|
||||
|
||||
// we now remove the command name from the args
|
||||
if (args.length == 1) {
|
||||
args = new String[0];
|
||||
} else {
|
||||
String[] cmdArgs = new String[args.length - 1];
|
||||
System.arraycopy(args, 1, cmdArgs, 0, cmdArgs.length);
|
||||
args = cmdArgs;
|
||||
}
|
||||
}
|
||||
|
||||
Command command = null;
|
||||
try {
|
||||
command = parse(cmd, args);
|
||||
return command.execute(settings, env).status;
|
||||
} catch (IOException ioe) {
|
||||
terminal.println(ioe.getMessage());
|
||||
return ExitStatus.IO_ERROR.status;
|
||||
} catch (Exception e) {
|
||||
terminal.println(e.getMessage());
|
||||
if (command == null) {
|
||||
return ExitStatus.USAGE.status;
|
||||
}
|
||||
return ExitStatus.CODE_ERROR.status;
|
||||
}
|
||||
}
|
||||
|
||||
public Command parse(String cmdName, String[] args) throws Exception {
|
||||
CliToolConfig.Cmd cmd = config.cmd(cmdName);
|
||||
return parse(cmd, args);
|
||||
}
|
||||
|
||||
public Command parse(CliToolConfig.Cmd cmd, String[] args) throws Exception {
|
||||
CommandLineParser parser = new GnuParser();
|
||||
CommandLine cli = parser.parse(cmd.options(), args);
|
||||
if (cli.hasOption("h")) {
|
||||
return helpCmd(cmd);
|
||||
}
|
||||
return parse(cmd.name(), cli);
|
||||
}
|
||||
|
||||
protected Command.Help helpCmd(CliToolConfig.Cmd cmd) {
|
||||
return new Command.Help(cmd, terminal);
|
||||
}
|
||||
|
||||
protected static Command.Exit exitCmd(ExitStatus status) {
|
||||
return new Command.Exit(null, status, null);
|
||||
}
|
||||
|
||||
protected static Command.Exit exitCmd(ExitStatus status, Terminal terminal, String msg, Object... args) {
|
||||
return new Command.Exit(String.format(Locale.ROOT, msg, args), status, terminal);
|
||||
}
|
||||
|
||||
protected abstract Command parse(String cmdName, CommandLine cli) throws Exception;
|
||||
|
||||
public static abstract class Command {
|
||||
|
||||
protected final Terminal terminal;
|
||||
|
||||
protected Command(Terminal terminal) {
|
||||
this.terminal = terminal;
|
||||
}
|
||||
|
||||
public abstract ExitStatus execute(Settings settings, Environment env) throws Exception;
|
||||
|
||||
public static class Help extends Command {
|
||||
|
||||
private final CliToolConfig.Cmd cmd;
|
||||
|
||||
private Help(CliToolConfig.Cmd cmd, Terminal terminal) {
|
||||
super(terminal);
|
||||
this.cmd = cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
cmd.printUsage(terminal);
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Exit extends Command {
|
||||
private final String msg;
|
||||
private final ExitStatus status;
|
||||
|
||||
private Exit(String msg, ExitStatus status, Terminal terminal) {
|
||||
super(terminal);
|
||||
this.msg = msg;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
if (msg != null) {
|
||||
terminal.println(msg);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
public ExitStatus status() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.support.cli;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.apache.commons.cli.Option;
|
||||
import org.apache.commons.cli.Options;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CliToolConfig {
|
||||
|
||||
public static Builder config(String name, Class<? extends CliTool> toolType) {
|
||||
return new Builder(name, toolType);
|
||||
}
|
||||
|
||||
private final Class<? extends CliTool> toolType;
|
||||
private final String name;
|
||||
private final ImmutableMap<String, Cmd> cmds;
|
||||
|
||||
private static final HelpPrinter helpPrinter = new HelpPrinter();
|
||||
|
||||
private CliToolConfig(String name, Class<? extends CliTool> toolType, Cmd[] cmds) {
|
||||
this.name = name;
|
||||
this.toolType = toolType;
|
||||
ImmutableMap.Builder<String, Cmd> cmdsBuilder = ImmutableMap.builder();
|
||||
for (int i = 0; i < cmds.length; i++) {
|
||||
cmdsBuilder.put(cmds[i].name, cmds[i]);
|
||||
}
|
||||
this.cmds = cmdsBuilder.build();
|
||||
}
|
||||
|
||||
public boolean isSingle() {
|
||||
return cmds.size() == 1;
|
||||
}
|
||||
|
||||
public Cmd single() {
|
||||
assert isSingle() : "Requesting single command on a multi-command tool";
|
||||
return cmds.values().iterator().next();
|
||||
}
|
||||
|
||||
public Class<? extends CliTool> toolType() {
|
||||
return toolType;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Collection<Cmd> cmds() {
|
||||
return cmds.values();
|
||||
}
|
||||
|
||||
public Cmd cmd(String name) {
|
||||
return cmds.get(name);
|
||||
}
|
||||
|
||||
public void printUsage(Terminal terminal) {
|
||||
helpPrinter.print(this, terminal);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
public static Cmd.Builder cmd(String name, Class<? extends CliTool.Command> cmdType) {
|
||||
return new Cmd.Builder(name, cmdType);
|
||||
}
|
||||
|
||||
public static OptionBuilder option(String shortName, String longName) {
|
||||
return new OptionBuilder(shortName, longName);
|
||||
}
|
||||
|
||||
private final Class<? extends CliTool> toolType;
|
||||
private final String name;
|
||||
private Cmd[] cmds;
|
||||
|
||||
private Builder(String name, Class<? extends CliTool> toolType) {
|
||||
this.name = name;
|
||||
this.toolType = toolType;
|
||||
}
|
||||
|
||||
public Builder cmds(Cmd.Builder... cmds) {
|
||||
this.cmds = new Cmd[cmds.length];
|
||||
for (int i = 0; i < cmds.length; i++) {
|
||||
this.cmds[i] = cmds[i].build();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder cmds(Cmd... cmds) {
|
||||
this.cmds = cmds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CliToolConfig build() {
|
||||
return new CliToolConfig(name, toolType, cmds);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Cmd {
|
||||
|
||||
private final String name;
|
||||
private final Class<? extends CliTool.Command> cmdType;
|
||||
private final Options options;
|
||||
|
||||
private Cmd(String name, Class<? extends CliTool.Command> cmdType, Options options) {
|
||||
this.name = name;
|
||||
this.cmdType = cmdType;
|
||||
this.options = options;
|
||||
this.options.addOption(new OptionBuilder("h", "help").required(false).build());
|
||||
}
|
||||
|
||||
public Class<? extends CliTool.Command> cmdType() {
|
||||
return cmdType;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Options options() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void printUsage(Terminal terminal) {
|
||||
helpPrinter.print(this, terminal);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final String name;
|
||||
private final Class<? extends CliTool.Command> cmdType;
|
||||
private Options options = new Options();
|
||||
|
||||
private Builder(String name, Class<? extends CliTool.Command> cmdType) {
|
||||
this.name = name;
|
||||
this.cmdType = cmdType;
|
||||
}
|
||||
|
||||
public Builder options(OptionBuilder... optionBuilder) {
|
||||
for (int i = 0; i < optionBuilder.length; i++) {
|
||||
options.addOption(optionBuilder[i].build());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Cmd build() {
|
||||
return new Cmd(name, cmdType, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class OptionBuilder {
|
||||
|
||||
private final Option option;
|
||||
|
||||
private OptionBuilder(String shortName, String longName) {
|
||||
option = new Option(shortName, "");
|
||||
option.setLongOpt(longName);
|
||||
option.setArgName(longName);
|
||||
}
|
||||
|
||||
public OptionBuilder required(boolean required) {
|
||||
option.setRequired(required);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionBuilder hasArg(boolean optional) {
|
||||
option.setOptionalArg(optional);
|
||||
option.setArgs(1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Option build() {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.support.cli;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class HelpPrinter {
|
||||
|
||||
public void print(CliToolConfig config, Terminal terminal) {
|
||||
URL url = config.toolType().getResource(config.name() + ".help");
|
||||
print(url, terminal);
|
||||
}
|
||||
|
||||
public void print(CliToolConfig.Cmd cmd, Terminal terminal) {
|
||||
URL url = cmd.cmdType().getResource(cmd.name() + ".help");
|
||||
print(url, terminal);
|
||||
}
|
||||
|
||||
private static void print(URL url, Terminal terminal) {
|
||||
terminal.println();
|
||||
try {
|
||||
Path helpFile = Paths.get(url.toURI());
|
||||
for (String line : Files.readAllLines(helpFile, Charsets.UTF_8)) {
|
||||
terminal.println(line);
|
||||
}
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
e.printStackTrace(terminal.writer());
|
||||
}
|
||||
terminal.println();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.support.cli;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class Terminal {
|
||||
|
||||
public static final Terminal INSTANCE = ConsoleTerminal.supported() ? new ConsoleTerminal() : new SystemTerminal();
|
||||
|
||||
public abstract String readText(String text, Object... args);
|
||||
|
||||
public abstract char[] readSecret(String text, Object... args);
|
||||
|
||||
public abstract void println();
|
||||
|
||||
public abstract void println(String msg, Object... args);
|
||||
|
||||
public abstract void print(String msg, Object... args);
|
||||
|
||||
public abstract PrintWriter writer();
|
||||
|
||||
public static abstract class Base extends Terminal {
|
||||
|
||||
@Override
|
||||
public void println() {
|
||||
println("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(String msg, Object... args) {
|
||||
print(msg + System.lineSeparator(), args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ConsoleTerminal extends Base {
|
||||
|
||||
final Console console = System.console();
|
||||
|
||||
static boolean supported() {
|
||||
return System.console() != null;
|
||||
}
|
||||
|
||||
private ConsoleTerminal() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String msg, Object... args) {
|
||||
console.printf(msg, args);
|
||||
console.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String text, Object... args) {
|
||||
return console.readLine(text, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
return console.readPassword(text, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter writer() {
|
||||
return console.writer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SystemTerminal extends Base {
|
||||
|
||||
private final PrintWriter printWriter = new PrintWriter(System.out);
|
||||
|
||||
@Override
|
||||
public void print(String msg, Object... args) {
|
||||
System.out.print(String.format(Locale.ROOT, msg, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String text, Object... args) {
|
||||
print(text, args);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
try {
|
||||
return reader.readLine();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
return readText(text, args).toCharArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter writer() {
|
||||
return printWriter;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
admin:
|
||||
cluster: ALL
|
||||
indices:
|
||||
- *::ALL
|
||||
|
||||
power_user:
|
||||
cluster: MONITOR
|
||||
indices:
|
||||
- *::MONITOR,DATA_ACCESS
|
||||
|
||||
user:
|
||||
indices:
|
||||
- *::READ,INDEX,MANAGE
|
|
@ -1,3 +0,0 @@
|
|||
admin:{plain}changeme
|
||||
poweruser:{plain)changeme
|
||||
user:{plain}changeme
|
|
@ -1,3 +0,0 @@
|
|||
admin:admin
|
||||
poweruser:poweruser
|
||||
user:user
|
|
@ -1,12 +0,0 @@
|
|||
logger:
|
||||
shield.audit.logfile: INFO, audit_file
|
||||
|
||||
appender:
|
||||
|
||||
audit_file:
|
||||
type: dailyRollingFile
|
||||
file: ${path.logs}/${cluster.name}.log
|
||||
datePattern: "'.'yyyy-MM-dd"
|
||||
layout:
|
||||
type: pattern
|
||||
conversionPattern: "[%d{ISO8601}] %m%n"
|
|
@ -0,0 +1,22 @@
|
|||
SYNTAX:
|
||||
|
||||
esusers <command>
|
||||
|
||||
DESCRIPTION:
|
||||
|
||||
This tool manages all native security aspects in elasticsearch, saving
|
||||
the administratorfrom needing to modify security related fiels manually.
|
||||
This tool provides several commands for different security management
|
||||
tasks
|
||||
|
||||
COMMANDS:
|
||||
|
||||
passwd Changes passwords for a native user
|
||||
|
||||
useradd Adds a new native user to the system
|
||||
|
||||
userdel Removes an existing native user from the system
|
||||
|
||||
NOTES:
|
||||
|
||||
[1] For usage help on specific commands please type "security <command> -h"
|
|
@ -0,0 +1,18 @@
|
|||
USAGE:
|
||||
|
||||
esusers passwd <username> [-p <password>]
|
||||
|
||||
DESCRIPTION:
|
||||
|
||||
The passwd command changes passwords for native user accounts. The tool
|
||||
prompts twice for a replacement password. The second entry is compared
|
||||
against the first and both are required to match in order for the
|
||||
password to be changed. If non-default users file is used (a different
|
||||
file location is configured in elasticsearch.yml) the appropriate file
|
||||
will be resolved from the settings.
|
||||
|
||||
OPTIONS:
|
||||
|
||||
-h,--help Shows this message
|
||||
|
||||
-p,--password <password> The new password for the user
|
|
@ -0,0 +1,21 @@
|
|||
USAGE:
|
||||
|
||||
esusers useradd <username> [-p <password>] [-r <roles>]
|
||||
|
||||
DESCRIPTION:
|
||||
|
||||
Adds a native user to elasticsearch (via internal realm). The user will
|
||||
be added to the users file and its roles will be added to the
|
||||
users_roles file. If non-default files are used (different file
|
||||
locations are configured in elasticsearch.yml) the appropriate files
|
||||
will be resolved from the settings and the user and its roles will be
|
||||
added to them.
|
||||
|
||||
OPTIONS:
|
||||
|
||||
-h,--help Shows this message
|
||||
|
||||
-p,--password <password> The user password
|
||||
|
||||
-r,--roles <roles> Comma-separated list of the roles of the
|
||||
user
|
|
@ -0,0 +1,16 @@
|
|||
USAGE:
|
||||
|
||||
esusers userdel <username>
|
||||
|
||||
DESCRIPTION:
|
||||
|
||||
Removes an existing native user from elasticsearch. The user will be
|
||||
removed from the users file and its roles will be removed to the
|
||||
users_roles file. If non-default files are used (different file
|
||||
locations are configured in elasticsearch.yml) the appropriate files
|
||||
will be resolved from the settings and the user and its roles will be
|
||||
removed to them.
|
||||
|
||||
OPTIONS:
|
||||
|
||||
-h,--help Shows this message
|
|
@ -62,7 +62,7 @@ public class FileUserPasswdStoreTests extends ElasticsearchTestCase {
|
|||
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("watcher.interval", "2s")
|
||||
.put("shield.authc.esusers.file.users", tmp.toAbsolutePath())
|
||||
.put("shield.authc.esusers.files.users", tmp.toAbsolutePath())
|
||||
.build();
|
||||
|
||||
Environment env = new Environment(settings);
|
||||
|
|
|
@ -58,7 +58,7 @@ public class FileUserRolesStoreTests extends ElasticsearchTestCase {
|
|||
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("watcher.interval", "2s")
|
||||
.put("shield.authc.esusers.file.users_roles", tmp.toAbsolutePath())
|
||||
.put("shield.authc.esusers.files.users_roles", tmp.toAbsolutePath())
|
||||
.build();
|
||||
|
||||
Environment env = new Environment(settings);
|
||||
|
|
|
@ -0,0 +1,368 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.authc.esusers.tool;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.shield.authc.support.Hasher;
|
||||
import org.elasticsearch.shield.support.cli.CliToolTestCase;
|
||||
import org.elasticsearch.shield.support.cli.CliTool;
|
||||
import org.elasticsearch.shield.support.cli.Terminal;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ESUsersToolTests extends CliToolTestCase {
|
||||
|
||||
@Test
|
||||
public void testUseradd_Parse_AllOptions() throws Exception {
|
||||
ESUsersTool tool = new ESUsersTool();
|
||||
CliTool.Command command = tool.parse("useradd", args("username -p changeme -r r1,r2,r3"));
|
||||
assertThat(command, instanceOf(ESUsersTool.Useradd.class));
|
||||
ESUsersTool.Useradd cmd = (ESUsersTool.Useradd) command;
|
||||
assertThat(cmd.username, equalTo("username"));
|
||||
assertThat(new String(cmd.passwd), equalTo("changeme"));
|
||||
assertThat(cmd.roles, notNullValue());
|
||||
assertThat(cmd.roles, arrayContaining("r1", "r2", "r3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseradd_Parse_NoUsername() throws Exception {
|
||||
ESUsersTool tool = new ESUsersTool();
|
||||
CliTool.Command command = tool.parse("useradd", args("-p test123"));
|
||||
assertThat(command, instanceOf(CliTool.Command.Exit.class));
|
||||
assertThat(((CliTool.Command.Exit) command).status(), is(CliTool.ExitStatus.USAGE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseradd_Parse_NoPassword() throws Exception {
|
||||
ESUsersTool tool = new ESUsersTool(new TerminalMock() {
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
return "changeme".toCharArray();
|
||||
}
|
||||
});
|
||||
CliTool.Command command = tool.parse("useradd", args("username"));
|
||||
assertThat(command, instanceOf(ESUsersTool.Useradd.class));
|
||||
ESUsersTool.Useradd cmd = (ESUsersTool.Useradd) command;
|
||||
assertThat(cmd.username, equalTo("username"));
|
||||
assertThat(new String(cmd.passwd), equalTo("changeme"));
|
||||
assertThat(cmd.roles, notNullValue());
|
||||
assertThat(cmd.roles.length, is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseradd_Cmd_Create() throws Exception {
|
||||
Path dir = Files.createTempDirectory(null);
|
||||
Path users = dir.resolve("users");
|
||||
Path usersRoles = dir.resolve("users_roles");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
|
||||
.build();
|
||||
Environment env = new Environment(settings);
|
||||
|
||||
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new TerminalMock(), "user1", "changeme".toCharArray(), "r1", "r2");
|
||||
|
||||
CliTool.ExitStatus status = cmd.execute(settings, env);
|
||||
assertThat(status, is(CliTool.ExitStatus.OK));
|
||||
|
||||
assertThat(Files.exists(users), is(true));
|
||||
List<String> lines = Files.readAllLines(users, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(1));
|
||||
// we can't just hash again and compare the lines, as every time we hash a new salt is generated
|
||||
// instead we'll just verify the generated hash against the correct password.
|
||||
String line = lines.get(0);
|
||||
assertThat(line, startsWith("user1:"));
|
||||
String hash = line.substring("user1:".length());
|
||||
assertThat(Hasher.HTPASSWD.verify("changeme".toCharArray(), hash.toCharArray()), is(true));
|
||||
|
||||
assertThat(Files.exists(usersRoles), is(true));
|
||||
lines = Files.readAllLines(usersRoles, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(1));
|
||||
line = lines.get(0);
|
||||
assertThat(line, equalTo("user1:r1,r2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseradd_Cmd_Append() throws Exception {
|
||||
Path users = Files.createTempFile(null, null);
|
||||
Path usersRoles = Files.createTempFile(null, null);
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
|
||||
.build();
|
||||
Environment env = new Environment(settings);
|
||||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
|
||||
writer.write("user2:hash2");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(usersRoles, Charsets.UTF_8)) {
|
||||
writer.write("user2:r3,r4");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new TerminalMock(), "user1", "changeme".toCharArray(), "r1", "r2");
|
||||
|
||||
CliTool.ExitStatus status = cmd.execute(settings, env);
|
||||
assertThat(status, is(CliTool.ExitStatus.OK));
|
||||
|
||||
assertThat(Files.exists(users), is(true));
|
||||
List<String> lines = Files.readAllLines(users, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(2));
|
||||
assertThat(lines.get(0), equalTo("user2:hash2"));
|
||||
// we can't just hash again and compare the lines, as every time we hash a new salt is generated
|
||||
// instead we'll just verify the generated hash against the correct password.
|
||||
String line = lines.get(1);
|
||||
assertThat(line, startsWith("user1:"));
|
||||
String hash = line.substring("user1:".length());
|
||||
assertThat(Hasher.HTPASSWD.verify("changeme".toCharArray(), hash.toCharArray()), is(true));
|
||||
|
||||
assertThat(Files.exists(usersRoles), is(true));
|
||||
lines = Files.readAllLines(usersRoles, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(2));
|
||||
assertThat(lines.get(0), equalTo("user2:r3,r4"));
|
||||
line = lines.get(1);
|
||||
assertThat(line, equalTo("user1:r1,r2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseradd_Cmd_Append_UserAlreadyExists() throws Exception {
|
||||
Path users = Files.createTempFile(null, null);
|
||||
Path usersRoles = Files.createTempFile(null, null);
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
|
||||
.build();
|
||||
Environment env = new Environment(settings);
|
||||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
|
||||
writer.write("user1:hash1");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new TerminalMock(), "user1", "changeme".toCharArray(), "r1", "r2");
|
||||
|
||||
CliTool.ExitStatus status = cmd.execute(settings, env);
|
||||
assertThat(status, is(CliTool.ExitStatus.CODE_ERROR));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserdel_Parse() throws Exception {
|
||||
ESUsersTool tool = new ESUsersTool();
|
||||
CliTool.Command command = tool.parse("userdel", args("username"));
|
||||
assertThat(command, instanceOf(ESUsersTool.Userdel.class));
|
||||
ESUsersTool.Userdel userdel = (ESUsersTool.Userdel) command;
|
||||
assertThat(userdel.username, equalTo("username"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserdel_Parse_MissingUsername() throws Exception {
|
||||
ESUsersTool tool = new ESUsersTool();
|
||||
CliTool.Command command = tool.parse("userdel", args(null));
|
||||
assertThat(command, instanceOf(ESUsersTool.Command.Exit.class));
|
||||
ESUsersTool.Command.Exit exit = (ESUsersTool.Command.Exit) command;
|
||||
assertThat(exit.status(), equalTo(CliTool.ExitStatus.USAGE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserdel_Cmd() throws Exception {
|
||||
Path users = Files.createTempFile(null, null);
|
||||
Path usersRoles = Files.createTempFile(null, null);
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
|
||||
.build();
|
||||
Environment env = new Environment(settings);
|
||||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
|
||||
writer.write("user1:hash2");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(usersRoles, Charsets.UTF_8)) {
|
||||
writer.write("user1:r3,r4");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
ESUsersTool.Userdel cmd = new ESUsersTool.Userdel(new TerminalMock(), "user1");
|
||||
|
||||
CliTool.ExitStatus status = cmd.execute(settings, env);
|
||||
assertThat(status, is(CliTool.ExitStatus.OK));
|
||||
|
||||
assertThat(Files.exists(users), is(true));
|
||||
List<String> lines = Files.readAllLines(users, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(0));
|
||||
|
||||
assertThat(Files.exists(usersRoles), is(true));
|
||||
lines = Files.readAllLines(usersRoles, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserdel_Cmd_MissingUser() throws Exception {
|
||||
Path users = Files.createTempFile(null, null);
|
||||
Path usersRoles = Files.createTempFile(null, null);
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
|
||||
.build();
|
||||
Environment env = new Environment(settings);
|
||||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
|
||||
writer.write("user1:hash2");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(usersRoles, Charsets.UTF_8)) {
|
||||
writer.write("user1:r3,r4");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
ESUsersTool.Userdel cmd = new ESUsersTool.Userdel(new TerminalMock(), "user2");
|
||||
|
||||
CliTool.ExitStatus status = cmd.execute(settings, env);
|
||||
assertThat(status, is(CliTool.ExitStatus.OK));
|
||||
|
||||
assertThat(Files.exists(users), is(true));
|
||||
List<String> lines = Files.readAllLines(users, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(1));
|
||||
|
||||
assertThat(Files.exists(usersRoles), is(true));
|
||||
lines = Files.readAllLines(usersRoles, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserdel_Cmd_MissingFiles() throws Exception {
|
||||
Path dir = Files.createTempDirectory(null);
|
||||
Path users = dir.resolve("users");
|
||||
Path usersRoles = dir.resolve("users_roles");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoles.toAbsolutePath())
|
||||
.build();
|
||||
Environment env = new Environment(settings);
|
||||
|
||||
ESUsersTool.Userdel cmd = new ESUsersTool.Userdel(new TerminalMock(), "user2");
|
||||
|
||||
CliTool.ExitStatus status = cmd.execute(settings, env);
|
||||
assertThat(status, is(CliTool.ExitStatus.OK));
|
||||
|
||||
assertThat(Files.exists(users), is(false));
|
||||
assertThat(Files.exists(usersRoles), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswd_Parse_AllOptions() throws Exception {
|
||||
ESUsersTool tool = new ESUsersTool();
|
||||
CliTool.Command command = tool.parse("passwd", args("user1 -p changeme"));
|
||||
assertThat(command, instanceOf(ESUsersTool.Passwd.class));
|
||||
ESUsersTool.Passwd cmd = (ESUsersTool.Passwd) command;
|
||||
assertThat(cmd.username, equalTo("user1"));
|
||||
assertThat(new String(cmd.passwd), equalTo("changeme"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswd_Parse_MissingUsername() throws Exception {
|
||||
ESUsersTool tool = new ESUsersTool();
|
||||
CliTool.Command command = tool.parse("passwd", args("-p changeme"));
|
||||
assertThat(command, instanceOf(ESUsersTool.Command.Exit.class));
|
||||
ESUsersTool.Command.Exit cmd = (ESUsersTool.Command.Exit) command;
|
||||
assertThat(cmd.status(), is(CliTool.ExitStatus.USAGE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswd_Parse_MissingPassword() throws Exception {
|
||||
final AtomicReference<Boolean> secretRequested = new AtomicReference<>(false);
|
||||
Terminal terminal = new TerminalMock() {
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
secretRequested.set(true);
|
||||
return "changeme".toCharArray();
|
||||
}
|
||||
};
|
||||
ESUsersTool tool = new ESUsersTool(terminal);
|
||||
CliTool.Command command = tool.parse("passwd", args("user1"));
|
||||
assertThat(command, instanceOf(ESUsersTool.Passwd.class));
|
||||
ESUsersTool.Passwd cmd = (ESUsersTool.Passwd) command;
|
||||
assertThat(cmd.username, equalTo("user1"));
|
||||
assertThat(new String(cmd.passwd), equalTo("changeme"));
|
||||
assertThat(secretRequested.get(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswd_Cmd() throws Exception {
|
||||
Path users = Files.createTempFile(null, null);
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
|
||||
.build();
|
||||
Environment env = new Environment(settings);
|
||||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
|
||||
writer.write("user1:hash2");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
ESUsersTool.Passwd cmd = new ESUsersTool.Passwd(new TerminalMock(), "user1", "changeme".toCharArray());
|
||||
CliTool.ExitStatus status = cmd.execute(settings, env);
|
||||
assertThat(status, is(CliTool.ExitStatus.OK));
|
||||
|
||||
List<String> lines = Files.readAllLines(users, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(1));
|
||||
// we can't just hash again and compare the lines, as every time we hash a new salt is generated
|
||||
// instead we'll just verify the generated hash against the correct password.
|
||||
String line = lines.get(0);
|
||||
assertThat(line, startsWith("user1:"));
|
||||
String hash = line.substring("user1:".length());
|
||||
assertThat(Hasher.HTPASSWD.verify("changeme".toCharArray(), hash.toCharArray()), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswd_Cmd_UnknownUser() throws Exception {
|
||||
Path users = Files.createTempFile(null, null);
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
|
||||
.build();
|
||||
Environment env = new Environment(settings);
|
||||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(users, Charsets.UTF_8)) {
|
||||
writer.write("user1:hash2");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
ESUsersTool.Passwd cmd = new ESUsersTool.Passwd(new TerminalMock(), "user2", "changeme".toCharArray());
|
||||
CliTool.ExitStatus status = cmd.execute(settings, env);
|
||||
assertThat(status, is(CliTool.ExitStatus.NO_USER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswd_Cmd_MissingFiles() throws Exception {
|
||||
Path dir = Files.createTempDirectory(null);
|
||||
Path users = dir.resolve("users");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", users.toAbsolutePath())
|
||||
.build();
|
||||
Environment env = new Environment(settings);
|
||||
|
||||
ESUsersTool.Passwd cmd = new ESUsersTool.Passwd(new TerminalMock(), "user2", "changeme".toCharArray());
|
||||
CliTool.ExitStatus status = cmd.execute(settings, env);
|
||||
assertThat(status, is(CliTool.ExitStatus.NO_USER));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.support.cli;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CliToolTestCase extends ElasticsearchTestCase {
|
||||
|
||||
protected static String[] args(String command) {
|
||||
if (!Strings.hasLength(command)) {
|
||||
return Strings.EMPTY_ARRAY;
|
||||
}
|
||||
return command.split("\\s+");
|
||||
}
|
||||
|
||||
public static class TerminalMock extends Terminal {
|
||||
|
||||
@Override
|
||||
public void println() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(String msg, Object... args) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String text, Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] readSecret(String text, Object... args) {
|
||||
return new char[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String msg, Object... args) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter writer() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.shield.support.cli;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.elasticsearch.shield.support.cli.CliToolConfig.Builder.cmd;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CliToolTests extends CliToolTestCase {
|
||||
|
||||
@Test
|
||||
public void testOK() throws Exception {
|
||||
Terminal terminal = new TerminalMock();
|
||||
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
|
||||
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) {
|
||||
executed.set(true);
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
|
||||
int status = tool.execute();
|
||||
assertThat(executed.get(), is(true));
|
||||
assertThat(status, is(CliTool.ExitStatus.OK.status()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsageError() throws Exception {
|
||||
Terminal terminal = new TerminalMock();
|
||||
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
|
||||
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) {
|
||||
executed.set(true);
|
||||
return CliTool.ExitStatus.USAGE;
|
||||
}
|
||||
};
|
||||
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
|
||||
int status = tool.execute();
|
||||
assertThat(executed.get(), is(true));
|
||||
assertThat(status, is(CliTool.ExitStatus.USAGE.status()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIOError() throws Exception {
|
||||
Terminal terminal = new TerminalMock();
|
||||
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
|
||||
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
executed.set(true);
|
||||
throw new IOException("io error");
|
||||
}
|
||||
};
|
||||
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
|
||||
int status = tool.execute();
|
||||
assertThat(executed.get(), is(true));
|
||||
assertThat(status, is(CliTool.ExitStatus.IO_ERROR.status()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCodeError() throws Exception {
|
||||
Terminal terminal = new TerminalMock();
|
||||
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
|
||||
final NamedCommand cmd = new NamedCommand("cmd", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
executed.set(true);
|
||||
throw new Exception("random error");
|
||||
}
|
||||
};
|
||||
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
|
||||
int status = tool.execute();
|
||||
assertThat(executed.get(), is(true));
|
||||
assertThat(status, is(CliTool.ExitStatus.CODE_ERROR.status()));
|
||||
}
|
||||
|
||||
public void testMultiCommand() {
|
||||
Terminal terminal = new TerminalMock();
|
||||
int count = randomIntBetween(2, 7);
|
||||
final AtomicReference<Boolean>[] executed = new AtomicReference[count];
|
||||
for (int i = 0; i < executed.length; i++) {
|
||||
executed[i] = new AtomicReference<>(false);
|
||||
}
|
||||
NamedCommand[] cmds = new NamedCommand[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int index = i;
|
||||
cmds[i] = new NamedCommand("cmd" + index, terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
executed[index].set(true);
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
}
|
||||
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
|
||||
int cmdIndex = randomIntBetween(0, count-1);
|
||||
int status = tool.execute("cmd" + cmdIndex);
|
||||
assertThat(status, is(CliTool.ExitStatus.OK.status()));
|
||||
for (int i = 0; i < executed.length; i++) {
|
||||
assertThat(executed[i].get(), is(i == cmdIndex));
|
||||
}
|
||||
}
|
||||
|
||||
public void testMultiCommand_UnknownCommand() {
|
||||
Terminal terminal = new TerminalMock();
|
||||
int count = randomIntBetween(2, 7);
|
||||
final AtomicReference<Boolean>[] executed = new AtomicReference[count];
|
||||
for (int i = 0; i < executed.length; i++) {
|
||||
executed[i] = new AtomicReference<>(false);
|
||||
}
|
||||
NamedCommand[] cmds = new NamedCommand[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int index = i;
|
||||
cmds[i] = new NamedCommand("cmd" + index, terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
executed[index].set(true);
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
}
|
||||
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
|
||||
int status = tool.execute("cmd" + count); // "cmd" + count doesn't exist
|
||||
assertThat(status, is(CliTool.ExitStatus.USAGE.status()));
|
||||
for (int i = 0; i < executed.length; i++) {
|
||||
assertThat(executed[i].get(), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleCommand_ToolHelp() throws Exception {
|
||||
final AtomicReference<Boolean> helpWritten = new AtomicReference<>(false);
|
||||
Terminal terminal = new TerminalMock() {
|
||||
@Override
|
||||
public void println(String msg, Object... args) {
|
||||
assertThat(msg, equalTo("cmd1 help"));
|
||||
helpWritten.set(true);
|
||||
}
|
||||
};
|
||||
final AtomicReference<Boolean> executed = new AtomicReference<>(false);
|
||||
final NamedCommand cmd = new NamedCommand("cmd1", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
executed.set(true);
|
||||
throw new IOException("io error");
|
||||
}
|
||||
};
|
||||
SingleCmdTool tool = new SingleCmdTool("tool", terminal, cmd);
|
||||
int status = tool.execute(args("-h"));
|
||||
assertThat(status, is(CliTool.ExitStatus.OK.status()));
|
||||
assertThat(helpWritten.get(), is(true));
|
||||
}
|
||||
|
||||
public void testMultiCommand_ToolHelp() {
|
||||
final AtomicReference<Boolean> helpWritten = new AtomicReference<>(false);
|
||||
Terminal terminal = new TerminalMock() {
|
||||
@Override
|
||||
public void println(String msg, Object... args) {
|
||||
assertThat(msg, equalTo("tool help"));
|
||||
helpWritten.set(true);
|
||||
}
|
||||
};
|
||||
NamedCommand[] cmds = new NamedCommand[2];
|
||||
cmds[0] = new NamedCommand("cmd0", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
cmds[1] = new NamedCommand("cmd1", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
|
||||
int status = tool.execute(args("-h"));
|
||||
assertThat(status, is(CliTool.ExitStatus.OK.status()));
|
||||
assertThat(helpWritten.get(), is(true));
|
||||
}
|
||||
|
||||
public void testMultiCommand_CmdHelp() {
|
||||
final AtomicReference<Boolean> helpWritten = new AtomicReference<>(false);
|
||||
Terminal terminal = new TerminalMock() {
|
||||
@Override
|
||||
public void println(String msg, Object... args) {
|
||||
assertThat(msg, equalTo("cmd1 help"));
|
||||
helpWritten.set(true);
|
||||
}
|
||||
};
|
||||
NamedCommand[] cmds = new NamedCommand[2];
|
||||
cmds[0] = new NamedCommand("cmd0", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
cmds[1] = new NamedCommand("cmd1", terminal) {
|
||||
@Override
|
||||
public CliTool.ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
return CliTool.ExitStatus.OK;
|
||||
}
|
||||
};
|
||||
MultiCmdTool tool = new MultiCmdTool("tool", terminal, cmds);
|
||||
int status = tool.execute(args("cmd1 -h"));
|
||||
assertThat(status, is(CliTool.ExitStatus.OK.status()));
|
||||
assertThat(helpWritten.get(), is(true));
|
||||
}
|
||||
|
||||
private static class SingleCmdTool extends CliTool {
|
||||
|
||||
private final Command command;
|
||||
|
||||
private SingleCmdTool(String name, Terminal terminal, NamedCommand command) {
|
||||
super(CliToolConfig.config(name, SingleCmdTool.class)
|
||||
.cmds(cmd(command.name, command.getClass()))
|
||||
.build(), terminal);
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Command parse(String cmdName, CommandLine cli) throws Exception {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MultiCmdTool extends CliTool {
|
||||
|
||||
private final Map<String, Command> commands;
|
||||
|
||||
private MultiCmdTool(String name, Terminal terminal, NamedCommand... commands) {
|
||||
super(CliToolConfig.config(name, MultiCmdTool.class)
|
||||
.cmds(cmds(commands))
|
||||
.build(), terminal);
|
||||
ImmutableMap.Builder<String, Command> commandByName = ImmutableMap.builder();
|
||||
for (int i = 0; i < commands.length; i++) {
|
||||
commandByName.put(commands[i].name, commands[i]);
|
||||
}
|
||||
this.commands = commandByName.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Command parse(String cmdName, CommandLine cli) throws Exception {
|
||||
return commands.get(cmdName);
|
||||
}
|
||||
|
||||
private static CliToolConfig.Cmd[] cmds(NamedCommand... commands) {
|
||||
CliToolConfig.Cmd[] cmds = new CliToolConfig.Cmd[commands.length];
|
||||
for (int i = 0; i < commands.length; i++) {
|
||||
cmds[i] = cmd(commands[i].name, commands[i].getClass()).build();
|
||||
}
|
||||
return cmds;
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class NamedCommand extends CliTool.Command {
|
||||
|
||||
private final String name;
|
||||
|
||||
private NamedCommand(String name, Terminal terminal) {
|
||||
super(terminal);
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
cmd1 help
|
|
@ -0,0 +1 @@
|
|||
tool help
|
Loading…
Reference in New Issue