Changed esusers tool to use jopt-simple
Original commit: elastic/x-pack-elasticsearch@1f8763fcd6
This commit is contained in:
parent
fe377cfda2
commit
706216844b
|
@ -5,10 +5,14 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.esusers.tool;
|
package org.elasticsearch.shield.authc.esusers.tool;
|
||||||
|
|
||||||
|
import joptsimple.OptionSet;
|
||||||
|
import joptsimple.OptionSpec;
|
||||||
import org.apache.commons.cli.CommandLine;
|
import org.apache.commons.cli.CommandLine;
|
||||||
|
import org.elasticsearch.cli.Command;
|
||||||
|
import org.elasticsearch.cli.ExitCodes;
|
||||||
|
import org.elasticsearch.cli.MultiCommand;
|
||||||
|
import org.elasticsearch.cli.UserError;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
|
||||||
import org.elasticsearch.common.cli.CheckFileCommand;
|
|
||||||
import org.elasticsearch.common.cli.CliTool;
|
import org.elasticsearch.common.cli.CliTool;
|
||||||
import org.elasticsearch.common.cli.CliToolConfig;
|
import org.elasticsearch.common.cli.CliToolConfig;
|
||||||
import org.elasticsearch.common.cli.Terminal;
|
import org.elasticsearch.common.cli.Terminal;
|
||||||
|
@ -16,6 +20,7 @@ import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.util.ArrayUtils;
|
import org.elasticsearch.common.util.ArrayUtils;
|
||||||
import org.elasticsearch.common.util.set.Sets;
|
import org.elasticsearch.common.util.set.Sets;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.node.internal.InternalSettingsPreparer;
|
||||||
import org.elasticsearch.shield.authc.Realms;
|
import org.elasticsearch.shield.authc.Realms;
|
||||||
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
|
||||||
import org.elasticsearch.shield.authc.esusers.FileUserPasswdStore;
|
import org.elasticsearch.shield.authc.esusers.FileUserPasswdStore;
|
||||||
|
@ -23,6 +28,7 @@ import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
|
||||||
import org.elasticsearch.shield.authc.support.Hasher;
|
import org.elasticsearch.shield.authc.support.Hasher;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
import org.elasticsearch.shield.authz.store.FileRolesStore;
|
import org.elasticsearch.shield.authz.store.FileRolesStore;
|
||||||
|
import org.elasticsearch.shield.support.FileAttributesChecker;
|
||||||
import org.elasticsearch.shield.support.Validation;
|
import org.elasticsearch.shield.support.Validation;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -39,372 +45,322 @@ import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
|
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
|
|
||||||
|
|
||||||
/**
|
public class ESUsersTool extends MultiCommand {
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class ESUsersTool extends CliTool {
|
|
||||||
|
|
||||||
private static final CliToolConfig CONFIG = CliToolConfig.config("esusers", ESUsersTool.class)
|
|
||||||
.cmds(Useradd.CMD, Userdel.CMD, Passwd.CMD, Roles.CMD, ListUsersAndRoles.CMD)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
ExitStatus exitStatus = new ESUsersTool().execute(args);
|
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, Terminal.DEFAULT);
|
||||||
exit(exitStatus.status());
|
exit(new ESUsersTool(env).main(args, Terminal.DEFAULT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressForbidden(reason = "Allowed to exit explicitly from #main()")
|
ESUsersTool(Environment env) {
|
||||||
private static void exit(int status) {
|
super("Manages elasticsearch native users");
|
||||||
System.exit(status);
|
subcommands.put("useradd", new AddUserCommand(env));
|
||||||
|
subcommands.put("userdel", new DeleteUserCommand(env));
|
||||||
|
subcommands.put("passwd", new PasswordCommand(env));
|
||||||
|
subcommands.put("roles", new RolesCommand(env));
|
||||||
|
subcommands.put("list", new ListCommand(env));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ESUsersTool() {
|
static class AddUserCommand extends Command {
|
||||||
super(CONFIG);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ESUsersTool(Terminal terminal) {
|
private final Environment env;
|
||||||
super(CONFIG, terminal);
|
private final OptionSpec<String> passwordOption;
|
||||||
|
private final OptionSpec<String> rolesOption;
|
||||||
|
private final OptionSpec<String> arguments;
|
||||||
|
|
||||||
|
AddUserCommand(Environment env) {
|
||||||
|
super("Adds a native user");
|
||||||
|
this.env = env;
|
||||||
|
this.passwordOption = parser.acceptsAll(Arrays.asList("p", "password"),
|
||||||
|
"The user password")
|
||||||
|
.withRequiredArg();
|
||||||
|
this.rolesOption = parser.acceptsAll(Arrays.asList("r", "roles"),
|
||||||
|
"Comma-separated list of the roles of the user")
|
||||||
|
.withRequiredArg().defaultsTo("");
|
||||||
|
this.arguments = parser.nonOptions("username");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Command parse(String cmdName, CommandLine cli) throws Exception {
|
protected void printAdditionalHelp(Terminal terminal) {
|
||||||
switch (cmdName.toLowerCase(Locale.ROOT)) {
|
terminal.println("Adds a native user to elasticsearch (via internal realm). The user will");
|
||||||
case Useradd.NAME:
|
terminal.println("be added to the users file and its roles will be added to the");
|
||||||
return Useradd.parse(terminal, cli);
|
terminal.println("users_roles file. If non-default files are used (different file");
|
||||||
case Userdel.NAME:
|
terminal.println("locations are configured in elasticsearch.yml) the appropriate files");
|
||||||
return Userdel.parse(terminal, cli);
|
terminal.println("will be resolved from the settings and the user and its roles will be");
|
||||||
case Passwd.NAME:
|
terminal.println("added to them.");
|
||||||
return Passwd.parse(terminal, cli);
|
terminal.println("");
|
||||||
case ListUsersAndRoles.NAME:
|
|
||||||
return ListUsersAndRoles.parse(terminal, cli);
|
|
||||||
case Roles.NAME:
|
|
||||||
return Roles.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 CheckFileCommand {
|
@Override
|
||||||
|
protected int execute(Terminal terminal, OptionSet options) throws Exception {
|
||||||
private static final String NAME = "useradd";
|
String username = arguments.value(options);
|
||||||
|
String password = passwordOption.value(options);
|
||||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, Useradd.class)
|
String roles = rolesOption.value(options);
|
||||||
.options(
|
execute(terminal, username, password, roles);
|
||||||
option("p", "password").hasArg(false).required(false),
|
return ExitCodes.OK;
|
||||||
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");
|
|
||||||
} else if (cli.getArgs().length != 1) {
|
|
||||||
String[] extra = Arrays.copyOfRange(cli.getArgs(), 1, cli.getArgs().length);
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "extra arguments " + Arrays.toString(extra) + " were provided. please ensure " +
|
|
||||||
"all special characters are escaped");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = cli.getArgs()[0];
|
// pkg private for testing
|
||||||
|
void execute(Terminal terminal, String username, String passwordStr, String rolesCsv) throws Exception {
|
||||||
Validation.Error validationError = Validation.ESUsers.validateUsername(username);
|
Validation.Error validationError = Validation.ESUsers.validateUsername(username);
|
||||||
if (validationError != null) {
|
if (validationError != null) {
|
||||||
return exitCmd(ExitStatus.DATA_ERROR, terminal, "Invalid username [" + username + "]... " + validationError);
|
throw new UserError(ExitCodes.DATA_ERROR, "Invalid username [" + username + "]... " + validationError);
|
||||||
}
|
}
|
||||||
|
|
||||||
char[] password;
|
char[] password;
|
||||||
String passwordStr = cli.getOptionValue("password");
|
|
||||||
if (passwordStr != null) {
|
if (passwordStr != null) {
|
||||||
password = passwordStr.toCharArray();
|
password = passwordStr.toCharArray();
|
||||||
validationError = Validation.ESUsers.validatePassword(password);
|
validationError = Validation.ESUsers.validatePassword(password);
|
||||||
if (validationError != null) {
|
if (validationError != null) {
|
||||||
return exitCmd(ExitStatus.DATA_ERROR, terminal, "Invalid password..." + validationError);
|
throw new UserError(ExitCodes.DATA_ERROR, "Invalid password..." + validationError);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
password = terminal.readSecret("Enter new password: ");
|
password = terminal.readSecret("Enter new password: ");
|
||||||
validationError = Validation.ESUsers.validatePassword(password);
|
validationError = Validation.ESUsers.validatePassword(password);
|
||||||
if (validationError != null) {
|
if (validationError != null) {
|
||||||
return exitCmd(ExitStatus.DATA_ERROR, terminal, "Invalid password..." + validationError);
|
throw new UserError(ExitCodes.DATA_ERROR, "Invalid password..." + validationError);
|
||||||
}
|
}
|
||||||
char[] retyped = terminal.readSecret("Retype new password: ");
|
char[] retyped = terminal.readSecret("Retype new password: ");
|
||||||
if (!Arrays.equals(password, retyped)) {
|
if (Arrays.equals(password, retyped) == false) {
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "Password mismatch");
|
throw new UserError(ExitCodes.DATA_ERROR, "Password mismatch");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String rolesCsv = cli.getOptionValue("roles");
|
String[] roles = rolesCsv.split(",");
|
||||||
String[] roles = (rolesCsv != null) ? rolesCsv.split(",") : Strings.EMPTY_ARRAY;
|
|
||||||
for (String role : roles) {
|
for (String role : roles) {
|
||||||
validationError = Validation.Roles.validateRoleName(role);
|
validationError = Validation.Roles.validateRoleName(role);
|
||||||
if (validationError != null) {
|
if (validationError != null) {
|
||||||
return exitCmd(ExitStatus.DATA_ERROR, terminal, "Invalid role [" + role + "]... " + validationError);
|
throw new UserError(ExitCodes.DATA_ERROR, "Invalid role [" + role + "]... " + validationError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Useradd(terminal, username, new SecuredString(password), roles);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String username;
|
Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE);
|
||||||
final SecuredString passwd;
|
Path passwordFile = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
||||||
final String[] roles;
|
Path rolesFile = FileUserRolesStore.resolveFile(esusersSettings, env);
|
||||||
|
verifyRoles(terminal, env.settings(), env, roles);
|
||||||
|
FileAttributesChecker attributesChecker = new FileAttributesChecker(passwordFile, rolesFile);
|
||||||
|
|
||||||
Useradd(Terminal terminal, String username, SecuredString passwd, String... roles) {
|
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(passwordFile, null));
|
||||||
super(terminal);
|
|
||||||
this.username = username;
|
|
||||||
this.passwd = passwd;
|
|
||||||
this.roles = roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ExitStatus doExecute(Settings settings, Environment env) throws Exception {
|
|
||||||
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
|
|
||||||
verifyRoles(terminal, settings, env, roles);
|
|
||||||
Path file = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
|
||||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(file, null));
|
|
||||||
if (users.containsKey(username)) {
|
if (users.containsKey(username)) {
|
||||||
terminal.println(String.format(Locale.ROOT, "User [%s] already exists", username));
|
throw new UserError(ExitCodes.CODE_ERROR, "User [" + username + "] already exists");
|
||||||
return ExitStatus.CODE_ERROR;
|
|
||||||
}
|
}
|
||||||
Hasher hasher = Hasher.BCRYPT;
|
Hasher hasher = Hasher.BCRYPT;
|
||||||
users.put(username, hasher.hash(passwd));
|
users.put(username, hasher.hash(new SecuredString(password)));
|
||||||
FileUserPasswdStore.writeFile(users, file);
|
FileUserPasswdStore.writeFile(users, passwordFile);
|
||||||
|
|
||||||
if (roles != null && roles.length > 0) {
|
if (roles.length > 0) {
|
||||||
file = FileUserRolesStore.resolveFile(esusersSettings, env);
|
Map<String, String[]> userRoles = new HashMap<>(FileUserRolesStore.parseFile(rolesFile, null));
|
||||||
Map<String, String[]> userRoles = new HashMap<>(FileUserRolesStore.parseFile(file, null));
|
|
||||||
userRoles.put(username, roles);
|
userRoles.put(username, roles);
|
||||||
FileUserRolesStore.writeFile(userRoles, file);
|
FileUserRolesStore.writeFile(userRoles, rolesFile);
|
||||||
}
|
}
|
||||||
return ExitStatus.OK;
|
|
||||||
|
attributesChecker.check(terminal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DeleteUserCommand extends Command {
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
private final OptionSpec<String> arguments;
|
||||||
|
|
||||||
|
DeleteUserCommand(Environment env) {
|
||||||
|
super("Deletes a native user");
|
||||||
|
this.env = env;
|
||||||
|
this.arguments = parser.nonOptions("username");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Path[] pathsForPermissionsCheck(Settings settings, Environment env) {
|
protected void printAdditionalHelp(Terminal terminal) {
|
||||||
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
|
terminal.println("Removes an existing native user from elasticsearch. The user will be");
|
||||||
Path userPath = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
terminal.println("removed from the users file and its roles will be removed to the");
|
||||||
Path userRolesPath = FileUserRolesStore.resolveFile(esusersSettings, env);
|
terminal.println("users_roles file. If non-default files are used (different file");
|
||||||
return new Path[]{userPath, userRolesPath};
|
terminal.println("locations are configured in elasticsearch.yml) the appropriate files");
|
||||||
}
|
terminal.println("will be resolved from the settings and the user and its roles will be");
|
||||||
}
|
terminal.println("removed to them.");
|
||||||
|
terminal.println("");
|
||||||
static class Userdel extends CheckFileCommand {
|
|
||||||
|
|
||||||
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");
|
|
||||||
} else if (cli.getArgs().length != 1) {
|
|
||||||
String[] extra = Arrays.copyOfRange(cli.getArgs(), 1, cli.getArgs().length);
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "extra arguments " + Arrays.toString(extra) + " were provided. userdel only " +
|
|
||||||
"supports deleting one user at a time");
|
|
||||||
}
|
|
||||||
|
|
||||||
String username = cli.getArgs()[0];
|
|
||||||
return new Userdel(terminal, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String username;
|
|
||||||
|
|
||||||
Userdel(Terminal terminal, String username) {
|
|
||||||
super(terminal);
|
|
||||||
this.username = username;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Path[] pathsForPermissionsCheck(Settings settings, Environment env) {
|
protected int execute(Terminal terminal, OptionSet options) throws Exception {
|
||||||
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
|
String username = arguments.value(options);
|
||||||
Path userPath = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
execute(terminal, username);
|
||||||
Path userRolesPath = FileUserRolesStore.resolveFile(esusersSettings, env);
|
return ExitCodes.OK;
|
||||||
|
|
||||||
if (Files.exists(userRolesPath)) {
|
|
||||||
return new Path[]{userPath, userRolesPath};
|
|
||||||
}
|
|
||||||
return new Path[]{userPath};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// pkg private for testing
|
||||||
public ExitStatus doExecute(Settings settings, Environment env) throws Exception {
|
void execute(Terminal terminal, String username) throws Exception {
|
||||||
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
|
Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE);
|
||||||
Path file = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
Path passwordFile = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
||||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(file, null));
|
Path rolesFile = FileUserRolesStore.resolveFile(esusersSettings, env);
|
||||||
if (!users.containsKey(username)) {
|
FileAttributesChecker attributesChecker = new FileAttributesChecker(passwordFile, rolesFile);
|
||||||
terminal.println(String.format(Locale.ROOT, "User [%s] doesn't exist", username));
|
|
||||||
return ExitStatus.NO_USER;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Files.exists(file)) {
|
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(passwordFile, null));
|
||||||
|
if (users.containsKey(username) == false) {
|
||||||
|
throw new UserError(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
||||||
|
}
|
||||||
|
if (Files.exists(passwordFile)) {
|
||||||
char[] passwd = users.remove(username);
|
char[] passwd = users.remove(username);
|
||||||
if (passwd != null) {
|
if (passwd != null) {
|
||||||
FileUserPasswdStore.writeFile(users, file);
|
FileUserPasswdStore.writeFile(users, passwordFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file = FileUserRolesStore.resolveFile(esusersSettings, env);
|
Map<String, String[]> userRoles = new HashMap<>(FileUserRolesStore.parseFile(rolesFile, null));
|
||||||
Map<String, String[]> userRoles = new HashMap<>(FileUserRolesStore.parseFile(file, null));
|
if (Files.exists(rolesFile)) {
|
||||||
if (Files.exists(file)) {
|
|
||||||
String[] roles = userRoles.remove(username);
|
String[] roles = userRoles.remove(username);
|
||||||
if (roles != null) {
|
if (roles != null) {
|
||||||
FileUserRolesStore.writeFile(userRoles, file);
|
FileUserRolesStore.writeFile(userRoles, rolesFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExitStatus.OK;
|
attributesChecker.check(terminal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Passwd extends CheckFileCommand {
|
static class PasswordCommand extends Command {
|
||||||
|
|
||||||
private static final String NAME = "passwd";
|
private final Environment env;
|
||||||
|
private final OptionSpec<String> passwordOption;
|
||||||
|
private final OptionSpec<String> arguments;
|
||||||
|
|
||||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, Passwd.class)
|
PasswordCommand(Environment env) {
|
||||||
.options(option("p", "password").hasArg(false).required(false))
|
super("Changes the password of an existing native user");
|
||||||
.build();
|
this.env = env;
|
||||||
|
this.passwordOption = parser.acceptsAll(Arrays.asList("p", "password"),
|
||||||
public static Command parse(Terminal terminal, CommandLine cli) {
|
"The user password")
|
||||||
if (cli.getArgs().length == 0) {
|
.withRequiredArg();
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "username is missing");
|
this.arguments = parser.nonOptions("username");
|
||||||
} else if (cli.getArgs().length != 1) {
|
|
||||||
String[] extra = Arrays.copyOfRange(cli.getArgs(), 1, cli.getArgs().length);
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "extra arguments " + Arrays.toString(extra) + " were provided");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = cli.getArgs()[0];
|
@Override
|
||||||
|
protected void printAdditionalHelp(Terminal terminal) {
|
||||||
|
terminal.println("The passwd command changes passwords for native user accounts. The tool");
|
||||||
|
terminal.println("prompts twice for a replacement password. The second entry is compared");
|
||||||
|
terminal.println("against the first and both are required to match in order for the");
|
||||||
|
terminal.println("password to be changed. If non-default users file is used (a different");
|
||||||
|
terminal.println("file location is configured in elasticsearch.yml) the appropriate file");
|
||||||
|
terminal.println("will be resolved from the settings.");
|
||||||
|
terminal.println("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int execute(Terminal terminal, OptionSet options) throws Exception {
|
||||||
|
String username = arguments.value(options);
|
||||||
|
String password = passwordOption.value(options);
|
||||||
|
execute(terminal, username, password);
|
||||||
|
return ExitCodes.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pkg private for testing
|
||||||
|
void execute(Terminal terminal, String username, String passwordStr) throws Exception {
|
||||||
char[] password;
|
char[] password;
|
||||||
String passwordStr = cli.getOptionValue("password");
|
|
||||||
if (passwordStr != null) {
|
if (passwordStr != null) {
|
||||||
password = passwordStr.toCharArray();
|
password = passwordStr.toCharArray();
|
||||||
|
Validation.Error validationError = Validation.ESUsers.validatePassword(password);
|
||||||
|
if (validationError != null) {
|
||||||
|
throw new UserError(ExitCodes.DATA_ERROR, "Invalid password..." + validationError);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
password = terminal.readSecret("Enter new password: ");
|
password = terminal.readSecret("Enter new password: ");
|
||||||
|
Validation.Error validationError = Validation.ESUsers.validatePassword(password);
|
||||||
|
if (validationError != null) {
|
||||||
|
throw new UserError(ExitCodes.DATA_ERROR, "Invalid password..." + validationError);
|
||||||
|
}
|
||||||
char[] retyped = terminal.readSecret("Retype new password: ");
|
char[] retyped = terminal.readSecret("Retype new password: ");
|
||||||
if (!Arrays.equals(password, retyped)) {
|
if (Arrays.equals(password, retyped) == false) {
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "Password mismatch");
|
throw new UserError(ExitCodes.DATA_ERROR, "Password mismatch");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Passwd(terminal, username, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String username;
|
Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE);
|
||||||
final SecuredString passwd;
|
|
||||||
|
|
||||||
Passwd(Terminal terminal, String username, char[] passwd) {
|
|
||||||
super(terminal);
|
|
||||||
this.username = username;
|
|
||||||
this.passwd = new SecuredString(passwd);
|
|
||||||
Arrays.fill(passwd, (char) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Path[] pathsForPermissionsCheck(Settings settings, Environment env) {
|
|
||||||
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
|
|
||||||
Path path = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
|
||||||
return new Path[]{path};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ExitStatus doExecute(Settings settings, Environment env) throws Exception {
|
|
||||||
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
|
|
||||||
Path file = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
Path file = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
||||||
|
FileAttributesChecker attributesChecker = new FileAttributesChecker(file);
|
||||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(file, null));
|
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(file, null));
|
||||||
if (!users.containsKey(username)) {
|
if (users.containsKey(username) == false) {
|
||||||
terminal.println(String.format(Locale.ROOT, "User [%s] doesn't exist", username));
|
throw new UserError(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
||||||
return ExitStatus.NO_USER;
|
|
||||||
}
|
}
|
||||||
Hasher hasher = Hasher.BCRYPT;
|
Hasher hasher = Hasher.BCRYPT;
|
||||||
users.put(username, hasher.hash(passwd));
|
users.put(username, hasher.hash(new SecuredString(password)));
|
||||||
FileUserPasswdStore.writeFile(users, file);
|
FileUserPasswdStore.writeFile(users, file);
|
||||||
return ExitStatus.OK;
|
attributesChecker.check(terminal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class RolesCommand extends Command {
|
||||||
static class Roles extends CheckFileCommand {
|
|
||||||
|
|
||||||
private static final String NAME = "roles";
|
|
||||||
|
|
||||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, Roles.class)
|
|
||||||
.options(
|
|
||||||
option("a", "add").hasArg(true).required(false),
|
|
||||||
option("r", "remove").hasArg(true).required(false))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public static Command parse(Terminal terminal, CommandLine cli) {
|
|
||||||
if (cli.getArgs().length == 0) {
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "username is missing");
|
|
||||||
} else if (cli.getArgs().length != 1) {
|
|
||||||
String[] extra = Arrays.copyOfRange(cli.getArgs(), 1, cli.getArgs().length);
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "extra arguments " + Arrays.toString(extra) + " were provided. please ensure " +
|
|
||||||
"all special characters are escaped");
|
|
||||||
}
|
|
||||||
|
|
||||||
String username = cli.getArgs()[0];
|
|
||||||
String addRolesCsv = cli.getOptionValue("add");
|
|
||||||
String[] addRoles = (addRolesCsv != null) ? addRolesCsv.split(",") : Strings.EMPTY_ARRAY;
|
|
||||||
String removeRolesCsv = cli.getOptionValue("remove");
|
|
||||||
String[] removeRoles = (removeRolesCsv != null) ? removeRolesCsv.split(",") : Strings.EMPTY_ARRAY;
|
|
||||||
|
|
||||||
return new Roles(terminal, username, addRoles, removeRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Pattern ROLE_PATTERN = Pattern.compile("[\\w@-]+");
|
public static final Pattern ROLE_PATTERN = Pattern.compile("[\\w@-]+");
|
||||||
final String username;
|
|
||||||
final String[] addRoles;
|
|
||||||
final String[] removeRoles;
|
|
||||||
|
|
||||||
public Roles(Terminal terminal, String username, String[] addRoles, String[] removeRoles) {
|
private final Environment env;
|
||||||
super(terminal);
|
private final OptionSpec<String> addOption;
|
||||||
this.username = username;
|
private final OptionSpec<String> removeOption;
|
||||||
this.addRoles = addRoles;
|
private final OptionSpec<String> arguments;
|
||||||
this.removeRoles = removeRoles;
|
|
||||||
|
RolesCommand(Environment env) {
|
||||||
|
super("Edit roles of an existing user");
|
||||||
|
this.env = env;
|
||||||
|
this.addOption = parser.acceptsAll(Arrays.asList("a", "add"),
|
||||||
|
"Adds supplied roles to the specified user")
|
||||||
|
.withRequiredArg().defaultsTo("");
|
||||||
|
this.removeOption = parser.acceptsAll(Arrays.asList("r", "remove"),
|
||||||
|
"Remove supplied roles from the specified user")
|
||||||
|
.withRequiredArg().defaultsTo("");
|
||||||
|
this.arguments = parser.nonOptions("username");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Path[] pathsForPermissionsCheck(Settings settings, Environment env) {
|
protected void printAdditionalHelp(Terminal terminal) {
|
||||||
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
|
terminal.println("The roles command allows to edit roles for an existing user.");
|
||||||
Path path = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
terminal.println("corresponding roles. Alternatively you can also list just a");
|
||||||
return new Path[]{path};
|
terminal.println("single users roles, if you do not specify the -a or the -r parameter.");
|
||||||
|
terminal.println("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExitStatus doExecute(Settings settings, Environment env) throws Exception {
|
protected int execute(Terminal terminal, OptionSet options) throws Exception {
|
||||||
|
String username = arguments.value(options);
|
||||||
|
String addRoles = addOption.value(options);
|
||||||
|
String removeRoles = removeOption.value(options);
|
||||||
|
execute(terminal, username, addRoles, removeRoles);
|
||||||
|
return ExitCodes.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute(Terminal terminal, String username, String addRolesStr, String removeRolesStr) throws Exception {
|
||||||
|
String[] addRoles = addRolesStr.split(",");
|
||||||
|
String[] removeRoles = removeRolesStr.split(",");
|
||||||
// check if just need to return data as no write operation happens
|
// check if just need to return data as no write operation happens
|
||||||
// Nothing to add, just list the data for a username
|
// Nothing to add, just list the data for a username
|
||||||
boolean readOnlyUserListing = removeRoles.length == 0 && addRoles.length == 0;
|
boolean readOnlyUserListing = removeRoles.length == 0 && addRoles.length == 0;
|
||||||
if (readOnlyUserListing) {
|
if (readOnlyUserListing) {
|
||||||
return new ListUsersAndRoles(terminal, username).execute(settings, env);
|
listUsersAndRoles(terminal, env, username);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for roles if they match
|
// check for roles if they match
|
||||||
String[] allRoles = ArrayUtils.concat(addRoles, removeRoles, String.class);
|
String[] allRoles = ArrayUtils.concat(addRoles, removeRoles, String.class);
|
||||||
for (String role : allRoles) {
|
for (String role : allRoles) {
|
||||||
if (!ROLE_PATTERN.matcher(role).matches()) {
|
if (!ROLE_PATTERN.matcher(role).matches()) {
|
||||||
terminal.println(String.format(Locale.ROOT, "Role name [%s] is not valid. Please use lowercase and numbers only",
|
throw new UserError(ExitCodes.DATA_ERROR,
|
||||||
role));
|
"Role name [" + role + "] is not valid. Please use lowercase and numbers only");
|
||||||
return ExitStatus.DATA_ERROR;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
|
Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE);
|
||||||
|
Path usersFile = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
||||||
|
Path rolesFile = FileUserRolesStore.resolveFile(esusersSettings, env);
|
||||||
|
FileAttributesChecker attributesChecker = new FileAttributesChecker(usersFile, rolesFile);
|
||||||
|
|
||||||
Path path = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
Map<String, char[]> usersMap = FileUserPasswdStore.parseFile(usersFile, null);
|
||||||
Map<String, char[]> usersMap = FileUserPasswdStore.parseFile(path, null);
|
|
||||||
if (!usersMap.containsKey(username)) {
|
if (!usersMap.containsKey(username)) {
|
||||||
terminal.println(String.format(Locale.ROOT, "User [%s] doesn't exist", username));
|
throw new UserError(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
||||||
return ExitStatus.NO_USER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Path file = FileUserRolesStore.resolveFile(esusersSettings, env);
|
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(rolesFile, null);
|
||||||
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(file, null);
|
|
||||||
|
|
||||||
List<String> roles = new ArrayList<>();
|
List<String> roles = new ArrayList<>();
|
||||||
if (userRoles.get(username) != null) {
|
if (userRoles.get(username) != null) {
|
||||||
roles.addAll(Arrays.asList(userRoles.get(username)));
|
roles.addAll(Arrays.asList(userRoles.get(username)));
|
||||||
}
|
}
|
||||||
verifyRoles(terminal, settings, env, addRoles);
|
verifyRoles(terminal, env.settings(), env, addRoles);
|
||||||
roles.addAll(Arrays.asList(addRoles));
|
roles.addAll(Arrays.asList(addRoles));
|
||||||
roles.removeAll(Arrays.asList(removeRoles));
|
roles.removeAll(Arrays.asList(removeRoles));
|
||||||
|
|
||||||
|
@ -415,41 +371,44 @@ public class ESUsersTool extends CliTool {
|
||||||
} else {
|
} else {
|
||||||
userRolesToWrite.put(username, new LinkedHashSet<>(roles).toArray(new String[]{}));
|
userRolesToWrite.put(username, new LinkedHashSet<>(roles).toArray(new String[]{}));
|
||||||
}
|
}
|
||||||
FileUserRolesStore.writeFile(userRolesToWrite, file);
|
FileUserRolesStore.writeFile(userRolesToWrite, rolesFile);
|
||||||
|
|
||||||
return ExitStatus.OK;
|
attributesChecker.check(terminal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ListUsersAndRoles extends CliTool.Command {
|
static class ListCommand extends Command {
|
||||||
|
|
||||||
private static final String NAME = "list";
|
private final Environment env;
|
||||||
|
private final OptionSpec<String> arguments;
|
||||||
|
|
||||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, Useradd.class).build();
|
ListCommand(Environment env) {
|
||||||
|
super("List existing users and their corresponding roles");
|
||||||
public static Command parse(Terminal terminal, CommandLine cli) {
|
this.env = env;
|
||||||
String username = null;
|
this.arguments = parser.nonOptions("username");
|
||||||
if (cli.getArgs().length == 1) {
|
|
||||||
username = cli.getArgs()[0];
|
|
||||||
} else if (cli.getArgs().length > 1) {
|
|
||||||
String[] extra = Arrays.copyOfRange(cli.getArgs(), 1, cli.getArgs().length);
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "extra arguments " + Arrays.toString(extra) + " were provided. list can be " +
|
|
||||||
"used without a user or with a single user");
|
|
||||||
}
|
|
||||||
return new ListUsersAndRoles(terminal, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
String username;
|
|
||||||
|
|
||||||
public ListUsersAndRoles(Terminal terminal, String username) {
|
|
||||||
super(terminal);
|
|
||||||
this.username = username;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
protected void printAdditionalHelp(Terminal terminal) {
|
||||||
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
|
terminal.println("");
|
||||||
Set<String> knownRoles = loadRoleNames(terminal, settings, env);
|
terminal.println("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int execute(Terminal terminal, OptionSet options) throws Exception {
|
||||||
|
String username = null;
|
||||||
|
if (options.has(arguments)) {
|
||||||
|
username = arguments.value(options);
|
||||||
|
}
|
||||||
|
listUsersAndRoles(terminal, env, username);
|
||||||
|
return ExitCodes.OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pkg private for tests
|
||||||
|
static void listUsersAndRoles(Terminal terminal, Environment env, String username) throws Exception {
|
||||||
|
Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE);
|
||||||
|
Set<String> knownRoles = loadRoleNames(terminal, env.settings(), env);
|
||||||
Path userRolesFilePath = FileUserRolesStore.resolveFile(esusersSettings, env);
|
Path userRolesFilePath = FileUserRolesStore.resolveFile(esusersSettings, env);
|
||||||
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null);
|
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null);
|
||||||
Path userFilePath = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
Path userFilePath = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
||||||
|
@ -457,8 +416,7 @@ public class ESUsersTool extends CliTool {
|
||||||
|
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
if (!users.contains(username)) {
|
if (!users.contains(username)) {
|
||||||
terminal.println(String.format(Locale.ROOT, "User [%s] doesn't exist", username));
|
throw new UserError(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
||||||
return ExitStatus.NO_USER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userRoles.containsKey(username)) {
|
if (userRoles.containsKey(username)) {
|
||||||
|
@ -471,8 +429,8 @@ public class ESUsersTool extends CliTool {
|
||||||
// at least one role is marked... so printing the legend
|
// at least one role is marked... so printing the legend
|
||||||
Path rolesFile = FileRolesStore.resolveFile(esusersSettings, env).toAbsolutePath();
|
Path rolesFile = FileRolesStore.resolveFile(esusersSettings, env).toAbsolutePath();
|
||||||
terminal.println("");
|
terminal.println("");
|
||||||
terminal.println(String.format(Locale.ROOT, " [*] An unknown role. Please check [%s] to see available roles",
|
terminal.println(" [*] An unknown role. "
|
||||||
rolesFile.toAbsolutePath()));
|
+ "Please check [" + rolesFile.toAbsolutePath() + "] to see available roles");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
terminal.println(String.format(Locale.ROOT, "%-15s: -", username));
|
terminal.println(String.format(Locale.ROOT, "%-15s: -", username));
|
||||||
|
@ -498,20 +456,17 @@ public class ESUsersTool extends CliTool {
|
||||||
|
|
||||||
if (!usersExist) {
|
if (!usersExist) {
|
||||||
terminal.println("No users found");
|
terminal.println("No users found");
|
||||||
return ExitStatus.OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unknownRolesFound) {
|
if (unknownRolesFound) {
|
||||||
// at least one role is marked... so printing the legend
|
// at least one role is marked... so printing the legend
|
||||||
Path rolesFile = FileRolesStore.resolveFile(esusersSettings, env).toAbsolutePath();
|
Path rolesFile = FileRolesStore.resolveFile(esusersSettings, env).toAbsolutePath();
|
||||||
terminal.println("");
|
terminal.println("");
|
||||||
terminal.println(String.format(Locale.ROOT, " [*] An unknown role. Please check [%s] to see available roles",
|
terminal.println(" [*] An unknown role. "
|
||||||
rolesFile.toAbsolutePath()));
|
+ "Please check [" + rolesFile.toAbsolutePath() + "] to see available roles");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExitStatus.OK;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<String> loadRoleNames(Terminal terminal, Settings settings, Environment env) {
|
private static Set<String> loadRoleNames(Terminal terminal, Settings settings, Environment env) {
|
||||||
|
|
|
@ -65,7 +65,6 @@ public class SystemKeyTool extends Command {
|
||||||
if (keyPath == null) {
|
if (keyPath == null) {
|
||||||
keyPath = InternalCryptoService.resolveSystemKey(env.settings(), env);
|
keyPath = InternalCryptoService.resolveSystemKey(env.settings(), env);
|
||||||
}
|
}
|
||||||
FileAttributesChecker attributesChecker = new FileAttributesChecker(keyPath);
|
|
||||||
|
|
||||||
// write the key
|
// write the key
|
||||||
terminal.println(Terminal.Verbosity.VERBOSE, "generating...");
|
terminal.println(Terminal.Verbosity.VERBOSE, "generating...");
|
||||||
|
@ -80,9 +79,6 @@ public class SystemKeyTool extends Command {
|
||||||
terminal.println("Ensure the generated key can be read by the user that Elasticsearch runs as, "
|
terminal.println("Ensure the generated key can be read by the user that Elasticsearch runs as, "
|
||||||
+ "permissions are set to owner read/write only");
|
+ "permissions are set to owner read/write only");
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if attributes changed
|
|
||||||
attributesChecker.check(terminal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,6 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authc.esusers.tool;
|
package org.elasticsearch.shield.authc.esusers.tool;
|
||||||
|
|
||||||
import org.elasticsearch.common.Strings;
|
|
||||||
import org.elasticsearch.common.cli.CliTool;
|
|
||||||
import org.elasticsearch.common.cli.CliToolTestCase;
|
|
||||||
import org.elasticsearch.common.cli.Terminal;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.env.Environment;
|
|
||||||
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
|
|
||||||
import org.elasticsearch.shield.authc.support.Hasher;
|
|
||||||
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -24,6 +14,16 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.common.cli.CliTool;
|
||||||
|
import org.elasticsearch.common.cli.Terminal;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
|
||||||
|
import org.elasticsearch.shield.authc.support.Hasher;
|
||||||
|
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.allOf;
|
import static org.hamcrest.Matchers.allOf;
|
||||||
import static org.hamcrest.Matchers.arrayContaining;
|
import static org.hamcrest.Matchers.arrayContaining;
|
||||||
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
||||||
|
@ -40,10 +40,7 @@ import static org.hamcrest.Matchers.not;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
/**
|
public class ESUsersToolTests extends ESTestCase {
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class ESUsersToolTests extends CliToolTestCase {
|
|
||||||
public void testUseraddParseAllOptions() throws Exception {
|
public void testUseraddParseAllOptions() throws Exception {
|
||||||
ESUsersTool tool = new ESUsersTool();
|
ESUsersTool tool = new ESUsersTool();
|
||||||
CliTool.Command command = tool.parse("useradd", args("username -p changeme -r r1,r2,r3"));
|
CliTool.Command command = tool.parse("useradd", args("username -p changeme -r r1,r2,r3"));
|
||||||
|
|
Loading…
Reference in New Issue