Skip to content

Commit 9300607

Browse files
cwperksdk2k
authored andcommitted
Add runAs to Subject interface and introduce IdentityAwarePlugin extension point (opensearch-project#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>
1 parent eb8b1e1 commit 9300607

32 files changed

+423
-63
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
3030
- Add support for centralize snapshot creation with pinned timestamp ([#15124](https://github.com/opensearch-project/OpenSearch/pull/15124))
3131
- Add concurrent search support for Derived Fields ([#15326](https://github.com/opensearch-project/OpenSearch/pull/15326))
3232
- [Workload Management] Add query group stats constructs ([#15343](https://github.com/opensearch-project/OpenSearch/pull/15343)))
33+
- Add runAs to Subject interface and introduce IdentityAwarePlugin extension point ([#14630](https://github.com/opensearch-project/OpenSearch/pull/14630))
3334

3435
### Dependencies
3536
- Bump `netty` from 4.1.111.Final to 4.1.112.Final ([#15081](https://github.com/opensearch-project/OpenSearch/pull/15081))

plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java

+44-1
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,43 @@
1212
import org.apache.logging.log4j.Logger;
1313
import org.apache.shiro.SecurityUtils;
1414
import org.apache.shiro.mgt.SecurityManager;
15+
import org.opensearch.client.Client;
16+
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
17+
import org.opensearch.cluster.service.ClusterService;
18+
import org.opensearch.common.annotation.ExperimentalApi;
1519
import org.opensearch.common.settings.Settings;
20+
import org.opensearch.core.common.io.stream.NamedWriteableRegistry;
21+
import org.opensearch.core.xcontent.NamedXContentRegistry;
22+
import org.opensearch.env.Environment;
23+
import org.opensearch.env.NodeEnvironment;
24+
import org.opensearch.identity.PluginSubject;
1625
import org.opensearch.identity.Subject;
1726
import org.opensearch.identity.tokens.TokenManager;
1827
import org.opensearch.plugins.IdentityPlugin;
1928
import org.opensearch.plugins.Plugin;
29+
import org.opensearch.repositories.RepositoriesService;
30+
import org.opensearch.script.ScriptService;
31+
import org.opensearch.threadpool.ThreadPool;
32+
import org.opensearch.watcher.ResourceWatcherService;
33+
34+
import java.util.Collection;
35+
import java.util.Collections;
36+
import java.util.function.Supplier;
2037

2138
/**
2239
* Identity implementation with Shiro
2340
*
2441
* @opensearch.experimental
2542
*/
43+
@ExperimentalApi
2644
public final class ShiroIdentityPlugin extends Plugin implements IdentityPlugin {
2745
private Logger log = LogManager.getLogger(this.getClass());
2846

2947
private final Settings settings;
3048
private final ShiroTokenManager authTokenHandler;
3149

50+
private ThreadPool threadPool;
51+
3252
/**
3353
* Create a new instance of the Shiro Identity Plugin
3454
*
@@ -42,13 +62,31 @@ public ShiroIdentityPlugin(final Settings settings) {
4262
SecurityUtils.setSecurityManager(securityManager);
4363
}
4464

65+
@Override
66+
public Collection<Object> createComponents(
67+
Client client,
68+
ClusterService clusterService,
69+
ThreadPool threadPool,
70+
ResourceWatcherService resourceWatcherService,
71+
ScriptService scriptService,
72+
NamedXContentRegistry xContentRegistry,
73+
Environment environment,
74+
NodeEnvironment nodeEnvironment,
75+
NamedWriteableRegistry namedWriteableRegistry,
76+
IndexNameExpressionResolver expressionResolver,
77+
Supplier<RepositoriesService> repositoriesServiceSupplier
78+
) {
79+
this.threadPool = threadPool;
80+
return Collections.emptyList();
81+
}
82+
4583
/**
4684
* Return a Shiro Subject based on the provided authTokenHandler and current subject
4785
*
4886
* @return The current subject
4987
*/
5088
@Override
51-
public Subject getSubject() {
89+
public Subject getCurrentSubject() {
5290
return new ShiroSubject(authTokenHandler, SecurityUtils.getSubject());
5391
}
5492

@@ -61,4 +99,9 @@ public Subject getSubject() {
6199
public TokenManager getTokenManager() {
62100
return this.authTokenHandler;
63101
}
102+
103+
@Override
104+
public PluginSubject getPluginSubject(Plugin plugin) {
105+
return new ShiroPluginSubject(threadPool);
106+
}
64107
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.identity.shiro;
10+
11+
import org.opensearch.common.annotation.ExperimentalApi;
12+
import org.opensearch.common.util.concurrent.ThreadContext;
13+
import org.opensearch.identity.NamedPrincipal;
14+
import org.opensearch.identity.PluginSubject;
15+
import org.opensearch.threadpool.ThreadPool;
16+
17+
import java.security.Principal;
18+
import java.util.concurrent.Callable;
19+
20+
/**
21+
* Implementation of subject that is always authenticated
22+
* <p>
23+
* This class and related classes in this package will not return nulls or fail permissions checks
24+
*
25+
* This class is used by the ShiroIdentityPlugin to initialize IdentityAwarePlugins
26+
*
27+
* @opensearch.experimental
28+
*/
29+
@ExperimentalApi
30+
public class ShiroPluginSubject implements PluginSubject {
31+
private final ThreadPool threadPool;
32+
33+
ShiroPluginSubject(ThreadPool threadPool) {
34+
super();
35+
this.threadPool = threadPool;
36+
}
37+
38+
@Override
39+
public Principal getPrincipal() {
40+
return NamedPrincipal.UNAUTHENTICATED;
41+
}
42+
43+
@Override
44+
public <T> T runAs(Callable<T> callable) throws Exception {
45+
try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) {
46+
return callable.call();
47+
}
48+
}
49+
}

plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroSubject.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package org.opensearch.identity.shiro;
1010

1111
import org.opensearch.identity.Subject;
12+
import org.opensearch.identity.UserSubject;
1213
import org.opensearch.identity.tokens.AuthToken;
1314

1415
import java.security.Principal;
@@ -19,7 +20,7 @@
1920
*
2021
* @opensearch.experimental
2122
*/
22-
public class ShiroSubject implements Subject {
23+
public class ShiroSubject implements UserSubject {
2324
private final ShiroTokenManager authTokenHandler;
2425
private final org.apache.shiro.subject.Subject shiroSubject;
2526

plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroIdentityPluginTests.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.opensearch.identity.IdentityService;
1414
import org.opensearch.plugins.IdentityPlugin;
1515
import org.opensearch.test.OpenSearchTestCase;
16+
import org.opensearch.threadpool.TestThreadPool;
1617

1718
import java.util.List;
1819

@@ -24,19 +25,23 @@
2425
public class ShiroIdentityPluginTests extends OpenSearchTestCase {
2526

2627
public void testSingleIdentityPluginSucceeds() {
28+
TestThreadPool threadPool = new TestThreadPool(getTestName());
2729
IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(Settings.EMPTY);
2830
List<IdentityPlugin> pluginList1 = List.of(identityPlugin1);
29-
IdentityService identityService1 = new IdentityService(Settings.EMPTY, pluginList1);
31+
IdentityService identityService1 = new IdentityService(Settings.EMPTY, threadPool, pluginList1);
3032
assertThat(identityService1.getTokenManager(), is(instanceOf(ShiroTokenManager.class)));
33+
terminate(threadPool);
3134
}
3235

3336
public void testMultipleIdentityPluginsFail() {
37+
TestThreadPool threadPool = new TestThreadPool(getTestName());
3438
IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(Settings.EMPTY);
3539
IdentityPlugin identityPlugin2 = new ShiroIdentityPlugin(Settings.EMPTY);
3640
IdentityPlugin identityPlugin3 = new ShiroIdentityPlugin(Settings.EMPTY);
3741
List<IdentityPlugin> pluginList = List.of(identityPlugin1, identityPlugin2, identityPlugin3);
38-
Exception ex = assertThrows(OpenSearchException.class, () -> new IdentityService(Settings.EMPTY, pluginList));
42+
Exception ex = assertThrows(OpenSearchException.class, () -> new IdentityService(Settings.EMPTY, threadPool, pluginList));
3943
assert (ex.getMessage().contains("Multiple identity plugins are not supported,"));
44+
terminate(threadPool);
4045
}
4146

4247
}

server/src/main/java/org/opensearch/extensions/NoopExtensionsManager.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import org.opensearch.transport.TransportService;
2121

2222
import java.io.IOException;
23-
import java.util.List;
2423
import java.util.Optional;
2524
import java.util.Set;
2625

@@ -31,8 +30,8 @@
3130
*/
3231
public class NoopExtensionsManager extends ExtensionsManager {
3332

34-
public NoopExtensionsManager() throws IOException {
35-
super(Set.of(), new IdentityService(Settings.EMPTY, List.of()));
33+
public NoopExtensionsManager(IdentityService identityService) throws IOException {
34+
super(Set.of(), identityService);
3635
}
3736

3837
@Override

server/src/main/java/org/opensearch/extensions/rest/RestSendToExtensionAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ public String executor() {
249249
Map<String, List<String>> filteredHeaders = filterHeaders(headers, allowList, denyList);
250250

251251
TokenManager tokenManager = identityService.getTokenManager();
252-
Subject subject = this.identityService.getSubject();
252+
Subject subject = this.identityService.getCurrentSubject();
253253
OnBehalfOfClaims claims = new OnBehalfOfClaims(discoveryExtensionNode.getId(), subject.getPrincipal().getName());
254254

255255
transportService.sendRequest(

server/src/main/java/org/opensearch/identity/IdentityService.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
import org.opensearch.common.settings.Settings;
1212
import org.opensearch.identity.noop.NoopIdentityPlugin;
1313
import org.opensearch.identity.tokens.TokenManager;
14+
import org.opensearch.plugins.IdentityAwarePlugin;
1415
import org.opensearch.plugins.IdentityPlugin;
16+
import org.opensearch.plugins.Plugin;
17+
import org.opensearch.threadpool.ThreadPool;
1518

1619
import java.util.List;
1720
import java.util.stream.Collectors;
@@ -27,12 +30,12 @@ public class IdentityService {
2730
private final Settings settings;
2831
private final IdentityPlugin identityPlugin;
2932

30-
public IdentityService(final Settings settings, final List<IdentityPlugin> identityPlugins) {
33+
public IdentityService(final Settings settings, final ThreadPool threadPool, final List<IdentityPlugin> identityPlugins) {
3134
this.settings = settings;
3235

3336
if (identityPlugins.size() == 0) {
3437
log.debug("Identity plugins size is 0");
35-
identityPlugin = new NoopIdentityPlugin();
38+
identityPlugin = new NoopIdentityPlugin(threadPool);
3639
} else if (identityPlugins.size() == 1) {
3740
log.debug("Identity plugins size is 1");
3841
identityPlugin = identityPlugins.get(0);
@@ -47,8 +50,8 @@ public IdentityService(final Settings settings, final List<IdentityPlugin> ident
4750
/**
4851
* Gets the current Subject
4952
*/
50-
public Subject getSubject() {
51-
return identityPlugin.getSubject();
53+
public Subject getCurrentSubject() {
54+
return identityPlugin.getCurrentSubject();
5255
}
5356

5457
/**
@@ -57,4 +60,13 @@ public Subject getSubject() {
5760
public TokenManager getTokenManager() {
5861
return identityPlugin.getTokenManager();
5962
}
63+
64+
public void initializeIdentityAwarePlugins(final List<IdentityAwarePlugin> identityAwarePlugins) {
65+
if (identityAwarePlugins != null) {
66+
for (IdentityAwarePlugin plugin : identityAwarePlugins) {
67+
PluginSubject pluginSubject = identityPlugin.getPluginSubject((Plugin) plugin);
68+
plugin.assignSubject(pluginSubject);
69+
}
70+
}
71+
}
6072
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.identity;
10+
11+
import org.opensearch.common.annotation.ExperimentalApi;
12+
13+
/**
14+
* Similar to {@link Subject}, but represents a plugin executing actions
15+
*
16+
* @opensearch.experimental
17+
*/
18+
@ExperimentalApi
19+
public interface PluginSubject extends Subject {}

server/src/main/java/org/opensearch/identity/Subject.java

+7-7
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55

66
package org.opensearch.identity;
77

8-
import org.opensearch.identity.tokens.AuthToken;
8+
import org.opensearch.common.annotation.ExperimentalApi;
99

1010
import java.security.Principal;
11+
import java.util.concurrent.Callable;
1112

1213
/**
1314
* An individual, process, or device that causes information to flow among objects or change to the system state.
1415
*
1516
* @opensearch.experimental
1617
*/
18+
@ExperimentalApi
1719
public interface Subject {
1820

1921
/**
@@ -22,11 +24,9 @@ public interface Subject {
2224
Principal getPrincipal();
2325

2426
/**
25-
* Authenticate via an auth token
26-
* throws UnsupportedAuthenticationMethod
27-
* throws InvalidAuthenticationToken
28-
* throws SubjectNotFound
29-
* throws SubjectDisabled
27+
* runAs allows the caller to run a callable function as this subject
3028
*/
31-
void authenticate(final AuthToken token);
29+
default <T> T runAs(Callable<T> callable) throws Exception {
30+
return callable.call();
31+
};
3232
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.identity;
10+
11+
import org.opensearch.common.annotation.ExperimentalApi;
12+
import org.opensearch.identity.tokens.AuthToken;
13+
14+
/**
15+
* An instance of a subject representing a User. UserSubjects must pass credentials for authentication.
16+
*
17+
* @opensearch.experimental
18+
*/
19+
@ExperimentalApi
20+
public interface UserSubject extends Subject {
21+
/**
22+
* Authenticate via an auth token
23+
* throws UnsupportedAuthenticationMethod
24+
* throws InvalidAuthenticationToken
25+
* throws SubjectNotFound
26+
* throws SubjectDisabled
27+
*/
28+
void authenticate(final AuthToken token);
29+
}

0 commit comments

Comments
 (0)