From 706216844b5202ffcfcd4ce6bf23aebc4e8d6d6c Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Fri, 4 Mar 2016 12:14:34 -0800 Subject: [PATCH] Changed esusers tool to use jopt-simple Original commit: elastic/x-pack-elasticsearch@1f8763fcd611476738b484a2c26b77872946348b --- .../authc/esusers/tool/ESUsersTool.java | 599 ++++++++---------- .../shield/crypto/tool/SystemKeyTool.java | 4 - .../authc/esusers/tool/ESUsersToolTests.java | 25 +- 3 files changed, 288 insertions(+), 340 deletions(-) diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersTool.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersTool.java index b3bb68b57ca..c6e4e17e760 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersTool.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersTool.java @@ -5,10 +5,14 @@ */ package org.elasticsearch.shield.authc.esusers.tool; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; 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.SuppressForbidden; -import org.elasticsearch.common.cli.CheckFileCommand; import org.elasticsearch.common.cli.CliTool; import org.elasticsearch.common.cli.CliToolConfig; 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.set.Sets; import org.elasticsearch.env.Environment; +import org.elasticsearch.node.internal.InternalSettingsPreparer; import org.elasticsearch.shield.authc.Realms; import org.elasticsearch.shield.authc.esusers.ESUsersRealm; 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.SecuredString; import org.elasticsearch.shield.authz.store.FileRolesStore; +import org.elasticsearch.shield.support.FileAttributesChecker; import org.elasticsearch.shield.support.Validation; import java.nio.file.Files; @@ -39,372 +45,322 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd; -import static org.elasticsearch.common.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, Roles.CMD, ListUsersAndRoles.CMD) - .build(); +public class ESUsersTool extends MultiCommand { public static void main(String[] args) throws Exception { - ExitStatus exitStatus = new ESUsersTool().execute(args); - exit(exitStatus.status()); + Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, Terminal.DEFAULT); + exit(new ESUsersTool(env).main(args, Terminal.DEFAULT)); } - @SuppressForbidden(reason = "Allowed to exit explicitly from #main()") - private static void exit(int status) { - System.exit(status); + ESUsersTool(Environment env) { + super("Manages elasticsearch native users"); + 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() { - super(CONFIG); - } + static class AddUserCommand extends Command { - public ESUsersTool(Terminal terminal) { - super(CONFIG, terminal); - } + private final Environment env; + private final OptionSpec passwordOption; + private final OptionSpec rolesOption; + private final OptionSpec arguments; - @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); - 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; + 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"); } - } - static class Useradd extends CheckFileCommand { + @Override + protected void printAdditionalHelp(Terminal terminal) { + terminal.println("Adds a native user to elasticsearch (via internal realm). The user will"); + terminal.println("be added to the users file and its roles will be added to the"); + terminal.println("users_roles file. If non-default files are used (different file"); + 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("added to them."); + terminal.println(""); + } - private static final String NAME = "useradd"; + @Override + protected int execute(Terminal terminal, OptionSet options) throws Exception { + String username = arguments.value(options); + String password = passwordOption.value(options); + String roles = rolesOption.value(options); + execute(terminal, username, password, roles); + return ExitCodes.OK; + } - 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"); - } 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); 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; - String passwordStr = cli.getOptionValue("password"); if (passwordStr != null) { password = passwordStr.toCharArray(); validationError = Validation.ESUsers.validatePassword(password); if (validationError != null) { - return exitCmd(ExitStatus.DATA_ERROR, terminal, "Invalid password..." + validationError); + throw new UserError(ExitCodes.DATA_ERROR, "Invalid password..." + validationError); } } else { password = terminal.readSecret("Enter new password: "); validationError = Validation.ESUsers.validatePassword(password); 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: "); - if (!Arrays.equals(password, retyped)) { - return exitCmd(ExitStatus.USAGE, terminal, "Password mismatch"); + if (Arrays.equals(password, retyped) == false) { + throw new UserError(ExitCodes.DATA_ERROR, "Password mismatch"); } } - String rolesCsv = cli.getOptionValue("roles"); - String[] roles = (rolesCsv != null) ? rolesCsv.split(",") : Strings.EMPTY_ARRAY; + String[] roles = rolesCsv.split(","); for (String role : roles) { validationError = Validation.Roles.validateRoleName(role); 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; - final SecuredString passwd; - final String[] roles; + Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE); + Path passwordFile = FileUserPasswdStore.resolveFile(esusersSettings, env); + 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) { - 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 users = new HashMap<>(FileUserPasswdStore.parseFile(file, null)); + Map users = new HashMap<>(FileUserPasswdStore.parseFile(passwordFile, null)); if (users.containsKey(username)) { - terminal.println(String.format(Locale.ROOT, "User [%s] already exists", username)); - return ExitStatus.CODE_ERROR; + throw new UserError(ExitCodes.CODE_ERROR, "User [" + username + "] already exists"); } Hasher hasher = Hasher.BCRYPT; - users.put(username, hasher.hash(passwd)); - FileUserPasswdStore.writeFile(users, file); + users.put(username, hasher.hash(new SecuredString(password))); + FileUserPasswdStore.writeFile(users, passwordFile); - if (roles != null && roles.length > 0) { - file = FileUserRolesStore.resolveFile(esusersSettings, env); - Map userRoles = new HashMap<>(FileUserRolesStore.parseFile(file, null)); + if (roles.length > 0) { + Map userRoles = new HashMap<>(FileUserRolesStore.parseFile(rolesFile, null)); userRoles.put(username, roles); - FileUserRolesStore.writeFile(userRoles, file); + FileUserRolesStore.writeFile(userRoles, rolesFile); } - return ExitStatus.OK; - } - @Override - protected Path[] pathsForPermissionsCheck(Settings settings, Environment env) { - Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE); - Path userPath = FileUserPasswdStore.resolveFile(esusersSettings, env); - Path userRolesPath = FileUserRolesStore.resolveFile(esusersSettings, env); - return new Path[]{userPath, userRolesPath}; + attributesChecker.check(terminal); } } - static class Userdel extends CheckFileCommand { + static class DeleteUserCommand extends Command { - private static final String NAME = "userdel"; + private final Environment env; + private final OptionSpec arguments; - 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; + DeleteUserCommand(Environment env) { + super("Deletes a native user"); + this.env = env; + this.arguments = parser.nonOptions("username"); } @Override - protected Path[] pathsForPermissionsCheck(Settings settings, Environment env) { - Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE); - Path userPath = FileUserPasswdStore.resolveFile(esusersSettings, env); - Path userRolesPath = FileUserRolesStore.resolveFile(esusersSettings, env); - - if (Files.exists(userRolesPath)) { - return new Path[]{userPath, userRolesPath}; - } - return new Path[]{userPath}; + protected void printAdditionalHelp(Terminal terminal) { + terminal.println("Removes an existing native user from elasticsearch. The user will be"); + terminal.println("removed from the users file and its roles will be removed to the"); + terminal.println("users_roles file. If non-default files are used (different file"); + 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(""); } @Override - public ExitStatus doExecute(Settings settings, Environment env) throws Exception { - Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE); - Path file = FileUserPasswdStore.resolveFile(esusersSettings, env); - Map users = new HashMap<>(FileUserPasswdStore.parseFile(file, null)); - if (!users.containsKey(username)) { - terminal.println(String.format(Locale.ROOT, "User [%s] doesn't exist", username)); - return ExitStatus.NO_USER; - } + protected int execute(Terminal terminal, OptionSet options) throws Exception { + String username = arguments.value(options); + execute(terminal, username); + return ExitCodes.OK; + } - if (Files.exists(file)) { + // pkg private for testing + void execute(Terminal terminal, String username) throws Exception { + Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE); + Path passwordFile = FileUserPasswdStore.resolveFile(esusersSettings, env); + Path rolesFile = FileUserRolesStore.resolveFile(esusersSettings, env); + FileAttributesChecker attributesChecker = new FileAttributesChecker(passwordFile, rolesFile); + + Map 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); if (passwd != null) { - FileUserPasswdStore.writeFile(users, file); + FileUserPasswdStore.writeFile(users, passwordFile); } } - file = FileUserRolesStore.resolveFile(esusersSettings, env); - Map userRoles = new HashMap<>(FileUserRolesStore.parseFile(file, null)); - if (Files.exists(file)) { + Map userRoles = new HashMap<>(FileUserRolesStore.parseFile(rolesFile, null)); + if (Files.exists(rolesFile)) { String[] roles = userRoles.remove(username); 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 passwordOption; + private final OptionSpec arguments; - private static final CliToolConfig.Cmd CMD = cmd(NAME, Passwd.class) - .options(option("p", "password").hasArg(false).required(false)) - .build(); + PasswordCommand(Environment env) { + super("Changes the password of an existing native user"); + this.env = env; + this.passwordOption = parser.acceptsAll(Arrays.asList("p", "password"), + "The user password") + .withRequiredArg(); + this.arguments = parser.nonOptions("username"); + } - 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"); - } + @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(""); + } - String username = cli.getArgs()[0]; + @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; - String passwordStr = cli.getOptionValue("password"); if (passwordStr != null) { password = passwordStr.toCharArray(); + Validation.Error validationError = Validation.ESUsers.validatePassword(password); + if (validationError != null) { + throw new UserError(ExitCodes.DATA_ERROR, "Invalid password..." + validationError); + } } else { 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: "); - if (!Arrays.equals(password, retyped)) { - return exitCmd(ExitStatus.USAGE, terminal, "Password mismatch"); + if (Arrays.equals(password, retyped) == false) { + throw new UserError(ExitCodes.DATA_ERROR, "Password mismatch"); } } - return new Passwd(terminal, username, password); - } - final String username; - 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); + Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE); Path file = FileUserPasswdStore.resolveFile(esusersSettings, env); + FileAttributesChecker attributesChecker = new FileAttributesChecker(file); Map users = new HashMap<>(FileUserPasswdStore.parseFile(file, null)); - if (!users.containsKey(username)) { - terminal.println(String.format(Locale.ROOT, "User [%s] doesn't exist", username)); - return ExitStatus.NO_USER; + if (users.containsKey(username) == false) { + throw new UserError(ExitCodes.NO_USER, "User [" + username + "] doesn't exist"); } Hasher hasher = Hasher.BCRYPT; - users.put(username, hasher.hash(passwd)); + users.put(username, hasher.hash(new SecuredString(password))); FileUserPasswdStore.writeFile(users, file); - return ExitStatus.OK; + attributesChecker.check(terminal); } } - - 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); - } + static class RolesCommand extends Command { 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) { - super(terminal); - this.username = username; - this.addRoles = addRoles; - this.removeRoles = removeRoles; + private final Environment env; + private final OptionSpec addOption; + private final OptionSpec removeOption; + private final OptionSpec arguments; + + 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 - protected Path[] pathsForPermissionsCheck(Settings settings, Environment env) { - Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE); - Path path = FileUserPasswdStore.resolveFile(esusersSettings, env); - return new Path[]{path}; + protected void printAdditionalHelp(Terminal terminal) { + terminal.println("The roles command allows to edit roles for an existing user."); + terminal.println("corresponding roles. Alternatively you can also list just a"); + terminal.println("single users roles, if you do not specify the -a or the -r parameter."); + terminal.println(""); } @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 // Nothing to add, just list the data for a username boolean readOnlyUserListing = removeRoles.length == 0 && addRoles.length == 0; if (readOnlyUserListing) { - return new ListUsersAndRoles(terminal, username).execute(settings, env); + listUsersAndRoles(terminal, env, username); + return; } // check for roles if they match String[] allRoles = ArrayUtils.concat(addRoles, removeRoles, String.class); for (String role : allRoles) { if (!ROLE_PATTERN.matcher(role).matches()) { - terminal.println(String.format(Locale.ROOT, "Role name [%s] is not valid. Please use lowercase and numbers only", - role)); - return ExitStatus.DATA_ERROR; + throw new UserError(ExitCodes.DATA_ERROR, + "Role name [" + role + "] is not valid. Please use lowercase and numbers only"); } } - 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 usersMap = FileUserPasswdStore.parseFile(path, null); + Map usersMap = FileUserPasswdStore.parseFile(usersFile, null); if (!usersMap.containsKey(username)) { - terminal.println(String.format(Locale.ROOT, "User [%s] doesn't exist", username)); - return ExitStatus.NO_USER; + throw new UserError(ExitCodes.NO_USER, "User [" + username + "] doesn't exist"); } - Path file = FileUserRolesStore.resolveFile(esusersSettings, env); - Map userRoles = FileUserRolesStore.parseFile(file, null); - + Map userRoles = FileUserRolesStore.parseFile(rolesFile, null); List roles = new ArrayList<>(); if (userRoles.get(username) != null) { roles.addAll(Arrays.asList(userRoles.get(username))); } - verifyRoles(terminal, settings, env, addRoles); + verifyRoles(terminal, env.settings(), env, addRoles); roles.addAll(Arrays.asList(addRoles)); roles.removeAll(Arrays.asList(removeRoles)); @@ -415,102 +371,101 @@ public class ESUsersTool extends CliTool { } else { 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 arguments; - private static final CliToolConfig.Cmd CMD = cmd(NAME, Useradd.class).build(); - - public static Command parse(Terminal terminal, CommandLine cli) { - String username = null; - 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; + ListCommand(Environment env) { + super("List existing users and their corresponding roles"); + this.env = env; + this.arguments = parser.nonOptions("username"); } @Override - public ExitStatus execute(Settings settings, Environment env) throws Exception { - Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE); - Set knownRoles = loadRoleNames(terminal, settings, env); - Path userRolesFilePath = FileUserRolesStore.resolveFile(esusersSettings, env); - Map userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null); - Path userFilePath = FileUserPasswdStore.resolveFile(esusersSettings, env); - Set users = FileUserPasswdStore.parseFile(userFilePath, null).keySet(); + protected void printAdditionalHelp(Terminal terminal) { + terminal.println(""); + terminal.println(""); + } - if (username != null) { - if (!users.contains(username)) { - terminal.println(String.format(Locale.ROOT, "User [%s] doesn't exist", username)); - return ExitStatus.NO_USER; - } + @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; + } + } - if (userRoles.containsKey(username)) { - String[] roles = userRoles.get(username); - Set unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles); - String[] markedRoles = markUnknownRoles(roles, unknownRoles); - terminal.println(String.format(Locale.ROOT, "%-15s: %s", username, Arrays.stream(markedRoles).map(s -> s == null ? - "-" : s).collect(Collectors.joining(",")))); - if (!unknownRoles.isEmpty()) { - // at least one role is marked... so printing the legend - Path rolesFile = FileRolesStore.resolveFile(esusersSettings, env).toAbsolutePath(); - terminal.println(""); - terminal.println(String.format(Locale.ROOT, " [*] An unknown role. Please check [%s] to see available roles", - rolesFile.toAbsolutePath())); - } - } else { - terminal.println(String.format(Locale.ROOT, "%-15s: -", username)); - } - } else { - boolean unknownRolesFound = false; - boolean usersExist = false; - for (Map.Entry entry : userRoles.entrySet()) { - String[] roles = entry.getValue(); - Set unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles); - String[] markedRoles = markUnknownRoles(roles, unknownRoles); - terminal.println(String.format(Locale.ROOT, "%-15s: %s", entry.getKey(), String.join(",", markedRoles))); - unknownRolesFound = unknownRolesFound || !unknownRoles.isEmpty(); - usersExist = true; - } - // list users without roles - Set usersWithoutRoles = Sets.newHashSet(users); - usersWithoutRoles.removeAll(userRoles.keySet()); - for (String user : usersWithoutRoles) { - terminal.println(String.format(Locale.ROOT, "%-15s: -", user)); - usersExist = true; - } + // pkg private for tests + static void listUsersAndRoles(Terminal terminal, Environment env, String username) throws Exception { + Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE); + Set knownRoles = loadRoleNames(terminal, env.settings(), env); + Path userRolesFilePath = FileUserRolesStore.resolveFile(esusersSettings, env); + Map userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null); + Path userFilePath = FileUserPasswdStore.resolveFile(esusersSettings, env); + Set users = FileUserPasswdStore.parseFile(userFilePath, null).keySet(); - if (!usersExist) { - terminal.println("No users found"); - return ExitStatus.OK; - } + if (username != null) { + if (!users.contains(username)) { + throw new UserError(ExitCodes.NO_USER, "User [" + username + "] doesn't exist"); + } - if (unknownRolesFound) { + if (userRoles.containsKey(username)) { + String[] roles = userRoles.get(username); + Set unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles); + String[] markedRoles = markUnknownRoles(roles, unknownRoles); + terminal.println(String.format(Locale.ROOT, "%-15s: %s", username, Arrays.stream(markedRoles).map(s -> s == null ? + "-" : s).collect(Collectors.joining(",")))); + if (!unknownRoles.isEmpty()) { // at least one role is marked... so printing the legend Path rolesFile = FileRolesStore.resolveFile(esusersSettings, env).toAbsolutePath(); terminal.println(""); - terminal.println(String.format(Locale.ROOT, " [*] An unknown role. Please check [%s] to see available roles", - rolesFile.toAbsolutePath())); + terminal.println(" [*] An unknown role. " + + "Please check [" + rolesFile.toAbsolutePath() + "] to see available roles"); } + } else { + terminal.println(String.format(Locale.ROOT, "%-15s: -", username)); + } + } else { + boolean unknownRolesFound = false; + boolean usersExist = false; + for (Map.Entry entry : userRoles.entrySet()) { + String[] roles = entry.getValue(); + Set unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles); + String[] markedRoles = markUnknownRoles(roles, unknownRoles); + terminal.println(String.format(Locale.ROOT, "%-15s: %s", entry.getKey(), String.join(",", markedRoles))); + unknownRolesFound = unknownRolesFound || !unknownRoles.isEmpty(); + usersExist = true; + } + // list users without roles + Set usersWithoutRoles = Sets.newHashSet(users); + usersWithoutRoles.removeAll(userRoles.keySet()); + for (String user : usersWithoutRoles) { + terminal.println(String.format(Locale.ROOT, "%-15s: -", user)); + usersExist = true; } - return ExitStatus.OK; + if (!usersExist) { + terminal.println("No users found"); + return; + } + + if (unknownRolesFound) { + // at least one role is marked... so printing the legend + Path rolesFile = FileRolesStore.resolveFile(esusersSettings, env).toAbsolutePath(); + terminal.println(""); + terminal.println(" [*] An unknown role. " + + "Please check [" + rolesFile.toAbsolutePath() + "] to see available roles"); + } } } diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/crypto/tool/SystemKeyTool.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/crypto/tool/SystemKeyTool.java index 054145bd41e..2b97e13c0b4 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/crypto/tool/SystemKeyTool.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/crypto/tool/SystemKeyTool.java @@ -65,7 +65,6 @@ public class SystemKeyTool extends Command { if (keyPath == null) { keyPath = InternalCryptoService.resolveSystemKey(env.settings(), env); } - FileAttributesChecker attributesChecker = new FileAttributesChecker(keyPath); // write the key 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, " + "permissions are set to owner read/write only"); } - - // check if attributes changed - attributesChecker.check(terminal); } } diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersToolTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersToolTests.java index 6d36b38dcf9..d754d1ce0f1 100644 --- a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersToolTests.java +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersToolTests.java @@ -5,16 +5,6 @@ */ 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.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -24,6 +14,16 @@ import java.util.Locale; import java.util.Map; 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.arrayContaining; 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.startsWith; -/** - * - */ -public class ESUsersToolTests extends CliToolTestCase { +public class ESUsersToolTests extends ESTestCase { public void testUseraddParseAllOptions() throws Exception { ESUsersTool tool = new ESUsersTool(); CliTool.Command command = tool.parse("useradd", args("username -p changeme -r r1,r2,r3"));