From 33b89301fb054fc117fad122d9980fb1d4dd0861 Mon Sep 17 00:00:00 2001 From: uboness Date: Fri, 24 Oct 2014 20:43:42 +0200 Subject: [PATCH 1/3] Enforces cluster permission checks for all cluster actions Enforcing means that cluster actions will not be evaluated (as a fallback) by Index permissions. This enables us to move what typically would be considered indices actions and put them under the cluster privileges (a good example for this are all the template management APIs... we want to enforce cluster admin privileges over them). Original commit: elastic/x-pack-elasticsearch@ee870954f2973b90d2daea5ad046545c33932b42 --- .../shield/authz/Permission.java | 11 +- .../MultipleIndicesPermissionsTests.java | 16 +++ .../shield/PermissionPrecedenceTests.java | 126 ++++++++++++++++++ .../shield/test/ShieldIntegrationTest.java | 12 +- 4 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java diff --git a/src/main/java/org/elasticsearch/shield/authz/Permission.java b/src/main/java/org/elasticsearch/shield/authz/Permission.java index 433c719a64f..f084f70f602 100644 --- a/src/main/java/org/elasticsearch/shield/authz/Permission.java +++ b/src/main/java/org/elasticsearch/shield/authz/Permission.java @@ -46,6 +46,9 @@ public interface Permission { static class Global implements Permission { + final static Predicate clusterActionMatcher = Privilege.Cluster.ALL.predicate(); + final static Predicate indicesActionMatcher = Privilege.Index.ALL.predicate(); + private final Cluster cluster; private final Indices indices; @@ -67,11 +70,11 @@ public interface Permission { } public boolean check(User user, String action, TransportRequest request, MetaData metaData) { - if (cluster != null && cluster.check(user, action, request, metaData)) { - return true; + if (clusterActionMatcher.apply(action)) { + return cluster != null && cluster.check(user, action, request, metaData); } - if (indices != null && indices.check(user, action, request, metaData)) { - return true; + if (indicesActionMatcher.apply(action)) { + return indices != null && indices.check(user, action, request, metaData); } return false; } diff --git a/src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java b/src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java index f27a24de418..be83b19cc83 100644 --- a/src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java +++ b/src/test/java/org/elasticsearch/shield/MultipleIndicesPermissionsTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.shield; import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.MultiSearchResponse; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.Client; import org.elasticsearch.shield.authz.AuthorizationException; @@ -94,5 +95,20 @@ public class MultipleIndicesPermissionsTests extends ShieldIntegrationTest { } catch (AuthorizationException ae) { // expected } + + MultiSearchResponse msearchResponse = client.prepareMultiSearch() + .add(client.prepareSearch("test")) + .add(client.prepareSearch("test1")) + .get(); + MultiSearchResponse.Item[] items = msearchResponse.getResponses(); + assertThat(items.length, is(2)); + assertThat(items[0].isFailure(), is(false)); + searchResponse = items[0].getResponse(); + assertNoFailures(searchResponse); + assertHitCount(searchResponse, 1); + assertThat(items[1].isFailure(), is(false)); + searchResponse = items[1].getResponse(); + assertNoFailures(searchResponse); + assertHitCount(searchResponse, 1); } } diff --git a/src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java b/src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java new file mode 100644 index 00000000000..19f1718b6fc --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/PermissionPrecedenceTests.java @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.shield; + +import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse; +import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.cluster.ClusterService; +import org.elasticsearch.cluster.metadata.IndexTemplateMetaData; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.shield.authc.support.SecuredString; +import org.elasticsearch.shield.authc.support.SecuredStringTests; +import org.elasticsearch.shield.authz.AuthorizationException; +import org.elasticsearch.shield.test.ShieldIntegrationTest; +import org.junit.Test; + +import java.util.List; + +import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; +import static org.hamcrest.Matchers.hasSize; + +/** + * This test makes sure that if an action is a cluster action (according to our + * internal categorization in shield, then we apply the cluster priv checks and don't + * fallback on the indices privs at all. In particular, this is useful when we want to treat + * actions that are normally categorized as index actions as cluster actions - for example, + * index template actions. + */ +public class PermissionPrecedenceTests extends ShieldIntegrationTest { + + static final String ROLES = + "admin:\n" + + " cluster: all\n" + + " indices:\n" + + " '*': all\n" + + "\n" + + "user:\n" + + " indices:\n" + + " 'test_*': all\n"; + + static final String USERS = + "admin:{plain}test123\n" + + "user:{plain}test123\n"; + + static final String USERS_ROLES = + "admin:admin\n" + + "user:user\n"; + + @Override + protected String configRole() { + return ROLES; + } + + @Override + protected String configUsers() { + return USERS; + } + + @Override + protected String configUsersRoles() { + return USERS_ROLES; + } + + @Override + protected String getClientUsername() { + return "admin"; + } + + @Override + protected SecuredString getClientPassword() { + return SecuredStringTests.build("test123"); + } + + @Test + public void testDifferetCombinationsOfIndices() throws Exception { + + ClusterService clusterService = internalCluster().getInstance(ClusterService.class); + TransportAddress address = clusterService.localNode().address(); + + TransportClient client = new TransportClient(ImmutableSettings.builder() + .put("cluster.name", internalCluster().getClusterName()) + .put("node.mode", "network") + .put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient"))) + .addTransportAddress(address); + + // first lets try with "admin"... all should work + + PutIndexTemplateResponse putResponse = client.admin().indices().preparePutTemplate("template1") + .setTemplate("test_*") + .putHeader("Authorization", basicAuthHeaderValue("admin", SecuredStringTests.build("test123"))) + .get(); + assertAcked(putResponse); + + GetIndexTemplatesResponse getResponse = client.admin().indices().prepareGetTemplates("template1") + .putHeader("Authorization", basicAuthHeaderValue("admin", SecuredStringTests.build("test123"))) + .get(); + List templates = getResponse.getIndexTemplates(); + assertThat(templates, hasSize(1)); + + // now lets try with "user" + + try { + client.admin().indices().preparePutTemplate("template1") + .setTemplate("test_*") + .putHeader("Authorization", basicAuthHeaderValue("user", SecuredStringTests.build("test123"))) + .get(); + fail("expected an authorization exception as template APIs should require cluster ALL permission"); + } catch (AuthorizationException ae) { + // expected; + } + + try { + client.admin().indices().prepareGetTemplates("template1") + .putHeader("Authorization", basicAuthHeaderValue("user", SecuredStringTests.build("test123"))) + .get(); + fail("expected an authorization exception as template APIs should require cluster ALL permission"); + } catch (AuthorizationException ae) { + // expected + } + } +} diff --git a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java index d2825fa077f..101f1afd9b1 100644 --- a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java +++ b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java @@ -68,8 +68,8 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest .put("discovery.type", "zen") .put("node.mode", "network") .put("plugin.types", ShieldPlugin.class.getName()) - .put("shield.authc.esusers.files.users", writeFile(folder, "users", CONFIG_STANDARD_USER)) - .put("shield.authc.esusers.files.users_roles", writeFile(folder, "users_roles", CONFIG_STANDARD_USER_ROLES)) + .put("shield.authc.esusers.files.users", writeFile(folder, "users", configUsers())) + .put("shield.authc.esusers.files.users_roles", writeFile(folder, "users_roles", configUsersRoles())) .put("shield.authz.store.files.roles", writeFile(folder, "roles.yml", configRole())) .put("shield.transport.n2n.ip_filter.file", writeFile(folder, "ip_filter.yml", CONFIG_IPFILTER_ALLOW_ALL)) .put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "testnode")) @@ -87,6 +87,14 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest return CONFIG_ROLE_ALLOW_ALL; } + protected String configUsers() { + return CONFIG_STANDARD_USER; + } + + protected String configUsersRoles() { + return CONFIG_STANDARD_USER_ROLES; + } + @Override protected Settings transportClientSettings() { return ImmutableSettings.builder() From 99ddffe5104bf6903a09b09201453a2d2598adaf Mon Sep 17 00:00:00 2001 From: uboness Date: Fri, 24 Oct 2014 03:18:17 +0200 Subject: [PATCH 2/3] esusers tool - added warnings when using unknown roles When assigning roles to users, we now show a warning if the assigned roles don't exist. Closes elastic/elasticsearch#209 Original commit: elastic/x-pack-elasticsearch@c2e9bf03eb0459090094b91327e2a2d0a594e6eb --- .../authc/esusers/tool/ESUsersTool.java | 94 +++++++++++++++++-- .../shield/authz/store/FileRolesStore.java | 4 +- .../authc/esusers/tool/ESUsersToolTests.java | 29 ++++++ 3 files changed, 115 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersTool.java b/src/main/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersTool.java index 9bf61e424d7..a7bc41776ca 100644 --- a/src/main/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersTool.java +++ b/src/main/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersTool.java @@ -11,16 +11,19 @@ import org.elasticsearch.common.cli.CliTool; import org.elasticsearch.common.cli.CliToolConfig; import org.elasticsearch.common.cli.Terminal; import org.elasticsearch.common.cli.commons.CommandLine; -import org.elasticsearch.common.collect.Lists; -import org.elasticsearch.common.collect.Maps; -import org.elasticsearch.common.collect.ObjectArrays; -import org.elasticsearch.common.collect.Sets; +import org.elasticsearch.common.collect.*; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; +import org.elasticsearch.shield.User; 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.AuthorizationException; +import org.elasticsearch.shield.authz.AuthorizationService; +import org.elasticsearch.shield.authz.Permission; +import org.elasticsearch.shield.authz.store.FileRolesStore; +import org.elasticsearch.transport.TransportRequest; import java.nio.file.Files; import java.nio.file.Path; @@ -118,6 +121,7 @@ public class ESUsersTool extends CliTool { @Override public ExitStatus execute(Settings settings, Environment env) throws Exception { + verifyRoles(terminal, settings, env, roles); Path file = FileUserPasswdStore.resolveFile(settings, env); Map users = new HashMap<>(FileUserPasswdStore.parseFile(file, null)); if (users.containsKey(username)) { @@ -168,8 +172,6 @@ public class ESUsersTool extends CliTool { char[] passwd = users.remove(username); if (passwd != null) { FileUserPasswdStore.writeFile(users, file); - } else { - terminal.println("Warning: users file [%s] did not contain password entry for user [%s]", file.toAbsolutePath(), username); } } @@ -179,8 +181,6 @@ public class ESUsersTool extends CliTool { String[] roles = userRoles.remove(username); if (roles != null) { FileUserRolesStore.writeFile(userRoles, file); - } else { - terminal.println("Warning: users_roles file [%s] did not contain roles entry for user [%s]", file.toAbsolutePath(), username); } } @@ -311,6 +311,7 @@ public class ESUsersTool extends CliTool { if (userRoles.get(username) != null) { roles.addAll(Arrays.asList(userRoles.get(username))); } + verifyRoles(terminal, settings, env, addRoles); roles.addAll(Arrays.asList(addRoles)); roles.removeAll(Arrays.asList(removeRoles)); @@ -347,6 +348,7 @@ public class ESUsersTool extends CliTool { @Override public ExitStatus execute(Settings settings, Environment env) throws Exception { + ImmutableMap knownRoles = loadRoles(terminal, settings, env); Path userRolesFilePath = FileUserRolesStore.resolveFile(settings, env); Map userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null); Path userFilePath = FileUserPasswdStore.resolveFile(settings, env); @@ -359,13 +361,27 @@ public class ESUsersTool extends CliTool { } if (userRoles.containsKey(username)) { - terminal.println("%-15s: %s", username, Joiner.on(",").useForNull("-").join(userRoles.get(username))); + String[] roles = userRoles.get(username); + Set unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles.keySet()); + String[] markedRoles = markUnknownRoles(roles, unknownRoles); + terminal.println("%-15s: %s", username, Joiner.on(",").useForNull("-").join(markedRoles)); + if (!unknownRoles.isEmpty()) { + // at least one role is marked... so printing the legend + Path rolesFile = FileRolesStore.resolveFile(settings, env).toAbsolutePath(); + terminal.println(); + terminal.println(" [*] An unknown role. Please check [%s] to see available roles", rolesFile.toAbsolutePath()); + } } else { terminal.println("%-15s: -", username); } } else { + boolean unknownRolesFound = false; for (Map.Entry entry : userRoles.entrySet()) { - terminal.println("%-15s: %s", entry.getKey(), Joiner.on(",").join(entry.getValue())); + String[] roles = entry.getValue(); + Set unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles.keySet()); + String[] markedRoles = markUnknownRoles(roles, unknownRoles); + terminal.println("%-15s: %s", entry.getKey(), Joiner.on(",").join(markedRoles)); + unknownRolesFound = unknownRolesFound || !unknownRoles.isEmpty(); } // list users without roles Set usersWithoutRoles = Sets.newHashSet(users); @@ -374,9 +390,67 @@ public class ESUsersTool extends CliTool { terminal.println("%-15s: -", user); } } + if (unknownRolesFound) { + // at least one role is marked... so printing the legend + Path rolesFile = FileRolesStore.resolveFile(settings, env).toAbsolutePath(); + terminal.println(); + terminal.println(" [*] An unknown role. Please check [%s] to see available roles", rolesFile.toAbsolutePath()); + } } return ExitStatus.OK; } } + + private static ImmutableMap loadRoles(Terminal terminal, Settings settings, Environment env) { + Path rolesFile = FileRolesStore.resolveFile(settings, env); + try { + return FileRolesStore.parseFile(rolesFile, null, new DummyAuthzService()); + } 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()); + } + return null; + } + + private static String[] markUnknownRoles(String[] roles, Set unknownRoles) { + if (unknownRoles.isEmpty()) { + return roles; + } + String[] marked = new String[roles.length]; + for (int i = 0; i < roles.length; i++) { + if (unknownRoles.contains(roles[i])) { + marked[i] = roles[i] + "*"; + } else { + marked[i] = roles[i]; + } + } + return marked; + } + + private static void verifyRoles(Terminal terminal, Settings settings, Environment env, String[] roles) { + ImmutableMap knownRoles = loadRoles(terminal, settings, env); + if (knownRoles == null) { + return; + } + Set unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles.keySet()); + 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. " + + "Nonetheless the user will still be associated with all specified roles", + Strings.collectionToCommaDelimitedString(unknownRoles), rolesFile.toAbsolutePath()); + } + } + + private static class DummyAuthzService implements AuthorizationService { + @Override + public ImmutableList authorizedIndicesAndAliases(User user, String action) { + return ImmutableList.of(); + } + + @Override + public void authorize(User user, String action, TransportRequest request) throws AuthorizationException { + + } + } } \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/shield/authz/store/FileRolesStore.java b/src/main/java/org/elasticsearch/shield/authz/store/FileRolesStore.java index 28e34d8e72d..41734e600ba 100644 --- a/src/main/java/org/elasticsearch/shield/authz/store/FileRolesStore.java +++ b/src/main/java/org/elasticsearch/shield/authz/store/FileRolesStore.java @@ -59,7 +59,7 @@ public class FileRolesStore extends AbstractComponent implements RolesStore { public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, AuthorizationService authzService, Listener listener) { super(settings); - file = resolveFile(componentSettings, env); + file = resolveFile(settings, env); permissions = parseFile(file, logger, authzService); FileWatcher watcher = new FileWatcher(file.getParent().toFile()); watcher.addListener(new FileListener(authzService)); @@ -73,7 +73,7 @@ public class FileRolesStore extends AbstractComponent implements RolesStore { } public static Path resolveFile(Settings settings, Environment env) { - String location = settings.get("files.roles"); + String location = settings.get("shield.authz.store.files.roles"); if (location == null) { return ShieldPlugin.resolveConfigFile(env, "roles.yml"); } diff --git a/src/test/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersToolTests.java b/src/test/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersToolTests.java index c6e02e8711d..1f05fa6ee73 100644 --- a/src/test/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersToolTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/esusers/tool/ESUsersToolTests.java @@ -478,9 +478,11 @@ public class ESUsersToolTests extends CliToolTestCase { public void testRoles_Cmd_testNotAddingOrRemovingRolesShowsListingOfRoles() throws Exception { File usersFile = writeFile("admin:hash\nuser:hash"); File usersRoleFile = writeFile("admin: admin\nuser:user\nfoo:user\nbar:user\n"); + File rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all\n\nbar:\n cluster: all"); Settings settings = ImmutableSettings.builder() .put("shield.authc.esusers.files.users", usersFile) .put("shield.authc.esusers.files.users_roles", usersRoleFile) + .put("shield.authz.store.files.roles", rolesFile) .build(); CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal(); @@ -495,9 +497,11 @@ public class ESUsersToolTests extends CliToolTestCase { public void testRoles_cmd_testRoleCanBeAddedWhenUserIsNotInRolesFile() throws Exception { File usersFile = writeFile("admin:hash\nuser:hash"); File usersRoleFile = writeFile("admin: admin\n"); + File rolesFile = writeFile("admin:\n cluster: all\n\nmyrole:\n cluster: all"); Settings settings = ImmutableSettings.builder() .put("shield.authc.esusers.files.users", usersFile) .put("shield.authc.esusers.files.users_roles", usersRoleFile) + .put("shield.authz.store.files.roles", rolesFile) .build(); CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal(); @@ -523,8 +527,10 @@ public class ESUsersToolTests extends CliToolTestCase { @Test public void testListUsersAndRoles_Cmd_listAllUsers() throws Exception { File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n"); + File rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all\n\nbar:\n cluster: all"); Settings settings = ImmutableSettings.builder() .put("shield.authc.esusers.files.users_roles", usersRoleFile) + .put("shield.authz.store.files.roles", rolesFile) .build(); CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal(); @@ -537,13 +543,34 @@ public class ESUsersToolTests extends CliToolTestCase { assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(allOf(containsString("user"), containsString("user,foo,bar")))); } + @Test + public void testListUsersAndRoles_Cmd_listAllUsers_WithUnknownRoles() throws Exception { + File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n"); + File rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all"); + Settings settings = ImmutableSettings.builder() + .put("shield.authc.esusers.files.users_roles", usersRoleFile) + .put("shield.authz.store.files.roles", rolesFile) + .build(); + + CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal(); + ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, null); + CliTool.ExitStatus status = execute(cmd, settings); + + assertThat(status, is(CliTool.ExitStatus.OK)); + assertThat(catchTerminalOutput.getTerminalOutput(), hasSize(greaterThanOrEqualTo(2))); + assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(containsString("admin"))); + assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(allOf(containsString("user"), containsString("user,foo*,bar*")))); + } + @Test public void testListUsersAndRoles_Cmd_listSingleUser() throws Exception { 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"); + File rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all"); Settings settings = ImmutableSettings.builder() .put("shield.authc.esusers.files.users_roles", usersRoleFile) .put("shield.authc.esusers.files.users", usersFile) + .put("shield.authz.store.files.roles", rolesFile) .build(); CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal(); @@ -574,9 +601,11 @@ public class ESUsersToolTests extends CliToolTestCase { 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\nfoo:user\nbar:user\n"); + File rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all\n\nbar:\n cluster: all"); Settings settings = ImmutableSettings.builder() .put("shield.authc.esusers.files.users_roles", usersRoleFile) .put("shield.authc.esusers.files.users", usersFile) + .put("shield.authz.store.files.roles", rolesFile) .build(); CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal(); From 25d21570d6c9074409c80a5251a16cf3bd2a2130 Mon Sep 17 00:00:00 2001 From: uboness Date: Mon, 27 Oct 2014 16:10:28 +0100 Subject: [PATCH 3/3] Better shield user configuration Added `shield.user` setting so that the clients won't need to go through the unnatural and tedious process of configuring the `Authorization` header directly (that also requires the user to applicat the base64(username:password) logic. Now, the user can just set the following settings to bind a user to the client: ```yaml shield.user: 'username:password' ``` Original commit: elastic/x-pack-elasticsearch@94be3abd92d0320ad34c259c588adad5748dedb2 --- .../shield/plugin/ShieldPlugin.java | 32 +++++++++++++++++++ .../shield/test/ShieldIntegrationTest.java | 20 +++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/elasticsearch/shield/plugin/ShieldPlugin.java b/src/main/java/org/elasticsearch/shield/plugin/ShieldPlugin.java index a57f4ed8fe8..93019e01cd0 100644 --- a/src/main/java/org/elasticsearch/shield/plugin/ShieldPlugin.java +++ b/src/main/java/org/elasticsearch/shield/plugin/ShieldPlugin.java @@ -5,11 +5,17 @@ */ package org.elasticsearch.shield.plugin; +import org.elasticsearch.client.support.Headers; import org.elasticsearch.common.collect.ImmutableList; import org.elasticsearch.common.inject.Module; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.AbstractPlugin; import org.elasticsearch.shield.ShieldModule; +import org.elasticsearch.shield.ShieldSettingsException; +import org.elasticsearch.shield.authc.support.SecuredString; +import org.elasticsearch.shield.authc.support.UsernamePasswordToken; import java.io.File; import java.nio.file.Path; @@ -22,6 +28,12 @@ public class ShieldPlugin extends AbstractPlugin { public static final String NAME = "shield"; + private final Settings settings; + + public ShieldPlugin(Settings settings) { + this.settings = settings; + } + @Override public String name() { return NAME; @@ -37,6 +49,26 @@ public class ShieldPlugin extends AbstractPlugin { return ImmutableList.>of(ShieldModule.class); } + @Override + public Settings additionalSettings() { + String setting = Headers.PREFIX + "." + UsernamePasswordToken.BASIC_AUTH_HEADER; + if (settings.get(setting) != null) { + return ImmutableSettings.EMPTY; + } + String user = settings.get("shield.user"); + if (user == null) { + return ImmutableSettings.EMPTY; + } + int i = user.indexOf(":"); + if (i < 0 || i == user.length() - 1) { + throw new ShieldSettingsException("Invalid [shield.user] settings. Must be in the form of \":\""); + } + String username = user.substring(0, i); + String password = user.substring(i + 1); + return ImmutableSettings.builder() + .put(setting, UsernamePasswordToken.basicAuthHeaderValue(username, new SecuredString(password.toCharArray()))).build(); + } + public static Path configDir(Environment env) { return new File(env.configFile(), NAME).toPath(); } diff --git a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java index 101f1afd9b1..e037b4790cd 100644 --- a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java +++ b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java @@ -63,7 +63,6 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest File folder = newFolder(); ImmutableSettings.Builder builder = ImmutableSettings.builder() - .put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword())) .put("discovery.zen.ping.multicast.enabled", false) .put("discovery.type", "zen") .put("node.mode", "network") @@ -76,6 +75,8 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest .put("shield.audit.enabled", true) .put("plugins.load_classpath_plugins", false); + setUser(builder); + if (OsUtils.MAC) { builder.put("network.host", randomBoolean() ? "127.0.0.1" : "::1"); } @@ -97,13 +98,24 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest @Override protected Settings transportClientSettings() { - return ImmutableSettings.builder() + ImmutableSettings.Builder builder = ImmutableSettings.builder() .put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword())) .put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName()) .put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false) .put("node.mode", "network") - .put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient")) - .build(); + .put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient")); + + setUser(builder); + + return builder.build(); + } + + protected void setUser(ImmutableSettings.Builder settings) { + if (randomBoolean()) { + settings.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword())); + } else { + settings.put("shield.user", getClientUsername() + ":" + new String(getClientPassword().internalChars())); + } } protected String writeFile(File folder, String name, String content) {