forked from opensearch-project/security
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ability for plugins to inject roles (opensearch-project#560)
- Loading branch information
1 parent
7c47751
commit 5a1e66a
Showing
6 changed files
with
333 additions
and
7 deletions.
There are no files selected for viewing
83 changes: 83 additions & 0 deletions
83
src/main/java/com/amazon/opendistroforelasticsearch/security/auth/RolesInjector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
package com.amazon.opendistroforelasticsearch.security.auth; | ||
|
||
import com.amazon.opendistroforelasticsearch.security.support.ConfigConstants; | ||
import com.amazon.opendistroforelasticsearch.security.user.User; | ||
import com.google.common.collect.ImmutableSet; | ||
import org.apache.commons.lang.StringUtils; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.elasticsearch.common.Strings; | ||
import org.elasticsearch.common.util.concurrent.ThreadContext; | ||
|
||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
/** | ||
* This is used to inject opendistro-roles into the request when there is no user involved, like periodic plugin | ||
* background jobs. The roles injection is done using thread-context at transport layer only. You can't inject | ||
* roles using REST api. Using this we can enforce fine-grained-access-control for the transport layer calls plugins make. | ||
* | ||
* Format for the injected string: user_name|role_1,role_2 | ||
* User name is ignored. And roles are opendistro-roles. | ||
*/ | ||
final public class RolesInjector { | ||
protected final Logger log = LogManager.getLogger(RolesInjector.class); | ||
|
||
public RolesInjector() { | ||
//empty | ||
} | ||
|
||
public Set<String> injectUserAndRoles(final ThreadContext ctx) { | ||
final String injectedUserAndRoles = ctx.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES); | ||
if (injectedUserAndRoles == null) { | ||
return null; | ||
} | ||
log.debug("Injected roles: {}", injectedUserAndRoles); | ||
|
||
String[] strs = injectedUserAndRoles.split("\\|"); | ||
if (strs.length == 0) { | ||
log.error("Roles injected string malformed, could not extract parts. User string was '{}.'" + | ||
" Roles injection failed.", injectedUserAndRoles); | ||
return null; | ||
} | ||
|
||
if (StringUtils.isEmpty(StringUtils.trim(strs[0]))) { | ||
log.error("Username must be provided, injected string was '{}.' Roles injection failed.", injectedUserAndRoles); | ||
return null; | ||
} | ||
User user = new User(strs[0]); | ||
|
||
if (strs.length < 2 || StringUtils.isEmpty(StringUtils.trim(strs[0]))) { | ||
log.error("Roles must be provided, injected string was '{}.' Roles injection failed.", injectedUserAndRoles); | ||
return null; | ||
} | ||
Set<String> roles = ImmutableSet.copyOf(strs[1].split(",")); | ||
|
||
if(user != null && roles != null) { | ||
addUser(user, ctx); | ||
} | ||
return roles; | ||
} | ||
|
||
private void addUser(final User user, final ThreadContext threadContext) { | ||
if(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) != null) | ||
return; | ||
|
||
threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
src/test/java/com/amazon/opendistroforelasticsearch/security/RolesInjectorIntegTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
package com.amazon.opendistroforelasticsearch.security; | ||
|
||
import com.amazon.opendistroforelasticsearch.security.support.ConfigConstants; | ||
import com.amazon.opendistroforelasticsearch.security.test.DynamicSecurityConfig; | ||
import com.amazon.opendistroforelasticsearch.security.test.SingleClusterTest; | ||
import org.elasticsearch.ElasticsearchSecurityException; | ||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; | ||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; | ||
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; | ||
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest; | ||
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse; | ||
import org.elasticsearch.client.Client; | ||
import org.elasticsearch.cluster.health.ClusterHealthStatus; | ||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; | ||
import org.elasticsearch.cluster.service.ClusterService; | ||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.common.xcontent.NamedXContentRegistry; | ||
import org.elasticsearch.env.Environment; | ||
import org.elasticsearch.env.NodeEnvironment; | ||
import org.elasticsearch.node.Node; | ||
import org.elasticsearch.node.PluginAwareNode; | ||
import org.elasticsearch.plugins.ActionPlugin; | ||
import org.elasticsearch.plugins.Plugin; | ||
import org.elasticsearch.repositories.RepositoriesService; | ||
import org.elasticsearch.script.ScriptService; | ||
import org.elasticsearch.threadpool.ThreadPool; | ||
import org.elasticsearch.transport.Netty4Plugin; | ||
import org.elasticsearch.watcher.ResourceWatcherService; | ||
import org.junit.Assert; | ||
import org.junit.Test; | ||
|
||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.function.Supplier; | ||
|
||
public class RolesInjectorIntegTest extends SingleClusterTest { | ||
|
||
public static class RolesInjectorPlugin extends Plugin implements ActionPlugin { | ||
Settings settings; | ||
public static String injectedRoles = null; | ||
|
||
public RolesInjectorPlugin(final Settings settings, final Path configPath) { | ||
this.settings = settings; | ||
} | ||
|
||
@Override | ||
public Collection<Object> createComponents(Client client, ClusterService clusterService, ThreadPool threadPool, | ||
ResourceWatcherService resourceWatcherService, ScriptService scriptService, | ||
NamedXContentRegistry xContentRegistry, Environment environment, | ||
NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry, | ||
IndexNameExpressionResolver indexNameExpressionResolver, | ||
Supplier<RepositoriesService> repositoriesServiceSupplier) { | ||
if(injectedRoles != null) | ||
threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, injectedRoles); | ||
return new ArrayList<>(); | ||
} | ||
} | ||
|
||
//Wait for the security plugin to load roles. | ||
private void waitForInit(Client client) throws Exception { | ||
try { | ||
client.admin().cluster().health(new ClusterHealthRequest()).actionGet(); | ||
} catch (ElasticsearchSecurityException ex) { | ||
if(ex.getMessage().contains("Open Distro Security not initialized")) { | ||
Thread.sleep(500); | ||
waitForInit(client); | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
public void testRolesInject() throws Exception { | ||
setup(Settings.EMPTY, new DynamicSecurityConfig().setSecurityRoles("roles.yml"), Settings.EMPTY); | ||
|
||
Assert.assertEquals(clusterInfo.numNodes, clusterHelper.nodeClient().admin().cluster().health( | ||
new ClusterHealthRequest().waitForGreenStatus()).actionGet().getNumberOfNodes()); | ||
Assert.assertEquals(ClusterHealthStatus.GREEN, clusterHelper.nodeClient().admin().cluster(). | ||
health(new ClusterHealthRequest().waitForGreenStatus()).actionGet().getStatus()); | ||
|
||
final Settings tcSettings = Settings.builder() | ||
.put(minimumSecuritySettings(Settings.EMPTY).get(0)) | ||
.put("cluster.name", clusterInfo.clustername) | ||
.put("node.data", false) | ||
.put("node.master", false) | ||
.put("node.ingest", false) | ||
.put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") | ||
.put("path.logs", "./target/data/" + clusterInfo.clustername + "/cert/logs") | ||
.put("path.home", "./target") | ||
.put("node.name", "testclient") | ||
.put("discovery.initial_state_timeout", "8s") | ||
.put("opendistro_security.allow_default_init_securityindex", "true") | ||
.putList("discovery.zen.ping.unicast.hosts", clusterInfo.nodeHost + ":" + clusterInfo.nodePort) | ||
.build(); | ||
|
||
//1. Without roles injection. | ||
try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, | ||
OpenDistroSecurityPlugin.class, RolesInjectorPlugin.class).start()) { | ||
|
||
CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-1")).actionGet(); | ||
Assert.assertTrue(cir.isAcknowledged()); | ||
IndicesExistsResponse ier = node.client().admin().indices().exists(new IndicesExistsRequest("captain-logs-1")).actionGet(); | ||
Assert.assertTrue(ier.isExists()); | ||
} | ||
|
||
//2. With invalid roles, must throw security exception. | ||
RolesInjectorPlugin.injectedRoles = "invalid_user|invalid_role"; | ||
Exception exception = null; | ||
try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenDistroSecurityPlugin.class, RolesInjectorPlugin.class).start()) { | ||
waitForInit(node.client()); | ||
|
||
CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-2")).actionGet(); | ||
Assert.assertTrue(cir.isAcknowledged()); | ||
} catch (ElasticsearchSecurityException ex) { | ||
exception = ex; | ||
log.warn(ex); | ||
} | ||
Assert.assertNotNull(exception); | ||
Assert.assertTrue(exception.getMessage().contains("indices:admin/create")); | ||
|
||
//3. With valid roles - which has permission to create index. | ||
RolesInjectorPlugin.injectedRoles = "valid_user|opendistro_security_all_access"; | ||
try (Node node = new PluginAwareNode(false, tcSettings, Netty4Plugin.class, OpenDistroSecurityPlugin.class, RolesInjectorPlugin.class).start()) { | ||
waitForInit(node.client()); | ||
|
||
CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-3")).actionGet(); | ||
Assert.assertTrue(cir.isAcknowledged()); | ||
|
||
IndicesExistsResponse ier = node.client().admin().indices().exists(new IndicesExistsRequest("captain-logs-3")).actionGet(); | ||
Assert.assertTrue(ier.isExists()); | ||
} | ||
} | ||
} |
Oops, something went wrong.