Skip to content

Commit

Permalink
Add runAs to Subject interface and introduce IdentityAwarePlugin exte…
Browse files Browse the repository at this point in the history
…nsion point (#14630) (#15477)

* Create ExecutionContext and show example with ActionPluginProxy



* Only allow core to set the ExecutionContext



* WIP on plugin aware thread context



* Plugin Aware API Handling



* Add test to verify that ExecutionContext is being populated during RestHandling



* Clear context in a finally block



* Create switchContext method in ThreadContext and make pluginExecutionStack a stack



* WIP on plugin aware stash context



* Create class called PluginAwareNodeClient that provides a method called switchContext



* Remove ExecutionContext class



* Update javadoc



* Change createComponents to take in PluginAwareNodeClient



* Update all instances of createComponents



* Initialize clients



* Remove casting



* WIP on notion of ContextSwitcher



* Make stashContext package-private



* Make markAsSystemContext package-private



* Add javadoc on param



* Remove SystemContextSwitcher



* Merge with main



* Cleanup



* Remove SystemIndexFilter



* Add notion of Forbidden Headers to the ThreadContext



* Fix tests



* Fix test



* Add method to initialize plugins



* Create concept of pluginNodeClient that can be used for executing transport actions as the plugin



* Add test



* Add another test for setPluginNodeClient



* Remove newline



* Add another test



* Subject.runAs and introduce PluginSubject



* Do nothing when runAs is called for ShiroSubject and NoopSubject



* Remove extraneous changes



* Test all methods in PluginSubject



* Pass a Callable to runAs



* Update import



* Simplify PR, make NoopPluginSubject and introduce IdentityAwarePlugin



* Add final



* Remove server dependency



* Remove AbstractSubject



* Remove unnecessary changes



* Add javadoc to NoopPluginSubject



* Rename to assignSubject



* Add experimental label



* Add getPluginSubject(plugin) to IdentityPlugin



* Make runAs generic



* package-private constructor



* Move IdentityAwarePlugin initialization



* Create separate PluginSubject interface



* Remove authenticate method



* Remove import



* Separate UserSubject and PluginSubject



* Terminate TestThreadPool



* mock ThreadPool in RestSendToExtensionActionTests



* Fix Thread leak



* Add to CHANGELOG



* Rename to getCurrentSubject



* Add type check



* Rename to pluginSubject



* Add runAs to ActionRequest and surround doExecute in AbstractClient



* Return this



* Switch back to void



* Revert change to ActionRequest



---------


(cherry picked from commit ee17eca)

Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Ankit Jain <akjain@amazon.com>
  • Loading branch information
3 people authored Aug 29, 2024
1 parent 4d0af97 commit c2574a5
Show file tree
Hide file tree
Showing 32 changed files with 423 additions and 63 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- [Streaming Indexing] Introduce bulk HTTP API streaming flavor ([#15381](https://github.com/opensearch-project/OpenSearch/pull/15381))
- Add concurrent search support for Derived Fields ([#15326](https://github.com/opensearch-project/OpenSearch/pull/15326))
- [Workload Management] Add query group stats constructs ([#15343](https://github.com/opensearch-project/OpenSearch/pull/15343)))
- Add runAs to Subject interface and introduce IdentityAwarePlugin extension point ([#14630](https://github.com/opensearch-project/OpenSearch/pull/14630))

### Dependencies
- Bump `netty` from 4.1.111.Final to 4.1.112.Final ([#15081](https://github.com/opensearch-project/OpenSearch/pull/15081))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,43 @@
import org.apache.logging.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.opensearch.client.Client;
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.annotation.ExperimentalApi;
import org.opensearch.common.settings.Settings;
import org.opensearch.core.common.io.stream.NamedWriteableRegistry;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.env.Environment;
import org.opensearch.env.NodeEnvironment;
import org.opensearch.identity.PluginSubject;
import org.opensearch.identity.Subject;
import org.opensearch.identity.tokens.TokenManager;
import org.opensearch.plugins.IdentityPlugin;
import org.opensearch.plugins.Plugin;
import org.opensearch.repositories.RepositoriesService;
import org.opensearch.script.ScriptService;
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.watcher.ResourceWatcherService;

import java.util.Collection;
import java.util.Collections;
import java.util.function.Supplier;

/**
* Identity implementation with Shiro
*
* @opensearch.experimental
*/
@ExperimentalApi
public final class ShiroIdentityPlugin extends Plugin implements IdentityPlugin {
private Logger log = LogManager.getLogger(this.getClass());

private final Settings settings;
private final ShiroTokenManager authTokenHandler;

private ThreadPool threadPool;

/**
* Create a new instance of the Shiro Identity Plugin
*
Expand All @@ -42,13 +62,31 @@ public ShiroIdentityPlugin(final Settings settings) {
SecurityUtils.setSecurityManager(securityManager);
}

@Override
public Collection<Object> createComponents(
Client client,
ClusterService clusterService,
ThreadPool threadPool,
ResourceWatcherService resourceWatcherService,
ScriptService scriptService,
NamedXContentRegistry xContentRegistry,
Environment environment,
NodeEnvironment nodeEnvironment,
NamedWriteableRegistry namedWriteableRegistry,
IndexNameExpressionResolver expressionResolver,
Supplier<RepositoriesService> repositoriesServiceSupplier
) {
this.threadPool = threadPool;
return Collections.emptyList();
}

/**
* Return a Shiro Subject based on the provided authTokenHandler and current subject
*
* @return The current subject
*/
@Override
public Subject getSubject() {
public Subject getCurrentSubject() {
return new ShiroSubject(authTokenHandler, SecurityUtils.getSubject());
}

Expand All @@ -61,4 +99,9 @@ public Subject getSubject() {
public TokenManager getTokenManager() {
return this.authTokenHandler;
}

@Override
public PluginSubject getPluginSubject(Plugin plugin) {
return new ShiroPluginSubject(threadPool);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.identity.shiro;

import org.opensearch.common.annotation.ExperimentalApi;
import org.opensearch.common.util.concurrent.ThreadContext;
import org.opensearch.identity.NamedPrincipal;
import org.opensearch.identity.PluginSubject;
import org.opensearch.threadpool.ThreadPool;

import java.security.Principal;
import java.util.concurrent.Callable;

/**
* Implementation of subject that is always authenticated
* <p>
* This class and related classes in this package will not return nulls or fail permissions checks
*
* This class is used by the ShiroIdentityPlugin to initialize IdentityAwarePlugins
*
* @opensearch.experimental
*/
@ExperimentalApi
public class ShiroPluginSubject implements PluginSubject {
private final ThreadPool threadPool;

ShiroPluginSubject(ThreadPool threadPool) {
super();
this.threadPool = threadPool;
}

@Override
public Principal getPrincipal() {
return NamedPrincipal.UNAUTHENTICATED;
}

@Override
public <T> T runAs(Callable<T> callable) throws Exception {
try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) {
return callable.call();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package org.opensearch.identity.shiro;

import org.opensearch.identity.Subject;
import org.opensearch.identity.UserSubject;
import org.opensearch.identity.tokens.AuthToken;

import java.security.Principal;
Expand All @@ -19,7 +20,7 @@
*
* @opensearch.experimental
*/
public class ShiroSubject implements Subject {
public class ShiroSubject implements UserSubject {
private final ShiroTokenManager authTokenHandler;
private final org.apache.shiro.subject.Subject shiroSubject;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.opensearch.identity.IdentityService;
import org.opensearch.plugins.IdentityPlugin;
import org.opensearch.test.OpenSearchTestCase;
import org.opensearch.threadpool.TestThreadPool;

import java.util.List;

Expand All @@ -24,19 +25,23 @@
public class ShiroIdentityPluginTests extends OpenSearchTestCase {

public void testSingleIdentityPluginSucceeds() {
TestThreadPool threadPool = new TestThreadPool(getTestName());
IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(Settings.EMPTY);
List<IdentityPlugin> pluginList1 = List.of(identityPlugin1);
IdentityService identityService1 = new IdentityService(Settings.EMPTY, pluginList1);
IdentityService identityService1 = new IdentityService(Settings.EMPTY, threadPool, pluginList1);
assertThat(identityService1.getTokenManager(), is(instanceOf(ShiroTokenManager.class)));
terminate(threadPool);
}

public void testMultipleIdentityPluginsFail() {
TestThreadPool threadPool = new TestThreadPool(getTestName());
IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(Settings.EMPTY);
IdentityPlugin identityPlugin2 = new ShiroIdentityPlugin(Settings.EMPTY);
IdentityPlugin identityPlugin3 = new ShiroIdentityPlugin(Settings.EMPTY);
List<IdentityPlugin> pluginList = List.of(identityPlugin1, identityPlugin2, identityPlugin3);
Exception ex = assertThrows(OpenSearchException.class, () -> new IdentityService(Settings.EMPTY, pluginList));
Exception ex = assertThrows(OpenSearchException.class, () -> new IdentityService(Settings.EMPTY, threadPool, pluginList));
assert (ex.getMessage().contains("Multiple identity plugins are not supported,"));
terminate(threadPool);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import org.opensearch.transport.TransportService;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;

Expand All @@ -31,8 +30,8 @@
*/
public class NoopExtensionsManager extends ExtensionsManager {

public NoopExtensionsManager() throws IOException {
super(Set.of(), new IdentityService(Settings.EMPTY, List.of()));
public NoopExtensionsManager(IdentityService identityService) throws IOException {
super(Set.of(), identityService);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ public String executor() {
Map<String, List<String>> filteredHeaders = filterHeaders(headers, allowList, denyList);

TokenManager tokenManager = identityService.getTokenManager();
Subject subject = this.identityService.getSubject();
Subject subject = this.identityService.getCurrentSubject();
OnBehalfOfClaims claims = new OnBehalfOfClaims(discoveryExtensionNode.getId(), subject.getPrincipal().getName());

transportService.sendRequest(
Expand Down
20 changes: 16 additions & 4 deletions server/src/main/java/org/opensearch/identity/IdentityService.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
import org.opensearch.common.settings.Settings;
import org.opensearch.identity.noop.NoopIdentityPlugin;
import org.opensearch.identity.tokens.TokenManager;
import org.opensearch.plugins.IdentityAwarePlugin;
import org.opensearch.plugins.IdentityPlugin;
import org.opensearch.plugins.Plugin;
import org.opensearch.threadpool.ThreadPool;

import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -27,12 +30,12 @@ public class IdentityService {
private final Settings settings;
private final IdentityPlugin identityPlugin;

public IdentityService(final Settings settings, final List<IdentityPlugin> identityPlugins) {
public IdentityService(final Settings settings, final ThreadPool threadPool, final List<IdentityPlugin> identityPlugins) {
this.settings = settings;

if (identityPlugins.size() == 0) {
log.debug("Identity plugins size is 0");
identityPlugin = new NoopIdentityPlugin();
identityPlugin = new NoopIdentityPlugin(threadPool);
} else if (identityPlugins.size() == 1) {
log.debug("Identity plugins size is 1");
identityPlugin = identityPlugins.get(0);
Expand All @@ -47,8 +50,8 @@ public IdentityService(final Settings settings, final List<IdentityPlugin> ident
/**
* Gets the current subject
*/
public Subject getSubject() {
return identityPlugin.getSubject();
public Subject getCurrentSubject() {
return identityPlugin.getCurrentSubject();
}

/**
Expand All @@ -57,4 +60,13 @@ public Subject getSubject() {
public TokenManager getTokenManager() {
return identityPlugin.getTokenManager();
}

public void initializeIdentityAwarePlugins(final List<IdentityAwarePlugin> identityAwarePlugins) {
if (identityAwarePlugins != null) {
for (IdentityAwarePlugin plugin : identityAwarePlugins) {
PluginSubject pluginSubject = identityPlugin.getPluginSubject((Plugin) plugin);
plugin.assignSubject(pluginSubject);
}
}
}
}
19 changes: 19 additions & 0 deletions server/src/main/java/org/opensearch/identity/PluginSubject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.identity;

import org.opensearch.common.annotation.ExperimentalApi;

/**
* Similar to {@link Subject}, but represents a plugin executing actions
*
* @opensearch.experimental
*/
@ExperimentalApi
public interface PluginSubject extends Subject {}
14 changes: 7 additions & 7 deletions server/src/main/java/org/opensearch/identity/Subject.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@

package org.opensearch.identity;

import org.opensearch.identity.tokens.AuthToken;
import org.opensearch.common.annotation.ExperimentalApi;

import java.security.Principal;
import java.util.concurrent.Callable;

/**
* An individual, process, or device that causes information to flow among objects or change to the system state.
*
* @opensearch.experimental
*/
@ExperimentalApi
public interface Subject {

/**
Expand All @@ -22,11 +24,9 @@ public interface Subject {
Principal getPrincipal();

/**
* Authenticate via an auth token
* throws UnsupportedAuthenticationMethod
* throws InvalidAuthenticationToken
* throws SubjectNotFound
* throws SubjectDisabled
* runAs allows the caller to run a callable function as this subject
*/
void authenticate(final AuthToken token);
default <T> T runAs(Callable<T> callable) throws Exception {
return callable.call();
};
}
29 changes: 29 additions & 0 deletions server/src/main/java/org/opensearch/identity/UserSubject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.identity;

import org.opensearch.common.annotation.ExperimentalApi;
import org.opensearch.identity.tokens.AuthToken;

/**
* An instance of a subject representing a User. UserSubjects must pass credentials for authentication.
*
* @opensearch.experimental
*/
@ExperimentalApi
public interface UserSubject extends Subject {
/**
* Authenticate via an auth token
* throws UnsupportedAuthenticationMethod
* throws InvalidAuthenticationToken
* throws SubjectNotFound
* throws SubjectDisabled
*/
void authenticate(final AuthToken token);
}
Loading

0 comments on commit c2574a5

Please sign in to comment.