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:
parent
03520e0aa7
commit
d393cc2740
|
@ -21,7 +21,6 @@ import org.elasticsearch.shield.authc.esusers.FileUserPasswdStore;
|
||||||
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
|
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
|
||||||
import org.elasticsearch.shield.authc.support.Hasher;
|
import org.elasticsearch.shield.authc.support.Hasher;
|
||||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||||
import org.elasticsearch.shield.authz.Permission;
|
|
||||||
import org.elasticsearch.shield.authz.store.FileRolesStore;
|
import org.elasticsearch.shield.authz.store.FileRolesStore;
|
||||||
import org.elasticsearch.shield.support.Validation;
|
import org.elasticsearch.shield.support.Validation;
|
||||||
|
|
||||||
|
@ -430,7 +429,7 @@ public class ESUsersTool extends CliTool {
|
||||||
@Override
|
@Override
|
||||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||||
Settings esusersSettings = Realms.internalRealmSettings(settings, ESUsersRealm.TYPE);
|
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);
|
Path userRolesFilePath = FileUserRolesStore.resolveFile(esusersSettings, env);
|
||||||
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null);
|
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null);
|
||||||
Path userFilePath = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
Path userFilePath = FileUserPasswdStore.resolveFile(esusersSettings, env);
|
||||||
|
@ -444,7 +443,7 @@ public class ESUsersTool extends CliTool {
|
||||||
|
|
||||||
if (userRoles.containsKey(username)) {
|
if (userRoles.containsKey(username)) {
|
||||||
String[] roles = userRoles.get(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);
|
String[] markedRoles = markUnknownRoles(roles, unknownRoles);
|
||||||
terminal.println("%-15s: %s", username, Joiner.on(",").useForNull("-").join(markedRoles));
|
terminal.println("%-15s: %s", username, Joiner.on(",").useForNull("-").join(markedRoles));
|
||||||
if (!unknownRoles.isEmpty()) {
|
if (!unknownRoles.isEmpty()) {
|
||||||
|
@ -461,7 +460,7 @@ public class ESUsersTool extends CliTool {
|
||||||
boolean usersExist = false;
|
boolean usersExist = false;
|
||||||
for (Map.Entry<String, String[]> entry : userRoles.entrySet()) {
|
for (Map.Entry<String, String[]> entry : userRoles.entrySet()) {
|
||||||
String[] roles = entry.getValue();
|
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);
|
String[] markedRoles = markUnknownRoles(roles, unknownRoles);
|
||||||
terminal.println("%-15s: %s", entry.getKey(), Joiner.on(",").join(markedRoles));
|
terminal.println("%-15s: %s", entry.getKey(), Joiner.on(",").join(markedRoles));
|
||||||
unknownRolesFound = unknownRolesFound || !unknownRoles.isEmpty();
|
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);
|
Path rolesFile = FileRolesStore.resolveFile(settings, env);
|
||||||
try {
|
try {
|
||||||
return FileRolesStore.parseFile(rolesFile, null);
|
return FileRolesStore.parseFileForRoleNames(rolesFile, null);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
// if for some reason, parsing fails (malformatted perhaps) we just warn
|
// 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());
|
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) {
|
private static void verifyRoles(Terminal terminal, Settings settings, Environment env, String[] roles) {
|
||||||
ImmutableMap<String, Permission.Global.Role> knownRoles = loadRoles(terminal, settings, env);
|
ImmutableSet<String> knownRoles = loadRoleNames(terminal, settings, env);
|
||||||
if (knownRoles == null) {
|
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles);
|
||||||
return;
|
|
||||||
}
|
|
||||||
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles.keySet());
|
|
||||||
if (!unknownRoles.isEmpty()) {
|
if (!unknownRoles.isEmpty()) {
|
||||||
Path rolesFile = FileRolesStore.resolveFile(settings, env);
|
Path rolesFile = FileRolesStore.resolveFile(settings, env);
|
||||||
terminal.println("Warning: The following roles [%s] are unknown. Make sure to add them to the [%s] file. " +
|
terminal.println("Warning: The following roles [%s] are unknown. Make sure to add them to the [%s] file. " +
|
||||||
|
|
|
@ -100,11 +100,19 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
|
||||||
return env.homeFile().resolve(location);
|
return env.homeFile().resolve(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImmutableMap<String, Permission.Global.Role> parseFile(Path path, ESLogger logger) {
|
public static ImmutableSet<String> parseFileForRoleNames(Path path, ESLogger logger) {
|
||||||
return parseFile(path, Collections.<Permission.Global.Role>emptySet(), 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) {
|
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) {
|
if (logger == null) {
|
||||||
logger = NoOpLogger.INSTANCE;
|
logger = NoOpLogger.INSTANCE;
|
||||||
}
|
}
|
||||||
|
@ -121,7 +129,7 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
|
||||||
|
|
||||||
List<String> roleSegments = roleSegments(path);
|
List<String> roleSegments = roleSegments(path);
|
||||||
for (String segment : roleSegments) {
|
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 (role != null) {
|
||||||
if (SystemRole.NAME.equals(role.name())) {
|
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);
|
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);
|
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;
|
String roleName = null;
|
||||||
try {
|
try {
|
||||||
XContentParser parser = YamlXContent.yamlXContent.createParser(segment);
|
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);
|
logger.error("invalid role definition [{}] in roles file [{}]. invalid role name - {}. skipping role... ", roleName, path.toAbsolutePath(), validationError);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Permission.Global.Role.Builder permission = Permission.Global.Role.builder(roleName);
|
Permission.Global.Role.Builder permission = Permission.Global.Role.builder(roleName);
|
||||||
|
if (resolvePermissions == false) {
|
||||||
|
return permission.build();
|
||||||
|
}
|
||||||
|
|
||||||
token = parser.nextToken();
|
token = parser.nextToken();
|
||||||
if (token == XContentParser.Token.START_OBJECT) {
|
if (token == XContentParser.Token.START_OBJECT) {
|
||||||
String currentFieldName = null;
|
String currentFieldName = null;
|
||||||
|
|
|
@ -213,6 +213,52 @@ public class ESUsersToolTests extends CliToolTestCase {
|
||||||
assertThat(status, is(CliTool.ExitStatus.CODE_ERROR));
|
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
|
@Test
|
||||||
public void testUserdel_Parse() throws Exception {
|
public void testUserdel_Parse() throws Exception {
|
||||||
ESUsersTool tool = new ESUsersTool();
|
ESUsersTool tool = new ESUsersTool();
|
||||||
|
|
|
@ -15,9 +15,7 @@ import org.junit.Test;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
import static org.elasticsearch.shield.authz.Privilege.Index.*;
|
import static org.elasticsearch.shield.authz.Privilege.Index.*;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
|
||||||
import static org.hamcrest.Matchers.is;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -64,6 +62,13 @@ public class PermissionTests extends ElasticsearchTestCase {
|
||||||
assertThat(count, is(equalTo(permission.indices().groups().length)));
|
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/"
|
// "baz_*foo", "/fool.*bar/"
|
||||||
private void testAllowedIndicesMatcher(Predicate<String> indicesMatcher) {
|
private void testAllowedIndicesMatcher(Predicate<String> indicesMatcher) {
|
||||||
assertThat(indicesMatcher.apply("foobar"), is(false));
|
assertThat(indicesMatcher.apply("foobar"), is(false));
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
|
||||||
@Test
|
@Test
|
||||||
public void testParseFile() throws Exception {
|
public void testParseFile() throws Exception {
|
||||||
Path path = getDataPath("roles.yml");
|
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, notNullValue());
|
||||||
assertThat(roles.size(), is(4));
|
assertThat(roles.size(), is(4));
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
|
||||||
@Test
|
@Test
|
||||||
public void testDefaultRolesFile() throws Exception {
|
public void testDefaultRolesFile() throws Exception {
|
||||||
Path path = getDataPath("default_roles.yml");
|
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, notNullValue());
|
||||||
assertThat(roles.size(), is(8));
|
assertThat(roles.size(), is(8));
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
|
||||||
public void testThatEmptyFileDoesNotResultInLoop() throws Exception {
|
public void testThatEmptyFileDoesNotResultInLoop() throws Exception {
|
||||||
Path file = createTempFile();
|
Path file = createTempFile();
|
||||||
Files.write(file, ImmutableList.of("#"), Charsets.UTF_8);
|
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()));
|
assertThat(roles.keySet(), is(empty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
|
||||||
public void testThatInvalidRoleDefinitions() throws Exception {
|
public void testThatInvalidRoleDefinitions() throws Exception {
|
||||||
Path path = getDataPath("invalid_roles.yml");
|
Path path = getDataPath("invalid_roles.yml");
|
||||||
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.ERROR);
|
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.size(), is(1));
|
||||||
assertThat(roles, hasKey("valid_role"));
|
assertThat(roles, hasKey("valid_role"));
|
||||||
Permission.Global.Role role = roles.get("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]"));
|
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
|
@Test
|
||||||
public void testReservedRoles() throws Exception {
|
public void testReservedRoles() throws Exception {
|
||||||
Set<Permission.Global.Role> reservedRoles = ImmutableSet.<Permission.Global.Role>builder()
|
Set<Permission.Global.Role> reservedRoles = ImmutableSet.<Permission.Global.Role>builder()
|
||||||
|
|
Loading…
Reference in New Issue