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)

* Create ExecutionContext and show example with ActionPluginProxy

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Only allow core to set the ExecutionContext

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* WIP on plugin aware thread context

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Plugin Aware API Handling

Signed-off-by: Craig Perkins <cwperx@amazon.com>

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

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Clear context in a finally block

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Create switchContext method in ThreadContext and make pluginExecutionStack a stack

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* WIP on plugin aware stash context

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Create class called PluginAwareNodeClient that provides a method called switchContext

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove ExecutionContext class

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Update javadoc

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Change createComponents to take in PluginAwareNodeClient

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Update all instances of createComponents

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Initialize clients

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove casting

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* WIP on notion of ContextSwitcher

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Make stashContext package-private

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Make markAsSystemContext package-private

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add javadoc on param

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove SystemContextSwitcher

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Merge with main

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Cleanup

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove SystemIndexFilter

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add notion of Forbidden Headers to the ThreadContext

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Fix tests

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Fix test

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add method to initialize plugins

Signed-off-by: Craig Perkins <cwperx@amazon.com>

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

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add test

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add another test for setPluginNodeClient

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove newline

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add another test

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Subject.runAs and introduce PluginSubject

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Do nothing when runAs is called for ShiroSubject and NoopSubject

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove extraneous changes

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Test all methods in PluginSubject

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Pass a Callable to runAs

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Update import

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Simplify PR, make NoopPluginSubject and introduce IdentityAwarePlugin

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add final

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove server dependency

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove AbstractSubject

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove unnecessary changes

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add javadoc to NoopPluginSubject

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Rename to assignSubject

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add experimental label

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add getPluginSubject(plugin) to IdentityPlugin

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Make runAs generic

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* package-private constructor

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Move IdentityAwarePlugin initialization

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Create separate PluginSubject interface

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove authenticate method

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove import

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Separate UserSubject and PluginSubject

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Terminate TestThreadPool

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* mock ThreadPool in RestSendToExtensionActionTests

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Fix Thread leak

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add to CHANGELOG

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Rename to getCurrentSubject

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add type check

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Rename to pluginSubject

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add runAs to ActionRequest and surround doExecute in AbstractClient

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Return this

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Switch back to void

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Revert change to ActionRequest

Signed-off-by: Craig Perkins <cwperx@amazon.com>

---------

Signed-off-by: Craig Perkins <cwperx@amazon.com>
  • Loading branch information
cwperks authored Aug 28, 2024
1 parent b30df02 commit ee17eca
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 @@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Add support for centralize snapshot creation with pinned timestamp ([#15124](https://github.com/opensearch-project/OpenSearch/pull/15124))
- 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 ee17eca

Please sign in to comment.