diff --git a/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/KeyPairGeneratorTool.java b/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/KeyPairGeneratorTool.java index 64bc8b85e84..701c7c602fe 100644 --- a/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/KeyPairGeneratorTool.java +++ b/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/KeyPairGeneratorTool.java @@ -17,7 +17,7 @@ import joptsimple.OptionSpec; import org.elasticsearch.cli.Command; import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.UserError; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; import static org.elasticsearch.license.core.CryptUtils.writeEncryptedPrivateKey; import static org.elasticsearch.license.core.CryptUtils.writeEncryptedPublicKey; @@ -49,11 +49,10 @@ public class KeyPairGeneratorTool extends Command { } @Override - protected int execute(Terminal terminal, OptionSet options) throws Exception { + protected void execute(Terminal terminal, OptionSet options) throws Exception { File publicKeyPath = publicKeyPathOption.value(options); File privateKeyPath = privateKeyPathOption.value(options); execute(terminal, publicKeyPath.toPath(), privateKeyPath.toPath()); - return ExitCodes.OK; } // pkg private for tests diff --git a/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java b/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java index 99ff97eb35d..c871314c055 100644 --- a/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java +++ b/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java @@ -13,7 +13,7 @@ import joptsimple.OptionSpec; import org.elasticsearch.cli.Command; import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.UserError; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -57,7 +57,7 @@ public class LicenseGeneratorTool extends Command { } @Override - protected int execute(Terminal terminal, OptionSet options) throws Exception { + protected void execute(Terminal terminal, OptionSet options) throws Exception { Path publicKeyPath = PathUtils.get(publicKeyPathOption.value(options)); Path privateKeyPath = PathUtils.get(privateKeyPathOption.value(options)); String licenseSpecString = null; @@ -69,7 +69,6 @@ public class LicenseGeneratorTool extends Command { licenseSpecPath = PathUtils.get(licenseFileOption.value(options)); } execute(terminal, publicKeyPath, privateKeyPath, licenseSpecString, licenseSpecPath); - return ExitCodes.OK; } // pkg private for testing diff --git a/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/LicenseVerificationTool.java b/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/LicenseVerificationTool.java index 6af0bc565f7..56702e5dc93 100644 --- a/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/LicenseVerificationTool.java +++ b/elasticsearch/license/licensor/src/main/java/org/elasticsearch/license/licensor/tools/LicenseVerificationTool.java @@ -5,35 +5,23 @@ */ package org.elasticsearch.license.licensor.tools; +import java.nio.file.Files; +import java.nio.file.Path; + 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.UserError; -import org.elasticsearch.common.SuppressForbidden; -import org.elasticsearch.common.cli.CliTool; -import org.elasticsearch.common.cli.CliToolConfig; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; import org.elasticsearch.common.io.PathUtils; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.env.Environment; import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.LicenseVerifier; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd; -import static org.elasticsearch.common.cli.CliToolConfig.Builder.option; -import static org.elasticsearch.common.cli.CliToolConfig.config; - public class LicenseVerificationTool extends Command { private final OptionSpec publicKeyPathOption; @@ -57,7 +45,7 @@ public class LicenseVerificationTool extends Command { } @Override - protected int execute(Terminal terminal, OptionSet options) throws Exception { + protected void execute(Terminal terminal, OptionSet options) throws Exception { Path publicKeyPath = PathUtils.get(publicKeyPathOption.value(options)); String licenseSpecString = null; if (options.has(licenseOption)) { @@ -68,7 +56,6 @@ public class LicenseVerificationTool extends Command { licenseSpecPath = PathUtils.get(licenseFileOption.value(options)); } execute(terminal, publicKeyPath, licenseSpecString, licenseSpecPath); - return ExitCodes.OK; } // pkg private for tests diff --git a/elasticsearch/license/licensor/src/main/resources/org/elasticsearch/license/licensor/tools/licensor-key-pair-generator.help b/elasticsearch/license/licensor/src/main/resources/org/elasticsearch/license/licensor/tools/licensor-key-pair-generator.help deleted file mode 100644 index c41ed5c0ed7..00000000000 --- a/elasticsearch/license/licensor/src/main/resources/org/elasticsearch/license/licensor/tools/licensor-key-pair-generator.help +++ /dev/null @@ -1,22 +0,0 @@ -NAME - - key-pair-generator - generates a key pair with RSA 2048-bit security - -SYNOPSIS - - key-pair-generator -pub publicKeyPath -pri privateKeyPath - -DESCRIPTION - - This tool generates and saves a key pair to the provided publicKeyPath - and privateKeyPath. The tool checks the existence of the provided key paths - and will not override if any existing keys are found. - -OPTIONS - - -h,--help Shows this message - - -pub,--publicKeyPath Save the generated public key to path - - -pri,--privateKeyPath Save the generated private key to path - diff --git a/elasticsearch/license/licensor/src/main/resources/org/elasticsearch/license/licensor/tools/licensor-license-generator.help b/elasticsearch/license/licensor/src/main/resources/org/elasticsearch/license/licensor/tools/licensor-license-generator.help deleted file mode 100644 index 75d507f7dd9..00000000000 --- a/elasticsearch/license/licensor/src/main/resources/org/elasticsearch/license/licensor/tools/licensor-license-generator.help +++ /dev/null @@ -1,26 +0,0 @@ -NAME - - license-generator - generates signed elasticsearch license(s) for a given license spec(s) - -SYNOPSIS - - license-generator -l licenseSpec -pub publicKeyPath -pri privateKeyPath - -DESCRIPTION - - This tool generate elasticsearch license(s) for the provided license spec(s). The tool - can take arbitrary number of `--license` and/or `--licenseFile` to generate corrosponding - signed license(s). - -OPTIONS - - -h,--help Shows this message - - -l,--license License spec to generate a signed license from - - -lf,--licenseFile Path to a license spec file - - -pub,--publicKeyPath Path to public key to be used - - -pri,--privateKeyPath Path to private key to be used - diff --git a/elasticsearch/license/licensor/src/main/resources/org/elasticsearch/license/licensor/tools/licensor-verify-license.help b/elasticsearch/license/licensor/src/main/resources/org/elasticsearch/license/licensor/tools/licensor-verify-license.help deleted file mode 100644 index e36007f3838..00000000000 --- a/elasticsearch/license/licensor/src/main/resources/org/elasticsearch/license/licensor/tools/licensor-verify-license.help +++ /dev/null @@ -1,28 +0,0 @@ -NAME - - verify-license - verifies the integrity of elasticsearch signed license(s) - -SYNOPSIS - - verify-license -l signedLicense -pub publicKeyPath - -DESCRIPTION - - This tool assumes the configured public key to be the same as that of the production license plugin public key. - The tool can take arbitrary number of `--license` and/or `--licenseFile` for verifying signed license(s). If any - of the provided license(s) are invalid, the tool will error out, otherwise it will output a effective licenses file. - - Effective Licenses: - A set of licenses that only has one effective sub-license for every feature provided through the input license file. - Where effective sub-licenses are identified as the sub-licenses with the latest `expiry_date` for a `feature` - and the sub-license has not already expired. - -OPTIONS - - -h,--help Shows this message - - -l,--license signed license(s) string - - -lf,--licenseFile Path to signed license(s) file - - -pub,--publicKeyPath Path to public key to verify against diff --git a/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/KeyPairGenerationToolTests.java b/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/KeyPairGenerationToolTests.java index 348c3b58279..12ee2791293 100644 --- a/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/KeyPairGenerationToolTests.java +++ b/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/KeyPairGenerationToolTests.java @@ -10,7 +10,7 @@ import java.nio.file.Path; import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.UserError; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; import org.elasticsearch.test.ESTestCase; import static org.hamcrest.CoreMatchers.containsString; diff --git a/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/LicenseGenerationToolTests.java b/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/LicenseGenerationToolTests.java index 2e75d17e28a..ddad9c99f28 100644 --- a/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/LicenseGenerationToolTests.java +++ b/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/LicenseGenerationToolTests.java @@ -12,7 +12,7 @@ import java.nio.file.Path; import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.UserError; import org.elasticsearch.cli.MockTerminal; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; import org.elasticsearch.license.core.License; import org.elasticsearch.license.licensor.TestUtils; import org.elasticsearch.test.ESTestCase; diff --git a/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/LicenseVerificationToolTests.java b/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/LicenseVerificationToolTests.java index dd99ac9b410..8318c7ea541 100644 --- a/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/LicenseVerificationToolTests.java +++ b/elasticsearch/license/licensor/src/test/java/org/elasticsearch/license/licensor/tools/LicenseVerificationToolTests.java @@ -11,7 +11,7 @@ import java.nio.file.Path; import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.UserError; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.license.core.License; import org.elasticsearch.license.licensor.TestUtils; 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 fce937f563c..e8f6a07a3a9 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,19 +5,27 @@ */ package org.elasticsearch.shield.authc.esusers.tool; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + 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.cli.CliTool; -import org.elasticsearch.common.cli.CliToolConfig; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; 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; @@ -31,21 +39,6 @@ import org.elasticsearch.shield.authz.store.FileRolesStore; import org.elasticsearch.shield.support.FileAttributesChecker; import org.elasticsearch.shield.support.Validation; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd; - public class ESUsersTool extends MultiCommand { public static void main(String[] args) throws Exception { @@ -93,47 +86,19 @@ public class ESUsersTool extends MultiCommand { } @Override - protected int execute(Terminal terminal, OptionSet options) throws Exception { - String username = arguments.value(options); - String passwordStr = passwordOption.value(options); - String rolesCsv = rolesOption.value(options); - + protected void execute(Terminal terminal, OptionSet options) throws Exception { + String username = parseUsername(arguments.values(options)); Validation.Error validationError = Validation.ESUsers.validateUsername(username); if (validationError != null) { throw new UserError(ExitCodes.DATA_ERROR, "Invalid username [" + username + "]... " + validationError); } - char[] password; - if (passwordStr != null) { - password = passwordStr.toCharArray(); - validationError = Validation.ESUsers.validatePassword(password); - if (validationError != null) { - throw new UserError(ExitCodes.DATA_ERROR, "Invalid password..." + validationError); - } - } else { - password = terminal.readSecret("Enter new password: "); - 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) == false) { - throw new UserError(ExitCodes.DATA_ERROR, "Password mismatch"); - } - } - - String[] roles = rolesCsv.split(","); - for (String role : roles) { - validationError = Validation.Roles.validateRoleName(role); - if (validationError != null) { - throw new UserError(ExitCodes.DATA_ERROR, "Invalid role [" + role + "]... " + validationError); - } - } + char[] password = parsePassword(terminal, passwordOption.value(options)); + String[] roles = parseRoles(terminal, env, rolesOption.value(options)); 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); Map users = new HashMap<>(FileUserPasswdStore.parseFile(passwordFile, null)); @@ -151,7 +116,6 @@ public class ESUsersTool extends MultiCommand { } attributesChecker.check(terminal); - return ExitCodes.OK; } } @@ -178,14 +142,8 @@ public class ESUsersTool extends MultiCommand { } @Override - protected int execute(Terminal terminal, OptionSet options) throws Exception { - String username = arguments.value(options); - execute(terminal, username); - return ExitCodes.OK; - } - - // pkg private for testing - void execute(Terminal terminal, String username) throws Exception { + protected void execute(Terminal terminal, OptionSet options) throws Exception { + String username = parseUsername(arguments.values(options)); Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE); Path passwordFile = FileUserPasswdStore.resolveFile(esusersSettings, env); Path rolesFile = FileUserRolesStore.resolveFile(esusersSettings, env); @@ -241,33 +199,9 @@ public class ESUsersTool extends MultiCommand { } @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; - 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) == false) { - throw new UserError(ExitCodes.DATA_ERROR, "Password mismatch"); - } - } + protected void execute(Terminal terminal, OptionSet options) throws Exception { + String username = parseUsername(arguments.values(options)); + char[] password = parsePassword(terminal, passwordOption.value(options)); Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE); Path file = FileUserPasswdStore.resolveFile(esusersSettings, env); @@ -276,17 +210,15 @@ public class ESUsersTool extends MultiCommand { 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(new SecuredString(password))); + users.put(username, Hasher.BCRYPT.hash(new SecuredString(password))); FileUserPasswdStore.writeFile(users, file); + attributesChecker.check(terminal); } } static class RolesCommand extends Command { - public static final Pattern ROLE_PATTERN = Pattern.compile("[\\w@-]+"); - private final Environment env; private final OptionSpec addOption; private final OptionSpec removeOption; @@ -313,17 +245,11 @@ public class ESUsersTool extends MultiCommand { } @Override - 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; - } + protected void execute(Terminal terminal, OptionSet options) throws Exception { + String username = parseUsername(arguments.values(options)); + String[] addRoles = parseRoles(terminal, env, addOption.value(options)); + String[] removeRoles = parseRoles(terminal, env, removeOption.value(options)); - 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; @@ -332,15 +258,6 @@ public class ESUsersTool extends MultiCommand { 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()) { - throw new UserError(ExitCodes.DATA_ERROR, - "Role name [" + role + "] is not valid. Please use lowercase and numbers only"); - } - } - Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE); Path usersFile = FileUserPasswdStore.resolveFile(esusersSettings, env); Path rolesFile = FileUserRolesStore.resolveFile(esusersSettings, env); @@ -356,7 +273,6 @@ public class ESUsersTool extends MultiCommand { if (userRoles.get(username) != null) { roles.addAll(Arrays.asList(userRoles.get(username))); } - verifyRoles(terminal, env.settings(), env, addRoles); roles.addAll(Arrays.asList(addRoles)); roles.removeAll(Arrays.asList(removeRoles)); @@ -391,21 +307,20 @@ public class ESUsersTool extends MultiCommand { } @Override - protected int execute(Terminal terminal, OptionSet options) throws Exception { + protected void 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 knownRoles = loadRoleNames(terminal, env.settings(), env); Path userRolesFilePath = FileUserRolesStore.resolveFile(esusersSettings, env); + Set knownRoles = FileRolesStore.parseFileForRoleNames(userRolesFilePath, null); Map userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null); Path userFilePath = FileUserPasswdStore.resolveFile(esusersSettings, env); Set users = FileUserPasswdStore.parseFile(userFilePath, null).keySet(); @@ -465,18 +380,6 @@ public class ESUsersTool extends MultiCommand { } } - private static Set loadRoleNames(Terminal terminal, Settings settings, Environment env) { - Path rolesFile = FileRolesStore.resolveFile(settings, env); - try { - return FileRolesStore.parseFileForRoleNames(rolesFile, null); - } catch (Throwable t) { - // if for some reason, parsing fails (malformatted perhaps) we just warn - terminal.println(String.format(Locale.ROOT, "Warning: Could not parse [%s] for roles verification. Please revise and fix it." + - " Nonetheless, the user will still be associated with all specified roles", rolesFile.toAbsolutePath())); - } - return null; - } - private static String[] markUnknownRoles(String[] roles, Set unknownRoles) { if (unknownRoles.isEmpty()) { return roles; @@ -492,14 +395,73 @@ public class ESUsersTool extends MultiCommand { return marked; } + // pkg private for testing + static String parseUsername(List args) throws UserError { + if (args.isEmpty()) { + throw new UserError(ExitCodes.USAGE, "Missing username argument"); + } else if (args.size() > 1) { + throw new UserError(ExitCodes.USAGE, "Expected a single username argument, found extra: " + args.toString()); + } + String username = args.get(0); + Validation.Error validationError = Validation.ESUsers.validateUsername(username); + if (validationError != null) { + throw new UserError(ExitCodes.DATA_ERROR, "Invalid username [" + username + "]... " + validationError); + } + return username; + } + + // pkg private for testing + static char[] parsePassword(Terminal terminal, String passwordStr) throws UserError { + char[] 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) == false) { + throw new UserError(ExitCodes.DATA_ERROR, "Password mismatch"); + } + } + return password; + } + private static void verifyRoles(Terminal terminal, Settings settings, Environment env, String[] roles) { - Set knownRoles = loadRoleNames(terminal, settings, env); + Path rolesFile = FileRolesStore.resolveFile(settings, env); + assert Files.exists(rolesFile); + Set knownRoles = FileRolesStore.parseFileForRoleNames(rolesFile, null); Set unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles); if (!unknownRoles.isEmpty()) { - Path rolesFile = FileRolesStore.resolveFile(settings, env); terminal.println(String.format(Locale.ROOT, "Warning: The following roles [%s] are unknown. Make sure to add them to the [%s]" + " file. Nonetheless the user will still be associated with all specified roles", - Strings.collectionToCommaDelimitedString(unknownRoles), rolesFile.toAbsolutePath())); + Strings.collectionToCommaDelimitedString(unknownRoles), rolesFile.toAbsolutePath())); + terminal.println("Known roles: " + knownRoles.toString()); } } + + // pkg private for testing + static String[] parseRoles(Terminal terminal, Environment env, String rolesStr) throws UserError { + if (rolesStr.isEmpty()) { + return Strings.EMPTY_ARRAY; + } + String[] roles = rolesStr.split(","); + for (String role : roles) { + Validation.Error validationError = Validation.Roles.validateRoleName(role); + if (validationError != null) { + throw new UserError(ExitCodes.DATA_ERROR, "Invalid role [" + role + "]... " + validationError); + } + } + + Settings esusersSettings = Realms.internalRealmSettings(env.settings(), ESUsersRealm.TYPE); + verifyRoles(terminal, esusersSettings, env, roles); + + return 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 2b97e13c0b4..9a194eeb8d8 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 @@ -19,14 +19,13 @@ import joptsimple.OptionSpec; import org.elasticsearch.cli.Command; import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.UserError; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.env.Environment; import org.elasticsearch.node.internal.InternalSettingsPreparer; import org.elasticsearch.shield.crypto.InternalCryptoService; -import org.elasticsearch.shield.support.FileAttributesChecker; public class SystemKeyTool extends Command { @@ -48,7 +47,7 @@ public class SystemKeyTool extends Command { } @Override - protected int execute(Terminal terminal, OptionSet options) throws Exception { + protected void execute(Terminal terminal, OptionSet options) throws Exception { Path keyPath = null; List args = arguments.values(options); if (args.size() > 1) { @@ -57,7 +56,6 @@ public class SystemKeyTool extends Command { keyPath = PathUtils.get(args.get(0)); } execute(terminal, keyPath); - return ExitCodes.OK; } // pkg private for tests diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/support/FileAttributesChecker.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/support/FileAttributesChecker.java index 568c23b700f..3a4b1b88195 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/support/FileAttributesChecker.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/support/FileAttributesChecker.java @@ -12,7 +12,7 @@ import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermissions; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; /** * A utility for cli tools to capture file attributes diff --git a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-list.help b/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-list.help deleted file mode 100644 index 97061c10ffc..00000000000 --- a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-list.help +++ /dev/null @@ -1,17 +0,0 @@ -NAME - - list - List existing users and their corresponding roles - -SYNOPSIS - - esusers list - -DESCRIPTION - - The list command allows to list all existing users and their - corresponding roles. Alternatively you can also list just a - single user. - -OPTIONS - - -h,--help Shows this message diff --git a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-passwd.help b/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-passwd.help deleted file mode 100644 index e94204fed28..00000000000 --- a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-passwd.help +++ /dev/null @@ -1,22 +0,0 @@ -NAME - - passwd - Changes the password of an existing native user - -SYNOPSIS - - esusers passwd [-p ] - -DESCRIPTION - - The passwd command changes passwords for native user accounts. The tool - prompts twice for a replacement password. The second entry is compared - against the first and both are required to match in order for the - password to be changed. If non-default users file is used (a different - file location is configured in elasticsearch.yml) the appropriate file - will be resolved from the settings. - -OPTIONS - - -h,--help Shows this message - - -p,--password The new password for the user \ No newline at end of file diff --git a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-roles.help b/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-roles.help deleted file mode 100644 index 05d9fe3fa45..00000000000 --- a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-roles.help +++ /dev/null @@ -1,21 +0,0 @@ -NAME - - roles - Edit roles of an existing user - -SYNOPSIS - - esusers roles [-a roles] [-r roles] - -DESCRIPTION - - The roles command allows to edit roles for an existing user. - corresponding roles. Alternatively you can also list just a - single users roles, if you do not specify the -a or the -r parameter. - -OPTIONS - - -h,--help Shows this message - - -a,--add Adds supplied roles for the specified user - - -r,--remove Adds supplied roles for the specified user diff --git a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-useradd.help b/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-useradd.help deleted file mode 100644 index 67e620a713c..00000000000 --- a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-useradd.help +++ /dev/null @@ -1,28 +0,0 @@ -NAME - - useradd - Adds a native user - -SYNOPSIS - - esusers useradd [-p ] [-r ] - -DESCRIPTION - - Adds a native user to elasticsearch (via internal realm). The user will - be added to the users file and its roles will be added to the - users_roles file. If non-default files are used (different file - locations are configured in elasticsearch.yml) the appropriate files - will be resolved from the settings and the user and its roles will be - added to them. - -OPTIONS - - -h,--help Shows this message - - -p,--password The user password - - -r,--roles Comma-separated list of the roles of the user - -SEE ALSO - - [1] esusers userdel diff --git a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-userdel.help b/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-userdel.help deleted file mode 100644 index 15d556ed076..00000000000 --- a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers-userdel.help +++ /dev/null @@ -1,24 +0,0 @@ -NAME - - userdel - Delete an existing native user - -SYNOPSIS - - esusers userdel - -DESCRIPTION - - Removes an existing native user from elasticsearch. The user will be - removed from the users file and its roles will be removed to the - users_roles file. If non-default files are used (different file - locations are configured in elasticsearch.yml) the appropriate files - will be resolved from the settings and the user and its roles will be - removed to them. - -OPTIONS - - -h,--help Shows this message - -SEE ALSO - - [1] esusers useradd \ No newline at end of file diff --git a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers.help b/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers.help deleted file mode 100644 index b29ede99b7b..00000000000 --- a/elasticsearch/x-pack/shield/src/main/resources/org/elasticsearch/shield/authc/esusers/tool/esusers.help +++ /dev/null @@ -1,30 +0,0 @@ -NAME - - esusers - Manages elasticsearch native users - -SYNOPSIS - - esusers - -DESCRIPTION - - This tool manages all native security aspects in elasticsearch, saving - the administrator from needing to modify security related fields manually. - This tool provides several commands for different security management - tasks - -COMMANDS - - passwd Changes passwords for a native user - - useradd Adds a new native user to the system - - userdel Removes an existing native user from the system - - roles Manage roles of a single user - - list List users and roles - -NOTES - - [*] For usage help on specific commands please type "esusers -h" 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 415fd0fdd26..5abf9d00aad 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 @@ -7,51 +7,84 @@ package org.elasticsearch.shield.authc.esusers.tool; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; +import java.util.Objects; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import org.apache.lucene.util.IOUtils; import org.elasticsearch.cli.Command; import org.elasticsearch.cli.CommandTestCase; import org.elasticsearch.cli.ExitCodes; -import org.elasticsearch.cli.MockTerminal; import org.elasticsearch.cli.UserError; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.cli.CliTool; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.common.io.PathUtilsForTesting; 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.SecuredString; import org.elasticsearch.shield.authc.support.SecuredStringTests; +import org.junit.AfterClass; import org.junit.Before; - -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.arrayContaining; -import static org.hamcrest.Matchers.arrayContainingInAnyOrder; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.startsWith; +import org.junit.BeforeClass; public class ESUsersToolTests extends CommandTestCase { + // the mock filesystem we use so permissions/users/groups can be modified + static FileSystem jimfs; + + // the config dir for each test to use + Path confDir; + // settings used to create an Environment for tools Settings.Builder settingsBuilder; + + @BeforeClass + public static void setupJimfs() throws IOException { + String view = randomFrom("basic", "posix"); + Configuration conf = Configuration.unix().toBuilder().setAttributeViews(view).build(); + jimfs = Jimfs.newFileSystem(conf); + PathUtilsForTesting.installMock(jimfs); + } + @Before - public void resetSettings() { - // TODO: use jimfs so we can run without SM... - settingsBuilder = Settings.builder().put("path.home", createTempDir()); + public void setupHome() throws IOException { + Path homeDir = jimfs.getPath("eshome"); + IOUtils.rm(homeDir); + confDir = homeDir.resolve("config").resolve("xpack"); + Files.createDirectories(confDir); + Files.write(confDir.resolve("users"), Arrays.asList( + "existing_user:" + new String(Hasher.BCRYPT.hash(new SecuredString("changeme".toCharArray()))), + "existing_user2:" + new String(Hasher.BCRYPT.hash(new SecuredString("changeme2".toCharArray()))), + "existing_user3:" + new String(Hasher.BCRYPT.hash(new SecuredString("changeme3".toCharArray()))) + ), StandardCharsets.UTF_8); + Files.write(confDir.resolve("users_roles"), Arrays.asList( + "test_admin:existing_user,existing_user2", + "test_r1:existing_user2" + ), StandardCharsets.UTF_8); + Files.write(confDir.resolve("roles.yml"), Arrays.asList( + "test_admin:", + " cluster: all", + "test_r1:", + " cluster: all", + "test_r2:", + " cluster: all" + ), StandardCharsets.UTF_8); + settingsBuilder = Settings.builder() + .put("path.home", homeDir) + .put("shield.authc.realms.esusers.type", "esusers"); + } + + @AfterClass + public static void closeJimfs() throws IOException { + if (jimfs != null) { + jimfs.close(); + } } @Override @@ -59,799 +92,294 @@ public class ESUsersToolTests extends CommandTestCase { return new ESUsersTool(new Environment(settingsBuilder.build())); } - public void testUseraddInvalidUsername() throws Exception { + /** checks the user exists with the given password */ + void assertUser(String username, String password) throws IOException { + List lines = Files.readAllLines(confDir.resolve("users"), StandardCharsets.UTF_8); + for (String line : lines) { + String[] usernameHash = line.split(":", 2); + if (usernameHash.length != 2) { + fail("Corrupted users file, line: " + line); + } + if (username.equals(usernameHash[0]) == false) { + continue; + } + String gotHash = usernameHash[1]; + SecuredString expectedHash = SecuredStringTests.build(password); + assertTrue("Expected hash " + expectedHash + " for password " + password + " but got " + gotHash, + Hasher.BCRYPT.verify(expectedHash, gotHash.toCharArray())); + return; + } + fail("Could not find username " + username + " in users file:\n" + lines.toString()); + } + + /** Checks the user does not exist in the users or users_roles files*/ + void assertNoUser(String username) throws IOException { + List lines = Files.readAllLines(confDir.resolve("users"), StandardCharsets.UTF_8); + for (String line : lines) { + String[] usernameHash = line.split(":", 2); + if (usernameHash.length != 2) { + fail("Corrupted users file, line: " + line); + } + assertNotEquals(username, usernameHash[0]); + } + lines = Files.readAllLines(confDir.resolve("users_roles"), StandardCharsets.UTF_8); + for (String line : lines) { + String[] roleUsers = line.split(":", 2); + if (roleUsers.length != 2) { + fail("Corrupted users_roles file, line: " + line); + } + String[] users = roleUsers[1].split(","); + for (String user : users) { + assertNotEquals(user, username); + } + } + + } + + /** checks the role has the given users, or that the role does not exist if not users are passed. */ + void assertRole(String role, String... users) throws IOException { + List lines = Files.readAllLines(confDir.resolve("users_roles"), StandardCharsets.UTF_8); + for (String line : lines) { + String[] roleUsers = line.split(":", 2); + if (roleUsers.length != 2) { + fail("Corrupted users_roles file, line: " + line); + } + if (role.equals(roleUsers[0]) == false) { + continue; + } + if (users.length == 0) { + fail("Found role " + role + " in users_roles file with users [" + roleUsers[1] + "]"); + } + List gotUsers = Arrays.asList(roleUsers[1].split(",")); + for (String user : users) { + if (gotUsers.contains(user) == false) { + fail("Expected users [" + Arrays.toString(users) + "] for role " + role + + " but found [" + gotUsers.toString() + "]"); + } + } + return; + } + if (users.length != 0) { + fail("Could not find role " + role + " in users_roles file:\n" + lines.toString()); + } + } + + public void testParseInvalidUsername() throws Exception { UserError e = expectThrows(UserError.class, () -> { - execute("useradd", "$34dkl", "-p", "changeme", "-r", "r1"); + ESUsersTool.parseUsername(Collections.singletonList("$34dkl")); }); assertEquals(ExitCodes.DATA_ERROR, e.exitCode); assertTrue(e.getMessage(), e.getMessage().contains("Invalid username")); } - public void testUseraddInvalidRoleName() throws Exception { + public void testParseUsernameMissing() throws Exception { UserError e = expectThrows(UserError.class, () -> { - execute("useradd", "username", "-p", "changeme", "-r", "$343"); + ESUsersTool.parseUsername(Collections.emptyList()); }); - assertEquals(ExitCodes.DATA_ERROR, e.exitCode); - assertTrue(e.getMessage(), e.getMessage().contains("Invalid role")); + assertEquals(ExitCodes.USAGE, e.exitCode); + assertTrue(e.getMessage(), e.getMessage().contains("Missing username argument")); } - public void testUseraddInvalidPassword() throws Exception { + public void testParseUsernameExtraArgs() throws Exception { UserError e = expectThrows(UserError.class, () -> { - execute("useradd", "username", "-p", "123", "-r", "r1"); + ESUsersTool.parseUsername(Arrays.asList("username", "extra")); + }); + assertEquals(ExitCodes.USAGE, e.exitCode); + assertTrue(e.getMessage(), e.getMessage().contains("Expected a single username argument")); + } + + public void testParseInvalidPasswordOption() throws Exception { + UserError e = expectThrows(UserError.class, () -> { + ESUsersTool.parsePassword(terminal, "123"); }); assertEquals(ExitCodes.DATA_ERROR, e.exitCode); assertTrue(e.getMessage(), e.getMessage().contains("Invalid password")); } - public void testUseraddNoUsername() throws Exception { + public void testParseInvalidPasswordInput() throws Exception { + terminal.addSecretInput("123"); UserError e = expectThrows(UserError.class, () -> { - execute("useradd"); + ESUsersTool.parsePassword(terminal, null); }); - assertEquals(ExitCodes.USAGE, e.exitCode); - assertTrue(e.getMessage(), e.getMessage().contains("TBD")); + assertEquals(ExitCodes.DATA_ERROR, e.exitCode); + assertTrue(e.getMessage(), e.getMessage().contains("Invalid password")); } - public void testUseraddParseNoPassword() throws Exception { + public void testParseMismatchPasswordInput() throws Exception { + terminal.addSecretInput("password1"); + terminal.addSecretInput("password2"); + UserError e = expectThrows(UserError.class, () -> { + ESUsersTool.parsePassword(terminal, null); + }); + assertEquals(ExitCodes.DATA_ERROR, e.exitCode); + assertTrue(e.getMessage(), e.getMessage().contains("Password mismatch")); + } + + public void testParseUnknownRole() throws Exception { + ESUsersTool.parseRoles(terminal, new Environment(settingsBuilder.build()), "test_r1,r2,r3"); + String output = terminal.getOutput(); + assertTrue(output, output.contains("The following roles [r2,r3] are unknown")); + } + + public void testParseInvalidRole() throws Exception { + UserError e = expectThrows(UserError.class, () -> { + ESUsersTool.parseRoles(terminal, new Environment(settingsBuilder.build()), "$345"); + }); + assertEquals(ExitCodes.DATA_ERROR, e.exitCode); + assertTrue(e.getMessage(), e.getMessage().contains("Invalid role [$345]")); + } + + public void testParseMultipleRoles() throws Exception { + String[] roles = ESUsersTool.parseRoles(terminal, new Environment(settingsBuilder.build()), "test_r1,test_r2"); + assertEquals(Objects.toString(roles), 2, roles.length); + assertEquals("test_r1", roles[0]); + assertEquals("test_r2", roles[1]); + } + + public void testUseraddNoPassword() throws Exception { + terminal.addSecretInput("changeme"); terminal.addSecretInput("changeme"); execute("useradd", "username"); assertUser("username", "changeme"); } - public void testUseraddCmdCreate() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path userFile = createTempFile(); - Path userRolesFile = createTempFile(); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("shield.authc.realms.esusers.files.users_roles", userRolesFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new MockTerminal(), "user1", SecuredStringTests.build("changeme"), "r1", "r2"); - - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.OK)); - - assertFileExists(userFile); - List lines = Files.readAllLines(userFile, StandardCharsets.UTF_8); - assertThat(lines.size(), is(1)); - // we can't just hash again and compare the lines, as every time we hash a new salt is generated - // instead we'll just verify the generated hash against the correct password. - String line = lines.get(0); - assertThat(line, startsWith("user1:")); - String hash = line.substring("user1:".length()); - assertThat(Hasher.BCRYPT.verify(SecuredStringTests.build("changeme"), hash.toCharArray()), is(true)); - - assertFileExists(userRolesFile); - lines = Files.readAllLines(userRolesFile, StandardCharsets.UTF_8); - assertThat(lines, hasSize(2)); - assertThat(lines, containsInAnyOrder("r1:user1", "r2:user1")); + public void testUseraddPasswordOption() throws Exception { + execute("useradd", "username", "-p", "changeme"); + assertUser("username", "changeme"); } - public void testUseraddCmdAppend() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path userFile = writeFile("user2:hash2"); - Path userRolesFile = writeFile("r3:user2\nr4:user2"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("shield.authc.realms.esusers.files.users_roles", userRolesFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new MockTerminal(), "user1", SecuredStringTests.build("changeme"), "r1", "r2"); - - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.OK)); - - assertFileExists(userFile); - List lines = Files.readAllLines(userFile, StandardCharsets.UTF_8); - assertThat(lines, hasSize(2)); - assertThat(lines, hasItem("user2:hash2")); - assertThat(lines, hasItem(startsWith("user1:"))); - - // we can't just hash again and compare the lines, as every time we hash a new salt is generated - // instead we'll just verify the generated hash against the correct password. - for (String line : lines) { - if (line.startsWith("user1")) { - String hash = line.substring("user1:".length()); - assertThat(Hasher.BCRYPT.verify(SecuredStringTests.build("changeme"), hash.toCharArray()), is(true)); - } - } - - assertFileExists(userRolesFile); - lines = Files.readAllLines(userRolesFile, StandardCharsets.UTF_8); - assertThat(lines, hasSize(4)); - assertThat(lines, containsInAnyOrder("r1:user1", "r2:user1", "r3:user2", "r4:user2")); + public void testUseraddUserExists() throws Exception { + UserError e = expectThrows(UserError.class, () -> { + execute("useradd", "existing_user", "-p", "changeme"); + }); + assertEquals(ExitCodes.CODE_ERROR, e.exitCode); + assertEquals("User [existing_user] already exists", e.getMessage()); } - public void testUseraddCmdAddingUserWithoutRolesDoesNotAddEmptyRole() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path userFile = writeFile("user2:hash2"); - Path userRolesFile = writeFile("r3:user2\nr4:user2"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("shield.authc.realms.esusers.files.users_roles", userRolesFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new MockTerminal(), "user1", SecuredStringTests.build("changeme")); - - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.OK)); - - assertFileExists(userRolesFile); - List lines = Files.readAllLines(userRolesFile, StandardCharsets.UTF_8); - assertThat(lines, hasSize(2)); - assertThat(lines, not(hasItem(containsString("user1")))); + public void testUseraddNoRoles() throws Exception { + Files.delete(confDir.resolve("users_roles")); + Files.createFile(confDir.resolve("users_roles")); + execute("useradd", "username", "-p", "changeme"); + List lines = Files.readAllLines(confDir.resolve("users_roles"), StandardCharsets.UTF_8); + assertTrue(lines.toString(), lines.isEmpty()); } - public void testUseraddCmdAppendUserAlreadyExists() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path userFile = writeFile("user1:hash1"); - Path userRolesFile = createTempFile(); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("shield.authc.realms.esusers.files.users_roles", userRolesFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(new MockTerminal(), "user1", SecuredStringTests.build("changeme"), "r1", "r2"); - - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.CODE_ERROR)); + public void testUserdelUnknownUser() throws Exception { + UserError e = expectThrows(UserError.class, () -> { + execute("userdel", "unknown"); + }); + assertEquals(ExitCodes.NO_USER, e.exitCode); + assertTrue(e.getMessage(), e.getMessage().contains("User [unknown] doesn't exist")); } - public void testUseraddCustomRole() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path usersFile = createTempFile(); - Path userRolesFile = createTempFile(); - Path rolesFile = writeFile("plugin_admin:\n" + - " manage_plugin"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", userRolesFile) - .put("shield.authz.store.files.roles", rolesFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal terminal = new MockTerminal(); - ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(terminal, "user1", SecuredStringTests.build("changeme"), "plugin_admin"); - - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.OK)); - assertTrue(terminal.getOutput(), terminal.getOutput().isEmpty()); + public void testUserdel() throws Exception { + execute("userdel", "existing_user"); + assertNoUser("existing_user"); } - public void testUseraddNonExistantRole() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path usersFile = createTempFile(); - Path userRolesFile = createTempFile(); - Path rolesFile = writeFile("plugin_admin:\n" + - " manage_plugin"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", userRolesFile) - .put("shield.authz.store.files.roles", rolesFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal terminal = new MockTerminal(); - ESUsersTool.Useradd cmd = new ESUsersTool.Useradd(terminal, "user1", SecuredStringTests.build("changeme"), "plugin_admin_2"); - - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.OK)); - assertThat(terminal.getOutput(), containsString("[plugin_admin_2]")); + public void testPasswdUnknownUser() throws Exception { + UserError e = expectThrows(UserError.class, () -> { + execute("passwd", "unknown", "-p", "changeme"); + }); + assertEquals(ExitCodes.NO_USER, e.exitCode); + assertTrue(e.getMessage(), e.getMessage().contains("User [unknown] doesn't exist")); } - public void testUserdelParse() throws Exception { - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("userdel", args("username")); - assertThat(command, instanceOf(ESUsersTool.Userdel.class)); - ESUsersTool.Userdel userdel = (ESUsersTool.Userdel) command; - assertThat(userdel.username, equalTo("username")); + public void testPasswdNoPasswordOption() throws Exception { + terminal.addSecretInput("newpassword"); + terminal.addSecretInput("newpassword"); + execute("passwd", "existing_user"); + assertUser("existing_user", "newpassword"); + assertRole("test_admin", "existing_user", "existing_user2"); // roles unchanged } - public void testUserdelParseMissingUsername() throws Exception { - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("userdel", args(null)); - assertThat(command, instanceOf(ESUsersTool.Command.Exit.class)); - ESUsersTool.Command.Exit exit = (ESUsersTool.Command.Exit) command; - assertThat(exit.status(), equalTo(CliTool.ExitStatus.USAGE)); + public void testPasswd() throws Exception { + execute("passwd", "existing_user", "-p", "newpassword"); + assertUser("existing_user", "newpassword"); + assertRole("test_admin", "existing_user"); // roles unchanged } - public void testUserdelParseExtraArgs() throws Exception { - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("userdel", args("user1 user2")); - assertThat(command, instanceOf(ESUsersTool.Command.Exit.class)); - ESUsersTool.Command.Exit exit = (ESUsersTool.Command.Exit) command; - assertThat(exit.status(), equalTo(CliTool.ExitStatus.USAGE)); + public void testRolesUnknownUser() throws Exception { + UserError e = expectThrows(UserError.class, () -> { + execute("roles", "unknown"); + }); + assertEquals(ExitCodes.NO_USER, e.exitCode); + assertTrue(e.getMessage(), e.getMessage().contains("User [unknown] doesn't exist")); } - public void testUserdelCmd() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path userFile = writeFile("user1:hash2"); - Path userRolesFile = writeFile("r3:user1\nr4:user1"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("shield.authc.realms.esusers.files.users_roles", userRolesFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Userdel cmd = new ESUsersTool.Userdel(new MockTerminal(), "user1"); - - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.OK)); - - assertFileExists(userFile); - List lines = Files.readAllLines(userFile, StandardCharsets.UTF_8); - assertThat(lines.size(), is(0)); - - assertFileExists(userRolesFile); - lines = Files.readAllLines(userRolesFile, StandardCharsets.UTF_8); - assertThat(lines.size(), is(0)); + public void testRolesAdd() throws Exception { + execute("roles", "existing_user", "-a", "test_r1"); + assertRole("test_admin", "existing_user"); + assertRole("test_r1", "existing_user"); } - public void testUserdelCmdMissingUser() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path userFile = writeFile("user1:hash2"); - Path userRolesFile = writeFile("r3:user1\nr4:user1"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("shield.authc.realms.esusers.files.users_roles", userRolesFile) - .put("path.home", createTempDir()) - .build(); - MockTerminal terminal = new MockTerminal(); - - ESUsersTool.Userdel cmd = new ESUsersTool.Userdel(terminal, "user2"); - - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.NO_USER)); - - assertThat(terminal.getOutput(), startsWith("User [user2] doesn't exist")); - - assertFileExists(userFile); - List lines = Files.readAllLines(userFile, StandardCharsets.UTF_8); - assertThat(lines.size(), is(1)); - - assertFileExists(userRolesFile); - lines = Files.readAllLines(userRolesFile, StandardCharsets.UTF_8); - assertThat(lines, hasSize(2)); + public void testRolesRemove() throws Exception { + execute("roles", "existing_user", "-r", "test_admin"); + assertRole("test_admin", "existing_user2"); } - public void testUserdelCmdMissingFiles() throws Exception { - Path dir = createTempDir(); - Path userFile = dir.resolve("users"); - Path userRolesFile = dir.resolve("users_roles"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("shield.authc.realms.esusers.files.users_roles", userRolesFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Userdel cmd = new ESUsersTool.Userdel(new MockTerminal(), "user2"); - - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.NO_USER)); - - assertThat(Files.exists(userFile), is(false)); - assertThat(Files.exists(userRolesFile), is(false)); + public void testRolesAddAndRemove() throws Exception { + execute("roles", "existing_user", "-a", "test_r1", "-r", "test_admin"); + assertRole("test_admin", "existing_user2"); + assertRole("test_r1", "existing_user"); } - public void testPasswdParseAllOptions() throws Exception { - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("passwd", args("user1 -p changeme")); - assertThat(command, instanceOf(ESUsersTool.Passwd.class)); - ESUsersTool.Passwd cmd = (ESUsersTool.Passwd) command; - assertThat(cmd.username, equalTo("user1")); - assertThat(new String(cmd.passwd.internalChars()), equalTo("changeme")); + public void testRolesRemoveLeavesExisting() throws Exception { + execute("useradd", "username", "-p", "changeme", "-r", "test_admin"); + execute("roles", "existing_user", "-r", "test_admin"); + assertRole("test_admin", "username"); } - public void testPasswdParseMissingUsername() throws Exception { - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("passwd", args("-p changeme")); - assertThat(command, instanceOf(ESUsersTool.Command.Exit.class)); - ESUsersTool.Command.Exit cmd = (ESUsersTool.Command.Exit) command; - assertThat(cmd.status(), is(CliTool.ExitStatus.USAGE)); + public void testRolesNoAddOrRemove() throws Exception { + String output = execute("roles", "existing_user"); + assertTrue(output, output.contains("existing_user")); + assertTrue(output, output.contains("test_admin")); } - public void testPasswdParseExtraArgs() throws Exception { - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("passwd", args("user1 user2 -p changeme")); - assertThat(command, instanceOf(ESUsersTool.Command.Exit.class)); - ESUsersTool.Command.Exit cmd = (ESUsersTool.Command.Exit) command; - assertThat(cmd.status(), is(CliTool.ExitStatus.USAGE)); + public void testListUnknownUser() throws Exception { + UserError e = expectThrows(UserError.class, () -> { + execute("list", "unknown"); + }); + assertEquals(ExitCodes.NO_USER, e.exitCode); + assertTrue(e.getMessage(), e.getMessage().contains("User [unknown] doesn't exist")); } - public void testPasswdParseMissingPassword() throws Exception { - final AtomicReference secretRequested = new AtomicReference<>(false); - Terminal terminal = new MockTerminal() { - @Override - public char[] readSecret(String text) { - secretRequested.set(true); - return "changeme".toCharArray(); - } - }; - ESUsersTool tool = new ESUsersTool(terminal); - CliTool.Command command = tool.parse("passwd", args("user1")); - assertThat(command, instanceOf(ESUsersTool.Passwd.class)); - ESUsersTool.Passwd cmd = (ESUsersTool.Passwd) command; - assertThat(cmd.username, equalTo("user1")); - assertThat(new String(cmd.passwd.internalChars()), equalTo("changeme")); - assertThat(secretRequested.get(), is(true)); + public void testListAllUsers() throws Exception { + String output = execute("list"); + assertTrue(output, output.contains("existing_user")); + assertTrue(output, output.contains("test_admin")); + assertTrue(output, output.contains("existing_user2")); + assertTrue(output, output.contains("test_r1")); } - public void testPasswdCmd() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path userFile = writeFile("user1:hash2"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Passwd cmd = new ESUsersTool.Passwd(new MockTerminal(), "user1", "changeme".toCharArray()); - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.OK)); - - List lines = Files.readAllLines(userFile, StandardCharsets.UTF_8); - assertThat(lines.size(), is(1)); - // we can't just hash again and compare the lines, as every time we hash a new salt is generated - // instead we'll just verify the generated hash against the correct password. - String line = lines.get(0); - assertThat(line, startsWith("user1:")); - String hash = line.substring("user1:".length()); - assertThat(Hasher.BCRYPT.verify(SecuredStringTests.build("changeme"), hash.toCharArray()), is(true)); + public void testListSingleUser() throws Exception { + String output = execute("list", "existing_user"); + assertTrue(output, output.contains("existing_user")); + assertTrue(output, output.contains("test_admin")); + assertFalse(output, output.contains("existing_user2")); + assertFalse(output, output.contains("test_r1")); } - public void testPasswdCmdUnknownUser() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path userFile = writeFile("user1:hash2"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Passwd cmd = new ESUsersTool.Passwd(new MockTerminal(), "user2", "changeme".toCharArray()); - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.NO_USER)); + public void testListUnknownRoles() throws Exception { + execute("useradd", "username", "-p", "changeme", "-r", "test_r1,r2,r3"); + String output = execute("list", "username"); + assertTrue(output, output.contains("username")); + assertTrue(output, output.contains("r2*,r3*,test_r1")); } - public void testPasswdCmdMissingFiles() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path userFile = createTempFile(); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Passwd cmd = new ESUsersTool.Passwd(new MockTerminal(), "user2", "changeme".toCharArray()); - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.NO_USER)); + public void testListNoUsers() throws Exception { + Files.delete(confDir.resolve("users")); + Files.createFile(confDir.resolve("users")); + Files.delete(confDir.resolve("users_roles")); + Files.createFile(confDir.resolve("users_roles")); + String output = execute("list"); + assertTrue(output, output.contains("No users found")); } - public void testRolesParseAllOptions() throws Exception { - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("roles", args("someuser -a test1,test2,test3 -r test4,test5,test6")); - assertThat(command, instanceOf(ESUsersTool.Roles.class)); - ESUsersTool.Roles rolesCommand = (ESUsersTool.Roles) command; - assertThat(rolesCommand.username, is("someuser")); - assertThat(rolesCommand.addRoles, arrayContaining("test1", "test2", "test3")); - assertThat(rolesCommand.removeRoles, arrayContaining("test4", "test5", "test6")); - } - - public void testRolesParseExtraArgs() throws Exception { - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("roles", args("someuser -a test1,test2,test3 foo -r test4,test5,test6 bar")); - assertThat(command, instanceOf(ESUsersTool.Command.Exit.class)); - ESUsersTool.Command.Exit cmd = (ESUsersTool.Command.Exit) command; - assertThat(cmd.status(), is(CliTool.ExitStatus.USAGE)); - } - - public void testRolesCmdValidatingRoleNames() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - ESUsersTool tool = new ESUsersTool(); - Path usersFile = writeFile("admin:hash"); - Path usersRoleFile = writeFile("admin: admin\n"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("path.home", createTempDir()) - .build(); - - // invalid role names - assertThat(execute(tool.parse("roles", args("admin -a r0le!")), settings), is(CliTool.ExitStatus.DATA_ERROR)); - assertThat(execute(tool.parse("roles", args("admin -a role%")), settings), is(CliTool.ExitStatus.DATA_ERROR)); - assertThat(execute(tool.parse("roles", args("admin -a role:")), settings), is(CliTool.ExitStatus.DATA_ERROR)); - assertThat(execute(tool.parse("roles", args("admin -a role>")), settings), is(CliTool.ExitStatus.DATA_ERROR)); - - // valid role names - assertThat(execute(tool.parse("roles", args("admin -a test01")), settings), is(CliTool.ExitStatus.OK)); - assertThat(execute(tool.parse("roles", args("admin -a @role")), settings), is(CliTool.ExitStatus.OK)); - assertThat(execute(tool.parse("roles", args("admin -a _role")), settings), is(CliTool.ExitStatus.OK)); - assertThat(execute(tool.parse("roles", args("admin -a -role")), settings), is(CliTool.ExitStatus.OK)); - assertThat(execute(tool.parse("roles", args("admin -a -Role")), settings), is(CliTool.ExitStatus.OK)); - assertThat(execute(tool.parse("roles", args("admin -a role0")), settings), is(CliTool.ExitStatus.OK)); - } - - public void testRolesCmdAddingRoleWorks() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path usersFile = writeFile("admin:hash\nuser:hash"); - Path usersRoleFile = writeFile("admin: admin\nuser: user\n"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Roles cmd = new ESUsersTool.Roles(new MockTerminal(), "user", new String[]{"foo"}, Strings.EMPTY_ARRAY); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - - Map userRoles = FileUserRolesStore.parseFile(usersRoleFile, logger); - assertThat(userRoles.keySet(), hasSize(2)); - assertThat(userRoles.keySet(), hasItems("admin", "user")); - assertThat(userRoles.get("admin"), arrayContaining("admin")); - assertThat(userRoles.get("user"), arrayContainingInAnyOrder("user", "foo")); - } - - public void testRolesCmdRemovingRoleWorks() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path usersFile = writeFile("admin:hash\nuser:hash"); - Path usersRoleFile = writeFile("admin: admin\nuser: user\nfoo: user\nbar: user\n"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Roles cmd = new ESUsersTool.Roles(new MockTerminal(), "user", Strings.EMPTY_ARRAY, new String[]{"foo"}); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - - Map userRoles = FileUserRolesStore.parseFile(usersRoleFile, logger); - assertThat(userRoles.keySet(), hasSize(2)); - assertThat(userRoles.keySet(), hasItems("admin", "user")); - assertThat(userRoles.get("admin"), arrayContaining("admin")); - assertThat(userRoles.get("user"), arrayContainingInAnyOrder("user", "bar")); - } - - public void testRolesCmdAddingAndRemovingRoleWorks() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path usersFile = writeFile("admin:hash\nuser:hash"); - Path usersRoleFile = writeFile("admin: admin\nuser:user\nfoo:user\nbar:user\n"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Roles cmd = new ESUsersTool.Roles(new MockTerminal(), "user", new String[]{"newrole"}, new String[]{"foo"}); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - - Map userRoles = FileUserRolesStore.parseFile(usersRoleFile, logger); - assertThat(userRoles.keySet(), hasSize(2)); - assertThat(userRoles.keySet(), hasItems("admin", "user")); - assertThat(userRoles.get("admin"), arrayContaining("admin")); - assertThat(userRoles.get("user"), arrayContainingInAnyOrder("user", "bar", "newrole")); - } - - public void testRolesCmdRemovingLastRoleRemovesEntryFromRolesFile() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path usersFile = writeFile("admin:hash\nuser:hash"); - Path usersRoleFile = writeFile("admin: admin\nuser:user\nfoo:user\nbar:user\n"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Roles cmd = new ESUsersTool.Roles(new MockTerminal(), "user", Strings.EMPTY_ARRAY, new String[]{"user", "foo", "bar"}); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - - List usersRoleFileLines = Files.readAllLines(usersRoleFile, StandardCharsets.UTF_8); - assertThat(usersRoleFileLines, not(hasItem(containsString("user")))); - } - - public void testRolesCmdUserNotFound() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path usersFile = writeFile("admin:hash\nuser:hash"); - Path usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool.Roles cmd = new ESUsersTool.Roles(new MockTerminal(), "does-not-exist", Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.NO_USER)); - } - - public void testRolesCmdTestNotAddingOrRemovingRolesShowsListingOfRoles() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path usersFile = writeFile("admin:hash\nuser:hash"); - Path usersRoleFile = writeFile("admin: admin\nuser:user\nfoo:user\nbar:user\n"); - Path rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all\n\nbar:\n cluster: all"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("shield.authz.store.files.roles", rolesFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal catchTerminalOutput = new MockTerminal(); - ESUsersTool.Roles cmd = new ESUsersTool.Roles(catchTerminalOutput, "user", Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - assertThat(catchTerminalOutput.getOutput(), allOf(containsString("user"), containsString("user,foo,bar"))); - } - - public void testRolesCmdRoleCanBeAddedWhenUserIsNotInRolesFile() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path usersFile = writeFile("admin:hash\nuser:hash"); - Path usersRoleFile = writeFile("admin: admin\n"); - Path rolesFile = writeFile("admin:\n cluster: all\n\nmyrole:\n cluster: all"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("shield.authz.store.files.roles", rolesFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal catchTerminalOutput = new MockTerminal(); - ESUsersTool.Roles cmd = new ESUsersTool.Roles(catchTerminalOutput, "user", new String[]{"myrole"}, Strings.EMPTY_ARRAY); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - Map userRoles = FileUserRolesStore.parseFile(usersRoleFile, logger); - assertThat(userRoles.keySet(), hasSize(2)); - assertThat(userRoles.keySet(), hasItems("admin", "user")); - assertThat(userRoles.get("user"), arrayContaining("myrole")); - } - - public void testListUsersAndRolesCmdParsingWorks() throws Exception { - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("list", args("someuser")); - assertThat(command, instanceOf(ESUsersTool.ListUsersAndRoles.class)); - ESUsersTool.ListUsersAndRoles listUsersAndRolesCommand = (ESUsersTool.ListUsersAndRoles) command; - assertThat(listUsersAndRolesCommand.username, is("someuser")); - } - - public void testListUsersAndRolesCmdParsingExtraArgs() throws Exception { - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("list", args("someuser two")); - assertThat(command, instanceOf(ESUsersTool.Command.Exit.class)); - ESUsersTool.Command.Exit cmd = (ESUsersTool.Command.Exit) command; - assertThat(cmd.status(), is(CliTool.ExitStatus.USAGE)); - } - - public void testListUsersAndRolesCmdListAllUsers() throws Exception { - Path usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n"); - Path rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all\n\nbar:\n cluster: all"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("shield.authz.store.files.roles", rolesFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal catchTerminalOutput = new MockTerminal(); - ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, null); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - String output = catchTerminalOutput.getOutput(); - assertThat(output, containsString("admin")); - assertThat(output, allOf(containsString("user"), containsString("user,foo,bar"))); - } - - public void testListUsersAndRolesCmdListAllUsersWithUnknownRoles() throws Exception { - Path usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n"); - Path rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("shield.authz.store.files.roles", rolesFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal catchTerminalOutput = new MockTerminal(); - ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, null); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - String output = catchTerminalOutput.getOutput(); - assertThat(output, containsString("admin")); - assertThat(output, allOf(containsString("user"), containsString("user,foo*,bar*"))); - } - - public void testListUsersAndRolesCmdListSingleUser() throws Exception { - Path usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n"); - Path usersFile = writeFile("admin:{plain}changeme\nuser:{plain}changeme\nno-roles-user:{plain}changeme\n"); - Path rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authz.store.files.roles", rolesFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal catchTerminalOutput = new MockTerminal(); - ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, "admin"); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - assertThat(catchTerminalOutput.getOutput(), containsString("admin")); - assertThat(catchTerminalOutput.getOutput(), not(containsString("user"))); - } - - public void testListUsersAndRolesCmdNoUsers() throws Exception { - Path usersFile = writeFile(""); - Path usersRoleFile = writeFile(""); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal terminal = new MockTerminal(); - ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(terminal, null); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - assertThat(terminal.getOutput(), equalTo("No users found\n")); - } - - public void testListUsersAndRolesCmdListSingleUserNotFound() throws Exception { - Path usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal catchTerminalOutput = new MockTerminal(); - ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, "does-not-exist"); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.NO_USER)); - } - - public void testListUsersAndRolesCmdUsersWithAndWithoutRolesAreListed() throws Exception { - Path usersFile = writeFile("admin:{plain}changeme\nuser:{plain}changeme\nno-roles-user:{plain}changeme\n"); - Path usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n"); - Path rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all\n\nbar:\n cluster: all"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("shield.authz.store.files.roles", rolesFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal catchTerminalOutput = new MockTerminal(); - ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, null); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - String output = catchTerminalOutput.getOutput(); - assertThat(output, containsString("admin")); - assertThat(output, allOf(containsString("user"), containsString("user,foo,bar"))); - assertThat(output, allOf(containsString("no-roles-user"), containsString("-"))); - } - - public void testListUsersAndRolesCmdUsersWithoutRolesAreListed() throws Exception { - Path usersFile = writeFile("admin:{plain}changeme\nuser:{plain}changeme\nno-roles-user:{plain}changeme\n"); - Path usersRoleFile = writeFile(""); - Path rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all\n\nbar:\n cluster: all"); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("shield.authz.store.files.roles", rolesFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal catchTerminalOutput = new MockTerminal(); - ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, null); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - String output = catchTerminalOutput.getOutput(); - assertThat(output, allOf(containsString("admin"), containsString("-"))); - assertThat(output, allOf(containsString("user"), containsString("-"))); - assertThat(output, allOf(containsString("no-roles-user"), containsString("-"))); - } - - public void testListUsersAndRolesCmdUsersWithoutRolesAreListedForSingleUser() throws Exception { - Path usersFile = writeFile("admin:{plain}changeme"); - Path usersRoleFile = writeFile(""); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users_roles", usersRoleFile) - .put("shield.authc.realms.esusers.files.users", usersFile) - .put("path.home", createTempDir()) - .build(); - - MockTerminal loggingTerminal = new MockTerminal(); - ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(loggingTerminal, "admin"); - CliTool.ExitStatus status = execute(cmd, settings); - - assertThat(status, is(CliTool.ExitStatus.OK)); - assertThat(loggingTerminal.getOutput(), allOf(containsString("admin"), containsString("-"))); - } - - public void testUseraddUsernameWithPeriod() throws Exception { - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - Path userFile = createTempFile(); - Path userRolesFile = createTempFile(); - Settings settings = Settings.builder() - .put("shield.authc.realms.esusers.type", "esusers") - .put("shield.authc.realms.esusers.files.users", userFile) - .put("shield.authc.realms.esusers.files.users_roles", userRolesFile) - .put("path.home", createTempDir()) - .build(); - - ESUsersTool tool = new ESUsersTool(); - CliTool.Command command = tool.parse("useradd", args("john.doe -p changeme -r r1,r2,r3")); - assertThat(command, instanceOf(ESUsersTool.Useradd.class)); - ESUsersTool.Useradd cmd = (ESUsersTool.Useradd) command; - - CliTool.ExitStatus status = execute(cmd, settings); - assertThat(status, is(CliTool.ExitStatus.OK)); - - assertFileExists(userFile); - List lines = Files.readAllLines(userFile, StandardCharsets.UTF_8); - assertThat(lines.size(), is(1)); - // we can't just hash again and compare the lines, as every time we hash a new salt is generated - // instead we'll just verify the generated hash against the correct password. - String line = lines.get(0); - assertThat(line, startsWith("john.doe:")); - String hash = line.substring("john.doe:".length()); - assertThat(Hasher.BCRYPT.verify(SecuredStringTests.build("changeme"), hash.toCharArray()), is(true)); - - assertFileExists(userRolesFile); - lines = Files.readAllLines(userRolesFile, StandardCharsets.UTF_8); - assertThat(lines, hasSize(3)); - assertThat(lines, containsInAnyOrder("r1:john.doe", "r2:john.doe", "r3:john.doe")); - } - - private Path writeFile(String content) throws IOException { - Path file = createTempFile(); - Files.write(file, content.getBytes(StandardCharsets.UTF_8)); - return file; - } - - private void assertFileExists(Path file) { - assertThat(String.format(Locale.ROOT, "Expected file [%s] to exist", file), Files.exists(file), is(true)); + public void testListUserWithoutRoles() throws Exception { + String output = execute("list", "existing_user3"); + assertTrue(output, output.contains("existing_user3")); + output = execute("list"); + assertTrue(output, output.contains("existing_user3")); } } diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/crypto/tool/SystemKeyToolTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/crypto/tool/SystemKeyToolTests.java index 7b9e9a07604..c077a759aba 100644 --- a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/crypto/tool/SystemKeyToolTests.java +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/crypto/tool/SystemKeyToolTests.java @@ -12,7 +12,7 @@ import java.nio.file.attribute.PosixFilePermission; import java.util.Set; import org.elasticsearch.cli.MockTerminal; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.shield.crypto.InternalCryptoService; diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/trigger/schedule/tool/CronEvalTool.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/trigger/schedule/tool/CronEvalTool.java index 32191cabeb6..54151cc8e7b 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/trigger/schedule/tool/CronEvalTool.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/watcher/trigger/schedule/tool/CronEvalTool.java @@ -10,26 +10,16 @@ import java.util.List; 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.UserError; -import org.elasticsearch.common.SuppressForbidden; -import org.elasticsearch.common.cli.CliTool; -import org.elasticsearch.common.cli.CliToolConfig; -import org.elasticsearch.common.cli.Terminal; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.env.Environment; +import org.elasticsearch.cli.Terminal; import org.elasticsearch.watcher.trigger.schedule.Cron; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; -import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd; -import static org.elasticsearch.common.cli.CliToolConfig.Builder.option; -import static org.elasticsearch.common.cli.CliToolConfig.config; - public class CronEvalTool extends Command { public static void main(String[] args) throws Exception { @@ -52,14 +42,13 @@ public class CronEvalTool extends Command { } @Override - protected int execute(Terminal terminal, OptionSet options) throws Exception { + protected void execute(Terminal terminal, OptionSet options) throws Exception { int count = Integer.parseInt(countOption.value(options)); List args = arguments.values(options); if (args.size() != 1) { throw new UserError(ExitCodes.USAGE, "expecting a single argument that is the cron expression to evaluate"); } execute(terminal, args.get(0), count); - return ExitCodes.OK; } void execute(Terminal terminal, String expression, int count) throws Exception { diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/actions/email/service/ManualPublicSmtpServersTester.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/actions/email/service/ManualPublicSmtpServersTester.java index 2a1cdfb3124..f9915bd3bfb 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/actions/email/service/ManualPublicSmtpServersTester.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/actions/email/service/ManualPublicSmtpServersTester.java @@ -6,7 +6,7 @@ package org.elasticsearch.watcher.actions.email.service; import org.apache.lucene.util.LuceneTestCase.AwaitsFix; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ToXContent; diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/trigger/schedule/tool/EvalCron.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/trigger/schedule/tool/EvalCron.java index dfa95f64559..da33910c62c 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/trigger/schedule/tool/EvalCron.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/watcher/trigger/schedule/tool/EvalCron.java @@ -5,7 +5,7 @@ */ package org.elasticsearch.watcher.trigger.schedule.tool; -import org.elasticsearch.common.cli.Terminal; +import org.elasticsearch.cli.Terminal; /** * A small executable tool that can eval crons