Changed esusers tool to use jopt-simple

Original commit: elastic/x-pack-elasticsearch@1f8763fcd6
This commit is contained in:
Ryan Ernst 2016-03-04 12:14:34 -08:00
parent fe377cfda2
commit 706216844b
3 changed files with 288 additions and 340 deletions

View File

@ -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) {

View File

@ -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);
} }
} }

View File

@ -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"));