Skip to content

Commit

Permalink
add flush cache for individual user
Browse files Browse the repository at this point in the history
  • Loading branch information
dlin2028 committed Dec 9, 2023
1 parent 87de7e2 commit 9bb7efc
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,8 @@ public List<RestHandler> getRestHandlers(
Objects.requireNonNull(auditLog),
sks,
Objects.requireNonNull(userService),
sslCertReloadEnabled
sslCertReloadEnabled,
backendRegistry
)
);
log.debug("Added {} rest handler(s)", handlers.size());
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/org/opensearch/security/auth/BackendRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,29 @@ public void invalidateCache() {
restRoleCache.invalidateAll();
}

public void invalidateUserCache(String username) {
if (username == null || username.isEmpty()) {
log.debug("No username given, not invalidating user cache.");
return;
}

// Invalidate entries in the userCache by iterating over the keys and matching the username.
userCache.asMap().keySet().stream()
.filter(authCreds -> username.equals(authCreds.getUsername()))
.forEach(userCache::invalidate);

// Invalidate entries in the restImpersonationCache directly since it uses the username as the key.
restImpersonationCache.invalidate(username);

// Invalidate entries in the restRoleCache by iterating over the keys and matching the username.
restRoleCache.asMap().keySet().stream()
.filter(user -> username.equals(user.getName()))
.forEach(restRoleCache::invalidate);

// If the user isn't found it still says this, which could be bad
log.debug("Invalidated cache for user {}", username);
}

@Subscribe
public void onDynamicConfigModelChanged(DynamicConfigModel dcm) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.inject.Inject;
import org.opensearch.core.action.ActionListener;
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestRequest;
import org.opensearch.rest.RestRequest.Method;
import org.opensearch.security.action.configupdate.ConfigUpdateAction;
Expand All @@ -41,7 +42,8 @@ public class FlushCacheApiAction extends AbstractApiAction {
new Route(Method.DELETE, "/cache"),
new Route(Method.GET, "/cache"),
new Route(Method.PUT, "/cache"),
new Route(Method.POST, "/cache")
new Route(Method.POST, "/cache"),
new Route(Method.DELETE, "/cache/user/{username}")
)
);

Expand All @@ -64,36 +66,57 @@ private void flushCacheApiRequestHandlers(RequestHandler.RequestHandlersBuilder
requestHandlersBuilder.allMethodsNotImplemented()
.override(
Method.DELETE,
(channel, request, client) -> client.execute(
ConfigUpdateAction.INSTANCE,
new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0])),
new ActionListener<>() {

@Override
public void onResponse(ConfigUpdateResponse configUpdateResponse) {
if (configUpdateResponse.hasFailures()) {
LOGGER.error("Cannot flush cache due to", configUpdateResponse.failures().get(0));
internalSeverError(
channel,
"Cannot flush cache due to " + configUpdateResponse.failures().get(0).getMessage() + "."
);
return;
}
LOGGER.debug("cache flushed successfully");
ok(channel, "Cache flushed successfully.");
}

@Override
public void onFailure(final Exception e) {
LOGGER.error("Cannot flush cache due to", e);
internalSeverError(channel, "Cannot flush cache due to " + e.getMessage() + ".");
}
(channel, request, client) -> {
if (request.path().contains("/user/")) {
// Extract the username from the request
final String username = request.param("username");
// Validate and handle user-specific cache invalidation
handleUserCacheInvalidation(channel, username);
}
else
{
client.execute(
ConfigUpdateAction.INSTANCE,
new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0])),
new ActionListener<>() {

@Override
public void onResponse(ConfigUpdateResponse configUpdateResponse) {
if (configUpdateResponse.hasFailures()) {
LOGGER.error("Cannot flush cache due to", configUpdateResponse.failures().get(0));
internalSeverError(
channel,
"Cannot flush cache due to " + configUpdateResponse.failures().get(0).getMessage() + "."
);
return;
}
LOGGER.debug("cache flushed successfully");
ok(channel, "Cache flushed successfully.");
}

@Override
public void onFailure(final Exception e) {
LOGGER.error("Cannot flush cache due to", e);
internalSeverError(channel, "Cannot flush cache due to " + e.getMessage() + ".");
}

}
);
}
)
}
);
}

private void handleUserCacheInvalidation(RestChannel channel, String username) {
if (username == null || username.isEmpty()) {
internalSeverError(channel, "No username provided for cache invalidation.");
return;
}
// Use BackendRegistry's method to invalidate cache for the specific user
securityApiDependencies.backendRegistry().invalidateUserCache(username);
ok(channel, "Cache invalidated for user: " + username);
}

@Override
protected CType getConfigType() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import org.opensearch.common.settings.Settings;
import org.opensearch.security.auditlog.AuditLog;
import org.opensearch.security.auth.BackendRegistry;
import org.opensearch.security.configuration.AdminDNs;
import org.opensearch.security.configuration.ConfigurationRepository;
import org.opensearch.security.privileges.PrivilegesEvaluator;
Expand All @@ -23,6 +24,7 @@ public class SecurityApiDependencies {
private final ConfigurationRepository configurationRepository;
private final RestApiPrivilegesEvaluator restApiPrivilegesEvaluator;
private final RestApiAdminPrivilegesEvaluator restApiAdminPrivilegesEvaluator;
private final BackendRegistry backendRegistry;
private final AuditLog auditLog;
private final Settings settings;

Expand All @@ -35,7 +37,8 @@ public SecurityApiDependencies(
final RestApiPrivilegesEvaluator restApiPrivilegesEvaluator,
final RestApiAdminPrivilegesEvaluator restApiAdminPrivilegesEvaluator,
final AuditLog auditLog,
final Settings settings
final Settings settings,
final BackendRegistry backendRegistry
) {
this.adminDNs = adminDNs;
this.configurationRepository = configurationRepository;
Expand All @@ -44,6 +47,7 @@ public SecurityApiDependencies(
this.restApiAdminPrivilegesEvaluator = restApiAdminPrivilegesEvaluator;
this.auditLog = auditLog;
this.settings = settings;
this.backendRegistry = backendRegistry;
}

public AdminDNs adminDNs() {
Expand Down Expand Up @@ -74,6 +78,10 @@ public Settings settings() {
return settings;
}

public BackendRegistry backendRegistry() {
return backendRegistry;
}

public String securityIndexName() {
return settings().get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.opensearch.rest.RestController;
import org.opensearch.rest.RestHandler;
import org.opensearch.security.auditlog.AuditLog;
import org.opensearch.security.auth.BackendRegistry;
import org.opensearch.security.configuration.AdminDNs;
import org.opensearch.security.configuration.ConfigurationRepository;
import org.opensearch.security.privileges.PrivilegesEvaluator;
Expand All @@ -47,7 +48,8 @@ public static Collection<RestHandler> getHandler(
final AuditLog auditLog,
final SecurityKeyStore securityKeyStore,
final UserService userService,
final boolean certificatesReloadEnabled
final boolean certificatesReloadEnabled,
final BackendRegistry backendRegistry
) {
final var securityApiDependencies = new SecurityApiDependencies(
adminDns,
Expand All @@ -61,7 +63,8 @@ public static Collection<RestHandler> getHandler(
settings.getAsBoolean(SECURITY_RESTAPI_ADMIN_ENABLED, false)
),
auditLog,
settings
settings,
backendRegistry
);
return List.of(
new InternalUsersApiAction(clusterService, threadPool, userService, securityApiDependencies),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ public void setup() {
null,
restApiAdminPrivilegesEvaluator,
null,
Settings.EMPTY
Settings.EMPTY,
null
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public void testFlushCache() throws Exception {
rh.keystore = "restapi/kirk-keystore.jks";
rh.sendAdminCertificate = true;

// Username to test cache invalidation
String username = "testuser";

// GET
HttpResponse response = rh.executeGetRequest(ENDPOINT);
Assert.assertEquals(HttpStatus.SC_NOT_IMPLEMENTED, response.getStatusCode());
Expand All @@ -66,5 +69,12 @@ public void testFlushCache() throws Exception {
settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build();
Assert.assertEquals(settings.get("message"), "Cache flushed successfully.");

// DELETE request for a specific user's cache
String userEndpoint = ENDPOINT + "/user/" + username;
response = rh.executeDeleteRequest(userEndpoint, new Header[0]);
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build();
Assert.assertEquals(settings.get("message"), "Cache invalidated for user: " + username);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void accessHandlerForDefaultSettings() {
final var securityConfigApiAction = new SecurityConfigApiAction(
clusterService,
threadPool,
new SecurityApiDependencies(null, configurationRepository, null, null, restApiAdminPrivilegesEvaluator, null, Settings.EMPTY)
new SecurityApiDependencies(null, configurationRepository, null, null, restApiAdminPrivilegesEvaluator, null, Settings.EMPTY, null)
);
assertTrue(securityConfigApiAction.accessHandler(FakeRestRequest.builder().withMethod(RestRequest.Method.GET).build()));
assertFalse(securityConfigApiAction.accessHandler(FakeRestRequest.builder().withMethod(RestRequest.Method.PUT).build()));
Expand All @@ -49,7 +49,8 @@ public void accessHandlerForUnsupportedSetting() {
null,
restApiAdminPrivilegesEvaluator,
null,
Settings.builder().put(SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build()
Settings.builder().put(SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build(),
null
)
);
assertTrue(securityConfigApiAction.accessHandler(FakeRestRequest.builder().withMethod(RestRequest.Method.GET).build()));
Expand All @@ -70,7 +71,8 @@ public void accessHandlerForRestAdmin() {
null,
restApiAdminPrivilegesEvaluator,
null,
Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()
Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build(),
null
)
);
assertTrue(securityConfigApiAction.accessHandler(FakeRestRequest.builder().withMethod(RestRequest.Method.GET).build()));
Expand Down

0 comments on commit 9bb7efc

Please sign in to comment.