Skip to content

Commit

Permalink
Add cache for application privileges (#55836)
Browse files Browse the repository at this point in the history
Add caching support for application privileges to reduce number of round-trips to security index when building application privilege descriptors.

Privilege retrieving in NativePrivilegeStore is changed to always fetching all privilege documents for a given application. The caching is applied to all places including "get privilege", "has privileges" APIs and CompositeRolesStore (for authentication).
  • Loading branch information
ywangd authored Jun 29, 2020
1 parent e966155 commit 38185e5
Show file tree
Hide file tree
Showing 22 changed files with 1,526 additions and 165 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.elasticsearch.client.security.AuthenticateRequest;
import org.elasticsearch.client.security.AuthenticateResponse;
import org.elasticsearch.client.security.ChangePasswordRequest;
import org.elasticsearch.client.security.ClearPrivilegesCacheRequest;
import org.elasticsearch.client.security.ClearPrivilegesCacheResponse;
import org.elasticsearch.client.security.ClearRealmCacheRequest;
import org.elasticsearch.client.security.ClearRealmCacheResponse;
import org.elasticsearch.client.security.ClearRolesCacheRequest;
Expand Down Expand Up @@ -510,6 +512,38 @@ public Cancellable clearRolesCacheAsync(ClearRolesCacheRequest request, RequestO
ClearRolesCacheResponse::fromXContent, listener, emptySet());
}

/**
* Clears the privileges cache for a set of privileges.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-clear-privilege-cache.html">
* the docs</a> for more.
*
* @param request the request with the privileges for which the cache should be cleared.
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the response from the clear privileges cache call
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public ClearPrivilegesCacheResponse clearPrivilegesCache(ClearPrivilegesCacheRequest request,
RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::clearPrivilegesCache, options,
ClearPrivilegesCacheResponse::fromXContent, emptySet());
}

/**
* Clears the privileges cache for a set of privileges asynchronously.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-clear-privilege-cache.html">
* the docs</a> for more.
*
* @param request the request with the privileges for which the cache should be cleared.
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @param listener the listener to be notified upon request completion
* @return cancellable that may be used to cancel the request
*/
public Cancellable clearPrivilegesCacheAsync(ClearPrivilegesCacheRequest request, RequestOptions options,
ActionListener<ClearPrivilegesCacheResponse> listener) {
return restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::clearPrivilegesCache, options,
ClearPrivilegesCacheResponse::fromXContent, listener, emptySet());
}

/**
* Synchronously retrieve the X.509 certificates that are used to encrypt communications in an Elasticsearch cluster.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-ssl.html">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.elasticsearch.client.security.ChangePasswordRequest;
import org.elasticsearch.client.security.ClearPrivilegesCacheRequest;
import org.elasticsearch.client.security.ClearRealmCacheRequest;
import org.elasticsearch.client.security.ClearRolesCacheRequest;
import org.elasticsearch.client.security.CreateApiKeyRequest;
Expand Down Expand Up @@ -183,6 +184,15 @@ static Request clearRolesCache(ClearRolesCacheRequest disableCacheRequest) {
return new Request(HttpPost.METHOD_NAME, endpoint);
}

static Request clearPrivilegesCache(ClearPrivilegesCacheRequest disableCacheRequest) {
String endpoint = new RequestConverters.EndpointBuilder()
.addPathPartAsIs("_security/privilege")
.addCommaSeparatedPathParts(disableCacheRequest.applications())
.addPathPart("_clear_cache")
.build();
return new Request(HttpPost.METHOD_NAME, endpoint);
}

static Request deleteRoleMapping(DeleteRoleMappingRequest deleteRoleMappingRequest) {
final String endpoint = new RequestConverters.EndpointBuilder()
.addPathPartAsIs("_security/role_mapping")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.client.security;

import org.elasticsearch.client.Validatable;

import java.util.Arrays;

/**
* The request used to clear the cache for native application privileges stored in an index.
*/
public final class ClearPrivilegesCacheRequest implements Validatable {

private final String[] applications;

/**
* Sets the applications for which caches will be evicted. When not set all privileges will be evicted from the cache.
*
* @param applications The application names
*/
public ClearPrivilegesCacheRequest(String... applications) {
this.applications = applications;
}

/**
* @return an array of application names that will have the cache evicted or <code>null</code> if all
*/
public String[] applications() {
return applications;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClearPrivilegesCacheRequest that = (ClearPrivilegesCacheRequest) o;
return Arrays.equals(applications, that.applications);
}

@Override
public int hashCode() {
return Arrays.hashCode(applications);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.client.security;

import org.elasticsearch.client.NodesResponseHeader;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentParser;

import java.io.IOException;
import java.util.List;

/**
* The response object that will be returned when clearing the privileges cache
*/
public final class ClearPrivilegesCacheResponse extends SecurityNodesResponse {

@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<ClearPrivilegesCacheResponse, Void> PARSER =
new ConstructingObjectParser<>("clear_privileges_cache_response", false,
args -> new ClearPrivilegesCacheResponse((List<Node>)args[0], (NodesResponseHeader) args[1], (String) args[2]));

static {
SecurityNodesResponse.declareCommonNodesResponseParsing(PARSER);
}

public ClearPrivilegesCacheResponse(List<Node> nodes, NodesResponseHeader header, String clusterName) {
super(nodes, header, clusterName);
}

public static ClearPrivilegesCacheResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import org.elasticsearch.client.security.AuthenticateResponse;
import org.elasticsearch.client.security.AuthenticateResponse.RealmInfo;
import org.elasticsearch.client.security.ChangePasswordRequest;
import org.elasticsearch.client.security.ClearPrivilegesCacheRequest;
import org.elasticsearch.client.security.ClearPrivilegesCacheResponse;
import org.elasticsearch.client.security.ClearRealmCacheRequest;
import org.elasticsearch.client.security.ClearRealmCacheResponse;
import org.elasticsearch.client.security.ClearRolesCacheRequest;
Expand Down Expand Up @@ -1003,6 +1005,52 @@ public void onFailure(Exception e) {
}
}

public void testClearPrivilegesCache() throws Exception {
RestHighLevelClient client = highLevelClient();
{
//tag::clear-privileges-cache-request
ClearPrivilegesCacheRequest request = new ClearPrivilegesCacheRequest("my_app"); // <1>
//end::clear-privileges-cache-request
//tag::clear-privileges-cache-execute
ClearPrivilegesCacheResponse response = client.security().clearPrivilegesCache(request, RequestOptions.DEFAULT);
//end::clear-privileges-cache-execute

assertNotNull(response);
assertThat(response.getNodes(), not(empty()));

//tag::clear-privileges-cache-response
List<ClearPrivilegesCacheResponse.Node> nodes = response.getNodes(); // <1>
//end::clear-privileges-cache-response
}

{
//tag::clear-privileges-cache-execute-listener
ClearPrivilegesCacheRequest request = new ClearPrivilegesCacheRequest("my_app");
ActionListener<ClearPrivilegesCacheResponse> listener = new ActionListener<>() {
@Override
public void onResponse(ClearPrivilegesCacheResponse clearPrivilegesCacheResponse) {
// <1>
}

@Override
public void onFailure(Exception e) {
// <2>
}
};
//end::clear-privileges-cache-execute-listener

// Replace the empty listener by a blocking listener in test
final CountDownLatch latch = new CountDownLatch(1);
listener = new LatchedActionListener<>(listener, latch);

// tag::clear-privileges-cache-execute-async
client.security().clearPrivilegesCacheAsync(request, RequestOptions.DEFAULT, listener); // <1>
// end::clear-privileges-cache-execute-async

assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}

public void testGetSslCertificates() throws Exception {
RestHighLevelClient client = highLevelClient();
{
Expand Down
33 changes: 33 additions & 0 deletions docs/java-rest/high-level/security/clear-privileges-cache.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

--
:api: clear-privileges-cache
:request: ClearPrivilegesCacheRequest
:response: ClearPrivilegesCacheResponse
--
[role="xpack"]
[id="{upid}-{api}"]
=== Clear Privileges Cache API

[id="{upid}-{api}-request"]
==== Clear Privileges Cache Request

A +{request}+ supports defining the name of applications that the cache should be cleared for.

["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request]
--------------------------------------------------
<1> the name of the application(s) for which the cache should be cleared

include::../execution.asciidoc[]

[id="{upid}-{api}-response"]
==== Clear Privileges Cache Response

The returned +{response}+ allows to retrieve information about where the cache was cleared.

["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-response]
--------------------------------------------------
<1> the list of nodes that the cache was cleared on
2 changes: 2 additions & 0 deletions docs/java-rest/high-level/supported-apis.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ The Java High Level REST Client supports the following Security APIs:
* <<{upid}-get-roles>>
* <<java-rest-high-security-delete-role>>
* <<{upid}-clear-roles-cache>>
* <<{upid}-clear-privileges-cache>>
* <<{upid}-clear-realm-cache>>
* <<{upid}-authenticate>>
* <<{upid}-has-privileges>>
Expand Down Expand Up @@ -486,6 +487,7 @@ include::security/delete-privileges.asciidoc[]
include::security/get-builtin-privileges.asciidoc[]
include::security/get-privileges.asciidoc[]
include::security/clear-roles-cache.asciidoc[]
include::security/clear-privileges-cache.asciidoc[]
include::security/clear-realm-cache.asciidoc[]
include::security/authenticate.asciidoc[]
include::security/has-privileges.asciidoc[]
Expand Down
8 changes: 5 additions & 3 deletions x-pack/docs/en/rest-api/security.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ You can use the following APIs to perform security activities.
[[security-api-app-privileges]]
=== Application privileges

You can use the following APIs to add, update, retrieve, and remove application
You can use the following APIs to add, update, retrieve, and remove application
privileges:

* <<security-api-put-privileges,Create or update privileges>>
* <<security-api-put-privileges,Create or update privileges>>
* <<security-api-clear-privilege-cache,Clear privileges cache>>
* <<security-api-delete-privilege,Delete privileges>>
* <<security-api-get-privileges,Get privileges>>

Expand All @@ -28,7 +29,7 @@ privileges:

You can use the following APIs to add, remove, update, and retrieve role mappings:

* <<security-api-put-role-mapping,Create or update role mappings>>
* <<security-api-put-role-mapping,Create or update role mappings>>
* <<security-api-delete-role-mapping,Delete role mappings>>
* <<security-api-get-role-mapping,Get role mappings>>

Expand Down Expand Up @@ -106,6 +107,7 @@ include::security/authenticate.asciidoc[]
include::security/change-password.asciidoc[]
include::security/clear-cache.asciidoc[]
include::security/clear-roles-cache.asciidoc[]
include::security/clear-privileges-cache.asciidoc[]
include::security/create-api-keys.asciidoc[]
include::security/put-app-privileges.asciidoc[]
include::security/create-role-mappings.asciidoc[]
Expand Down
4 changes: 3 additions & 1 deletion x-pack/docs/en/rest-api/security/clear-cache.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ There are realm settings that you can use to configure the user cache. For more
information, see
<<controlling-user-cache>>.

To evict roles from the role cache, see the
To evict roles from the role cache, see the
<<security-api-clear-role-cache,Clear roles cache API>>.
To evict privileges from the privilege cache, see the
<<security-api-clear-privilege-cache,Clear privileges cache API>>.

[[security-api-clear-path-params]]
==== {api-path-parms-title}
Expand Down
43 changes: 43 additions & 0 deletions x-pack/docs/en/rest-api/security/clear-privileges-cache.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[role="xpack"]
[[security-api-clear-privilege-cache]]
=== Clear privileges cache API
++++
<titleabbrev>Clear privileges cache</titleabbrev>
++++

Evicts privileges from the native application privilege cache.
The cache is also automatically cleared for applications that have their privileges updated.

[[security-api-clear-privilege-cache-request]]
==== {api-request-title}

`POST /_security/privilege/<application>/_clear_cache`

[[security-api-clear-privilege-cache-prereqs]]
==== {api-prereq-title}

* To use this API, you must have at least the `manage_security` cluster
privilege.

[[security-api-clear-privilege-cache-desc]]
==== {api-description-title}

For more information about the native realm, see
<<realms>> and <<native-realm>>.

[[security-api-clear-privilege-cache-path-params]]
==== {api-path-parms-title}

`application`::
(string) The name of the application. If omitted, all entries are evicted from the cache.

[[security-api-clear-privilege-cache-example]]
==== {api-examples-title}

The clear privileges cache API evicts privileges from the native application privilege cache.
For example, to clear the cache for `myapp`:

[source,console]
--------------------------------------------------
POST /_security/privilege/myapp/_clear_cache
--------------------------------------------------
Loading

0 comments on commit 38185e5

Please sign in to comment.