do not attempt to resolve permissions in the esusers tool

The esusers tool reads the list of roles to provide validation feedback to the user,
however since we have added custom roles the tool doesn't know about these roles
as they come from outside of Shield. When a custom role was found, a warning
was printed that can be confusing to users.

Now when validating roles, we only read the names from the roles.yml file.

Closes elastic/elasticsearch#835

Original commit: elastic/x-pack-elasticsearch@89d0e3efce
This commit is contained in:
jaymode 2015-05-01 10:26:56 -04:00
parent 03520e0aa7
commit d393cc2740
5 changed files with 95 additions and 22 deletions

View File

@ -21,7 +21,6 @@ import org.elasticsearch.shield.authc.esusers.FileUserPasswdStore;
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authz.Permission;
import org.elasticsearch.shield.authz.store.FileRolesStore;
import org.elasticsearch.shield.support.Validation;
@ -430,7 +429,7 @@ public class ESUsersTool extends CliTool {
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
ImmutableMap<String, Permission.Global.Role> knownRoles = loadRoles(terminal, settings, env);
ImmutableSet<String> knownRoles = loadRoleNames(terminal, settings, env);
Path userRolesFilePath = FileUserRolesStore.resolveFile(esusersSettings, env);
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null);
Path userFilePath = FileUserPasswdStore.resolveFile(esusersSettings, env);
@ -444,7 +443,7 @@ public class ESUsersTool extends CliTool {
if (userRoles.containsKey(username)) {
String[] roles = userRoles.get(username);
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles.keySet());
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles);
String[] markedRoles = markUnknownRoles(roles, unknownRoles);
terminal.println("%-15s: %s", username, Joiner.on(",").useForNull("-").join(markedRoles));
if (!unknownRoles.isEmpty()) {
@ -461,7 +460,7 @@ public class ESUsersTool extends CliTool {
boolean usersExist = false;
for (Map.Entry<String, String[]> entry : userRoles.entrySet()) {
String[] roles = entry.getValue();
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles.keySet());
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles);
String[] markedRoles = markUnknownRoles(roles, unknownRoles);
terminal.println("%-15s: %s", entry.getKey(), Joiner.on(",").join(markedRoles));
unknownRolesFound = unknownRolesFound || !unknownRoles.isEmpty();
@ -492,10 +491,10 @@ public class ESUsersTool extends CliTool {
}
}
private static ImmutableMap<String, Permission.Global.Role> loadRoles(Terminal terminal, Settings settings, Environment env) {
private static ImmutableSet<String> loadRoleNames(Terminal terminal, Settings settings, Environment env) {
Path rolesFile = FileRolesStore.resolveFile(settings, env);
try {
return FileRolesStore.parseFile(rolesFile, null);
return FileRolesStore.parseFileForRoleNames(rolesFile, null);
} catch (Throwable t) {
// if for some reason, parsing fails (malformatted perhaps) we just warn
terminal.println("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());
@ -519,11 +518,8 @@ public class ESUsersTool extends CliTool {
}
private static void verifyRoles(Terminal terminal, Settings settings, Environment env, String[] roles) {
ImmutableMap<String, Permission.Global.Role> knownRoles = loadRoles(terminal, settings, env);
if (knownRoles == null) {
return;
}
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles.keySet());
ImmutableSet<String> knownRoles = loadRoleNames(terminal, settings, env);
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles);
if (!unknownRoles.isEmpty()) {
Path rolesFile = FileRolesStore.resolveFile(settings, env);
terminal.println("Warning: The following roles [%s] are unknown. Make sure to add them to the [%s] file. " +

View File

@ -100,11 +100,19 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
return env.homeFile().resolve(location);
}
public static ImmutableMap<String, Permission.Global.Role> parseFile(Path path, ESLogger logger) {
return parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger);
public static ImmutableSet<String> parseFileForRoleNames(Path path, ESLogger logger) {
ImmutableMap<String, Permission.Global.Role> roleMap = parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger, false);
if (roleMap == null) {
return ImmutableSet.<String>builder().build();
}
return roleMap.keySet();
}
public static ImmutableMap<String, Permission.Global.Role> parseFile(Path path, Set<Permission.Global.Role> reservedRoles, ESLogger logger) {
return parseFile(path, reservedRoles, logger, true);
}
public static ImmutableMap<String, Permission.Global.Role> parseFile(Path path, Set<Permission.Global.Role> reservedRoles, ESLogger logger, boolean resolvePermission) {
if (logger == null) {
logger = NoOpLogger.INSTANCE;
}
@ -121,7 +129,7 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
List<String> roleSegments = roleSegments(path);
for (String segment : roleSegments) {
Permission.Global.Role role = parseRole(segment, path, logger);
Permission.Global.Role role = parseRole(segment, path, logger, resolvePermission);
if (role != null) {
if (SystemRole.NAME.equals(role.name())) {
logger.warn("role [{}] is reserved to the system. the relevant role definition in the mapping file will be ignored", SystemRole.NAME);
@ -146,7 +154,7 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
return ImmutableMap.copyOf(roles);
}
private static Permission.Global.Role parseRole(String segment, Path path, ESLogger logger) {
private static Permission.Global.Role parseRole(String segment, Path path, ESLogger logger, boolean resolvePermissions) {
String roleName = null;
try {
XContentParser parser = YamlXContent.yamlXContent.createParser(segment);
@ -160,7 +168,12 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
logger.error("invalid role definition [{}] in roles file [{}]. invalid role name - {}. skipping role... ", roleName, path.toAbsolutePath(), validationError);
return null;
}
Permission.Global.Role.Builder permission = Permission.Global.Role.builder(roleName);
if (resolvePermissions == false) {
return permission.build();
}
token = parser.nextToken();
if (token == XContentParser.Token.START_OBJECT) {
String currentFieldName = null;

View File

@ -213,6 +213,52 @@ public class ESUsersToolTests extends CliToolTestCase {
assertThat(status, is(CliTool.ExitStatus.CODE_ERROR));
}
@Test
public void testUseradd_CustomRole() throws Exception {
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();
final CaptureOutputTerminal terminal = new CaptureOutputTerminal();
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));
assertThat(terminal.getTerminalOutput(), hasSize(0));
}
@Test
public void testUseradd_NonExistantRole() throws Exception {
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();
final CaptureOutputTerminal terminal = new CaptureOutputTerminal();
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.getTerminalOutput(), hasSize(1));
assertThat(terminal.getTerminalOutput().get(0), containsString("[plugin_admin_2]"));
}
@Test
public void testUserdel_Parse() throws Exception {
ESUsersTool tool = new ESUsersTool();

View File

@ -15,9 +15,7 @@ import org.junit.Test;
import java.util.Iterator;
import static org.elasticsearch.shield.authz.Privilege.Index.*;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.*;
/**
*
@ -64,6 +62,13 @@ public class PermissionTests extends ElasticsearchTestCase {
assertThat(count, is(equalTo(permission.indices().groups().length)));
}
@Test
public void buildEmptyRole() {
Permission.Global.Role.Builder permission = Permission.Global.Role.builder("some_role");
Permission.Global.Role role = permission.build();
assertThat(role, notNullValue());
}
// "baz_*foo", "/fool.*bar/"
private void testAllowedIndicesMatcher(Predicate<String> indicesMatcher) {
assertThat(indicesMatcher.apply("foobar"), is(false));

View File

@ -41,7 +41,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
@Test
public void testParseFile() throws Exception {
Path path = getDataPath("roles.yml");
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, logger);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger);
assertThat(roles, notNullValue());
assertThat(roles.size(), is(4));
@ -107,7 +107,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
@Test
public void testDefaultRolesFile() throws Exception {
Path path = getDataPath("default_roles.yml");
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, logger);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger);
assertThat(roles, notNullValue());
assertThat(roles.size(), is(8));
@ -187,7 +187,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
public void testThatEmptyFileDoesNotResultInLoop() throws Exception {
Path file = createTempFile();
Files.write(file, ImmutableList.of("#"), Charsets.UTF_8);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(file, logger);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(file, Collections.<Permission.Global.Role>emptySet(), logger);
assertThat(roles.keySet(), is(empty()));
}
@ -195,7 +195,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
public void testThatInvalidRoleDefinitions() throws Exception {
Path path = getDataPath("invalid_roles.yml");
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.ERROR);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, logger);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger);
assertThat(roles.size(), is(1));
assertThat(roles, hasKey("valid_role"));
Permission.Global.Role role = roles.get("valid_role");
@ -211,6 +211,19 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
assertThat(entries.get(4).text, startsWith("invalid role definition [role4] in roles file [" + path.toAbsolutePath() + "]. could not resolve indices privileges [al;kjdlkj;lkj]"));
}
@Test
public void testThatRoleNamesDoesNotResolvePermissions() throws Exception {
Path path = getDataPath("invalid_roles.yml");
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.ERROR);
ImmutableSet<String> roleNames = FileRolesStore.parseFileForRoleNames(path, logger);
assertThat(roleNames.size(), is(5));
assertThat(roleNames, containsInAnyOrder("valid_role", "role1", "role2", "role3", "role4"));
List<CapturingLogger.Msg> entries = logger.output(CapturingLogger.Level.ERROR);
assertThat(entries, hasSize(1));
assertThat(entries.get(0).text, startsWith("invalid role definition [$dlk39] in roles file [" + path.toAbsolutePath() + "]. invalid role name"));
}
@Test
public void testReservedRoles() throws Exception {
Set<Permission.Global.Role> reservedRoles = ImmutableSet.<Permission.Global.Role>builder()