Skip to content

Commit

Permalink
Add api token action to handle creation of api token index
Browse files Browse the repository at this point in the history
Signed-off-by: Derek Ho <dxho@amazon.com>
  • Loading branch information
derek-ho committed Nov 15, 2024
1 parent 6ae7090 commit 0a708f9
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public class BackendRegistry {
private Cache<AuthCredentials, User> userCache; // rest standard
private Cache<String, User> restImpersonationCache; // used for rest impersonation
private Cache<User, Set<String>> restRoleCache; //
private Cache<AuthCredentials, User> apiTokensCache;

private void createCaches() {
userCache = CacheBuilder.newBuilder()
Expand Down Expand Up @@ -135,6 +136,12 @@ public void onRemoval(RemovalNotification<User, Set<String>> notification) {
})
.build();

apiTokensCache = CacheBuilder.newBuilder()
.expireAfterWrite(ttlInMin, TimeUnit.MINUTES)
.removalListener((RemovalListener<AuthCredentials, User>) notification -> log.debug("Clear api token cache for {} due to {}", notification.getKey(), notification.getCause()))
.build();


}

public BackendRegistry(
Expand Down Expand Up @@ -170,6 +177,7 @@ public void invalidateCache() {
userCache.invalidateAll();
restImpersonationCache.invalidateAll();
restRoleCache.invalidateAll();
apiTokensCache.invalidateAll();
}

@Subscribe
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* 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.dlic.rest.api;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.opensearch.action.admin.indices.create.CreateIndexRequest;
import org.opensearch.action.index.IndexResponse;
import org.opensearch.client.Client;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.settings.Settings;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.core.xcontent.ToXContent;
import org.opensearch.security.DefaultObjectMapper;
import org.opensearch.security.dlic.rest.validation.EndpointValidator;
import org.opensearch.security.dlic.rest.validation.RequestContentValidator;
import org.opensearch.security.dlic.rest.validation.RequestContentValidator.DataType;
import org.opensearch.security.dlic.rest.validation.ValidationResult;
import org.opensearch.security.securityconf.impl.CType;
import org.opensearch.security.securityconf.impl.v7.ConfigV7;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.security.support.SecurityJsonNode;
import org.opensearch.threadpool.ThreadPool;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.opensearch.rest.RestRequest.Method.*;
import static org.opensearch.security.dlic.rest.api.RateLimitersApiAction.NAME_JSON_PROPERTY;
import static org.opensearch.security.dlic.rest.api.Responses.*;
import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix;
import static org.opensearch.security.securityconf.impl.v7.ConfigV7.*;

public class ApiTokenApiAction extends AbstractApiAction {

public static final String NAME_JSON_PROPERTY = "ip";


private static final List<Route> ROUTES = addRoutesPrefix(
ImmutableList.of(
new Route(GET, "/apitokens"),
new Route(PUT, "/apitokens/{name}")
// new Route(DELETE, "/apitokens/{name}"),
)
);

protected ApiTokenApiAction(ClusterService clusterService, ThreadPool threadPool, SecurityApiDependencies securityApiDependencies) {
super(Endpoint.APITOKENS, clusterService, threadPool, securityApiDependencies);
this.requestHandlersBuilder.configureRequestHandlers(this::authFailureConfigApiRequestHandlers);
}

@Override
public String getName() {
return "API Token actions to retrieve / update configs.";
}

@Override
public List<Route> routes() {
return ROUTES;
}

@Override
protected CType<ConfigV7> getConfigType() {
return CType.CONFIG;
}

private void authFailureConfigApiRequestHandlers(RequestHandler.RequestHandlersBuilder requestHandlersBuilder) {

requestHandlersBuilder.override(
GET,
(channel, request, client) -> loadConfiguration(getConfigType(), false, false).valid(configuration -> {
if (!apiTokenIndexExists()) {
ok(channel, "empty list");
} else {
ok(channel, "non-empty list");
}
}).error((status, toXContent) -> response(channel, status, toXContent)))
.override(PUT, (channel, request, client) -> loadConfiguration(getConfigType(), false, false).valid(configuration -> {
String token = createApiToken(request.param(NAME_JSON_PROPERTY), client);
ok(channel, token + " created successfully");
}).error((status, toXContent) -> response(channel, status, toXContent)));

}

public String createApiToken(String name, Client client) {
createApiTokenIndexIfAbsent(client);

return "test-token";
}

public Boolean apiTokenIndexExists() {
return clusterService.state().metadata().hasConcreteIndex(ConfigConstants.OPENSEARCH_API_TOKENS_INDEX);
}

public void createApiTokenIndexIfAbsent(Client client) {
if (!apiTokenIndexExists()) {
final Map<String, Object> indexSettings = ImmutableMap.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all");
final CreateIndexRequest createIndexRequest = new CreateIndexRequest(ConfigConstants.OPENSEARCH_API_TOKENS_INDEX).settings(indexSettings);
logger.info(client.admin().indices().create(createIndexRequest).actionGet().isAcknowledged());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public enum Endpoint {
AUTHTOKEN,
TENANTS,
RATELIMITERS,
APITOKENS,
MIGRATE,
VALIDATE,
WHITELIST,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public static Collection<RestHandler> getHandler(
new AuditApiAction(clusterService, threadPool, securityApiDependencies),
new MultiTenancyConfigApiAction(clusterService, threadPool, securityApiDependencies),
new RateLimitersApiAction(clusterService, threadPool, securityApiDependencies),
new ApiTokenApiAction(clusterService, threadPool, securityApiDependencies),
new ConfigUpgradeApiAction(clusterService, threadPool, securityApiDependencies),
new SecuritySSLCertsApiAction(
clusterService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ public abstract class DynamicConfigModel {

public abstract Settings getDynamicOnBehalfOfSettings();

public abstract Settings getDynamicApiTokenSettings();



protected final Map<String, String> authImplMap = new HashMap<>();

public DynamicConfigModel() {
Expand Down Expand Up @@ -142,5 +146,4 @@ public DynamicConfigModel() {
authImplMap.put("ip_authFailureListener", AddressBasedRateLimiter.class.getName());
authImplMap.put("username_authFailureListener", UserNameBasedRateLimiter.class.getName());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,13 @@ public Settings getDynamicOnBehalfOfSettings() {
.build();
}

@Override
public Settings getDynamicApiTokenSettings() {
return Settings.builder()
.put(Settings.builder().loadFromSource(config.dynamic.api_tokens.configAsJson(), XContentType.JSON).build())
.build();
}

private void buildAAA() {

final SortedSet<AuthDomain> restAuthDomains0 = new TreeSet<>();
Expand Down Expand Up @@ -387,6 +394,17 @@ private void buildAAA() {
restAuthDomains0.add(_ad);
}

Settings apiTokenSettings = getDynamicApiTokenSettings();
if (!isKeyNull(apiTokenSettings, "signing_key") && !isKeyNull(apiTokenSettings, "encryption_key")) {
final AuthDomain _ad = new AuthDomain(
new NoOpAuthenticationBackend(Settings.EMPTY, null),
new OnBehalfOfAuthenticator(getDynamicOnBehalfOfSettings(), this.cih.getClusterName()),
false,
-1
);
restAuthDomains0.add(_ad);
}

List<Destroyable> originalDestroyableComponents = destroyableComponents;

restAuthDomains = Collections.unmodifiableSortedSet(restAuthDomains0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public static class Dynamic {
public String transport_userrname_attribute;
public boolean do_not_fail_on_forbidden_empty;
public OnBehalfOfSettings on_behalf_of = new OnBehalfOfSettings();
public ApiTokenSettings api_tokens = new ApiTokenSettings();

@Override
public String toString() {
Expand Down Expand Up @@ -495,4 +496,51 @@ public String toString() {
}
}

public static class ApiTokenSettings {
@JsonProperty("enabled")
private Boolean apiTokenEnabled = Boolean.FALSE;
@JsonProperty("signing_key")
private String signingKey;
@JsonProperty("encryption_key")
private String encryptionKey;

@JsonIgnore
public String configAsJson() {
try {
return DefaultObjectMapper.writeValueAsString(this, false);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}

public Boolean getApiTokenEnabled() {
return apiTokenEnabled;
}

public void setOboEnabled(Boolean apiTokenEnabled) {
this.apiTokenEnabled = apiTokenEnabled;
}

public String getSigningKey() {
return signingKey;
}

public void setSigningKey(String signingKey) {
this.signingKey = signingKey;
}

public String getEncryptionKey() {
return encryptionKey;
}

public void setEncryptionKey(String encryptionKey) {
this.encryptionKey = encryptionKey;
}

@Override
public String toString() {
return "ApiTokenSettings [ enabled=" + apiTokenEnabled + ", signing_key=" + signingKey + ", encryption_key=" + encryptionKey + "]";
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ public enum RolesMappingResolution {
// Variable for initial admin password support
public static final String OPENSEARCH_INITIAL_ADMIN_PASSWORD = "OPENSEARCH_INITIAL_ADMIN_PASSWORD";

public static final String OPENSEARCH_API_TOKENS_INDEX = ".opensearch_security_api_tokens";

public static Set<String> getSettingAsSet(
final Settings settings,
final String key,
Expand Down

0 comments on commit 0a708f9

Please sign in to comment.