Skip to content

Commit

Permalink
Implements token handler
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Liang <jiallian@amazon.com>
  • Loading branch information
RyanL1997 committed Jul 19, 2023
1 parent c8af989 commit ca45f55
Show file tree
Hide file tree
Showing 7 changed files with 511 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
import org.opensearch.security.http.SecurityHttpServerTransport;
import org.opensearch.security.http.SecurityNonSslHttpServerTransport;
import org.opensearch.security.http.XFFResolver;
import org.opensearch.security.identity.SecurityTokenManager;
import org.opensearch.security.privileges.PrivilegesEvaluator;
import org.opensearch.security.privileges.PrivilegesInterceptor;
import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator;
Expand Down Expand Up @@ -192,9 +193,13 @@
import org.opensearch.transport.TransportResponseHandler;
import org.opensearch.transport.TransportService;
import org.opensearch.watcher.ResourceWatcherService;

import org.opensearch.identity.Subject;
import org.opensearch.identity.tokens.TokenManager;
import org.opensearch.plugins.IdentityPlugin;
// CS-ENFORCE-SINGLE

public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin implements ClusterPlugin, MapperPlugin {
public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin implements ClusterPlugin, MapperPlugin, IdentityPlugin {

private static final String KEYWORD = ".keyword";
private static final Logger actionTrace = LogManager.getLogger("opendistro_security_action_trace");
Expand All @@ -208,6 +213,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
private volatile SecurityInterceptor si;
private volatile PrivilegesEvaluator evaluator;
private volatile UserService userService;
private volatile SecurityTokenManager securityTokenManager;
private volatile RestLayerPrivilegesEvaluator restLayerEvaluator;
private volatile ThreadPool threadPool;
private volatile ConfigurationRepository cr;
Expand Down Expand Up @@ -997,6 +1003,8 @@ public Collection<Object> createComponents(

userService = new UserService(cs, cr, settings, localClient);

securityTokenManager = new SecurityTokenManager(threadPool, clusterService, cr, localClient, settings, userService);

final XFFResolver xffResolver = new XFFResolver(threadPool);
backendRegistry = new BackendRegistry(settings, adminDns, xffResolver, auditLog, threadPool);

Expand Down Expand Up @@ -1898,6 +1906,16 @@ public static void setLocalNode(DiscoveryNode node) {
localNode = node;
}

@Override
public Subject getSubject() {
return null;
}

@Override
public TokenManager getTokenManager() {
return securityTokenManager;
}

public static class GuiceHolder implements LifecycleComponent {

private static RepositoriesService repositoriesService;
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/org/opensearch/security/dlic/rest/support/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

import java.io.IOException;
import java.security.AccessController;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
Expand All @@ -32,6 +34,7 @@
import org.apache.commons.lang3.tuple.Pair;
import org.bouncycastle.crypto.generators.OpenBSDBCrypt;

import org.bouncycastle.util.encoders.Hex;
import org.opensearch.ExceptionsHelper;
import org.opensearch.OpenSearchParseException;
import org.opensearch.SpecialPermission;
Expand All @@ -50,6 +53,7 @@
import org.opensearch.security.DefaultObjectMapper;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.security.user.User;
import java.nio.charset.StandardCharsets;

import static org.opensearch.core.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION;

Expand Down Expand Up @@ -213,6 +217,19 @@ public static String hash(final char[] clearTextPassword) {
return hash;
}

/**
* This generates a SHA-256 hash for a given password.
* It is used for validating internal user tokens since we don't want to store the salt in the plugin.
* @param password The password to be hashed
* @return hash of the password
*/
public static String universalHash(String password) throws NoSuchAlgorithmException {

MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(password.getBytes(StandardCharsets.UTF_8));
return new String(Hex.encode(hash));
}

/**
* Generate field resource paths
* @param fields fields
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* 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.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.identity;

import java.util.Collections;
import org.opensearch.client.Client;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.inject.Inject;
import org.opensearch.common.settings.Settings;
import org.opensearch.identity.tokens.AuthToken;
import org.opensearch.identity.tokens.BasicAuthToken;
import org.opensearch.identity.tokens.BearerAuthToken;
import org.opensearch.identity.tokens.TokenManager;
import org.opensearch.security.configuration.ConfigurationRepository;
import org.opensearch.security.securityconf.DynamicConfigFactory;
import org.opensearch.security.securityconf.impl.CType;
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
import org.opensearch.security.user.InternalUserTokenHandler;
import org.opensearch.security.user.UserService;
import org.opensearch.security.user.UserServiceException;
import org.opensearch.security.user.UserTokenHandler;
import org.opensearch.threadpool.ThreadPool;

/**
* This class serves as a funneling implementation of the TokenManager interface.
* The class allows the Security Plugin to implement two separate types of token managers without requiring specific interfaces
* in the IdentityPlugin.
*/
public class SecurityTokenManager implements TokenManager {

Settings settings;

ThreadPool threadPool;

ClusterService clusterService;
Client client;
ConfigurationRepository configurationRepository;
UserService userService;
UserTokenHandler userTokenHandler;
InternalUserTokenHandler internalUserTokenHandler;

public final String TOKEN_NOT_SUPPORTED_MESSAGE = "The provided token type is not supported by the Security Plugin.";

@Inject
public SecurityTokenManager(
ThreadPool threadPool,
ClusterService clusterService,
ConfigurationRepository configurationRepository,
Client client,
Settings settings,
UserService userService
) {
this.threadPool = threadPool;
this.clusterService = clusterService;
this.client = client;
this.configurationRepository = configurationRepository;
this.settings = settings;
this.userService = userService;
userTokenHandler = new UserTokenHandler(threadPool, clusterService, configurationRepository, client);
internalUserTokenHandler = new InternalUserTokenHandler(settings, userService);

}

@Override
public AuthToken issueToken(String account) {

AuthToken token;
final SecurityDynamicConfiguration<?> internalUsersConfiguration = load(UserService.getUserConfigName(), false);
if (internalUsersConfiguration.exists(account)) {
token = internalUserTokenHandler.issueToken(account);
} else {
token = userTokenHandler.issueToken(account);
}
return token;
}

public boolean validateToken(AuthToken authToken) {

if (authToken instanceof BearerAuthToken) {
return userTokenHandler.validateToken(authToken);
}
if (authToken instanceof BasicAuthToken) {
return internalUserTokenHandler.validateToken(authToken);
}
throw new UserServiceException(TOKEN_NOT_SUPPORTED_MESSAGE);
}

public String getTokenInfo(AuthToken authToken) {

if (authToken instanceof BearerAuthToken) {
return userTokenHandler.getTokenInfo(authToken);
}
if (authToken instanceof BasicAuthToken) {
return internalUserTokenHandler.getTokenInfo(authToken);
}
throw new UserServiceException(TOKEN_NOT_SUPPORTED_MESSAGE);
}

public void revokeToken(AuthToken authToken) {
if (authToken instanceof BearerAuthToken) {
userTokenHandler.revokeToken(authToken);
return;
}
if (authToken instanceof BasicAuthToken) {
internalUserTokenHandler.revokeToken(authToken);
return;
}
throw new UserServiceException(TOKEN_NOT_SUPPORTED_MESSAGE);
}

/**
* Only for testing
*/
public void setInternalUserTokenHandler(InternalUserTokenHandler handler) {
this.internalUserTokenHandler = handler;
}

/**
* Only for testing
*/
public void setUserTokenHandler(UserTokenHandler handler) {
this.userTokenHandler = handler;
}

/**
* Load data for a given CType
* @param config CType whose data is to be loaded in-memory
* @return configuration loaded with given CType data
*/
protected final SecurityDynamicConfiguration<?> load(final CType config, boolean logComplianceEvent) {
SecurityDynamicConfiguration<?> loaded = configurationRepository.getConfigurationsFromIndex(
Collections.singleton(config),
logComplianceEvent
).get(config).deepClone();
return DynamicConfigFactory.addStatics(loaded);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.opensearch.security.securityconf.impl.v7.RoleMappingsV7;
import org.opensearch.security.securityconf.impl.v7.RoleV7;
import org.opensearch.security.securityconf.impl.v7.TenantV7;
import org.opensearch.identity.tokens.BearerAuthToken;

public enum CType {

Expand All @@ -59,7 +60,8 @@ public enum CType {
NODESDN(toMap(1, NodesDn.class, 2, NodesDn.class)),
WHITELIST(toMap(1, WhitelistingSettings.class, 2, WhitelistingSettings.class)),
ALLOWLIST(toMap(1, AllowlistingSettings.class, 2, AllowlistingSettings.class)),
AUDIT(toMap(1, AuditConfig.class, 2, AuditConfig.class));
AUDIT(toMap(1, AuditConfig.class, 2, AuditConfig.class)),
REVOKEDTOKENS(toMap(1, BearerAuthToken.class));

private Map<Integer, Class<?>> implementations;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.user;

import org.opensearch.common.inject.Inject;
import org.opensearch.common.settings.Settings;
import org.opensearch.identity.tokens.AuthToken;
import org.opensearch.identity.tokens.BasicAuthToken;
import org.opensearch.identity.tokens.TokenManager;
import org.opensearch.security.securityconf.Hashed;
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;

import static org.opensearch.security.dlic.rest.support.Utils.universalHash;

public class InternalUserTokenHandler implements TokenManager {

Settings settings;

UserService userService;

public SecurityDynamicConfiguration<?> internalUsersConfiguration;

@Inject
public InternalUserTokenHandler(final Settings settings, UserService userService) {
this.settings = settings;
this.userService = userService;
this.internalUsersConfiguration = userService.geInternalUsersConfigurationRepository();
}

public AuthToken issueToken() {
throw new UserServiceException(
"The InternalUserTokenHandler is unable to issue generic auth tokens. Please specify a valid internal user."
);
}

public AuthToken issueToken(String internalUser) {
String tokenAsString;
try {
tokenAsString = this.userService.generateAuthToken(internalUser);
} catch (IOException | UserServiceException ex) {
throw new UserServiceException("Failed to generate an auth token for " + internalUser);
}
return new BasicAuthToken(tokenAsString);
}

public boolean validateToken(AuthToken token) {
if (!(token instanceof BasicAuthToken)) {
throw new UserServiceException("The provided auth token is of an incorrect type. Please provide a BasicAuthToken object.");
}
BasicAuthToken basicToken = (BasicAuthToken) token;
String accountName = basicToken.getUser();
String password = basicToken.getPassword();
String hash;
try {
hash = universalHash(password);
} catch (NoSuchAlgorithmException e) {
throw new UserServiceException("The provided token could not be validated.");
}
return (internalUsersConfiguration.exists(accountName)
&& hash.equals(((Hashed) internalUsersConfiguration.getCEntry(accountName)).getHash()));
}

public String getTokenInfo(AuthToken token) {
if (!(token instanceof BasicAuthToken)) {
throw new UserServiceException("The provided token is not a BasicAuthToken.");
}
BasicAuthToken basicAuthToken = (BasicAuthToken) token;
return "The provided token is a BasicAuthToken with content: " + basicAuthToken;
}

public void revokeToken(AuthToken token) {
if (validateToken(token)) {
BasicAuthToken basicToken = (BasicAuthToken) token;
String accountName = basicToken.getUser();
try {
userService.clearHash(accountName);
return;
} catch (IOException e) {
throw new UserServiceException(e.getMessage());
}
}
throw new UserServiceException("The token could not be revoked.");
}

public void resetToken(AuthToken token) {
throw new UserServiceException("The InternalUserTokenHandler is unable to reset auth tokens.");
}
}
Loading

0 comments on commit ca45f55

Please sign in to comment.