Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces IdentityPlugin with initial interface for extensions use-cases #56

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,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.index.IndexModule;
import org.opensearch.index.IndexService;
import org.opensearch.index.IndicesModuleRequest;
Expand Down Expand Up @@ -114,6 +115,7 @@ public static enum OpenSearchRequestType {
private CustomSettingsRequestHandler customSettingsRequestHandler;
private TransportService transportService;
private ClusterService clusterService;
private IdentityService identityService;
private Settings environmentSettings;
private AddSettingsUpdateConsumerRequestHandler addSettingsUpdateConsumerRequestHandler;
private NodeClient client;
Expand Down Expand Up @@ -163,13 +165,20 @@ public void initializeServicesAndRestHandler(
SettingsModule settingsModule,
TransportService transportService,
ClusterService clusterService,
IdentityService identityService,
Settings initialEnvironmentSettings,
NodeClient client
) {
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;
this.identityService = identityService;
this.environmentSettings = initialEnvironmentSettings;
this.addSettingsUpdateConsumerRequestHandler = new AddSettingsUpdateConsumerRequestHandler(
clusterService,
Expand Down Expand Up @@ -663,6 +672,10 @@ public ClusterService getClusterService() {
return clusterService;
}

public void setIdentityService(IdentityService identityService) {
this.identityService = identityService;
}

public static String getRequestExtensionRegisterRestActions() {
return REQUEST_EXTENSION_REGISTER_REST_ACTIONS;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

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.transport.TransportResponse;
Expand All @@ -28,6 +29,8 @@ public class RestActionsRequestHandler {
private final Map<String, DiscoveryExtensionNode> extensionIdMap;
private final TransportService transportService;

private final IdentityService identityService;

/**
* Instantiates a new REST Actions Request Handler using the Node's RestController.
*
Expand All @@ -38,11 +41,13 @@ public class RestActionsRequestHandler {
public RestActionsRequestHandler(
RestController restController,
Map<String, DiscoveryExtensionNode> extensionIdMap,
TransportService transportService
TransportService transportService,
IdentityService identityService
) {
this.restController = restController;
this.extensionIdMap = extensionIdMap;
this.transportService = transportService;
this.identityService = identityService;
}

/**
Expand All @@ -54,7 +59,7 @@ public RestActionsRequestHandler(
*/
public TransportResponse handleRegisterRestActionsRequest(RegisterRestActionsRequest restActionsRequest) throws Exception {
DiscoveryExtensionNode discoveryExtensionNode = extensionIdMap.get(restActionsRequest.getUniqueId());
RestHandler handler = new RestSendToExtensionAction(restActionsRequest, discoveryExtensionNode, transportService);
RestHandler handler = new RestSendToExtensionAction(restActionsRequest, discoveryExtensionNode, transportService, identityService);
restController.registerHandler(handler);
return new AcknowledgedResponse(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import org.opensearch.common.xcontent.XContentType;
import org.opensearch.extensions.DiscoveryExtensionNode;
import org.opensearch.extensions.ExtensionsManager;
import org.opensearch.identity.IdentityService;
import org.opensearch.identity.tokens.AuthToken;
import org.opensearch.rest.BaseRestHandler;
import org.opensearch.rest.BytesRestResponse;
import org.opensearch.rest.RestRequest;
Expand Down Expand Up @@ -64,6 +66,7 @@ public String getName() {
private final String pathPrefix;
private final DiscoveryExtensionNode discoveryExtensionNode;
private final TransportService transportService;
private final IdentityService identityService;

private static final Set<String> allowList = Set.of("Content-Type");
private static final Set<String> denyList = Set.of("Authorization", "Proxy-Authorization");
Expand All @@ -78,7 +81,8 @@ public String getName() {
public RestSendToExtensionAction(
RegisterRestActionsRequest restActionsRequest,
DiscoveryExtensionNode discoveryExtensionNode,
TransportService transportService
TransportService transportService,
IdentityService identityService
) {
this.pathPrefix = "/_extensions/_" + restActionsRequest.getUniqueId();
RestRequest.Method method;
Expand Down Expand Up @@ -118,6 +122,7 @@ public RestSendToExtensionAction(

this.discoveryExtensionNode = discoveryExtensionNode;
this.transportService = transportService;
this.identityService = identityService;
}

@Override
Expand Down Expand Up @@ -207,9 +212,8 @@ 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";
final AuthToken accessToken = identityService.getTokenManager()
.issueAccessTokenOnBehalfOfAuthenticatedUser(discoveryExtensionNode.getId());

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

Expand All @@ -226,7 +230,7 @@ public String executor() {
filteredHeaders,
contentType,
content,
requestIssuerIdentity,
accessToken.getTokenValue(),
httpVersion
),
restExecuteOnExtensionResponseHandler
Expand Down
59 changes: 59 additions & 0 deletions server/src/main/java/org/opensearch/identity/IdentityService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.identity;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.OpenSearchException;
import org.opensearch.identity.noop.NoopIdentityPlugin;
import java.util.List;
import org.opensearch.common.settings.Settings;
import org.opensearch.plugins.IdentityPlugin;
import java.util.stream.Collectors;

/**
* Identity and access control for OpenSearch.
*
* @opensearch.experimental
* */
public class IdentityService {
private static final Logger logger = LogManager.getLogger(IdentityService.class);

private final Settings settings;
private final IdentityPlugin identityPlugin;

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

if (identityPlugins.size() == 0) {
identityPlugin = new NoopIdentityPlugin();
} else if (identityPlugins.size() == 1) {
identityPlugin = identityPlugins.get(0);
} else {
throw new OpenSearchException(
"Multiple identity plugins are not supported, found: "
+ identityPlugins.stream().map(Object::getClass).map(Class::getName).collect(Collectors.joining(","))
);
}

logger.info("Identity module loaded with " + identityPlugin.getClass().getName());
logger.info("Current subject " + getSubject());
}

/**
* Gets the current subject
*/
public Subject getSubject() {
return identityPlugin.getSubject();
}

/**
* Gets the token manager
*/
public TokenManager getTokenManager() {
return identityPlugin.getTokenManager();
}
}
50 changes: 50 additions & 0 deletions server/src/main/java/org/opensearch/identity/NamedPrincipal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.identity;

import java.security.Principal;
import java.util.Objects;

/**
* Create a principal from a string
*
* @opensearch.experimental
*/
public class NamedPrincipal implements Principal {

private final String name;

/**
* Creates a principal for an identity specified as a string
* @param name A persistent string that represent an identity
*/
public NamedPrincipal(final String name) {
this.name = name;
}

@Override
public String getName() {
return name;
}

@Override
public boolean equals(final Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
final Principal that = (Principal) obj;
return Objects.equals(name, that.getName());
}

@Override
public int hashCode() {
return Objects.hash(name);
}

@Override
public String toString() {
return "StringPrincipal(" + "name=" + name + ")";
}
}
36 changes: 36 additions & 0 deletions server/src/main/java/org/opensearch/identity/Principals.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.identity;

import java.security.Principal;

/**
* Available OpenSearch internal principals
*
* @opensearch.experimental
*/
public enum Principals {

/**
* Represents a principal which has not been authenticated
*/
UNAUTHENTICATED(new NamedPrincipal("Unauthenticated")),
NONE(new NamedPrincipal("None"));

private final Principal principal;

Principals(final Principal principal) {
this.principal = principal;
}

/**
* Returns the underlying principal for this
*/
public Principal getPrincipal() {
return principal;
}

}
36 changes: 36 additions & 0 deletions server/src/main/java/org/opensearch/identity/Subject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.identity;

import java.security.Principal;

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

/**
* Get the application-wide uniquely identifying principal
* */
Principal getPrincipal();

/**
* Login through an authentication token
* throws UnsupportedAuthenticationMethod
* throws InvalidAuthenticationToken
* throws SubjectNotFound
* throws SubjectDisabled
*/
// TODO Uncomment login and make a richer interface to IdentityPlugin for authc and authz use-cases
// void login(final AuthToken token);

/**
* Method that returns whether the current subject of the running thread is authenticated
*/
boolean isAuthenticated();
}
35 changes: 35 additions & 0 deletions server/src/main/java/org/opensearch/identity/TokenManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.OpenSearchSecurityException;
import org.opensearch.identity.tokens.AuthToken;

/**
* Interface for a token manager to issue Auth Tokens for a User
*
* @opensearch.experimental
*/
public interface TokenManager {
/**
* Issue an access token on-behalf-of authenticated user for a service to utilize to interact with
* the OpenSearch cluster on-behalf-of the original user
* */
AuthToken issueAccessTokenOnBehalfOfAuthenticatedUser(String extensionUniqueId) throws OpenSearchSecurityException;

/**
* Issue a refresh token on-behalf-of authenticated user for a service to refresh access tokens
* */
AuthToken issueRefreshTokenOnBehalfOfAuthenticatedUser(String extensionUniqueId) throws OpenSearchSecurityException;

/**
* Issue a service account token for an extension's service account
* */
AuthToken generateServiceAccountToken(String extensionUniqueId) throws OpenSearchSecurityException;
}
Loading