Change users_roles format to be keyed by roles
Having roles as the keys is more aligned with the LDAP role_mapping file and with linux's group file (where the groups serve as the keys) Also added support for comment lines (starting with `#`) in `.users` and `.users_roles` files Original commit: elastic/x-pack-elasticsearch@60faf7330f
This commit is contained in:
parent
4621bb7620
commit
78377c7cd2
|
@ -105,6 +105,9 @@ public class FileUserPasswdStore extends AbstractComponent implements UserPasswd
|
|||
int lineNr = 0;
|
||||
for (String line : lines) {
|
||||
lineNr++;
|
||||
if (line.startsWith("#")) { // comment
|
||||
continue;
|
||||
}
|
||||
int i = line.indexOf(":");
|
||||
if (i <= 0 || i == line.length() - 1) {
|
||||
if (logger != null) {
|
||||
|
|
|
@ -28,9 +28,7 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
|
@ -38,7 +36,7 @@ import java.util.regex.Pattern;
|
|||
*/
|
||||
public class FileUserRolesStore extends AbstractComponent implements UserRolesStore {
|
||||
|
||||
private static final Pattern ROLES_DELIM = Pattern.compile("\\s*,\\s*");
|
||||
private static final Pattern USERS_DELIM = Pattern.compile("\\s*,\\s*");
|
||||
|
||||
private final Path file;
|
||||
|
||||
|
@ -79,7 +77,8 @@ public class FileUserRolesStore extends AbstractComponent implements UserRolesSt
|
|||
|
||||
/**
|
||||
* parses the users_roles file. Should never return return {@code null}, if the file doesn't exist
|
||||
* an empty map is returned
|
||||
* an empty map is returned. The read file holds a mapping per line of the form "role -> users" while the returned
|
||||
* map holds entries of the form "user -> roles".
|
||||
*/
|
||||
public static ImmutableMap<String, String[]> parseFile(Path path, @Nullable ESLogger logger) {
|
||||
if (logger != null) {
|
||||
|
@ -97,49 +96,79 @@ public class FileUserRolesStore extends AbstractComponent implements UserRolesSt
|
|||
throw new ElasticsearchException("Could not read users file [" + path.toAbsolutePath() + "]", ioe);
|
||||
}
|
||||
|
||||
ImmutableMap.Builder<String, String[]> usersRoles = ImmutableMap.builder();
|
||||
Map<String, List<String>> userToRoles = new HashMap<>();
|
||||
|
||||
int lineNr = 0;
|
||||
for (String line : lines) {
|
||||
lineNr++;
|
||||
if (line.startsWith("#")) { //comment
|
||||
continue;
|
||||
}
|
||||
int i = line.indexOf(":");
|
||||
if (i <= 0 || i == line.length() - 1) {
|
||||
if (logger != null) {
|
||||
logger.error("Invalid entry in users file [" + path.toAbsolutePath() + "], line [" + lineNr + "]. Skipping...");
|
||||
logger.error("Invalid entry in users_roles file [" + path.toAbsolutePath() + "], line [" + lineNr + "]. Skipping...");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
String username = line.substring(0, i).trim();
|
||||
if (Strings.isEmpty(username)) {
|
||||
String role = line.substring(0, i).trim();
|
||||
if (Strings.isEmpty(role)) {
|
||||
if (logger != null) {
|
||||
logger.error("Invalid username entry in users file [" + path.toAbsolutePath() + "], line [" + lineNr + "]. Skipping...");
|
||||
logger.error("Invalid username entry in users_roles file [" + path.toAbsolutePath() + "], line [" + lineNr + "]. Skipping...");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
String rolesStr = line.substring(i + 1).trim();
|
||||
if (Strings.isEmpty(rolesStr)) {
|
||||
String usersStr = line.substring(i + 1).trim();
|
||||
if (Strings.isEmpty(usersStr)) {
|
||||
if (logger != null) {
|
||||
logger.error("Invalid roles entry in users file [" + path.toAbsolutePath() + "], line [" + lineNr + "]. Skipping...");
|
||||
logger.error("Invalid roles entry in users_roles file [" + path.toAbsolutePath() + "], line [" + lineNr + "]. Skipping...");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
String[] roles = ROLES_DELIM.split(rolesStr);
|
||||
if (roles.length == 0) {
|
||||
String[] roleUsers = USERS_DELIM.split(usersStr);
|
||||
if (roleUsers.length == 0) {
|
||||
if (logger != null) {
|
||||
logger.error("Invalid roles entry in users file [" + path.toAbsolutePath() + "], line [" + lineNr + "]. Skipping...");
|
||||
logger.error("Invalid roles entry in users_roles file [" + path.toAbsolutePath() + "], line [" + lineNr + "]. Skipping...");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
usersRoles.put(username, roles);
|
||||
|
||||
for (String user : roleUsers) {
|
||||
List<String> roles = userToRoles.get(user);
|
||||
if (roles == null) {
|
||||
roles = new ArrayList<>();
|
||||
userToRoles.put(user, roles);
|
||||
}
|
||||
roles.add(role);
|
||||
}
|
||||
}
|
||||
|
||||
ImmutableMap.Builder<String, String[]> usersRoles = ImmutableMap.builder();
|
||||
for (Map.Entry<String, List<String>> entry : userToRoles.entrySet()) {
|
||||
usersRoles.put(entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()]));
|
||||
}
|
||||
|
||||
return usersRoles.build();
|
||||
}
|
||||
|
||||
public static void writeFile(Map<String, String[]> userRoles, Path path) {
|
||||
/**
|
||||
* Accepts a mapping of user -> list of roles
|
||||
*/
|
||||
public static void writeFile(Map<String, String[]> userToRoles, Path path) {
|
||||
HashMap<String, List<String>> roleToUsers = new HashMap<>();
|
||||
for (Map.Entry<String, String[]> entry : userToRoles.entrySet()) {
|
||||
for (String role : entry.getValue()) {
|
||||
List<String> users = roleToUsers.get(role);
|
||||
if (users == null) {
|
||||
users = new ArrayList<>();
|
||||
roleToUsers.put(role, users);
|
||||
}
|
||||
users.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path, Charsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE, StandardOpenOption.WRITE))) {
|
||||
for (Map.Entry<String, String[]> entry : userRoles.entrySet()) {
|
||||
writer.printf(Locale.ROOT, "%s:%s%s", entry.getKey(), Strings.arrayToCommaDelimitedString(entry.getValue()), System.lineSeparator());
|
||||
for (Map.Entry<String, List<String>> entry : roleToUsers.entrySet()) {
|
||||
writer.printf(Locale.ROOT, "%s:%s%s", entry.getKey(), Strings.collectionToCommaDelimitedString(entry.getValue()), System.lineSeparator());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new ElasticsearchException("Could not write users file [" + path.toAbsolutePath() + "], please check file permissions");
|
||||
|
|
|
@ -90,7 +90,7 @@ public class FileUserRolesStoreTests extends ElasticsearchTestCase {
|
|||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(tmp, Charsets.UTF_8, StandardOpenOption.APPEND)) {
|
||||
writer.newLine();
|
||||
writer.append("user4:role4,role5");
|
||||
writer.append("role4:user4\nrole5:user4\n");
|
||||
}
|
||||
|
||||
if (!latch.await(5, TimeUnit.SECONDS)) {
|
||||
|
@ -141,15 +141,15 @@ public class FileUserRolesStoreTests extends ElasticsearchTestCase {
|
|||
|
||||
@Test
|
||||
public void testThatEmptyUserNameDoesNotThrowException() throws Exception {
|
||||
assertInvalidInputIsSilentlyIgnored(":role1,role2");
|
||||
assertInvalidInputIsSilentlyIgnored(" :role1,role2");
|
||||
assertInvalidInputIsSilentlyIgnored(":user1,user2");
|
||||
assertInvalidInputIsSilentlyIgnored(" :user1,user2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatEmptyRoleDoesNotThrowException() throws Exception {
|
||||
assertInvalidInputIsSilentlyIgnored("user:");
|
||||
assertInvalidInputIsSilentlyIgnored("user: ");
|
||||
assertInvalidInputIsSilentlyIgnored("user: , ");
|
||||
assertInvalidInputIsSilentlyIgnored("role:");
|
||||
assertInvalidInputIsSilentlyIgnored("role: ");
|
||||
assertInvalidInputIsSilentlyIgnored("role: , ");
|
||||
}
|
||||
|
||||
private File writeUsersRoles(String input) throws Exception {
|
||||
|
|
|
@ -102,15 +102,14 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
|
||||
assertFileExists(userRolesFile);
|
||||
lines = Files.readLines(userRolesFile, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(1));
|
||||
line = lines.get(0);
|
||||
assertThat(line, equalTo("user1:r1,r2"));
|
||||
assertThat(lines, hasSize(2));
|
||||
assertThat(lines, containsInAnyOrder("r1:user1", "r2:user1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseradd_Cmd_Append() throws Exception {
|
||||
File userFile = writeFile("user2:hash2");
|
||||
File userRolesFile = writeFile("user2:r3,r4");
|
||||
File userRolesFile = writeFile("r3:user2\nr4:user2");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", userFile)
|
||||
.put("shield.authc.esusers.files.users_roles", userRolesFile)
|
||||
|
@ -138,14 +137,14 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
|
||||
assertFileExists(userRolesFile);
|
||||
lines = Files.readLines(userRolesFile, Charsets.UTF_8);
|
||||
assertThat(lines, hasSize(2));
|
||||
assertThat(lines, containsInAnyOrder("user2:r3,r4", "user1:r1,r2"));
|
||||
assertThat(lines, hasSize(4));
|
||||
assertThat(lines, containsInAnyOrder("r1:user1", "r2:user1", "r3:user2", "r4:user2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseradd_Cmd_AddingUserWithoutRolesDoesNotAddEmptyRole() throws Exception {
|
||||
File userFile = writeFile("user2:hash2");
|
||||
File userRolesFile = writeFile("user2:r3,r4");
|
||||
File userRolesFile = writeFile("r3:user2\nr4:user2");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", userFile)
|
||||
.put("shield.authc.esusers.files.users_roles", userRolesFile)
|
||||
|
@ -158,8 +157,8 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
|
||||
assertFileExists(userRolesFile);
|
||||
List<String> lines = Files.readLines(userRolesFile, Charsets.UTF_8);
|
||||
assertThat(lines, hasSize(1));
|
||||
assertThat(lines, not(hasItem(startsWith("user1"))));
|
||||
assertThat(lines, hasSize(2));
|
||||
assertThat(lines, not(hasItem(containsString("user1"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -198,7 +197,7 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
@Test
|
||||
public void testUserdel_Cmd() throws Exception {
|
||||
File userFile = writeFile("user1:hash2");
|
||||
File userRolesFile = writeFile("user1:r3,r4");
|
||||
File userRolesFile = writeFile("r3:user1\nr4:user1");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", userFile)
|
||||
.put("shield.authc.esusers.files.users_roles", userRolesFile)
|
||||
|
@ -221,7 +220,7 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
@Test
|
||||
public void testUserdel_Cmd_MissingUser() throws Exception {
|
||||
File userFile = writeFile("user1:hash2");
|
||||
File userRolesFile = writeFile("user1:r3,r4");
|
||||
File userRolesFile = writeFile("r3:user1\nr4:user1");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", userFile)
|
||||
.put("shield.authc.esusers.files.users_roles", userRolesFile)
|
||||
|
@ -238,7 +237,7 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
|
||||
assertFileExists(userRolesFile);
|
||||
lines = Files.readLines(userRolesFile, Charsets.UTF_8);
|
||||
assertThat(lines.size(), is(1));
|
||||
assertThat(lines, hasSize(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -397,13 +396,13 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
assertThat(userRoles.keySet(), hasSize(2));
|
||||
assertThat(userRoles.keySet(), hasItems("admin", "user"));
|
||||
assertThat(userRoles.get("admin"), arrayContaining("admin"));
|
||||
assertThat(userRoles.get("user"), arrayContaining("user", "foo"));
|
||||
assertThat(userRoles.get("user"), arrayContainingInAnyOrder("user", "foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoles_Cmd_removingRoleWorks() throws Exception {
|
||||
File usersFile = writeFile("admin:hash\nuser:hash");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user,foo,bar\n");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo: user\nbar: user\n");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", usersFile)
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
|
||||
|
@ -418,13 +417,13 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
assertThat(userRoles.keySet(), hasSize(2));
|
||||
assertThat(userRoles.keySet(), hasItems("admin", "user"));
|
||||
assertThat(userRoles.get("admin"), arrayContaining("admin"));
|
||||
assertThat(userRoles.get("user"), arrayContaining("user", "bar"));
|
||||
assertThat(userRoles.get("user"), arrayContainingInAnyOrder("user", "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoles_Cmd_addingAndRemovingRoleWorks() throws Exception {
|
||||
File usersFile = writeFile("admin:hash\nuser:hash");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser:user,foo,bar\n");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser:user\nfoo:user\nbar:user\n");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", usersFile)
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
|
||||
|
@ -439,13 +438,13 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
assertThat(userRoles.keySet(), hasSize(2));
|
||||
assertThat(userRoles.keySet(), hasItems("admin", "user"));
|
||||
assertThat(userRoles.get("admin"), arrayContaining("admin"));
|
||||
assertThat(userRoles.get("user"), arrayContaining("user", "bar", "newrole"));
|
||||
assertThat(userRoles.get("user"), arrayContainingInAnyOrder("user", "bar", "newrole"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoles_Cmd_removingLastRoleRemovesEntryFromRolesFile() throws Exception {
|
||||
File usersFile = writeFile("admin:hash\nuser:hash");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser:user,foo,bar\n");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser:user\nfoo:user\nbar:user\n");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", usersFile)
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
|
||||
|
@ -457,13 +456,13 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
assertThat(status, is(CliTool.ExitStatus.OK));
|
||||
|
||||
List<String> usersRoleFileLines = Files.readLines(usersRoleFile, Charsets.UTF_8);
|
||||
assertThat(usersRoleFileLines, not(hasItem(startsWith("user:"))));
|
||||
assertThat(usersRoleFileLines, not(hasItem(containsString("user"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoles_Cmd_userNotFound() throws Exception {
|
||||
File usersFile = writeFile("admin:hash\nuser:hash");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user,foo,bar\n");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", usersFile)
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
|
||||
|
@ -478,7 +477,7 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
@Test
|
||||
public void testRoles_Cmd_testNotAddingOrRemovingRolesShowsListingOfRoles() throws Exception {
|
||||
File usersFile = writeFile("admin:hash\nuser:hash");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser:user,foo,bar\n");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser:user\nfoo:user\nbar:user\n");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users", usersFile)
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
|
||||
|
@ -523,7 +522,7 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
|
||||
@Test
|
||||
public void testListUsersAndRoles_Cmd_listAllUsers() throws Exception {
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user,foo,bar\n");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
|
||||
.build();
|
||||
|
@ -540,7 +539,7 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
|
||||
@Test
|
||||
public void testListUsersAndRoles_Cmd_listSingleUser() throws Exception {
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user,foo,bar\n");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n");
|
||||
File usersFile = writeFile("admin:{plain}changeme\nuser:{plain}changeme\nno-roles-user:{plain}changeme\n");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
|
||||
|
@ -559,7 +558,7 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
|
||||
@Test
|
||||
public void testListUsersAndRoles_Cmd_listSingleUserNotFound() throws Exception {
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user,foo,bar\n");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
|
||||
.build();
|
||||
|
@ -574,7 +573,7 @@ public class ESUsersToolTests extends CliToolTestCase {
|
|||
@Test
|
||||
public void testListUsersAndRoles_Cmd_testThatUsersWithoutRolesAreListed() throws Exception {
|
||||
File usersFile = writeFile("admin:{plain}changeme\nuser:{plain}changeme\nno-roles-user:{plain}changeme\n");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user,foo,bar\n");
|
||||
File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n");
|
||||
Settings settings = ImmutableSettings.builder()
|
||||
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
|
||||
.put("shield.authc.esusers.files.users", usersFile)
|
||||
|
|
|
@ -49,7 +49,7 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
|
|||
|
||||
public static final String CONFIG_IPFILTER_ALLOW_ALL = "allow: all\n";
|
||||
public static final String CONFIG_STANDARD_USER = DEFAULT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n";
|
||||
public static final String CONFIG_STANDARD_USER_ROLES = DEFAULT_USER_NAME + ":" + DEFAULT_ROLE + "\n";
|
||||
public static final String CONFIG_STANDARD_USER_ROLES = DEFAULT_ROLE + ":" + DEFAULT_USER_NAME+ "\n";
|
||||
public static final String CONFIG_ROLE_ALLOW_ALL = DEFAULT_ROLE + ":\n" +
|
||||
" cluster: ALL\n" +
|
||||
" indices:\n" +
|
||||
|
|
|
@ -3,4 +3,6 @@ md5: $apr1$R3DdqiAZ$aljIkaIVPSarmDMlJUBBP.
|
|||
crypt: hsP1PYSLsEEvs
|
||||
plain: {plain}test123
|
||||
sha:{SHA}cojt0Pw//L6ToM8G41aOKFIWh7w=
|
||||
# this is a comment line
|
||||
# another comment line
|
||||
bcrypt10: $2y$10$FMhmFjwU5.qxQ/BsEciS9OqcJVkFMgXMo4uH5CelOR1j4N9zIv67e
|
|
@ -1,3 +1,6 @@
|
|||
user1:role1,role2,role3
|
||||
user2 : role2 , role3
|
||||
user3:role3
|
||||
role1:user1
|
||||
role2: user1,user2
|
||||
# this is a comment line
|
||||
role3: user1, user2 , user3
|
||||
# another comment line
|
||||
# and another one
|
Loading…
Reference in New Issue