diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eae52e2d981e..5334190bce754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Allow mmap to use new JDK-19 preview APIs in Apache Lucene 9.4+ ([#5151](https://github.com/opensearch-project/OpenSearch/pull/5151)) - Add events correlation engine plugin ([#6854](https://github.com/opensearch-project/OpenSearch/issues/6854)) - Introduce new dynamic cluster setting to control slice computation for concurrent segment search ([#9107](https://github.com/opensearch-project/OpenSearch/pull/9107)) +- Implement on behalf of token passing for extensions ([#8679](https://github.com/opensearch-project/OpenSearch/pull/8679)) ### Dependencies - Bump `log4j-core` from 2.18.0 to 2.19.0 diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java index c3c08b1359aaa..77cab13880c27 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java @@ -8,15 +8,15 @@ package org.opensearch.identity.shiro; -import org.opensearch.identity.Subject; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.mgt.SecurityManager; +import org.opensearch.common.settings.Settings; +import org.opensearch.identity.Subject; import org.opensearch.identity.tokens.TokenManager; import org.opensearch.plugins.IdentityPlugin; -import org.opensearch.common.settings.Settings; import org.opensearch.plugins.Plugin; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.mgt.SecurityManager; /** * Identity implementation with Shiro diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java index 110095a5cd4ef..429acfcd55128 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java @@ -22,8 +22,11 @@ import org.apache.shiro.authc.UsernamePasswordToken; import org.opensearch.common.Randomness; import org.opensearch.identity.IdentityService; +import org.opensearch.identity.Subject; +import org.opensearch.identity.noop.NoopSubject; import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; +import org.opensearch.identity.tokens.OnBehalfOfClaims; import org.opensearch.identity.tokens.TokenManager; import org.passay.CharacterRule; import org.passay.EnglishCharacterData; @@ -51,15 +54,16 @@ public Optional translateAuthToken(org.opensearch.identity. final BasicAuthToken basicAuthToken = (BasicAuthToken) authenticationToken; return Optional.of(new UsernamePasswordToken(basicAuthToken.getUser(), basicAuthToken.getPassword())); } - return Optional.empty(); } @Override - public AuthToken issueToken(String audience) { + public AuthToken issueOnBehalfOfToken(Subject subject, OnBehalfOfClaims claims) { String password = generatePassword(); - final byte[] rawEncoded = Base64.getEncoder().encode((audience + ":" + password).getBytes(UTF_8)); + final byte[] rawEncoded = Base64.getEncoder().encode((claims.getAudience() + ":" + password).getBytes(UTF_8)); // Make a new + // ShiroSubject w/ + // audience as name final String usernamePassword = new String(rawEncoded, UTF_8); final String header = "Basic " + usernamePassword; BasicAuthToken token = new BasicAuthToken(header); @@ -68,6 +72,11 @@ public AuthToken issueToken(String audience) { return token; } + @Override + public Subject authenticateToken(AuthToken authToken) { + return new NoopSubject(); + } + public boolean validateToken(AuthToken token) { if (token instanceof BasicAuthToken) { final BasicAuthToken basicAuthToken = (BasicAuthToken) token; diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index 540fed368aeda..448a138e68181 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -12,10 +12,13 @@ import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.junit.Before; +import org.opensearch.identity.Subject; +import org.opensearch.identity.noop.NoopSubject; import org.opensearch.identity.noop.NoopTokenManager; import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; import org.opensearch.identity.tokens.BearerAuthToken; +import org.opensearch.identity.tokens.OnBehalfOfClaims; import org.opensearch.test.OpenSearchTestCase; import org.passay.CharacterCharacteristicsRule; import org.passay.CharacterRule; @@ -31,16 +34,15 @@ public class AuthTokenHandlerTests extends OpenSearchTestCase { private ShiroTokenManager shiroAuthTokenHandler; - private NoopTokenManager noopTokenManager; @Before public void testSetup() { shiroAuthTokenHandler = new ShiroTokenManager(); - noopTokenManager = new NoopTokenManager(); } public void testShouldExtractBasicAuthTokenSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic YWRtaW46YWRtaW4="); // admin:admin + assertEquals(authToken.asAuthHeaderValue(), "YWRtaW46YWRtaW4="); final AuthenticationToken translatedToken = shiroAuthTokenHandler.translateAuthToken(authToken).get(); assertThat(translatedToken, is(instanceOf(UsernamePasswordToken.class))); @@ -106,7 +108,7 @@ public void testShoudPassMapLookupWithToken() { assertTrue(authToken.getPassword().equals(shiroAuthTokenHandler.getShiroTokenPasswordMap().get(authToken))); } - public void testShouldPassThrougbResetToken(AuthToken token) { + public void testShouldPassThroughResetToken() { final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature"); shiroAuthTokenHandler.resetToken(bearerAuthToken); } @@ -121,6 +123,7 @@ public void testVerifyBearerTokenObject() { assertEquals(testGoodToken.getPayload(), "payload"); assertEquals(testGoodToken.getSignature(), "signature"); assertEquals(testGoodToken.toString(), "Bearer auth token with header=header, payload=payload, signature=signature"); + assertEquals(testGoodToken.asAuthHeaderValue(), "header.payload.signature"); } public void testGeneratedPasswordContents() { @@ -144,4 +147,23 @@ public void testGeneratedPasswordContents() { validator.validate(data); } + public void testIssueOnBehalfOfTokenFromClaims() { + Subject subject = new NoopSubject(); + OnBehalfOfClaims claims = new OnBehalfOfClaims("test", "test"); + BasicAuthToken authToken = (BasicAuthToken) shiroAuthTokenHandler.issueOnBehalfOfToken(subject, claims); + assertTrue(authToken instanceof BasicAuthToken); + UsernamePasswordToken translatedToken = (UsernamePasswordToken) shiroAuthTokenHandler.translateAuthToken(authToken).get(); + assertEquals(authToken.getPassword(), new String(translatedToken.getPassword())); + assertTrue(shiroAuthTokenHandler.getShiroTokenPasswordMap().containsKey(authToken)); + assertEquals(shiroAuthTokenHandler.getShiroTokenPasswordMap().get(authToken), new String(translatedToken.getPassword())); + } + + public void testTokenNoopIssuance() { + NoopTokenManager tokenManager = new NoopTokenManager(); + OnBehalfOfClaims claims = new OnBehalfOfClaims("test", "test"); + Subject subject = new NoopSubject(); + AuthToken token = tokenManager.issueOnBehalfOfToken(subject, claims); + assertTrue(token instanceof AuthToken); + } + } diff --git a/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java b/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java index c6ebe295976ce..727bca83f2c94 100644 --- a/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java +++ b/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java @@ -49,6 +49,7 @@ import org.opensearch.extensions.rest.RestActionsRequestHandler; import org.opensearch.extensions.settings.CustomSettingsRequestHandler; import org.opensearch.extensions.settings.RegisterCustomSettingsRequest; +import org.opensearch.identity.IdentityService; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.ConnectTransportException; import org.opensearch.transport.TransportException; @@ -142,9 +143,15 @@ public void initializeServicesAndRestHandler( TransportService transportService, ClusterService clusterService, Settings initialEnvironmentSettings, - NodeClient client + NodeClient client, + IdentityService identityService ) { - this.restActionsRequestHandler = new RestActionsRequestHandler(actionModule.getRestController(), extensionIdMap, transportService); + this.restActionsRequestHandler = new RestActionsRequestHandler( + actionModule.getRestController(), + extensionIdMap, + transportService, + identityService + ); this.customSettingsRequestHandler = new CustomSettingsRequestHandler(settingsModule); this.transportService = transportService; this.clusterService = clusterService; diff --git a/server/src/main/java/org/opensearch/extensions/NoopExtensionsManager.java b/server/src/main/java/org/opensearch/extensions/NoopExtensionsManager.java index d434074279041..d0f69efc0de3a 100644 --- a/server/src/main/java/org/opensearch/extensions/NoopExtensionsManager.java +++ b/server/src/main/java/org/opensearch/extensions/NoopExtensionsManager.java @@ -21,6 +21,7 @@ import org.opensearch.extensions.action.ExtensionActionRequest; import org.opensearch.extensions.action.ExtensionActionResponse; import org.opensearch.extensions.action.RemoteExtensionActionResponse; +import org.opensearch.identity.IdentityService; import org.opensearch.transport.TransportService; /** @@ -41,7 +42,8 @@ public void initializeServicesAndRestHandler( TransportService transportService, ClusterService clusterService, Settings initialEnvironmentSettings, - NodeClient client + NodeClient client, + IdentityService identityService ) { // no-op } diff --git a/server/src/main/java/org/opensearch/extensions/rest/ExtensionRestRequest.java b/server/src/main/java/org/opensearch/extensions/rest/ExtensionRestRequest.java index 8b13061def8d0..f49c5a8291c7f 100644 --- a/server/src/main/java/org/opensearch/extensions/rest/ExtensionRestRequest.java +++ b/server/src/main/java/org/opensearch/extensions/rest/ExtensionRestRequest.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import static java.util.Objects.requireNonNull; /** * Request to execute REST actions on extension node. @@ -86,7 +87,7 @@ public ExtensionRestRequest( this.headers = headers; this.mediaType = mediaType; this.content = content; - this.principalIdentifierToken = principalIdentifier; + this.principalIdentifierToken = requireNonNull(principalIdentifier); this.httpVersion = httpVersion; } @@ -280,7 +281,7 @@ public boolean isContentConsumed() { } /** - * Gets a parser for the contents of this request if there is content and an xContentType. + * Gets a parser for the contents of this request if there is content, an xContentType, and a principal identifier. * * @param xContentRegistry The extension's xContentRegistry * @return A parser for the given content and content type. @@ -291,6 +292,9 @@ public final XContentParser contentParser(NamedXContentRegistry xContentRegistry if (!hasContent() || getXContentType() == null) { throw new OpenSearchParseException("There is no request body or the ContentType is invalid."); } + if (getRequestIssuerIdentity() == null) { + throw new OpenSearchParseException("There is no request body or the requester identity is invalid."); + } return getXContentType().xContent().createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, content.streamInput()); } diff --git a/server/src/main/java/org/opensearch/extensions/rest/RestActionsRequestHandler.java b/server/src/main/java/org/opensearch/extensions/rest/RestActionsRequestHandler.java index 563bfc5a5c9da..f19675e3bfe4c 100644 --- a/server/src/main/java/org/opensearch/extensions/rest/RestActionsRequestHandler.java +++ b/server/src/main/java/org/opensearch/extensions/rest/RestActionsRequestHandler.java @@ -8,16 +8,16 @@ package org.opensearch.extensions.rest; +import java.util.Map; import org.opensearch.action.ActionModule.DynamicActionRegistry; import org.opensearch.extensions.AcknowledgedResponse; import org.opensearch.extensions.DiscoveryExtensionNode; +import org.opensearch.identity.IdentityService; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; import org.opensearch.core.transport.TransportResponse; import org.opensearch.transport.TransportService; -import java.util.Map; - /** * Handles requests to register extension REST actions. * @@ -28,6 +28,7 @@ public class RestActionsRequestHandler { private final RestController restController; private final Map extensionIdMap; private final TransportService transportService; + private final IdentityService identityService; /** * Instantiates a new REST Actions Request Handler using the Node's RestController. @@ -39,11 +40,13 @@ public class RestActionsRequestHandler { public RestActionsRequestHandler( RestController restController, Map extensionIdMap, - TransportService transportService + TransportService transportService, + IdentityService identityService ) { this.restController = restController; this.extensionIdMap = extensionIdMap; this.transportService = transportService; + this.identityService = identityService; } /** @@ -62,7 +65,8 @@ public TransportResponse handleRegisterRestActionsRequest( restActionsRequest, discoveryExtensionNode, transportService, - dynamicActionRegistry + dynamicActionRegistry, + identityService ); restController.registerHandler(handler); return new AcknowledgedResponse(true); diff --git a/server/src/main/java/org/opensearch/extensions/rest/RestSendToExtensionAction.java b/server/src/main/java/org/opensearch/extensions/rest/RestSendToExtensionAction.java index a96e87b0200cc..29217b2cd7e36 100644 --- a/server/src/main/java/org/opensearch/extensions/rest/RestSendToExtensionAction.java +++ b/server/src/main/java/org/opensearch/extensions/rest/RestSendToExtensionAction.java @@ -17,6 +17,10 @@ import org.opensearch.core.xcontent.MediaType; import org.opensearch.extensions.DiscoveryExtensionNode; import org.opensearch.extensions.ExtensionsManager; +import org.opensearch.identity.IdentityService; +import org.opensearch.identity.Subject; +import org.opensearch.identity.tokens.OnBehalfOfClaims; +import org.opensearch.identity.tokens.TokenManager; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.NamedRoute; @@ -31,7 +35,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.Principal; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -54,19 +57,13 @@ public class RestSendToExtensionAction extends BaseRestHandler { private static final String SEND_TO_EXTENSION_ACTION = "send_to_extension_action"; private static final Logger logger = LogManager.getLogger(RestSendToExtensionAction.class); - // To replace with user identity see https://github.com/opensearch-project/OpenSearch/pull/4247 - private static final Principal DEFAULT_PRINCIPAL = new Principal() { - @Override - public String getName() { - return "OpenSearchUser"; - } - }; private final List routes; private final List deprecatedRoutes; private final String pathPrefix; private final DiscoveryExtensionNode discoveryExtensionNode; private final TransportService transportService; + private final IdentityService identityService; private static final Set allowList = Set.of("Content-Type"); private static final Set denyList = Set.of("Authorization", "Proxy-Authorization"); @@ -82,7 +79,8 @@ public RestSendToExtensionAction( RegisterRestActionsRequest restActionsRequest, DiscoveryExtensionNode discoveryExtensionNode, TransportService transportService, - DynamicActionRegistry dynamicActionRegistry + DynamicActionRegistry dynamicActionRegistry, + IdentityService identityService ) { this.pathPrefix = "/_extensions/_" + restActionsRequest.getUniqueId(); RestRequest.Method method; @@ -147,6 +145,7 @@ public RestSendToExtensionAction( this.discoveryExtensionNode = discoveryExtensionNode; this.transportService = transportService; + this.identityService = identityService; } @Override @@ -240,12 +239,15 @@ public String executor() { }; try { + // Will be replaced with ExtensionTokenProcessor and PrincipalIdentifierToken classes from feature/identity - final String extensionTokenProcessor = "placeholder_token_processor"; - final String requestIssuerIdentity = "placeholder_request_issuer_identity"; Map> filteredHeaders = filterHeaders(headers, allowList, denyList); + TokenManager tokenManager = identityService.getTokenManager(); + Subject subject = this.identityService.getSubject(); + OnBehalfOfClaims claims = new OnBehalfOfClaims(discoveryExtensionNode.getId(), subject.getPrincipal().getName()); + transportService.sendRequest( discoveryExtensionNode, ExtensionsManager.REQUEST_REST_EXECUTE_ON_EXTENSION_ACTION, @@ -259,7 +261,7 @@ public String executor() { filteredHeaders, contentType, content, - requestIssuerIdentity, + tokenManager.issueOnBehalfOfToken(subject, claims).asAuthHeaderValue(), httpVersion ), restExecuteOnExtensionResponseHandler diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java b/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java index c6ed8d57da435..c2367383df595 100644 --- a/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java +++ b/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java @@ -8,9 +8,9 @@ package org.opensearch.identity.noop; +import org.opensearch.identity.Subject; import org.opensearch.identity.tokens.TokenManager; import org.opensearch.plugins.IdentityPlugin; -import org.opensearch.identity.Subject; /** * Implementation of identity plugin that does not enforce authentication or authorization diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java index a55f28e02a8aa..1255e822cea6e 100644 --- a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java +++ b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java @@ -11,7 +11,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.identity.IdentityService; +import org.opensearch.identity.Subject; import org.opensearch.identity.tokens.AuthToken; +import org.opensearch.identity.tokens.OnBehalfOfClaims; import org.opensearch.identity.tokens.TokenManager; /** @@ -26,8 +28,17 @@ public class NoopTokenManager implements TokenManager { * @return a new Noop Token */ @Override - public AuthToken issueToken(String audience) { + public AuthToken issueOnBehalfOfToken(final Subject subject, final OnBehalfOfClaims claims) { return new AuthToken() { + @Override + public String asAuthHeaderValue() { + return "noopToken"; + } }; } + + @Override + public Subject authenticateToken(AuthToken authToken) { + return null; + } } diff --git a/server/src/main/java/org/opensearch/identity/tokens/AuthToken.java b/server/src/main/java/org/opensearch/identity/tokens/AuthToken.java index 6e113f6eaa96a..c929e7421b3d8 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/AuthToken.java +++ b/server/src/main/java/org/opensearch/identity/tokens/AuthToken.java @@ -15,4 +15,5 @@ */ public interface AuthToken { + String asAuthHeaderValue(); } diff --git a/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java b/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java index 9cd6cb6b6208a..71b8fe504a5d1 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java +++ b/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java @@ -51,4 +51,13 @@ public void revoke() { this.password = ""; this.user = ""; } + + @Override + public String asAuthHeaderValue() { + if (user == null || password == null) { + return null; + } + String usernamepassword = user + ":" + password; + return Base64.getEncoder().encodeToString(usernamepassword.getBytes(StandardCharsets.UTF_8)); + } } diff --git a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java index eac164af1c5d3..217538c7b001b 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java +++ b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java @@ -58,4 +58,9 @@ public String getTokenIdentifier() { public String toString() { return "Bearer auth token with header=" + header + ", payload=" + payload + ", signature=" + signature; } + + @Override + public String asAuthHeaderValue() { + return completeToken; + } } diff --git a/server/src/main/java/org/opensearch/identity/tokens/OnBehalfOfClaims.java b/server/src/main/java/org/opensearch/identity/tokens/OnBehalfOfClaims.java new file mode 100644 index 0000000000000..3fef248ee6d3a --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/tokens/OnBehalfOfClaims.java @@ -0,0 +1,87 @@ +/* + * 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.tokens; + +/** + * This class represents the claims of an OnBehalfOf token. + */ +public class OnBehalfOfClaims { + + private final String audience; + private final String subject; + private final Long expiration; + private final Long not_before; + private final Long issued_at; + + /** + * Constructor for OnBehalfOfClaims + * @param aud the Audience for the token + * @param subject the subject of the token + * @param expiration the expiration time in seconds for the token + * @param not_before the not_before time in seconds for the token + * @param issued_at the issued_at time in seconds for the token + */ + public OnBehalfOfClaims(String aud, String subject, Long expiration, Long not_before, Long issued_at) { + this.audience = aud; + this.subject = subject; + this.expiration = expiration; + this.not_before = not_before; + this.issued_at = issued_at; + } + + /** + * A constructor that sets a default issued at time of the current time + * @param aud the Audience for the token + * @param subject the subject of the token + * @param expiration the expiration time in seconds for the token + * @param not_before the not_before time in seconds for the token + */ + public OnBehalfOfClaims(String aud, String subject, Long expiration, Long not_before) { + this(aud, subject, expiration, not_before, System.currentTimeMillis() / 1000); + } + + /** + * A constructor which sets a default not before time of the current time + * @param aud the Audience for the token + * @param subject the subject of the token + * @param expiration the expiration time in seconds for the token + */ + public OnBehalfOfClaims(String aud, String subject, Long expiration) { + this(aud, subject, expiration, System.currentTimeMillis() / 1000); + } + + /** + * A constructor which sets the default expiration time of 5 minutes from the current time + * @param aud the Audience for the token + * @param subject the subject of the token + */ + public OnBehalfOfClaims(String aud, String subject) { + this(aud, subject, System.currentTimeMillis() / 1000 + 300); + } + + public String getAudience() { + return audience; + } + + public String getSubject() { + return subject; + } + + public Long getExpiration() { + return expiration; + } + + public Long getNot_before() { + return not_before; + } + + public Long getIssued_at() { + return issued_at; + } +} diff --git a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java index 029ce430e7532..4f6ddeb48dea3 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java +++ b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java @@ -8,15 +8,25 @@ package org.opensearch.identity.tokens; +import org.opensearch.identity.Subject; + /** * This interface defines the expected methods of a token manager */ public interface TokenManager { /** - * Create a new auth token - * @param audience: The audience for the token + * Create a new on behalf of token + * + * @param claims: A list of claims for the token to be generated with * @return A new auth token */ - public AuthToken issueToken(String audience); + public AuthToken issueOnBehalfOfToken(final Subject subject, final OnBehalfOfClaims claims); + + /** + * Authenticates a provided authToken + * @param authToken: The authToken to authenticate + * @return The authenticated subject + */ + public Subject authenticateToken(AuthToken authToken); } diff --git a/server/src/main/java/org/opensearch/node/Node.java b/server/src/main/java/org/opensearch/node/Node.java index f39fcd2714b6f..4c87caa9d4ab5 100644 --- a/server/src/main/java/org/opensearch/node/Node.java +++ b/server/src/main/java/org/opensearch/node/Node.java @@ -870,7 +870,8 @@ protected Node( transportService, clusterService, environment.settings(), - client + client, + identityService ); final GatewayMetaState gatewayMetaState = new GatewayMetaState(); final ResponseCollectorService responseCollectorService = new ResponseCollectorService(clusterService); diff --git a/server/src/test/java/org/opensearch/extensions/ExtensionsManagerTests.java b/server/src/test/java/org/opensearch/extensions/ExtensionsManagerTests.java index 840605b61c500..b3e2214a21ee7 100644 --- a/server/src/test/java/org/opensearch/extensions/ExtensionsManagerTests.java +++ b/server/src/test/java/org/opensearch/extensions/ExtensionsManagerTests.java @@ -8,33 +8,30 @@ package org.opensearch.extensions; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.mock; -import static org.opensearch.test.ClusterServiceUtils.createClusterService; - import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collections; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.junit.After; import org.junit.Before; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import org.opensearch.OpenSearchException; import org.opensearch.Version; import org.opensearch.action.ActionModule; @@ -42,18 +39,18 @@ import org.opensearch.action.admin.cluster.state.ClusterStateResponse; import org.opensearch.client.node.NodeClient; import org.opensearch.cluster.ClusterSettingsResponse; -import org.opensearch.common.util.FeatureFlags; -import org.opensearch.env.EnvironmentSettingsResponse; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.common.network.NetworkService; import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.Setting.Property; import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.SettingsModule; import org.opensearch.common.settings.WriteableSetting; -import org.opensearch.common.settings.Setting.Property; import org.opensearch.common.settings.WriteableSetting.SettingType; +import org.opensearch.common.util.FeatureFlags; import org.opensearch.common.settings.SettingsModule; import org.opensearch.core.common.transport.TransportAddress; import org.opensearch.common.util.PageCacheRecycler; @@ -61,14 +58,16 @@ import org.opensearch.core.common.io.stream.BytesStreamInput; import org.opensearch.core.common.io.stream.NamedWriteableRegistry; import org.opensearch.env.Environment; +import org.opensearch.env.EnvironmentSettingsResponse; +import org.opensearch.extensions.ExtensionsSettings.Extension; import org.opensearch.extensions.proto.ExtensionRequestProto; import org.opensearch.extensions.rest.RegisterRestActionsRequest; import org.opensearch.extensions.settings.RegisterCustomSettingsRequest; -import org.opensearch.extensions.ExtensionsSettings.Extension; import org.opensearch.identity.IdentityService; import org.opensearch.core.indices.breaker.NoneCircuitBreakerService; import org.opensearch.plugins.ExtensionAwarePlugin; import org.opensearch.rest.RestController; +import static org.opensearch.test.ClusterServiceUtils.createClusterService; import org.opensearch.test.FeatureFlagSetter; import org.opensearch.test.MockLogAppender; import org.opensearch.test.OpenSearchTestCase; @@ -93,6 +92,8 @@ public class ExtensionsManagerTests extends OpenSearchTestCase { private Setting customSetting = Setting.simpleString("custom_extension_setting", "none", Property.ExtensionScope); private NodeClient client; private MockNioTransport transport; + private IdentityService identityService; + private final ThreadPool threadPool = new TestThreadPool(ExtensionsManagerTests.class.getSimpleName()); private final Settings settings = Settings.builder() .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT) @@ -164,6 +165,7 @@ public List> getExtensionSettings() { Collections.emptyList() ); client = new NoOpNodeClient(this.getTestName()); + identityService = new IdentityService(Settings.EMPTY, List.of()); } @Override @@ -767,7 +769,8 @@ public void testRegisterHandler() throws Exception { mockTransportService, clusterService, settings, - client + client, + identityService ); verify(mockTransportService, times(9)).registerRequestHandler(anyString(), anyString(), anyBoolean(), anyBoolean(), any(), any()); @@ -884,7 +887,8 @@ private void initialize(ExtensionsManager extensionsManager) { transportService, clusterService, settings, - client + client, + identityService ); } } diff --git a/server/src/test/java/org/opensearch/extensions/rest/ExtensionRestRequestTests.java b/server/src/test/java/org/opensearch/extensions/rest/ExtensionRestRequestTests.java index 2f8cec957e42e..6ac639daf14c2 100644 --- a/server/src/test/java/org/opensearch/extensions/rest/ExtensionRestRequestTests.java +++ b/server/src/test/java/org/opensearch/extensions/rest/ExtensionRestRequestTests.java @@ -8,6 +8,7 @@ package org.opensearch.extensions.rest; +import org.opensearch.common.settings.Settings; import org.opensearch.core.rest.RestStatus; import org.opensearch.OpenSearchParseException; import org.opensearch.core.common.bytes.BytesArray; @@ -20,6 +21,10 @@ import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.core.common.io.stream.NamedWriteableAwareStreamInput; +import org.opensearch.identity.IdentityService; +import org.opensearch.identity.Subject; +import org.opensearch.identity.tokens.OnBehalfOfClaims; +import org.opensearch.identity.tokens.TokenManager; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestRequest.Method; import org.opensearch.http.HttpRequest; @@ -45,10 +50,10 @@ public class ExtensionRestRequestTests extends OpenSearchTestCase { String extensionUniqueId1; Principal userPrincipal; HttpRequest.HttpVersion expectedHttpVersion; - // Will be replaced with ExtensionTokenProcessor and PrincipalIdentifierToken classes from feature/identity String extensionTokenProcessor; String expectedRequestIssuerIdentity; NamedWriteableRegistry registry; + private IdentityService identityService; public void setUp() throws Exception { super.setUp(); @@ -66,7 +71,13 @@ public void setUp() throws Exception { userPrincipal = () -> "user1"; expectedHttpVersion = HttpRequest.HttpVersion.HTTP_1_1; extensionTokenProcessor = "placeholder_extension_token_processor"; - expectedRequestIssuerIdentity = "placeholder_request_issuer_identity"; + identityService = new IdentityService(Settings.EMPTY, List.of()); + TokenManager tokenManager = identityService.getTokenManager(); + Subject subject = this.identityService.getSubject(); + OnBehalfOfClaims claims = new OnBehalfOfClaims("testID", subject.getPrincipal().getName()); + expectedRequestIssuerIdentity = identityService.getTokenManager() + .issueOnBehalfOfToken(identityService.getSubject(), claims) + .asAuthHeaderValue(); } public void testExtensionRestRequest() throws Exception { diff --git a/server/src/test/java/org/opensearch/extensions/rest/RestSendToExtensionActionTests.java b/server/src/test/java/org/opensearch/extensions/rest/RestSendToExtensionActionTests.java index f0cdbd6c96d41..d82210a3ed792 100644 --- a/server/src/test/java/org/opensearch/extensions/rest/RestSendToExtensionActionTests.java +++ b/server/src/test/java/org/opensearch/extensions/rest/RestSendToExtensionActionTests.java @@ -10,20 +10,14 @@ import java.net.InetAddress; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Set; import java.util.Map; -import java.util.Arrays; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; - -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; -import static org.mockito.Mockito.mock; - import org.junit.After; import org.junit.Before; import org.opensearch.Version; @@ -57,6 +51,10 @@ import org.opensearch.transport.TransportService; import org.opensearch.transport.nio.MockNioTransport; import org.opensearch.usage.UsageService; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static org.mockito.Mockito.mock; public class RestSendToExtensionActionTests extends OpenSearchTestCase { @@ -65,6 +63,7 @@ public class RestSendToExtensionActionTests extends OpenSearchTestCase { private DiscoveryExtensionNode discoveryExtensionNode; private ActionModule actionModule; private DynamicActionRegistry dynamicActionRegistry; + private IdentityService identityService; private final ThreadPool threadPool = new TestThreadPool(RestSendToExtensionActionTests.class.getSimpleName()); @Before @@ -121,6 +120,7 @@ public void setup() throws Exception { new IdentityService(Settings.EMPTY, new ArrayList<>()), new ExtensionsManager(Set.of()) ); + identityService = new IdentityService(Settings.EMPTY, new ArrayList<>()); dynamicActionRegistry = actionModule.getDynamicActionRegistry(); } @@ -142,7 +142,8 @@ public void testRestSendToExtensionAction() throws Exception { registerRestActionRequest, discoveryExtensionNode, transportService, - dynamicActionRegistry + dynamicActionRegistry, + identityService ); assertEquals("send_to_extension_action", restSendToExtensionAction.getName()); @@ -174,7 +175,8 @@ public void testRestSendToExtensionActionWithNamedRoute() throws Exception { registerRestActionRequest, discoveryExtensionNode, transportService, - dynamicActionRegistry + dynamicActionRegistry, + identityService ); assertEquals("send_to_extension_action", restSendToExtensionAction.getName()); @@ -219,7 +221,8 @@ public void testRestSendToExtensionActionWithNamedRouteAndLegacyActionName() thr registerRestActionRequest, discoveryExtensionNode, transportService, - dynamicActionRegistry + dynamicActionRegistry, + identityService ); assertEquals("send_to_extension_action", restSendToExtensionAction.getName()); @@ -271,7 +274,13 @@ public void testRestSendToExtensionActionWithoutUniqueNameShouldFail() { ); expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } @@ -283,7 +292,13 @@ public void testRestSendToExtensionMultipleNamedRoutesWithSameName() throws Exce ); expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } @@ -295,7 +310,13 @@ public void testRestSendToExtensionMultipleNamedRoutesWithSameLegacyActionName() ); expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } @@ -307,7 +328,13 @@ public void testRestSendToExtensionMultipleRoutesWithSameMethodAndPath() throws ); expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } @@ -319,7 +346,13 @@ public void testRestSendToExtensionMultipleRoutesWithSameMethodAndPathWithDiffer ); expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } @@ -331,7 +364,13 @@ public void testRestSendToExtensionMultipleRoutesWithSameMethodAndPathWithPathPa ); try { - new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry); + new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ); } catch (IllegalArgumentException e) { fail("IllegalArgumentException should not be thrown for different paths"); } @@ -353,7 +392,13 @@ public void testRestSendToExtensionWithNamedRouteCollidingWithDynamicTransportAc expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } @@ -367,7 +412,13 @@ public void testRestSendToExtensionWithNamedRouteCollidingWithNativeTransportAct ); expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } @@ -381,7 +432,8 @@ public void testRestSendToExtensionActionFilterHeaders() throws Exception { registerRestActionRequest, discoveryExtensionNode, transportService, - dynamicActionRegistry + dynamicActionRegistry, + identityService ); Map> headers = new HashMap<>(); @@ -407,7 +459,13 @@ public void testRestSendToExtensionActionBadMethod() throws Exception { ); expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } @@ -419,7 +477,13 @@ public void testRestSendToExtensionActionBadDeprecatedMethod() throws Exception ); expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } @@ -431,7 +495,13 @@ public void testRestSendToExtensionActionMissingUri() throws Exception { ); expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } @@ -443,7 +513,13 @@ public void testRestSendToExtensionActionMissingDeprecatedUri() throws Exception ); expectThrows( IllegalArgumentException.class, - () -> new RestSendToExtensionAction(registerRestActionRequest, discoveryExtensionNode, transportService, dynamicActionRegistry) + () -> new RestSendToExtensionAction( + registerRestActionRequest, + discoveryExtensionNode, + transportService, + dynamicActionRegistry, + identityService + ) ); } }