Skip to content

Commit 9ed4c45

Browse files
stephen-crawfordRyanL1997peternied
authored andcommitted
Implement on behalf of token passing for extensions (opensearch-project#8679)
Implement on behalf of token passing for extensions Signed-off-by: Stephen Crawford <[email protected]> Signed-off-by: Stephen Crawford <[email protected]> Signed-off-by: Ryan Liang <[email protected]> Co-authored-by: Ryan Liang <[email protected]> Co-authored-by: Peter Nied <[email protected]>
1 parent 507d38b commit 9ed4c45

File tree

20 files changed

+349
-83
lines changed

20 files changed

+349
-83
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1010
- Allow mmap to use new JDK-19 preview APIs in Apache Lucene 9.4+ ([#5151](https://github.com/opensearch-project/OpenSearch/pull/5151))
1111
- Add events correlation engine plugin ([#6854](https://github.com/opensearch-project/OpenSearch/issues/6854))
1212
- Introduce new dynamic cluster setting to control slice computation for concurrent segment search ([#9107](https://github.com/opensearch-project/OpenSearch/pull/9107))
13+
- Implement on behalf of token passing for extensions ([#8679](https://github.com/opensearch-project/OpenSearch/pull/8679))
1314

1415
### Dependencies
1516
- Bump `log4j-core` from 2.18.0 to 2.19.0

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88

99
package org.opensearch.identity.shiro;
1010

11-
import org.opensearch.identity.Subject;
1211
import org.apache.logging.log4j.LogManager;
1312
import org.apache.logging.log4j.Logger;
13+
import org.apache.shiro.SecurityUtils;
14+
import org.apache.shiro.mgt.SecurityManager;
15+
import org.opensearch.common.settings.Settings;
16+
import org.opensearch.identity.Subject;
1417
import org.opensearch.identity.tokens.TokenManager;
1518
import org.opensearch.plugins.IdentityPlugin;
16-
import org.opensearch.common.settings.Settings;
1719
import org.opensearch.plugins.Plugin;
18-
import org.apache.shiro.SecurityUtils;
19-
import org.apache.shiro.mgt.SecurityManager;
2020

2121
/**
2222
* Identity implementation with Shiro

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222
import org.apache.shiro.authc.UsernamePasswordToken;
2323
import org.opensearch.common.Randomness;
2424
import org.opensearch.identity.IdentityService;
25+
import org.opensearch.identity.Subject;
26+
import org.opensearch.identity.noop.NoopSubject;
2527
import org.opensearch.identity.tokens.AuthToken;
2628
import org.opensearch.identity.tokens.BasicAuthToken;
29+
import org.opensearch.identity.tokens.OnBehalfOfClaims;
2730
import org.opensearch.identity.tokens.TokenManager;
2831
import org.passay.CharacterRule;
2932
import org.passay.EnglishCharacterData;
@@ -51,15 +54,16 @@ public Optional<AuthenticationToken> translateAuthToken(org.opensearch.identity.
5154
final BasicAuthToken basicAuthToken = (BasicAuthToken) authenticationToken;
5255
return Optional.of(new UsernamePasswordToken(basicAuthToken.getUser(), basicAuthToken.getPassword()));
5356
}
54-
5557
return Optional.empty();
5658
}
5759

5860
@Override
59-
public AuthToken issueToken(String audience) {
61+
public AuthToken issueOnBehalfOfToken(Subject subject, OnBehalfOfClaims claims) {
6062

6163
String password = generatePassword();
62-
final byte[] rawEncoded = Base64.getEncoder().encode((audience + ":" + password).getBytes(UTF_8));
64+
final byte[] rawEncoded = Base64.getEncoder().encode((claims.getAudience() + ":" + password).getBytes(UTF_8)); // Make a new
65+
// ShiroSubject w/
66+
// audience as name
6367
final String usernamePassword = new String(rawEncoded, UTF_8);
6468
final String header = "Basic " + usernamePassword;
6569
BasicAuthToken token = new BasicAuthToken(header);
@@ -68,6 +72,11 @@ public AuthToken issueToken(String audience) {
6872
return token;
6973
}
7074

75+
@Override
76+
public Subject authenticateToken(AuthToken authToken) {
77+
return new NoopSubject();
78+
}
79+
7180
public boolean validateToken(AuthToken token) {
7281
if (token instanceof BasicAuthToken) {
7382
final BasicAuthToken basicAuthToken = (BasicAuthToken) token;

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212
import org.apache.shiro.authc.AuthenticationToken;
1313
import org.apache.shiro.authc.UsernamePasswordToken;
1414
import org.junit.Before;
15+
import org.opensearch.identity.Subject;
16+
import org.opensearch.identity.noop.NoopSubject;
1517
import org.opensearch.identity.noop.NoopTokenManager;
1618
import org.opensearch.identity.tokens.AuthToken;
1719
import org.opensearch.identity.tokens.BasicAuthToken;
1820
import org.opensearch.identity.tokens.BearerAuthToken;
21+
import org.opensearch.identity.tokens.OnBehalfOfClaims;
1922
import org.opensearch.test.OpenSearchTestCase;
2023
import org.passay.CharacterCharacteristicsRule;
2124
import org.passay.CharacterRule;
@@ -31,16 +34,15 @@
3134
public class AuthTokenHandlerTests extends OpenSearchTestCase {
3235

3336
private ShiroTokenManager shiroAuthTokenHandler;
34-
private NoopTokenManager noopTokenManager;
3537

3638
@Before
3739
public void testSetup() {
3840
shiroAuthTokenHandler = new ShiroTokenManager();
39-
noopTokenManager = new NoopTokenManager();
4041
}
4142

4243
public void testShouldExtractBasicAuthTokenSuccessfully() {
4344
final BasicAuthToken authToken = new BasicAuthToken("Basic YWRtaW46YWRtaW4="); // admin:admin
45+
assertEquals(authToken.asAuthHeaderValue(), "YWRtaW46YWRtaW4=");
4446

4547
final AuthenticationToken translatedToken = shiroAuthTokenHandler.translateAuthToken(authToken).get();
4648
assertThat(translatedToken, is(instanceOf(UsernamePasswordToken.class)));
@@ -106,7 +108,7 @@ public void testShoudPassMapLookupWithToken() {
106108
assertTrue(authToken.getPassword().equals(shiroAuthTokenHandler.getShiroTokenPasswordMap().get(authToken)));
107109
}
108110

109-
public void testShouldPassThrougbResetToken(AuthToken token) {
111+
public void testShouldPassThroughResetToken() {
110112
final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature");
111113
shiroAuthTokenHandler.resetToken(bearerAuthToken);
112114
}
@@ -121,6 +123,7 @@ public void testVerifyBearerTokenObject() {
121123
assertEquals(testGoodToken.getPayload(), "payload");
122124
assertEquals(testGoodToken.getSignature(), "signature");
123125
assertEquals(testGoodToken.toString(), "Bearer auth token with header=header, payload=payload, signature=signature");
126+
assertEquals(testGoodToken.asAuthHeaderValue(), "header.payload.signature");
124127
}
125128

126129
public void testGeneratedPasswordContents() {
@@ -144,4 +147,23 @@ public void testGeneratedPasswordContents() {
144147
validator.validate(data);
145148
}
146149

150+
public void testIssueOnBehalfOfTokenFromClaims() {
151+
Subject subject = new NoopSubject();
152+
OnBehalfOfClaims claims = new OnBehalfOfClaims("test", "test");
153+
BasicAuthToken authToken = (BasicAuthToken) shiroAuthTokenHandler.issueOnBehalfOfToken(subject, claims);
154+
assertTrue(authToken instanceof BasicAuthToken);
155+
UsernamePasswordToken translatedToken = (UsernamePasswordToken) shiroAuthTokenHandler.translateAuthToken(authToken).get();
156+
assertEquals(authToken.getPassword(), new String(translatedToken.getPassword()));
157+
assertTrue(shiroAuthTokenHandler.getShiroTokenPasswordMap().containsKey(authToken));
158+
assertEquals(shiroAuthTokenHandler.getShiroTokenPasswordMap().get(authToken), new String(translatedToken.getPassword()));
159+
}
160+
161+
public void testTokenNoopIssuance() {
162+
NoopTokenManager tokenManager = new NoopTokenManager();
163+
OnBehalfOfClaims claims = new OnBehalfOfClaims("test", "test");
164+
Subject subject = new NoopSubject();
165+
AuthToken token = tokenManager.issueOnBehalfOfToken(subject, claims);
166+
assertTrue(token instanceof AuthToken);
167+
}
168+
147169
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.opensearch.extensions.rest.RestActionsRequestHandler;
5050
import org.opensearch.extensions.settings.CustomSettingsRequestHandler;
5151
import org.opensearch.extensions.settings.RegisterCustomSettingsRequest;
52+
import org.opensearch.identity.IdentityService;
5253
import org.opensearch.threadpool.ThreadPool;
5354
import org.opensearch.transport.ConnectTransportException;
5455
import org.opensearch.transport.TransportException;
@@ -142,9 +143,15 @@ public void initializeServicesAndRestHandler(
142143
TransportService transportService,
143144
ClusterService clusterService,
144145
Settings initialEnvironmentSettings,
145-
NodeClient client
146+
NodeClient client,
147+
IdentityService identityService
146148
) {
147-
this.restActionsRequestHandler = new RestActionsRequestHandler(actionModule.getRestController(), extensionIdMap, transportService);
149+
this.restActionsRequestHandler = new RestActionsRequestHandler(
150+
actionModule.getRestController(),
151+
extensionIdMap,
152+
transportService,
153+
identityService
154+
);
148155
this.customSettingsRequestHandler = new CustomSettingsRequestHandler(settingsModule);
149156
this.transportService = transportService;
150157
this.clusterService = clusterService;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.opensearch.extensions.action.ExtensionActionRequest;
2222
import org.opensearch.extensions.action.ExtensionActionResponse;
2323
import org.opensearch.extensions.action.RemoteExtensionActionResponse;
24+
import org.opensearch.identity.IdentityService;
2425
import org.opensearch.transport.TransportService;
2526

2627
/**
@@ -41,7 +42,8 @@ public void initializeServicesAndRestHandler(
4142
TransportService transportService,
4243
ClusterService clusterService,
4344
Settings initialEnvironmentSettings,
44-
NodeClient client
45+
NodeClient client,
46+
IdentityService identityService
4547
) {
4648
// no-op
4749
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Map;
3131
import java.util.Objects;
3232
import java.util.Set;
33+
import static java.util.Objects.requireNonNull;
3334

3435
/**
3536
* Request to execute REST actions on extension node.
@@ -86,7 +87,7 @@ public ExtensionRestRequest(
8687
this.headers = headers;
8788
this.mediaType = mediaType;
8889
this.content = content;
89-
this.principalIdentifierToken = principalIdentifier;
90+
this.principalIdentifierToken = requireNonNull(principalIdentifier);
9091
this.httpVersion = httpVersion;
9192
}
9293

@@ -280,7 +281,7 @@ public boolean isContentConsumed() {
280281
}
281282

282283
/**
283-
* Gets a parser for the contents of this request if there is content and an xContentType.
284+
* Gets a parser for the contents of this request if there is content, an xContentType, and a principal identifier.
284285
*
285286
* @param xContentRegistry The extension's xContentRegistry
286287
* @return A parser for the given content and content type.
@@ -291,6 +292,9 @@ public final XContentParser contentParser(NamedXContentRegistry xContentRegistry
291292
if (!hasContent() || getXContentType() == null) {
292293
throw new OpenSearchParseException("There is no request body or the ContentType is invalid.");
293294
}
295+
if (getRequestIssuerIdentity() == null) {
296+
throw new OpenSearchParseException("There is no request body or the requester identity is invalid.");
297+
}
294298
return getXContentType().xContent().createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, content.streamInput());
295299
}
296300

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@
88

99
package org.opensearch.extensions.rest;
1010

11+
import java.util.Map;
1112
import org.opensearch.action.ActionModule.DynamicActionRegistry;
1213
import org.opensearch.extensions.AcknowledgedResponse;
1314
import org.opensearch.extensions.DiscoveryExtensionNode;
15+
import org.opensearch.identity.IdentityService;
1416
import org.opensearch.rest.RestController;
1517
import org.opensearch.rest.RestHandler;
1618
import org.opensearch.core.transport.TransportResponse;
1719
import org.opensearch.transport.TransportService;
1820

19-
import java.util.Map;
20-
2121
/**
2222
* Handles requests to register extension REST actions.
2323
*
@@ -28,6 +28,7 @@ public class RestActionsRequestHandler {
2828
private final RestController restController;
2929
private final Map<String, DiscoveryExtensionNode> extensionIdMap;
3030
private final TransportService transportService;
31+
private final IdentityService identityService;
3132

3233
/**
3334
* Instantiates a new REST Actions Request Handler using the Node's RestController.
@@ -39,11 +40,13 @@ public class RestActionsRequestHandler {
3940
public RestActionsRequestHandler(
4041
RestController restController,
4142
Map<String, DiscoveryExtensionNode> extensionIdMap,
42-
TransportService transportService
43+
TransportService transportService,
44+
IdentityService identityService
4345
) {
4446
this.restController = restController;
4547
this.extensionIdMap = extensionIdMap;
4648
this.transportService = transportService;
49+
this.identityService = identityService;
4750
}
4851

4952
/**
@@ -62,7 +65,8 @@ public TransportResponse handleRegisterRestActionsRequest(
6265
restActionsRequest,
6366
discoveryExtensionNode,
6467
transportService,
65-
dynamicActionRegistry
68+
dynamicActionRegistry,
69+
identityService
6670
);
6771
restController.registerHandler(handler);
6872
return new AcknowledgedResponse(true);

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
import org.opensearch.core.xcontent.MediaType;
1818
import org.opensearch.extensions.DiscoveryExtensionNode;
1919
import org.opensearch.extensions.ExtensionsManager;
20+
import org.opensearch.identity.IdentityService;
21+
import org.opensearch.identity.Subject;
22+
import org.opensearch.identity.tokens.OnBehalfOfClaims;
23+
import org.opensearch.identity.tokens.TokenManager;
2024
import org.opensearch.rest.BaseRestHandler;
2125
import org.opensearch.rest.BytesRestResponse;
2226
import org.opensearch.rest.NamedRoute;
@@ -31,7 +35,6 @@
3135

3236
import java.io.IOException;
3337
import java.nio.charset.StandardCharsets;
34-
import java.security.Principal;
3538
import java.util.ArrayList;
3639
import java.util.HashSet;
3740
import java.util.List;
@@ -54,19 +57,13 @@ public class RestSendToExtensionAction extends BaseRestHandler {
5457

5558
private static final String SEND_TO_EXTENSION_ACTION = "send_to_extension_action";
5659
private static final Logger logger = LogManager.getLogger(RestSendToExtensionAction.class);
57-
// To replace with user identity see https://github.com/opensearch-project/OpenSearch/pull/4247
58-
private static final Principal DEFAULT_PRINCIPAL = new Principal() {
59-
@Override
60-
public String getName() {
61-
return "OpenSearchUser";
62-
}
63-
};
6460

6561
private final List<Route> routes;
6662
private final List<DeprecatedRoute> deprecatedRoutes;
6763
private final String pathPrefix;
6864
private final DiscoveryExtensionNode discoveryExtensionNode;
6965
private final TransportService transportService;
66+
private final IdentityService identityService;
7067

7168
private static final Set<String> allowList = Set.of("Content-Type");
7269
private static final Set<String> denyList = Set.of("Authorization", "Proxy-Authorization");
@@ -82,7 +79,8 @@ public RestSendToExtensionAction(
8279
RegisterRestActionsRequest restActionsRequest,
8380
DiscoveryExtensionNode discoveryExtensionNode,
8481
TransportService transportService,
85-
DynamicActionRegistry dynamicActionRegistry
82+
DynamicActionRegistry dynamicActionRegistry,
83+
IdentityService identityService
8684
) {
8785
this.pathPrefix = "/_extensions/_" + restActionsRequest.getUniqueId();
8886
RestRequest.Method method;
@@ -147,6 +145,7 @@ public RestSendToExtensionAction(
147145

148146
this.discoveryExtensionNode = discoveryExtensionNode;
149147
this.transportService = transportService;
148+
this.identityService = identityService;
150149
}
151150

152151
@Override
@@ -240,12 +239,15 @@ public String executor() {
240239
};
241240

242241
try {
242+
243243
// Will be replaced with ExtensionTokenProcessor and PrincipalIdentifierToken classes from feature/identity
244-
final String extensionTokenProcessor = "placeholder_token_processor";
245-
final String requestIssuerIdentity = "placeholder_request_issuer_identity";
246244

247245
Map<String, List<String>> filteredHeaders = filterHeaders(headers, allowList, denyList);
248246

247+
TokenManager tokenManager = identityService.getTokenManager();
248+
Subject subject = this.identityService.getSubject();
249+
OnBehalfOfClaims claims = new OnBehalfOfClaims(discoveryExtensionNode.getId(), subject.getPrincipal().getName());
250+
249251
transportService.sendRequest(
250252
discoveryExtensionNode,
251253
ExtensionsManager.REQUEST_REST_EXECUTE_ON_EXTENSION_ACTION,
@@ -259,7 +261,7 @@ public String executor() {
259261
filteredHeaders,
260262
contentType,
261263
content,
262-
requestIssuerIdentity,
264+
tokenManager.issueOnBehalfOfToken(subject, claims).asAuthHeaderValue(),
263265
httpVersion
264266
),
265267
restExecuteOnExtensionResponseHandler

server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99
package org.opensearch.identity.noop;
1010

11+
import org.opensearch.identity.Subject;
1112
import org.opensearch.identity.tokens.TokenManager;
1213
import org.opensearch.plugins.IdentityPlugin;
13-
import org.opensearch.identity.Subject;
1414

1515
/**
1616
* Implementation of identity plugin that does not enforce authentication or authorization

0 commit comments

Comments
 (0)