Add `manage_own_api_key` cluster privilege (#45897) (#46023)

The existing privilege model for API keys with privileges like
`manage_api_key`, `manage_security` etc. are too permissive and
we would want finer-grained control over the cluster privileges
for API keys. Previously APIs created would also need these
privileges to get its own information.

This commit adds support for `manage_own_api_key` cluster privilege
which only allows api key cluster actions on API keys owned by the
currently authenticated user. Also adds support for retrieval of
the API key self-information when authenticating via API key
without the need for the additional API key privileges.
To support this privilege, we are introducing additional
authentication context along with the request context such that
it can be used to authorize cluster actions based on the current
user authentication.

The API key get and invalidate APIs introduce an `owner` flag
that can be set to true if the API key request (Get or Invalidate)
is for the API keys owned by the currently authenticated user only.
In that case, `realm` and `username` cannot be set as they are
assumed to be the currently authenticated ones.

The changes cover HLRC changes, documentation for the API changes.

Closes #40031
This commit is contained in:
Yogesh Gaikwad 2019-08-28 00:44:23 +10:00 committed by GitHub
parent dd6c13fdf9
commit 7b6246ec67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1768 additions and 741 deletions

View File

@ -301,7 +301,7 @@ final class SecurityRequestConverters {
if (Strings.hasText(getApiKeyRequest.getRealmName())) {
request.addParameter("realm_name", getApiKeyRequest.getRealmName());
}
request.addParameter("owner", Boolean.toString(getApiKeyRequest.ownedByAuthenticatedUser()));
return request;
}

View File

@ -36,13 +36,14 @@ public final class GetApiKeyRequest implements Validatable, ToXContentObject {
private final String userName;
private final String id;
private final String name;
private final boolean ownedByAuthenticatedUser;
// pkg scope for testing
GetApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String apiKeyId,
@Nullable String apiKeyName) {
@Nullable String apiKeyName, boolean ownedByAuthenticatedUser) {
if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(apiKeyId) == false
&& Strings.hasText(apiKeyName) == false) {
throwValidationError("One of [api key id, api key name, username, realm name] must be specified");
&& Strings.hasText(apiKeyName) == false && ownedByAuthenticatedUser == false) {
throwValidationError("One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false");
}
if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
@ -50,6 +51,11 @@ public final class GetApiKeyRequest implements Validatable, ToXContentObject {
"username or realm name must not be specified when the api key id or api key name is specified");
}
}
if (ownedByAuthenticatedUser) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
throwValidationError("neither username nor realm-name may be specified when retrieving owned API keys");
}
}
if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) {
throwValidationError("only one of [api key id, api key name] can be specified");
}
@ -57,6 +63,7 @@ public final class GetApiKeyRequest implements Validatable, ToXContentObject {
this.userName = userName;
this.id = apiKeyId;
this.name = apiKeyName;
this.ownedByAuthenticatedUser = ownedByAuthenticatedUser;
}
private void throwValidationError(String message) {
@ -79,13 +86,17 @@ public final class GetApiKeyRequest implements Validatable, ToXContentObject {
return name;
}
public boolean ownedByAuthenticatedUser() {
return ownedByAuthenticatedUser;
}
/**
* Creates get API key request for given realm name
* @param realmName realm name
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingRealmName(String realmName) {
return new GetApiKeyRequest(realmName, null, null, null);
return new GetApiKeyRequest(realmName, null, null, null, false);
}
/**
@ -94,7 +105,7 @@ public final class GetApiKeyRequest implements Validatable, ToXContentObject {
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingUserName(String userName) {
return new GetApiKeyRequest(null, userName, null, null);
return new GetApiKeyRequest(null, userName, null, null, false);
}
/**
@ -104,25 +115,36 @@ public final class GetApiKeyRequest implements Validatable, ToXContentObject {
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingRealmAndUserName(String realmName, String userName) {
return new GetApiKeyRequest(realmName, userName, null, null);
return new GetApiKeyRequest(realmName, userName, null, null, false);
}
/**
* Creates get API key request for given api key id
* @param apiKeyId api key id
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current
* authenticated user else{@code false}
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingApiKeyId(String apiKeyId) {
return new GetApiKeyRequest(null, null, apiKeyId, null);
public static GetApiKeyRequest usingApiKeyId(String apiKeyId, boolean ownedByAuthenticatedUser) {
return new GetApiKeyRequest(null, null, apiKeyId, null, ownedByAuthenticatedUser);
}
/**
* Creates get API key request for given api key name
* @param apiKeyName api key name
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current
* authenticated user else{@code false}
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingApiKeyName(String apiKeyName) {
return new GetApiKeyRequest(null, null, null, apiKeyName);
public static GetApiKeyRequest usingApiKeyName(String apiKeyName, boolean ownedByAuthenticatedUser) {
return new GetApiKeyRequest(null, null, null, apiKeyName, ownedByAuthenticatedUser);
}
/**
* Creates get api key request to retrieve api key information for the api keys owned by the current authenticated user.
*/
public static GetApiKeyRequest forOwnedApiKeys() {
return new GetApiKeyRequest(null, null, null, null, true);
}
@Override

View File

@ -36,13 +36,14 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj
private final String userName;
private final String id;
private final String name;
private final boolean ownedByAuthenticatedUser;
// pkg scope for testing
InvalidateApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String apiKeyId,
@Nullable String apiKeyName) {
@Nullable String apiKeyName, boolean ownedByAuthenticatedUser) {
if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(apiKeyId) == false
&& Strings.hasText(apiKeyName) == false) {
throwValidationError("One of [api key id, api key name, username, realm name] must be specified");
&& Strings.hasText(apiKeyName) == false && ownedByAuthenticatedUser == false) {
throwValidationError("One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false");
}
if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
@ -50,6 +51,11 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj
"username or realm name must not be specified when the api key id or api key name is specified");
}
}
if (ownedByAuthenticatedUser) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
throwValidationError("neither username nor realm-name may be specified when invalidating owned API keys");
}
}
if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) {
throwValidationError("only one of [api key id, api key name] can be specified");
}
@ -57,6 +63,7 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj
this.userName = userName;
this.id = apiKeyId;
this.name = apiKeyName;
this.ownedByAuthenticatedUser = ownedByAuthenticatedUser;
}
private void throwValidationError(String message) {
@ -79,13 +86,17 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj
return name;
}
public boolean ownedByAuthenticatedUser() {
return ownedByAuthenticatedUser;
}
/**
* Creates invalidate API key request for given realm name
* @param realmName realm name
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingRealmName(String realmName) {
return new InvalidateApiKeyRequest(realmName, null, null, null);
return new InvalidateApiKeyRequest(realmName, null, null, null, false);
}
/**
@ -94,7 +105,7 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingUserName(String userName) {
return new InvalidateApiKeyRequest(null, userName, null, null);
return new InvalidateApiKeyRequest(null, userName, null, null, false);
}
/**
@ -104,25 +115,36 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingRealmAndUserName(String realmName, String userName) {
return new InvalidateApiKeyRequest(realmName, userName, null, null);
return new InvalidateApiKeyRequest(realmName, userName, null, null, false);
}
/**
* Creates invalidate API key request for given api key id
* @param apiKeyId api key id
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
* {@code false}
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingApiKeyId(String apiKeyId) {
return new InvalidateApiKeyRequest(null, null, apiKeyId, null);
public static InvalidateApiKeyRequest usingApiKeyId(String apiKeyId, boolean ownedByAuthenticatedUser) {
return new InvalidateApiKeyRequest(null, null, apiKeyId, null, ownedByAuthenticatedUser);
}
/**
* Creates invalidate API key request for given api key name
* @param apiKeyName api key name
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
* {@code false}
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingApiKeyName(String apiKeyName) {
return new InvalidateApiKeyRequest(null, null, null, apiKeyName);
public static InvalidateApiKeyRequest usingApiKeyName(String apiKeyName, boolean ownedByAuthenticatedUser) {
return new InvalidateApiKeyRequest(null, null, null, apiKeyName, ownedByAuthenticatedUser);
}
/**
* Creates invalidate api key request to invalidate api keys owned by the current authenticated user.
*/
public static InvalidateApiKeyRequest forOwnedApiKeys() {
return new InvalidateApiKeyRequest(null, null, null, null, true);
}
@Override
@ -140,6 +162,7 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj
if (name != null) {
builder.field("name", name);
}
builder.field("owner", ownedByAuthenticatedUser);
return builder.endObject();
}
}

View File

@ -462,10 +462,11 @@ public class SecurityRequestConvertersTests extends ESTestCase {
final Request request = SecurityRequestConverters.getApiKey(getApiKeyRequest);
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
assertEquals("/_security/api_key", request.getEndpoint());
Map<String, String> mapOfParameters = new HashMap<>();
mapOfParameters.put("realm_name", realmName);
mapOfParameters.put("username", userName);
assertThat(request.getParameters(), equalTo(mapOfParameters));
Map<String, String> expectedMapOfParameters = new HashMap<>();
expectedMapOfParameters.put("realm_name", realmName);
expectedMapOfParameters.put("username", userName);
expectedMapOfParameters.put("owner", Boolean.FALSE.toString());
assertThat(request.getParameters(), equalTo(expectedMapOfParameters));
}
public void testInvalidateApiKey() throws IOException {

View File

@ -1923,7 +1923,7 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
Instant.now().plusMillis(expiration.getMillis()), false, "test_user", "default_file");
{
// tag::get-api-key-id-request
GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId());
GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId(), false);
// end::get-api-key-id-request
// tag::get-api-key-execute
@ -1937,7 +1937,7 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
{
// tag::get-api-key-name-request
GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyName(createApiKeyResponse1.getName());
GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyName(createApiKeyResponse1.getName(), false);
// end::get-api-key-name-request
GetApiKeyResponse getApiKeyResponse = client.security().getApiKey(getApiKeyRequest, RequestOptions.DEFAULT);
@ -1971,6 +1971,18 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
verifyApiKey(getApiKeyResponse.getApiKeyInfos().get(0), expectedApiKeyInfo);
}
{
// tag::get-api-keys-owned-by-authenticated-user-request
GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.forOwnedApiKeys();
// end::get-api-keys-owned-by-authenticated-user-request
GetApiKeyResponse getApiKeyResponse = client.security().getApiKey(getApiKeyRequest, RequestOptions.DEFAULT);
assertThat(getApiKeyResponse.getApiKeyInfos(), is(notNullValue()));
assertThat(getApiKeyResponse.getApiKeyInfos().size(), is(1));
verifyApiKey(getApiKeyResponse.getApiKeyInfos().get(0), expectedApiKeyInfo);
}
{
// tag::get-user-realm-api-keys-request
GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingRealmAndUserName("default_file", "test_user");
@ -1986,7 +1998,7 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
}
{
GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId());
GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId(), false);
ActionListener<GetApiKeyResponse> listener;
// tag::get-api-key-execute-listener
@ -2047,7 +2059,7 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
{
// tag::invalidate-api-key-id-request
InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId());
InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId(), false);
// end::invalidate-api-key-id-request
// tag::invalidate-api-key-execute
@ -2072,7 +2084,8 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
assertNotNull(createApiKeyResponse2.getKey());
// tag::invalidate-api-key-name-request
InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyName(createApiKeyResponse2.getName());
InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyName(createApiKeyResponse2.getName(),
false);
// end::invalidate-api-key-name-request
InvalidateApiKeyResponse invalidateApiKeyResponse = client.security().invalidateApiKey(invalidateApiKeyRequest,
@ -2165,7 +2178,7 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
assertThat(createApiKeyResponse6.getName(), equalTo("k6"));
assertNotNull(createApiKeyResponse6.getKey());
InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(createApiKeyResponse6.getId());
InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(createApiKeyResponse6.getId(), false);
ActionListener<InvalidateApiKeyResponse> listener;
// tag::invalidate-api-key-execute-listener
@ -2201,6 +2214,30 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
assertThat(invalidatedApiKeyIds, containsInAnyOrder(expectedInvalidatedApiKeyIds.toArray(Strings.EMPTY_ARRAY)));
assertThat(response.getPreviouslyInvalidatedApiKeys().size(), equalTo(0));
}
{
createApiKeyRequest = new CreateApiKeyRequest("k7", roles, expiration, refreshPolicy);
CreateApiKeyResponse createApiKeyResponse7 = client.security().createApiKey(createApiKeyRequest, RequestOptions.DEFAULT);
assertThat(createApiKeyResponse7.getName(), equalTo("k7"));
assertNotNull(createApiKeyResponse7.getKey());
// tag::invalidate-api-keys-owned-by-authenticated-user-request
InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.forOwnedApiKeys();
// end::invalidate-api-keys-owned-by-authenticated-user-request
InvalidateApiKeyResponse invalidateApiKeyResponse = client.security().invalidateApiKey(invalidateApiKeyRequest,
RequestOptions.DEFAULT);
final List<ElasticsearchException> errors = invalidateApiKeyResponse.getErrors();
final List<String> invalidatedApiKeyIds = invalidateApiKeyResponse.getInvalidatedApiKeys();
final List<String> previouslyInvalidatedApiKeyIds = invalidateApiKeyResponse.getPreviouslyInvalidatedApiKeys();
assertTrue(errors.isEmpty());
List<String> expectedInvalidatedApiKeyIds = Arrays.asList(createApiKeyResponse7.getId());
assertThat(invalidatedApiKeyIds, containsInAnyOrder(expectedInvalidatedApiKeyIds.toArray(Strings.EMPTY_ARRAY)));
assertThat(previouslyInvalidatedApiKeyIds.size(), equalTo(0));
}
}
public void testDelegatePkiAuthentication() throws Exception {

View File

@ -30,10 +30,10 @@ import static org.hamcrest.Matchers.equalTo;
public class GetApiKeyRequestTests extends ESTestCase {
public void testRequestValidation() {
GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5));
GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5), randomBoolean());
Optional<ValidationException> ve = request.validate();
assertFalse(ve.isPresent());
request = GetApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5));
request = GetApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5), randomBoolean());
ve = request.validate();
assertFalse(ve.isPresent());
request = GetApiKeyRequest.usingRealmName(randomAlphaOfLength(5));
@ -45,28 +45,40 @@ public class GetApiKeyRequestTests extends ESTestCase {
request = GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), randomAlphaOfLength(7));
ve = request.validate();
assertFalse(ve.isPresent());
request = GetApiKeyRequest.forOwnedApiKeys();
ve = request.validate();
assertFalse(ve.isPresent());
}
public void testRequestValidationFailureScenarios() throws IOException {
String[][] inputs = new String[][] {
{ randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }),
randomFrom(new String[] { null, "" }) },
{ randomFrom(new String[] { null, "" }), "user", "api-kid", "api-kname" },
{ "realm", randomFrom(new String[] { null, "" }), "api-kid", "api-kname" },
{ "realm", "user", "api-kid", randomFrom(new String[] { null, "" }) },
{ randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), "api-kid", "api-kname" } };
String[] expectedErrorMessages = new String[] { "One of [api key id, api key name, username, realm name] must be specified",
{ randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "false" },
{ randomNullOrEmptyString(), "user", "api-kid", "api-kname", "false" },
{ "realm", randomNullOrEmptyString(), "api-kid", "api-kname", "false" },
{ "realm", "user", "api-kid", randomNullOrEmptyString(), "false" },
{ randomNullOrEmptyString(), randomNullOrEmptyString(), "api-kid", "api-kname", "false" },
{ "realm", randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "true"},
{ randomNullOrEmptyString(), "user", randomNullOrEmptyString(), randomNullOrEmptyString(), "true"} };
String[] expectedErrorMessages = new String[] {
"One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false",
"username or realm name must not be specified when the api key id or api key name is specified",
"username or realm name must not be specified when the api key id or api key name is specified",
"username or realm name must not be specified when the api key id or api key name is specified",
"only one of [api key id, api key name] can be specified" };
"only one of [api key id, api key name] can be specified",
"neither username nor realm-name may be specified when retrieving owned API keys",
"neither username nor realm-name may be specified when retrieving owned API keys" };
for (int i = 0; i < inputs.length; i++) {
final int caseNo = i;
IllegalArgumentException ve = expectThrows(IllegalArgumentException.class,
() -> new GetApiKeyRequest(inputs[caseNo][0], inputs[caseNo][1], inputs[caseNo][2], inputs[caseNo][3]));
() -> new GetApiKeyRequest(inputs[caseNo][0], inputs[caseNo][1], inputs[caseNo][2], inputs[caseNo][3],
Boolean.valueOf(inputs[caseNo][4])));
assertNotNull(ve);
assertThat(ve.getMessage(), equalTo(expectedErrorMessages[caseNo]));
}
}
private static String randomNullOrEmptyString() {
return randomBoolean() ? "" : null;
}
}

View File

@ -31,10 +31,10 @@ import static org.hamcrest.Matchers.is;
public class InvalidateApiKeyRequestTests extends ESTestCase {
public void testRequestValidation() {
InvalidateApiKeyRequest request = InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5));
InvalidateApiKeyRequest request = InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5), randomBoolean());
Optional<ValidationException> ve = request.validate();
assertThat(ve.isPresent(), is(false));
request = InvalidateApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5));
request = InvalidateApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5), randomBoolean());
ve = request.validate();
assertThat(ve.isPresent(), is(false));
request = InvalidateApiKeyRequest.usingRealmName(randomAlphaOfLength(5));
@ -46,28 +46,40 @@ public class InvalidateApiKeyRequestTests extends ESTestCase {
request = InvalidateApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), randomAlphaOfLength(7));
ve = request.validate();
assertThat(ve.isPresent(), is(false));
request = InvalidateApiKeyRequest.forOwnedApiKeys();
ve = request.validate();
assertFalse(ve.isPresent());
}
public void testRequestValidationFailureScenarios() throws IOException {
String[][] inputs = new String[][] {
{ randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }),
randomFrom(new String[] { null, "" }) },
{ randomFrom(new String[] { null, "" }), "user", "api-kid", "api-kname" },
{ "realm", randomFrom(new String[] { null, "" }), "api-kid", "api-kname" },
{ "realm", "user", "api-kid", randomFrom(new String[] { null, "" }) },
{ randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), "api-kid", "api-kname" } };
String[] expectedErrorMessages = new String[] { "One of [api key id, api key name, username, realm name] must be specified",
{ randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "false" },
{ randomNullOrEmptyString(), "user", "api-kid", "api-kname", "false" },
{ "realm", randomNullOrEmptyString(), "api-kid", "api-kname", "false" },
{ "realm", "user", "api-kid", randomNullOrEmptyString(), "false" },
{ randomNullOrEmptyString(), randomNullOrEmptyString(), "api-kid", "api-kname", "false" },
{ "realm", randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "true" },
{ randomNullOrEmptyString(), "user", randomNullOrEmptyString(), randomNullOrEmptyString(), "true" } };
String[] expectedErrorMessages = new String[] {
"One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false",
"username or realm name must not be specified when the api key id or api key name is specified",
"username or realm name must not be specified when the api key id or api key name is specified",
"username or realm name must not be specified when the api key id or api key name is specified",
"only one of [api key id, api key name] can be specified" };
"only one of [api key id, api key name] can be specified",
"neither username nor realm-name may be specified when invalidating owned API keys",
"neither username nor realm-name may be specified when invalidating owned API keys" };
for (int i = 0; i < inputs.length; i++) {
final int caseNo = i;
IllegalArgumentException ve = expectThrows(IllegalArgumentException.class,
() -> new InvalidateApiKeyRequest(inputs[caseNo][0], inputs[caseNo][1], inputs[caseNo][2], inputs[caseNo][3]));
() -> new InvalidateApiKeyRequest(inputs[caseNo][0], inputs[caseNo][1], inputs[caseNo][2], inputs[caseNo][3],
Boolean.valueOf(inputs[caseNo][4])));
assertNotNull(ve);
assertThat(ve.getMessage(), equalTo(expectedErrorMessages[caseNo]));
}
}
private static String randomNullOrEmptyString() {
return randomBoolean() ? "" : null;
}
}

View File

@ -21,6 +21,8 @@ The +{request}+ supports retrieving API key information for
. All API keys for a specific user in a specific realm
. A specific key or all API keys owned by the current authenticated user
===== Retrieve a specific API key by its id
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
@ -51,6 +53,12 @@ include-tagged::{doc-tests-file}[get-user-api-keys-request]
include-tagged::{doc-tests-file}[get-user-realm-api-keys-request]
--------------------------------------------------
===== Retrieve all API keys for the current authenticated user
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[get-api-keys-owned-by-authenticated-user-request]
--------------------------------------------------
include::../execution.asciidoc[]
[id="{upid}-{api}-response"]

View File

@ -21,6 +21,8 @@ The +{request}+ supports invalidating
. All API keys for a specific user in a specific realm
. A specific key or all API keys owned by the current authenticated user
===== Specific API key by API key id
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
@ -51,6 +53,12 @@ include-tagged::{doc-tests-file}[invalidate-user-api-keys-request]
include-tagged::{doc-tests-file}[invalidate-user-realm-api-keys-request]
--------------------------------------------------
===== Retrieve all API keys for the current authenticated user
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[invalidate-api-keys-owned-by-authenticated-user-request]
--------------------------------------------------
include::../execution.asciidoc[]
[id="{upid}-{api}-response"]

View File

@ -39,13 +39,20 @@ pertain to retrieving api keys:
`realm_name`::
(Optional, string) The name of an authentication realm. This parameter cannot be
used with either `id` or `name`.
used with either `id` or `name` or when `owner` flag is set to `true`.
`username`::
(Optional, string) The username of a user. This parameter cannot be used with
either `id` or `name`.
either `id` or `name` or when `owner` flag is set to `true`.
NOTE: While all parameters are optional, at least one of them is required.
`owner`::
(Optional, boolean) A boolean flag that can be used to query API keys owned
by the currently authenticated user. Defaults to false.
The 'realm_name' or 'username' parameters cannot be specified when this
parameter is set to 'true' as they are assumed to be the currently authenticated ones.
NOTE: At least one of "id", "name", "username" and "realm_name" must be specified
if "owner" is "false" (default).
[[security-api-get-api-key-example]]
==== {api-examples-title}
@ -114,6 +121,37 @@ GET /_security/api_key?username=myuser
// CONSOLE
// TEST[continued]
The following example retrieves all API keys owned by the currently authenticated user:
[source,js]
--------------------------------------------------
GET /_security/api_key?owner=true
--------------------------------------------------
// CONSOLE
// TEST[continued]
Following creates an API key
[source, js]
------------------------------------------------------------
POST /_security/api_key
{
"name": "my-api-key-1"
}
------------------------------------------------------------
// CONSOLE
The following example retrieves the API key identified by the specified `id` if
it is owned by the currently authenticated user:
[source,js]
--------------------------------------------------
GET /_security/api_key?id=VuaCfGcBCdbkQm-e5aOx&owner=true
--------------------------------------------------
// CONSOLE
// TEST[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
// TEST[continued]
Finally, the following example retrieves all API keys for the user `myuser` in
the `native1` realm immediately:

View File

@ -76,6 +76,7 @@ A successful call returns an object with "cluster" and "index" fields.
"manage_ingest_pipelines",
"manage_ml",
"manage_oidc",
"manage_own_api_key",
"manage_pipeline",
"manage_rollup",
"manage_saml",

View File

@ -40,13 +40,20 @@ pertain to invalidating api keys:
`realm_name`::
(Optional, string) The name of an authentication realm. This parameter cannot be
used with either `id` or `name`.
used with either `id` or `name` or when `owner` flag is set to `true`.
`username`::
(Optional, string) The username of a user. This parameter cannot be used with
either `id` or `name`.
either `id` or `name` or when `owner` flag is set to `true`.
NOTE: While all parameters are optional, at least one of them is required.
`owner`::
(Optional, boolean) A boolean flag that can be used to query API keys owned
by the currently authenticated user. Defaults to false.
The 'realm_name' or 'username' parameters cannot be specified when this
parameter is set to 'true' as they are assumed to be the currently authenticated ones.
NOTE: At least one of "id", "name", "username" and "realm_name" must be specified
if "owner" is "false" (default).
[[security-api-invalidate-api-key-response-body]]
==== {api-response-body-title}
@ -138,6 +145,32 @@ DELETE /_security/api_key
// CONSOLE
// TEST
The following example invalidates the API key identified by the specified `id` if
it is owned by the currently authenticated user immediately:
[source,js]
--------------------------------------------------
DELETE /_security/api_key
{
"id" : "VuaCfGcBCdbkQm-e5aOx",
"owner" : "true"
}
--------------------------------------------------
// CONSOLE
The following example invalidates all API keys owned by the currently authenticated
user immediately:
[source,js]
--------------------------------------------------
DELETE /_security/api_key
{
"owner" : "true"
}
--------------------------------------------------
// CONSOLE
// TEST
Finally, the following example invalidates all API keys for the user `myuser` in
the `native1` realm immediately:

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.core.security.action;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
@ -14,6 +15,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import java.util.Objects;
import static org.elasticsearch.action.ValidateActions.addValidationError;
@ -26,9 +28,10 @@ public final class GetApiKeyRequest extends ActionRequest {
private final String userName;
private final String apiKeyId;
private final String apiKeyName;
private final boolean ownedByAuthenticatedUser;
public GetApiKeyRequest() {
this(null, null, null, null);
this(null, null, null, null, false);
}
public GetApiKeyRequest(StreamInput in) throws IOException {
@ -37,14 +40,20 @@ public final class GetApiKeyRequest extends ActionRequest {
userName = in.readOptionalString();
apiKeyId = in.readOptionalString();
apiKeyName = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_7_4_0)) {
ownedByAuthenticatedUser = in.readOptionalBoolean();
} else {
ownedByAuthenticatedUser = false;
}
}
public GetApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String apiKeyId,
@Nullable String apiKeyName) {
@Nullable String apiKeyName, boolean ownedByAuthenticatedUser) {
this.realmName = realmName;
this.userName = userName;
this.apiKeyId = apiKeyId;
this.apiKeyName = apiKeyName;
this.ownedByAuthenticatedUser = ownedByAuthenticatedUser;
}
public String getRealmName() {
@ -63,13 +72,17 @@ public final class GetApiKeyRequest extends ActionRequest {
return apiKeyName;
}
public boolean ownedByAuthenticatedUser() {
return ownedByAuthenticatedUser;
}
/**
* Creates get API key request for given realm name
* @param realmName realm name
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingRealmName(String realmName) {
return new GetApiKeyRequest(realmName, null, null, null);
return new GetApiKeyRequest(realmName, null, null, null, false);
}
/**
@ -78,7 +91,7 @@ public final class GetApiKeyRequest extends ActionRequest {
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingUserName(String userName) {
return new GetApiKeyRequest(null, userName, null, null);
return new GetApiKeyRequest(null, userName, null, null, false);
}
/**
@ -88,34 +101,45 @@ public final class GetApiKeyRequest extends ActionRequest {
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingRealmAndUserName(String realmName, String userName) {
return new GetApiKeyRequest(realmName, userName, null, null);
return new GetApiKeyRequest(realmName, userName, null, null, false);
}
/**
* Creates get API key request for given api key id
* @param apiKeyId api key id
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
* {@code false}
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingApiKeyId(String apiKeyId) {
return new GetApiKeyRequest(null, null, apiKeyId, null);
public static GetApiKeyRequest usingApiKeyId(String apiKeyId, boolean ownedByAuthenticatedUser) {
return new GetApiKeyRequest(null, null, apiKeyId, null, ownedByAuthenticatedUser);
}
/**
* Creates get api key request for given api key name
* @param apiKeyName api key name
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
* {@code false}
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingApiKeyName(String apiKeyName) {
return new GetApiKeyRequest(null, null, null, apiKeyName);
public static GetApiKeyRequest usingApiKeyName(String apiKeyName, boolean ownedByAuthenticatedUser) {
return new GetApiKeyRequest(null, null, null, apiKeyName, ownedByAuthenticatedUser);
}
/**
* Creates get api key request to retrieve api key information for the api keys owned by the current authenticated user.
*/
public static GetApiKeyRequest forOwnedApiKeys() {
return new GetApiKeyRequest(null, null, null, null, true);
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(apiKeyId) == false
&& Strings.hasText(apiKeyName) == false) {
validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified",
validationException);
&& Strings.hasText(apiKeyName) == false && ownedByAuthenticatedUser == false) {
validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified if " +
"[owner] flag is false", validationException);
}
if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
@ -124,6 +148,13 @@ public final class GetApiKeyRequest extends ActionRequest {
validationException);
}
}
if (ownedByAuthenticatedUser) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
validationException = addValidationError(
"neither username nor realm-name may be specified when retrieving owned API keys",
validationException);
}
}
if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) {
validationException = addValidationError("only one of [api key id, api key name] can be specified", validationException);
}
@ -137,6 +168,29 @@ public final class GetApiKeyRequest extends ActionRequest {
out.writeOptionalString(userName);
out.writeOptionalString(apiKeyId);
out.writeOptionalString(apiKeyName);
if (out.getVersion().onOrAfter(Version.V_7_4_0)) {
out.writeOptionalBoolean(ownedByAuthenticatedUser);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GetApiKeyRequest that = (GetApiKeyRequest) o;
return ownedByAuthenticatedUser == that.ownedByAuthenticatedUser &&
Objects.equals(realmName, that.realmName) &&
Objects.equals(userName, that.userName) &&
Objects.equals(apiKeyId, that.apiKeyId) &&
Objects.equals(apiKeyName, that.apiKeyName);
}
@Override
public int hashCode() {
return Objects.hash(realmName, userName, apiKeyId, apiKeyName, ownedByAuthenticatedUser);
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.core.security.action;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
@ -14,6 +15,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import java.util.Objects;
import static org.elasticsearch.action.ValidateActions.addValidationError;
@ -26,9 +28,10 @@ public final class InvalidateApiKeyRequest extends ActionRequest {
private final String userName;
private final String id;
private final String name;
private final boolean ownedByAuthenticatedUser;
public InvalidateApiKeyRequest() {
this(null, null, null, null);
this(null, null, null, null, false);
}
public InvalidateApiKeyRequest(StreamInput in) throws IOException {
@ -37,14 +40,20 @@ public final class InvalidateApiKeyRequest extends ActionRequest {
userName = in.readOptionalString();
id = in.readOptionalString();
name = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_7_4_0)) {
ownedByAuthenticatedUser = in.readOptionalBoolean();
} else {
ownedByAuthenticatedUser = false;
}
}
public InvalidateApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String id,
@Nullable String name) {
@Nullable String name, boolean ownedByAuthenticatedUser) {
this.realmName = realmName;
this.userName = userName;
this.id = id;
this.name = name;
this.ownedByAuthenticatedUser = ownedByAuthenticatedUser;
}
public String getRealmName() {
@ -63,65 +72,92 @@ public final class InvalidateApiKeyRequest extends ActionRequest {
return name;
}
public boolean ownedByAuthenticatedUser() {
return ownedByAuthenticatedUser;
}
/**
* Creates invalidate api key request for given realm name
*
* @param realmName realm name
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingRealmName(String realmName) {
return new InvalidateApiKeyRequest(realmName, null, null, null);
return new InvalidateApiKeyRequest(realmName, null, null, null, false);
}
/**
* Creates invalidate API key request for given user name
*
* @param userName user name
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingUserName(String userName) {
return new InvalidateApiKeyRequest(null, userName, null, null);
return new InvalidateApiKeyRequest(null, userName, null, null, false);
}
/**
* Creates invalidate API key request for given realm and user name
*
* @param realmName realm name
* @param userName user name
* @param userName user name
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingRealmAndUserName(String realmName, String userName) {
return new InvalidateApiKeyRequest(realmName, userName, null, null);
return new InvalidateApiKeyRequest(realmName, userName, null, null, false);
}
/**
* Creates invalidate API key request for given api key id
*
* @param id api key id
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
* {@code false}
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingApiKeyId(String id) {
return new InvalidateApiKeyRequest(null, null, id, null);
public static InvalidateApiKeyRequest usingApiKeyId(String id, boolean ownedByAuthenticatedUser) {
return new InvalidateApiKeyRequest(null, null, id, null, ownedByAuthenticatedUser);
}
/**
* Creates invalidate api key request for given api key name
*
* @param name api key name
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
* {@code false}
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingApiKeyName(String name) {
return new InvalidateApiKeyRequest(null, null, null, name);
public static InvalidateApiKeyRequest usingApiKeyName(String name, boolean ownedByAuthenticatedUser) {
return new InvalidateApiKeyRequest(null, null, null, name, ownedByAuthenticatedUser);
}
/**
* Creates invalidate api key request to invalidate api keys owned by the current authenticated user.
*/
public static InvalidateApiKeyRequest forOwnedApiKeys() {
return new InvalidateApiKeyRequest(null, null, null, null, true);
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(id) == false
&& Strings.hasText(name) == false) {
validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified",
validationException);
&& Strings.hasText(name) == false && ownedByAuthenticatedUser == false) {
validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified if " +
"[owner] flag is false", validationException);
}
if (Strings.hasText(id) || Strings.hasText(name)) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
validationException = addValidationError(
"username or realm name must not be specified when the api key id or api key name is specified",
validationException);
"username or realm name must not be specified when the api key id or api key name is specified",
validationException);
}
}
if (ownedByAuthenticatedUser) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
validationException = addValidationError(
"neither username nor realm-name may be specified when invalidating owned API keys",
validationException);
}
}
if (Strings.hasText(id) && Strings.hasText(name)) {
@ -137,5 +173,29 @@ public final class InvalidateApiKeyRequest extends ActionRequest {
out.writeOptionalString(userName);
out.writeOptionalString(id);
out.writeOptionalString(name);
if (out.getVersion().onOrAfter(Version.V_7_4_0)) {
out.writeOptionalBoolean(ownedByAuthenticatedUser);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
InvalidateApiKeyRequest that = (InvalidateApiKeyRequest) o;
return ownedByAuthenticatedUser == that.ownedByAuthenticatedUser &&
Objects.equals(realmName, that.realmName) &&
Objects.equals(userName, that.userName) &&
Objects.equals(id, that.id) &&
Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(realmName, userName, id, name, ownedByAuthenticatedUser);
}
}

View File

@ -8,8 +8,8 @@ package org.elasticsearch.xpack.core.security.authz.permission;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege;
import org.elasticsearch.xpack.core.security.support.Automatons;
import java.util.ArrayList;
@ -35,14 +35,16 @@ public class ClusterPermission {
}
/**
* Checks permission to a cluster action for a given request.
* Checks permission to a cluster action for a given request in the context of given
* authentication.
*
* @param action cluster action
* @param request {@link TransportRequest}
* @param authentication {@link Authentication}
* @return {@code true} if the access is allowed else returns {@code false}
*/
public boolean check(final String action, final TransportRequest request) {
return checks.stream().anyMatch(permission -> permission.check(action, request));
public boolean check(final String action, final TransportRequest request, final Authentication authentication) {
return checks.stream().anyMatch(permission -> permission.check(action, request, authentication));
}
/**
@ -81,21 +83,15 @@ public class ClusterPermission {
public Builder add(final ClusterPrivilege clusterPrivilege, final Set<String> allowedActionPatterns,
final Set<String> excludeActionPatterns) {
this.clusterPrivileges.add(clusterPrivilege);
if (allowedActionPatterns.isEmpty() && excludeActionPatterns.isEmpty()) {
this.actionAutomatons.add(Automatons.EMPTY);
} else {
final Automaton allowedAutomaton = Automatons.patterns(allowedActionPatterns);
final Automaton excludedAutomaton = Automatons.patterns(excludeActionPatterns);
this.actionAutomatons.add(Automatons.minusAndMinimize(allowedAutomaton, excludedAutomaton));
}
final Automaton actionAutomaton = createAutomaton(allowedActionPatterns, excludeActionPatterns);
this.actionAutomatons.add(actionAutomaton);
return this;
}
public Builder add(final ConfigurableClusterPrivilege configurableClusterPrivilege, final Predicate<String> actionPredicate,
public Builder add(final ClusterPrivilege clusterPrivilege, final Set<String> allowedActionPatterns,
final Predicate<TransportRequest> requestPredicate) {
return add(configurableClusterPrivilege, new ActionRequestPredicatePermissionCheck(configurableClusterPrivilege,
actionPredicate,
requestPredicate));
final Automaton actionAutomaton = createAutomaton(allowedActionPatterns, Collections.emptySet());
return add(clusterPrivilege, new ActionRequestBasedPermissionCheck(clusterPrivilege, actionAutomaton, requestPredicate));
}
public Builder add(final ClusterPrivilege clusterPrivilege, final PermissionCheck permissionCheck) {
@ -117,6 +113,21 @@ public class ClusterPermission {
}
return new ClusterPermission(this.clusterPrivileges, checks);
}
private static Automaton createAutomaton(Set<String> allowedActionPatterns, Set<String> excludeActionPatterns) {
allowedActionPatterns = (allowedActionPatterns == null) ? Collections.emptySet() : allowedActionPatterns;
excludeActionPatterns = (excludeActionPatterns == null) ? Collections.emptySet() : excludeActionPatterns;
if (allowedActionPatterns.isEmpty()) {
return Automatons.EMPTY;
} else if (excludeActionPatterns.isEmpty()) {
return Automatons.patterns(allowedActionPatterns);
} else {
final Automaton allowedAutomaton = Automatons.patterns(allowedActionPatterns);
final Automaton excludedAutomaton = Automatons.patterns(excludeActionPatterns);
return Automatons.minusAndMinimize(allowedAutomaton, excludedAutomaton);
}
}
}
/**
@ -125,13 +136,15 @@ public class ClusterPermission {
*/
public interface PermissionCheck {
/**
* Checks permission to a cluster action for a given request.
* Checks permission to a cluster action for a given request in the context of given
* authentication.
*
* @param action action name
* @param request {@link TransportRequest}
* @param authentication {@link Authentication}
* @return {@code true} if the specified action for given request is allowed else returns {@code false}
*/
boolean check(String action, TransportRequest request);
boolean check(String action, TransportRequest request, Authentication authentication);
/**
* Checks whether specified {@link PermissionCheck} is implied by this {@link PermissionCheck}.<br>
@ -146,52 +159,80 @@ public class ClusterPermission {
boolean implies(PermissionCheck otherPermissionCheck);
}
// Automaton based permission check
private static class AutomatonPermissionCheck implements PermissionCheck {
/**
* Base for implementing cluster action based {@link PermissionCheck}.
* It enforces the checks at cluster action level and then hands it off to the implementations
* to enforce checks based on {@link TransportRequest} and/or {@link Authentication}.
*/
public abstract static class ActionBasedPermissionCheck implements PermissionCheck {
private final Automaton automaton;
private final Predicate<String> actionPredicate;
AutomatonPermissionCheck(final Automaton automaton) {
public ActionBasedPermissionCheck(final Automaton automaton) {
this.automaton = automaton;
this.actionPredicate = Automatons.predicate(automaton);
}
@Override
public boolean check(final String action, final TransportRequest request) {
return actionPredicate.test(action);
public final boolean check(final String action, final TransportRequest request, final Authentication authentication) {
return actionPredicate.test(action) && extendedCheck(action, request, authentication);
}
protected abstract boolean extendedCheck(String action, TransportRequest request, Authentication authentication);
@Override
public boolean implies(final PermissionCheck permissionCheck) {
if (permissionCheck instanceof AutomatonPermissionCheck) {
return Operations.subsetOf(((AutomatonPermissionCheck) permissionCheck).automaton, this.automaton);
public final boolean implies(final PermissionCheck permissionCheck) {
if (permissionCheck instanceof ActionBasedPermissionCheck) {
return Operations.subsetOf(((ActionBasedPermissionCheck) permissionCheck).automaton, this.automaton) &&
doImplies((ActionBasedPermissionCheck) permissionCheck);
}
return false;
}
protected abstract boolean doImplies(ActionBasedPermissionCheck permissionCheck);
}
// action and request based permission check
private static class ActionRequestPredicatePermissionCheck implements PermissionCheck {
// Automaton based permission check
private static class AutomatonPermissionCheck extends ActionBasedPermissionCheck {
AutomatonPermissionCheck(final Automaton automaton) {
super(automaton);
}
@Override
protected boolean extendedCheck(String action, TransportRequest request, Authentication authentication) {
return true;
}
@Override
protected boolean doImplies(ActionBasedPermissionCheck permissionCheck) {
return permissionCheck instanceof AutomatonPermissionCheck;
}
}
// action, request based permission check
private static class ActionRequestBasedPermissionCheck extends ActionBasedPermissionCheck {
private final ClusterPrivilege clusterPrivilege;
final Predicate<String> actionPredicate;
final Predicate<TransportRequest> requestPredicate;
private final Predicate<TransportRequest> requestPredicate;
ActionRequestPredicatePermissionCheck(final ClusterPrivilege clusterPrivilege, final Predicate<String> actionPredicate,
final Predicate<TransportRequest> requestPredicate) {
this.clusterPrivilege = clusterPrivilege;
this.actionPredicate = actionPredicate;
ActionRequestBasedPermissionCheck(ClusterPrivilege clusterPrivilege, final Automaton automaton,
final Predicate<TransportRequest> requestPredicate) {
super(automaton);
this.requestPredicate = requestPredicate;
this.clusterPrivilege = clusterPrivilege;
}
@Override
public boolean check(final String action, final TransportRequest request) {
return actionPredicate.test(action) && requestPredicate.test(request);
protected boolean extendedCheck(String action, TransportRequest request, Authentication authentication) {
return requestPredicate.test(request);
}
@Override
public boolean implies(final PermissionCheck permissionCheck) {
if (permissionCheck instanceof ActionRequestPredicatePermissionCheck) {
final ActionRequestPredicatePermissionCheck otherCheck = (ActionRequestPredicatePermissionCheck) permissionCheck;
protected boolean doImplies(final ActionBasedPermissionCheck permissionCheck) {
if (permissionCheck instanceof ActionRequestBasedPermissionCheck) {
final ActionRequestBasedPermissionCheck otherCheck =
(ActionRequestBasedPermissionCheck) permissionCheck;
return this.clusterPrivilege.equals(otherCheck.clusterPrivilege);
}
return false;

View File

@ -9,6 +9,7 @@ package org.elasticsearch.xpack.core.security.authz.permission;
import org.apache.lucene.util.automaton.Automaton;
import org.elasticsearch.cluster.metadata.AliasOrIndex;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
@ -122,15 +123,18 @@ public final class LimitedRole extends Role {
}
/**
* Check if cluster permissions allow for the given action, also checks whether the limited by role allows the given actions
* Check if cluster permissions allow for the given action,
* also checks whether the limited by role allows the given actions in the context of given
* authentication.
*
* @param action cluster action
* @param request {@link TransportRequest}
* @param authentication {@link Authentication}
* @return {@code true} if action is allowed else returns {@code false}
*/
@Override
public boolean checkClusterAction(String action, TransportRequest request) {
return super.checkClusterAction(action, request) && limitedBy.checkClusterAction(action, request);
public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) {
return super.checkClusterAction(action, request, authentication) && limitedBy.checkClusterAction(action, request, authentication);
}
/**

View File

@ -12,6 +12,7 @@ import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
@ -121,14 +122,16 @@ public class Role {
}
/**
* Check if cluster permissions allow for the given action
* Check if cluster permissions allow for the given action in the context of given
* authentication.
*
* @param action cluster action
* @param request {@link TransportRequest}
* @param authentication {@link Authentication}
* @return {@code true} if action is allowed else returns {@code false}
*/
public boolean checkClusterAction(String action, TransportRequest request) {
return cluster.check(action, request);
public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) {
return cluster.check(action, request, authentication);
}
/**

View File

@ -117,6 +117,8 @@ public class ClusterPrivilegeResolver {
public static final NamedClusterPrivilege DELEGATE_PKI = new ActionClusterPrivilege("delegate_pki",
Sets.newHashSet(DelegatePkiAuthenticationAction.NAME, InvalidateTokenAction.NAME));
public static final NamedClusterPrivilege MANAGE_OWN_API_KEY = ManageOwnApiKeyClusterPrivilege.INSTANCE;
private static final Map<String, NamedClusterPrivilege> VALUES = Collections.unmodifiableMap(
Stream.of(
NONE,
@ -147,7 +149,8 @@ public class ClusterPrivilegeResolver {
READ_ILM,
MANAGE_SLM,
READ_SLM,
DELEGATE_PKI).collect(Collectors.toMap(cp -> cp.name(), cp -> cp)));
DELEGATE_PKI,
MANAGE_OWN_API_KEY).collect(Collectors.toMap(cp -> cp.name(), cp -> cp)));
/**
* Resolves a {@link NamedClusterPrivilege} from a given name if it exists.

View File

@ -125,8 +125,6 @@ public final class ConfigurableClusterPrivileges {
* of applications (identified by a wildcard-aware application-name).
*/
public static class ManageApplicationPrivileges implements ConfigurableClusterPrivilege {
private static final Predicate<String> ACTION_PREDICATE = Automatons.predicate("cluster:admin/xpack/security/privilege/*");
public static final String WRITEABLE_NAME = "manage-application-privileges";
private final Set<String> applicationNames;
@ -145,6 +143,7 @@ public final class ConfigurableClusterPrivileges {
}
return false;
};
}
@Override
@ -215,7 +214,7 @@ public final class ConfigurableClusterPrivileges {
@Override
public ClusterPermission.Builder buildPermission(final ClusterPermission.Builder builder) {
return builder.add(this, ACTION_PREDICATE, requestPredicate);
return builder.add(this, Collections.singleton("cluster:admin/xpack/security/privilege/*"), requestPredicate);
}
private interface Fields {

View File

@ -0,0 +1,106 @@
/*
*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*
*/
package org.elasticsearch.xpack.core.security.authz.privilege;
import org.elasticsearch.common.Strings;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission;
import org.elasticsearch.xpack.core.security.support.Automatons;
/**
* Named cluster privilege for managing API keys owned by the current authenticated user.
*/
public class ManageOwnApiKeyClusterPrivilege implements NamedClusterPrivilege {
public static final ManageOwnApiKeyClusterPrivilege INSTANCE = new ManageOwnApiKeyClusterPrivilege();
private static final String PRIVILEGE_NAME = "manage_own_api_key";
private static final String API_KEY_REALM_TYPE = "_es_api_key";
private static final String API_KEY_ID_KEY = "_security_api_key_id";
private ManageOwnApiKeyClusterPrivilege() {
}
@Override
public String name() {
return PRIVILEGE_NAME;
}
@Override
public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) {
return builder.add(this, ManageOwnClusterPermissionCheck.INSTANCE);
}
private static final class ManageOwnClusterPermissionCheck extends ClusterPermission.ActionBasedPermissionCheck {
public static final ManageOwnClusterPermissionCheck INSTANCE = new ManageOwnClusterPermissionCheck();
private ManageOwnClusterPermissionCheck() {
super(Automatons.patterns("cluster:admin/xpack/security/api_key/*"));
}
@Override
protected boolean extendedCheck(String action, TransportRequest request, Authentication authentication) {
if (request instanceof CreateApiKeyRequest) {
return true;
} else if (request instanceof GetApiKeyRequest) {
final GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request;
return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName(),
getApiKeyRequest.getRealmName(), getApiKeyRequest.ownedByAuthenticatedUser());
} else if (request instanceof InvalidateApiKeyRequest) {
final InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request;
return checkIfUserIsOwnerOfApiKeys(authentication, invalidateApiKeyRequest.getId(),
invalidateApiKeyRequest.getUserName(), invalidateApiKeyRequest.getRealmName(),
invalidateApiKeyRequest.ownedByAuthenticatedUser());
}
throw new IllegalArgumentException(
"manage own api key privilege only supports API key requests (not " + request.getClass().getName() + ")");
}
@Override
protected boolean doImplies(ClusterPermission.ActionBasedPermissionCheck permissionCheck) {
return permissionCheck instanceof ManageOwnClusterPermissionCheck;
}
private boolean checkIfUserIsOwnerOfApiKeys(Authentication authentication, String apiKeyId, String username, String realmName,
boolean ownedByAuthenticatedUser) {
if (isCurrentAuthenticationUsingSameApiKeyIdFromRequest(authentication, apiKeyId)) {
return true;
} else {
/*
* TODO bizybot we need to think on how we can propagate appropriate error message to the end user when username, realm name
* is missing. This is similar to the problem of propagating right error messages in case of access denied.
*/
if (authentication.getAuthenticatedBy().getType().equals(API_KEY_REALM_TYPE)) {
// API key cannot own any other API key so deny access
return false;
} else if (ownedByAuthenticatedUser) {
return true;
} else if (Strings.hasText(username) && Strings.hasText(realmName)) {
final String authenticatedUserPrincipal = authentication.getUser().principal();
final String authenticatedUserRealm = authentication.getAuthenticatedBy().getName();
return username.equals(authenticatedUserPrincipal) && realmName.equals(authenticatedUserRealm);
}
}
return false;
}
private boolean isCurrentAuthenticationUsingSameApiKeyIdFromRequest(Authentication authentication, String apiKeyId) {
if (authentication.getAuthenticatedBy().getType().equals(API_KEY_REALM_TYPE)) {
// API key id from authentication must match the id from request
final String authenticatedApiKeyId = (String) authentication.getMetadata().get(API_KEY_ID_KEY);
if (Strings.hasText(apiKeyId)) {
return apiKeyId.equals(authenticatedApiKeyId);
}
}
return false;
}
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.core.security.action;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.InputStreamStreamInput;
@ -17,15 +18,18 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static org.elasticsearch.test.VersionUtils.randomVersionBetween;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
public class GetApiKeyRequestTests extends ESTestCase {
public void testRequestValidation() {
GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5));
GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5), randomBoolean());
ActionRequestValidationException ve = request.validate();
assertNull(ve);
request = GetApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5));
request = GetApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5), randomBoolean());
ve = request.validate();
assertNull(ve);
request = GetApiKeyRequest.usingRealmName(randomAlphaOfLength(5));
@ -45,12 +49,14 @@ public class GetApiKeyRequestTests extends ESTestCase {
String user;
String apiKeyId;
String apiKeyName;
boolean ownedByAuthenticatedUser;
Dummy(String[] a) {
realm = a[0];
user = a[1];
apiKeyId = a[2];
apiKeyName = a[3];
ownedByAuthenticatedUser = Boolean.parseBoolean(a[4]);
}
@Override
@ -65,23 +71,31 @@ public class GetApiKeyRequestTests extends ESTestCase {
out.writeOptionalString(user);
out.writeOptionalString(apiKeyId);
out.writeOptionalString(apiKeyName);
out.writeOptionalBoolean(ownedByAuthenticatedUser);
}
}
String[][] inputs = new String[][] {
{ randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }),
randomFrom(new String[] { null, "" }) },
{ randomFrom(new String[] { null, "" }), "user", "api-kid", "api-kname" },
{ "realm", randomFrom(new String[] { null, "" }), "api-kid", "api-kname" },
{ "realm", "user", "api-kid", randomFrom(new String[] { null, "" }) },
{ randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), "api-kid", "api-kname" } };
String[][] expectedErrorMessages = new String[][] { { "One of [api key id, api key name, username, realm name] must be specified" },
{ "username or realm name must not be specified when the api key id or api key name is specified",
"only one of [api key id, api key name] can be specified" },
{ "username or realm name must not be specified when the api key id or api key name is specified",
"only one of [api key id, api key name] can be specified" },
{ "username or realm name must not be specified when the api key id or api key name is specified" },
{ "only one of [api key id, api key name] can be specified" } };
String[][] inputs = new String[][]{
{randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(),
randomNullOrEmptyString(), "false"},
{randomNullOrEmptyString(), "user", "api-kid", "api-kname", "false"},
{"realm", randomNullOrEmptyString(), "api-kid", "api-kname", "false"},
{"realm", "user", "api-kid", randomNullOrEmptyString(), "false"},
{randomNullOrEmptyString(), randomNullOrEmptyString(), "api-kid", "api-kname", "false"},
{"realm", randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "true"},
{randomNullOrEmptyString(), "user", randomNullOrEmptyString(), randomNullOrEmptyString(), "true"}
};
String[][] expectedErrorMessages = new String[][]{
{"One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false"},
{"username or realm name must not be specified when the api key id or api key name is specified",
"only one of [api key id, api key name] can be specified"},
{"username or realm name must not be specified when the api key id or api key name is specified",
"only one of [api key id, api key name] can be specified"},
{"username or realm name must not be specified when the api key id or api key name is specified"},
{"only one of [api key id, api key name] can be specified"},
{"neither username nor realm-name may be specified when retrieving owned API keys"},
{"neither username nor realm-name may be specified when retrieving owned API keys"}
};
for (int caseNo = 0; caseNo < inputs.length; caseNo++) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
@ -100,4 +114,40 @@ public class GetApiKeyRequestTests extends ESTestCase {
}
}
}
public void testSerialization() throws IOException {
final String apiKeyId = randomAlphaOfLength(5);
final boolean ownedByAuthenticatedUser = true;
GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(apiKeyId, ownedByAuthenticatedUser);
{
ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
OutputStreamStreamOutput out = new OutputStreamStreamOutput(outBuffer);
out.setVersion(randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_3_0));
getApiKeyRequest.writeTo(out);
InputStreamStreamInput inputStreamStreamInput = new InputStreamStreamInput(new ByteArrayInputStream(outBuffer.toByteArray()));
inputStreamStreamInput.setVersion(randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_3_0));
GetApiKeyRequest requestFromInputStream = new GetApiKeyRequest(inputStreamStreamInput);
assertThat(requestFromInputStream.getApiKeyId(), equalTo(getApiKeyRequest.getApiKeyId()));
// old version so the default for `ownedByAuthenticatedUser` is false
assertThat(requestFromInputStream.ownedByAuthenticatedUser(), is(false));
}
{
ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
OutputStreamStreamOutput out = new OutputStreamStreamOutput(outBuffer);
out.setVersion(randomVersionBetween(random(), Version.V_7_4_0, Version.CURRENT));
getApiKeyRequest.writeTo(out);
InputStreamStreamInput inputStreamStreamInput = new InputStreamStreamInput(new ByteArrayInputStream(outBuffer.toByteArray()));
inputStreamStreamInput.setVersion(randomVersionBetween(random(), Version.V_7_4_0, Version.CURRENT));
GetApiKeyRequest requestFromInputStream = new GetApiKeyRequest(inputStreamStreamInput);
assertThat(requestFromInputStream, equalTo(getApiKeyRequest));
}
}
private static String randomNullOrEmptyString() {
return randomBoolean() ? "" : null;
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.core.security.action;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.InputStreamStreamInput;
@ -17,15 +18,18 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static org.elasticsearch.test.VersionUtils.randomVersionBetween;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
public class InvalidateApiKeyRequestTests extends ESTestCase {
public void testRequestValidation() {
InvalidateApiKeyRequest request = InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5));
InvalidateApiKeyRequest request = InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5), randomBoolean());
ActionRequestValidationException ve = request.validate();
assertNull(ve);
request = InvalidateApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5));
request = InvalidateApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5), randomBoolean());
ve = request.validate();
assertNull(ve);
request = InvalidateApiKeyRequest.usingRealmName(randomAlphaOfLength(5));
@ -45,12 +49,14 @@ public class InvalidateApiKeyRequestTests extends ESTestCase {
String user;
String apiKeyId;
String apiKeyName;
boolean ownedByAuthenticatedUser;
Dummy(String[] a) {
realm = a[0];
user = a[1];
apiKeyId = a[2];
apiKeyName = a[3];
ownedByAuthenticatedUser = Boolean.parseBoolean(a[4]);
}
@Override
@ -65,24 +71,31 @@ public class InvalidateApiKeyRequestTests extends ESTestCase {
out.writeOptionalString(user);
out.writeOptionalString(apiKeyId);
out.writeOptionalString(apiKeyName);
out.writeOptionalBoolean(ownedByAuthenticatedUser);
}
}
String[][] inputs = new String[][] {
{ randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }),
randomFrom(new String[] { null, "" }) },
{ randomFrom(new String[] { null, "" }), "user", "api-kid", "api-kname" },
{ "realm", randomFrom(new String[] { null, "" }), "api-kid", "api-kname" },
{ "realm", "user", "api-kid", randomFrom(new String[] { null, "" }) },
{ randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), "api-kid", "api-kname" } };
String[][] expectedErrorMessages = new String[][] { { "One of [api key id, api key name, username, realm name] must be specified" },
{ "username or realm name must not be specified when the api key id or api key name is specified",
"only one of [api key id, api key name] can be specified" },
{ "username or realm name must not be specified when the api key id or api key name is specified",
"only one of [api key id, api key name] can be specified" },
{ "username or realm name must not be specified when the api key id or api key name is specified" },
{ "only one of [api key id, api key name] can be specified" } };
String[][] inputs = new String[][]{
{randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(),
randomNullOrEmptyString(), "false"},
{randomNullOrEmptyString(), "user", "api-kid", "api-kname", "false"},
{"realm", randomNullOrEmptyString(), "api-kid", "api-kname", "false"},
{"realm", "user", "api-kid", randomNullOrEmptyString(), "false"},
{randomNullOrEmptyString(), randomNullOrEmptyString(), "api-kid", "api-kname", "false"},
{"realm", randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "true"},
{randomNullOrEmptyString(), "user", randomNullOrEmptyString(), randomNullOrEmptyString(), "true"},
};
String[][] expectedErrorMessages = new String[][]{
{"One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false"},
{"username or realm name must not be specified when the api key id or api key name is specified",
"only one of [api key id, api key name] can be specified"},
{"username or realm name must not be specified when the api key id or api key name is specified",
"only one of [api key id, api key name] can be specified"},
{"username or realm name must not be specified when the api key id or api key name is specified"},
{"only one of [api key id, api key name] can be specified"},
{"neither username nor realm-name may be specified when invalidating owned API keys"},
{"neither username nor realm-name may be specified when invalidating owned API keys"}
};
for (int caseNo = 0; caseNo < inputs.length; caseNo++) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
@ -101,4 +114,41 @@ public class InvalidateApiKeyRequestTests extends ESTestCase {
}
}
}
public void testSerialization() throws IOException {
final String apiKeyId = randomAlphaOfLength(5);
final boolean ownedByAuthenticatedUser = true;
InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(apiKeyId, ownedByAuthenticatedUser);
{
ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
OutputStreamStreamOutput out = new OutputStreamStreamOutput(outBuffer);
out.setVersion(randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_3_0));
invalidateApiKeyRequest.writeTo(out);
InputStreamStreamInput inputStreamStreamInput = new InputStreamStreamInput(new ByteArrayInputStream(outBuffer.toByteArray()));
inputStreamStreamInput.setVersion(randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_3_0));
InvalidateApiKeyRequest requestFromInputStream = new InvalidateApiKeyRequest(inputStreamStreamInput);
assertThat(requestFromInputStream.getId(), equalTo(invalidateApiKeyRequest.getId()));
// old version so the default for `ownedByAuthenticatedUser` is false
assertThat(requestFromInputStream.ownedByAuthenticatedUser(), is(false));
}
{
ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
OutputStreamStreamOutput out = new OutputStreamStreamOutput(outBuffer);
out.setVersion(randomVersionBetween(random(), Version.V_7_4_0, Version.CURRENT));
invalidateApiKeyRequest.writeTo(out);
InputStreamStreamInput inputStreamStreamInput = new InputStreamStreamInput(new ByteArrayInputStream(outBuffer.toByteArray()));
inputStreamStreamInput.setVersion(randomVersionBetween(random(), Version.V_7_4_0, Version.CURRENT));
InvalidateApiKeyRequest requestFromInputStream = new InvalidateApiKeyRequest(inputStreamStreamInput);
assertThat(requestFromInputStream, equalTo(invalidateApiKeyRequest));
}
}
private static String randomNullOrEmptyString() {
return randomFrom(new String[]{"", null});
}
}

View File

@ -12,12 +12,11 @@ import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver;
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege;
import org.elasticsearch.xpack.core.security.support.Automatons;
import org.junit.Before;
import org.mockito.Mockito;
import java.io.IOException;
import java.util.Collections;
@ -27,9 +26,11 @@ import java.util.function.Predicate;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
public class ClusterPermissionTests extends ESTestCase {
private TransportRequest mockTransportRequest = Mockito.mock(TransportRequest.class);
private TransportRequest mockTransportRequest;
private Authentication mockAuthentication;
private ClusterPrivilege cpThatDoesNothing = new ClusterPrivilege() {
@Override
public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) {
@ -39,7 +40,8 @@ public class ClusterPermissionTests extends ESTestCase {
@Before
public void setup() {
mockTransportRequest = Mockito.mock(TransportRequest.class);
mockTransportRequest = mock(TransportRequest.class);
mockAuthentication = mock(Authentication.class);
}
public void testClusterPermissionBuilder() {
@ -79,10 +81,12 @@ public class ClusterPermissionTests extends ESTestCase {
builder = mockConfigurableClusterPrivilege2.buildPermission(builder);
final ClusterPermission clusterPermission = builder.build();
assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest), is(true));
assertThat(clusterPermission.check("cluster:admin/ilm/stop", mockTransportRequest), is(true));
assertThat(clusterPermission.check("cluster:admin/xpack/security/privilege/get", mockTransportRequest), is(true));
assertThat(clusterPermission.check("cluster:admin/snapshot/status", mockTransportRequest), is(false));
assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest, mockAuthentication),
is(true));
assertThat(clusterPermission.check("cluster:admin/ilm/stop", mockTransportRequest, mockAuthentication), is(true));
assertThat(clusterPermission.check("cluster:admin/xpack/security/privilege/get", mockTransportRequest, mockAuthentication),
is(true));
assertThat(clusterPermission.check("cluster:admin/snapshot/status", mockTransportRequest, mockAuthentication), is(false));
}
public void testClusterPermissionCheckWithEmptyActionPatterns() {
@ -90,8 +94,9 @@ public class ClusterPermissionTests extends ESTestCase {
builder.add(cpThatDoesNothing, Collections.emptySet(), Collections.emptySet());
final ClusterPermission clusterPermission = builder.build();
assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest), is(false));
assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest), is(false));
assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest, mockAuthentication), is(false));
assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest, mockAuthentication),
is(false));
}
public void testClusterPermissionCheckWithExcludeOnlyActionPatterns() {
@ -99,8 +104,9 @@ public class ClusterPermissionTests extends ESTestCase {
builder.add(cpThatDoesNothing, Collections.emptySet(), Collections.singleton("cluster:some/thing/to/exclude"));
final ClusterPermission clusterPermission = builder.build();
assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest), is(false));
assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest), is(false));
assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest, mockAuthentication), is(false));
assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest, mockAuthentication),
is(false));
}
public void testClusterPermissionCheckWithActionPatterns() {
@ -108,8 +114,9 @@ public class ClusterPermissionTests extends ESTestCase {
builder.add(cpThatDoesNothing, Collections.singleton("cluster:admin/*"), Collections.singleton("cluster:admin/ilm/*"));
final ClusterPermission clusterPermission = builder.build();
assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest), is(false));
assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest), is(true));
assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest, mockAuthentication), is(false));
assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest, mockAuthentication),
is(true));
}
public void testClusterPermissionCheckWithActionPatternsAndNoExludePatterns() {
@ -117,8 +124,9 @@ public class ClusterPermissionTests extends ESTestCase {
builder.add(cpThatDoesNothing, Collections.singleton("cluster:admin/*"), Collections.emptySet());
final ClusterPermission clusterPermission = builder.build();
assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest), is(true));
assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest), is(true));
assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest, mockAuthentication), is(true));
assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest, mockAuthentication),
is(true));
}
public void testNoneClusterPermissionIsImpliedByNone() {
@ -224,7 +232,6 @@ public class ClusterPermissionTests extends ESTestCase {
}
private static class MockConfigurableClusterPrivilege implements ConfigurableClusterPrivilege {
static final Predicate<String> ACTION_PREDICATE = Automatons.predicate("cluster:admin/xpack/security/privilege/*");
private Predicate<TransportRequest> requestPredicate;
MockConfigurableClusterPrivilege(Predicate<TransportRequest> requestPredicate) {
@ -276,7 +283,7 @@ public class ClusterPermissionTests extends ESTestCase {
@Override
public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) {
return builder.add(this, ACTION_PREDICATE, requestPredicate);
return builder.add(this, Collections.singleton("cluster:admin/xpack/security/privilege/*"), requestPredicate);
}
}
}

View File

@ -18,6 +18,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
@ -128,24 +129,26 @@ public class LimitedRoleTests extends ESTestCase {
public void testCheckClusterAction() {
Role fromRole = Role.builder("a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList())
.build();
assertThat(fromRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(true));
.build();
Authentication authentication = mock(Authentication.class);
assertThat(fromRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(true));
{
Role limitedByRole = Role.builder("limited-role")
.cluster(Collections.singleton("all"), Collections.emptyList()).build();
assertThat(limitedByRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(true));
assertThat(limitedByRole.checkClusterAction("cluster:other-action", mock(TransportRequest.class)), is(true));
.cluster(Collections.singleton("all"), Collections.emptyList()).build();
assertThat(limitedByRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication),
is(true));
assertThat(limitedByRole.checkClusterAction("cluster:other-action", mock(TransportRequest.class), authentication), is(true));
Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole);
assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(true));
assertThat(role.checkClusterAction("cluster:other-action", mock(TransportRequest.class)), is(false));
assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(true));
assertThat(role.checkClusterAction("cluster:other-action", mock(TransportRequest.class), authentication), is(false));
}
{
Role limitedByRole = Role.builder("limited-role")
.cluster(Collections.singleton("monitor"), Collections.emptyList()).build();
assertThat(limitedByRole.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class)), is(true));
.cluster(Collections.singleton("monitor"), Collections.emptyList()).build();
assertThat(limitedByRole.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class), authentication), is(true));
Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole);
assertThat(role.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class)), is(false));
assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(false));
assertThat(role.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class), authentication), is(false));
assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(false));
}
}

View File

@ -24,6 +24,7 @@ import org.elasticsearch.xpack.core.XPackClientPlugin;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission;
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivileges.ManageApplicationPrivileges;
@ -40,6 +41,7 @@ import static org.elasticsearch.common.xcontent.DeprecationHandler.THROW_UNSUPPO
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Mockito.mock;
public class ManageApplicationPrivilegesTests extends ESTestCase {
@ -97,14 +99,15 @@ public class ManageApplicationPrivilegesTests extends ESTestCase {
assertThat(kibanaAndLogstashPermission, notNullValue());
assertThat(cloudAndSwiftypePermission, notNullValue());
final Authentication authentication = mock(Authentication.class);
final GetPrivilegesRequest getKibana1 = new GetPrivilegesRequest();
getKibana1.application("kibana-1");
assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", getKibana1));
assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", getKibana1));
assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", getKibana1, authentication));
assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", getKibana1, authentication));
final DeletePrivilegesRequest deleteLogstash = new DeletePrivilegesRequest("logstash", new String[]{"all"});
assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", deleteLogstash));
assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", deleteLogstash));
assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", deleteLogstash, authentication));
assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", deleteLogstash, authentication));
final PutPrivilegesRequest putKibana = new PutPrivilegesRequest();
@ -114,11 +117,12 @@ public class ManageApplicationPrivilegesTests extends ESTestCase {
randomAlphaOfLengthBetween(3, 6).toLowerCase(Locale.ROOT), Collections.emptySet(), Collections.emptyMap()));
}
putKibana.setPrivileges(kibanaPrivileges);
assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", putKibana));
assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", putKibana));
assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", putKibana, authentication));
assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", putKibana, authentication));
}
public void testSecurityForGetAllApplicationPrivileges() {
final Authentication authentication = mock(Authentication.class);
final GetPrivilegesRequest getAll = new GetPrivilegesRequest();
getAll.application(null);
getAll.privileges(new String[0]);
@ -130,8 +134,8 @@ public class ManageApplicationPrivilegesTests extends ESTestCase {
final ClusterPermission kibanaOnlyPermission = kibanaOnly.buildPermission(ClusterPermission.builder()).build();
final ClusterPermission allAppsPermission = allApps.buildPermission(ClusterPermission.builder()).build();
assertFalse(kibanaOnlyPermission.check("cluster:admin/xpack/security/privilege/get", getAll));
assertTrue(allAppsPermission.check("cluster:admin/xpack/security/privilege/get", getAll));
assertFalse(kibanaOnlyPermission.check("cluster:admin/xpack/security/privilege/get", getAll, authentication));
assertTrue(allAppsPermission.check("cluster:admin/xpack/security/privilege/get", getAll, authentication));
}
private ManageApplicationPrivileges clone(ManageApplicationPrivileges original) {

View File

@ -0,0 +1,111 @@
/*
*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*
*/
package org.elasticsearch.xpack.core.security.authz.privilege;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission;
import org.elasticsearch.xpack.core.security.user.User;
import java.util.Collections;
import java.util.Map;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ManageOwnApiKeyClusterPrivilegeTests extends ESTestCase {
public void testAuthenticationWithApiKeyAllowsAccessToApiKeyActionsWhenItIsOwner() {
final ClusterPermission clusterPermission =
ManageOwnApiKeyClusterPrivilege.INSTANCE.buildPermission(ClusterPermission.builder()).build();
final String apiKeyId = randomAlphaOfLengthBetween(4, 7);
final Authentication authentication = createMockAuthentication("joe","_es_api_key", "_es_api_key",
Collections.singletonMap("_security_api_key_id", apiKeyId));
final TransportRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(apiKeyId, randomBoolean());
final TransportRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(apiKeyId, randomBoolean());
assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/get", getApiKeyRequest, authentication));
assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/invalidate", invalidateApiKeyRequest, authentication));
assertFalse(clusterPermission.check("cluster:admin/something", mock(TransportRequest.class), authentication));
}
public void testAuthenticationWithApiKeyDeniesAccessToApiKeyActionsWhenItIsNotOwner() {
final ClusterPermission clusterPermission =
ManageOwnApiKeyClusterPrivilege.INSTANCE.buildPermission(ClusterPermission.builder()).build();
final String apiKeyId = randomAlphaOfLengthBetween(4, 7);
final Authentication authentication = createMockAuthentication("joe","_es_api_key", "_es_api_key",
Collections.singletonMap("_security_api_key_id", randomAlphaOfLength(7)));
final TransportRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(apiKeyId, randomBoolean());
final TransportRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(apiKeyId, randomBoolean());
assertFalse(clusterPermission.check("cluster:admin/xpack/security/api_key/get", getApiKeyRequest, authentication));
assertFalse(clusterPermission.check("cluster:admin/xpack/security/api_key/invalidate", invalidateApiKeyRequest, authentication));
}
public void testAuthenticationWithUserAllowsAccessToApiKeyActionsWhenItIsOwner() {
final ClusterPermission clusterPermission =
ManageOwnApiKeyClusterPrivilege.INSTANCE.buildPermission(ClusterPermission.builder()).build();
final Authentication authentication = createMockAuthentication("joe","realm1", "native", Collections.emptyMap());
final TransportRequest getApiKeyRequest = GetApiKeyRequest.usingRealmAndUserName("realm1", "joe");
final TransportRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingRealmAndUserName("realm1", "joe");
assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/get", getApiKeyRequest, authentication));
assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/invalidate", invalidateApiKeyRequest, authentication));
assertFalse(clusterPermission.check("cluster:admin/something", mock(TransportRequest.class), authentication));
}
public void testAuthenticationWithUserAllowsAccessToApiKeyActionsWhenItIsOwner_WithOwnerFlagOnly() {
final ClusterPermission clusterPermission =
ManageOwnApiKeyClusterPrivilege.INSTANCE.buildPermission(ClusterPermission.builder()).build();
final Authentication authentication = createMockAuthentication("joe","realm1", "native", Collections.emptyMap());
final TransportRequest getApiKeyRequest = GetApiKeyRequest.forOwnedApiKeys();
final TransportRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.forOwnedApiKeys();
assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/get", getApiKeyRequest, authentication));
assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/invalidate", invalidateApiKeyRequest, authentication));
assertFalse(clusterPermission.check("cluster:admin/something", mock(TransportRequest.class), authentication));
}
public void testAuthenticationWithUserDeniesAccessToApiKeyActionsWhenItIsNotOwner() {
final ClusterPermission clusterPermission =
ManageOwnApiKeyClusterPrivilege.INSTANCE.buildPermission(ClusterPermission.builder()).build();
final Authentication authentication = createMockAuthentication("joe", "realm1", "native", Collections.emptyMap());
final TransportRequest getApiKeyRequest = randomFrom(
GetApiKeyRequest.usingRealmAndUserName("realm1", randomAlphaOfLength(7)),
GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), "joe"),
new GetApiKeyRequest(randomAlphaOfLength(5), randomAlphaOfLength(7), null, null, false));
final TransportRequest invalidateApiKeyRequest = randomFrom(
InvalidateApiKeyRequest.usingRealmAndUserName("realm1", randomAlphaOfLength(7)),
InvalidateApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), "joe"),
new InvalidateApiKeyRequest(randomAlphaOfLength(5), randomAlphaOfLength(7), null, null, false));
assertFalse(clusterPermission.check("cluster:admin/xpack/security/api_key/get", getApiKeyRequest, authentication));
assertFalse(clusterPermission.check("cluster:admin/xpack/security/api_key/invalidate", invalidateApiKeyRequest, authentication));
}
private Authentication createMockAuthentication(String username, String realmName, String realmType, Map<String, Object> metadata) {
final User user = new User(username);
final Authentication authentication = mock(Authentication.class);
final Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class);
when(authentication.getUser()).thenReturn(user);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getName()).thenReturn(realmName);
when(authenticatedBy.getType()).thenReturn(realmType);
when(authentication.getMetadata()).thenReturn(metadata);
return authentication;
}
}

View File

@ -9,17 +9,18 @@ import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission;
import org.elasticsearch.xpack.core.security.support.Automatons;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import java.util.Set;
import java.util.function.Predicate;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.mock;
public class PrivilegeTests extends ESTestCase {
@Rule
@ -35,13 +36,13 @@ public class PrivilegeTests extends ESTestCase {
private void verifyClusterActionAllowed(ClusterPrivilege clusterPrivilege, String... actions) {
ClusterPermission clusterPermission = clusterPrivilege.buildPermission(ClusterPermission.builder()).build();
for (String action: actions) {
assertTrue(clusterPermission.check(action, Mockito.mock(TransportRequest.class)));
assertTrue(clusterPermission.check(action, mock(TransportRequest.class), mock(Authentication.class)));
}
}
private void verifyClusterActionDenied(ClusterPrivilege clusterPrivilege, String... actions) {
ClusterPermission clusterPermission = clusterPrivilege.buildPermission(ClusterPermission.builder()).build();
for (String action: actions) {
assertFalse(clusterPermission.check(action, Mockito.mock(TransportRequest.class)));
assertFalse(clusterPermission.check(action, mock(TransportRequest.class), mock(Authentication.class)));
}
}
public void testCluster() throws Exception {

View File

@ -125,6 +125,7 @@ import org.elasticsearch.xpack.core.security.action.saml.SamlPrepareAuthenticati
import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction;
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction;
import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache;
@ -201,34 +202,35 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testSnapshotUserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("snapshot_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role snapshotUserRole = Role.builder(roleDescriptor, null).build();
assertThat(snapshotUserRole.cluster().check(GetRepositoriesAction.NAME, request), is(true));
assertThat(snapshotUserRole.cluster().check(CreateSnapshotAction.NAME, request), is(true));
assertThat(snapshotUserRole.cluster().check(SnapshotsStatusAction.NAME, request), is(true));
assertThat(snapshotUserRole.cluster().check(GetSnapshotsAction.NAME, request), is(true));
assertThat(snapshotUserRole.cluster().check(GetRepositoriesAction.NAME, request, authentication), is(true));
assertThat(snapshotUserRole.cluster().check(CreateSnapshotAction.NAME, request, authentication), is(true));
assertThat(snapshotUserRole.cluster().check(SnapshotsStatusAction.NAME, request, authentication), is(true));
assertThat(snapshotUserRole.cluster().check(GetSnapshotsAction.NAME, request, authentication), is(true));
assertThat(snapshotUserRole.cluster().check(PutRepositoryAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(DeleteIndexTemplateAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(PutPipelineAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(GetPipelineAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(DeletePipelineAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(GetWatchAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(PutWatchAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(DeleteWatchAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(ExecuteWatchAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(AckWatchAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(ActivateWatchAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(WatcherServiceAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(PutRepositoryAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(PutPipelineAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(GetPipelineAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(DeletePipelineAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(GetWatchAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(PutWatchAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(DeleteWatchAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(AckWatchAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(ActivateWatchAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(WatcherServiceAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(snapshotUserRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(randomAlphaOfLengthBetween(8, 24)), is(false));
assertThat(snapshotUserRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)), is(false));
@ -249,23 +251,23 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testIngestAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("ingest_admin");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role ingestAdminRole = Role.builder(roleDescriptor, null).build();
assertThat(ingestAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(DeleteIndexTemplateAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(PutPipelineAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(GetPipelineAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(DeletePipelineAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(ingestAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(ingestAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true));
assertThat(ingestAdminRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true));
assertThat(ingestAdminRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(true));
assertThat(ingestAdminRole.cluster().check(PutPipelineAction.NAME, request, authentication), is(true));
assertThat(ingestAdminRole.cluster().check(GetPipelineAction.NAME, request, authentication), is(true));
assertThat(ingestAdminRole.cluster().check(DeletePipelineAction.NAME, request, authentication), is(true));
assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
assertThat(ingestAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(ingestAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
assertThat(ingestAdminRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)),
@ -278,39 +280,40 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testKibanaSystemRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_system");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role kibanaRole = Role.builder(roleDescriptor, null).build();
assertThat(kibanaRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(ClusterStateAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(ClusterStatsAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(kibanaRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(kibanaRole.cluster().check(MonitoringBulkAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
assertThat(kibanaRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
assertThat(kibanaRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
assertThat(kibanaRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true));
assertThat(kibanaRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true));
assertThat(kibanaRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(kibanaRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(kibanaRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true));
// SAML and token
assertThat(kibanaRole.cluster().check(SamlPrepareAuthenticationAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(SamlAuthenticateAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(InvalidateTokenAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(CreateTokenAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(SamlPrepareAuthenticationAction.NAME, request, authentication), is(true));
assertThat(kibanaRole.cluster().check(SamlAuthenticateAction.NAME, request, authentication), is(true));
assertThat(kibanaRole.cluster().check(InvalidateTokenAction.NAME, request, authentication), is(true));
assertThat(kibanaRole.cluster().check(CreateTokenAction.NAME, request, authentication), is(true));
// Application Privileges
DeletePrivilegesRequest deleteKibanaPrivileges = new DeletePrivilegesRequest("kibana-.kibana", new String[]{ "all", "read" });
DeletePrivilegesRequest deleteLogstashPrivileges = new DeletePrivilegesRequest("logstash", new String[]{ "all", "read" });
assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteKibanaPrivileges), is(true));
assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteLogstashPrivileges), is(false));
assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteKibanaPrivileges, authentication), is(true));
assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteLogstashPrivileges, authentication), is(false));
GetPrivilegesRequest getKibanaPrivileges = new GetPrivilegesRequest();
getKibanaPrivileges.application("kibana-.kibana-sales");
GetPrivilegesRequest getApmPrivileges = new GetPrivilegesRequest();
getApmPrivileges.application("apm");
assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getKibanaPrivileges), is(true));
assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getApmPrivileges), is(false));
assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getKibanaPrivileges, authentication), is(true));
assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getApmPrivileges, authentication), is(false));
PutPrivilegesRequest putKibanaPrivileges = new PutPrivilegesRequest();
putKibanaPrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor(
@ -318,14 +321,14 @@ public class ReservedRolesStoreTests extends ESTestCase {
PutPrivilegesRequest putSwiftypePrivileges = new PutPrivilegesRequest();
putSwiftypePrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor(
"swiftype-kibana" , "all", Collections.emptySet(), Collections.emptyMap())));
assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putKibanaPrivileges), is(true));
assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putSwiftypePrivileges), is(false));
assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putKibanaPrivileges, authentication), is(true));
assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putSwiftypePrivileges, authentication), is(false));
assertThat(kibanaRole.cluster().check(GetBuiltinPrivilegesAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(GetBuiltinPrivilegesAction.NAME, request, authentication), is(true));
// Everything else
assertThat(kibanaRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
assertThat(kibanaRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(true));
assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false));
@ -382,20 +385,21 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testKibanaUserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role kibanaUserRole = Role.builder(roleDescriptor, null).build();
assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterStateAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
assertThat(kibanaUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(kibanaUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
assertThat(kibanaUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(kibanaUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -420,22 +424,23 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testMonitoringUserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("monitoring_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role monitoringUserRole = Role.builder(roleDescriptor, null).build();
assertThat(monitoringUserRole.cluster().check(MainAction.NAME, request), is(true));
assertThat(monitoringUserRole.cluster().check(XPackInfoAction.NAME, request), is(true));
assertThat(monitoringUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterStateAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(MainAction.NAME, request, authentication), is(true));
assertThat(monitoringUserRole.cluster().check(XPackInfoAction.NAME, request, authentication), is(true));
assertThat(monitoringUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
assertThat(monitoringUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(monitoringUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
assertThat(monitoringUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(monitoringUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -477,29 +482,31 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testRemoteMonitoringAgentRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("remote_monitoring_agent");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role remoteMonitoringAgentRole = Role.builder(roleDescriptor, null).build();
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(GetWatchAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(PutWatchAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(DeleteWatchAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ExecuteWatchAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(AckWatchAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ActivateWatchAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(WatcherServiceAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(GetWatchAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(PutWatchAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(DeleteWatchAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(AckWatchAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ActivateWatchAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(WatcherServiceAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
// we get this from the cluster:monitor privilege
assertThat(remoteMonitoringAgentRole.cluster().check(WatcherStatsAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(WatcherStatsAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -537,22 +544,23 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testRemoteMonitoringCollectorRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("remote_monitoring_collector");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role remoteMonitoringAgentRole = Role.builder(roleDescriptor, null).build();
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(DeleteIndexTemplateAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(remoteMonitoringAgentRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -636,20 +644,21 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testReportingUserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("reporting_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role reportingUserRole = Role.builder(roleDescriptor, null).build();
assertThat(reportingUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(ClusterStateAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
assertThat(reportingUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
assertThat(reportingUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
assertThat(reportingUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(reportingUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(reportingUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(reportingUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
assertThat(reportingUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(reportingUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -677,20 +686,21 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testKibanaDashboardOnlyUserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_dashboard_only_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role dashboardsOnlyUserRole = Role.builder(roleDescriptor, null).build();
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStateAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(dashboardsOnlyUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -712,19 +722,20 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testSuperuserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("superuser");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role superuserRole = Role.builder(roleDescriptor, null).build();
assertThat(superuserRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(PutUserAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(PutRoleAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check("internal:admin/foo", request), is(false));
assertThat(superuserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
assertThat(superuserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(true));
assertThat(superuserRole.cluster().check(PutUserAction.NAME, request, authentication), is(true));
assertThat(superuserRole.cluster().check(PutRoleAction.NAME, request, authentication), is(true));
assertThat(superuserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true));
assertThat(superuserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(true));
assertThat(superuserRole.cluster().check("internal:admin/foo", request, authentication), is(false));
final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build();
final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6,
@ -783,20 +794,21 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testLogstashSystemRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("logstash_system");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role logstashSystemRole = Role.builder(roleDescriptor, null).build();
assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true));
assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(logstashSystemRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true));
assertThat(logstashSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -810,6 +822,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testBeatsAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
final RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("beats_admin");
assertNotNull(roleDescriptor);
@ -817,14 +830,14 @@ public class ReservedRolesStoreTests extends ESTestCase {
final Role beatsAdminRole = Role.builder(roleDescriptor, null).build();
assertThat(beatsAdminRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterStateAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterStatsAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
assertThat(beatsAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(beatsAdminRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
assertThat(beatsAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(beatsAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -849,20 +862,21 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testBeatsSystemRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor(BeatsSystemUser.ROLE_NAME);
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role beatsSystemRole = Role.builder(roleDescriptor, null).build();
assertThat(beatsSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(beatsSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true));
assertThat(beatsSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true));
assertThat(beatsSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(beatsSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(beatsSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(beatsSystemRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(beatsSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true));
assertThat(beatsSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
assertThat(beatsSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
assertThat(beatsSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
assertThat(beatsSystemRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(beatsSystemRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(beatsSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(beatsSystemRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(beatsSystemRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true));
assertThat(beatsSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -883,20 +897,21 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testAPMSystemRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor(APMSystemUser.ROLE_NAME);
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role APMSystemRole = Role.builder(roleDescriptor, null).build();
assertThat(APMSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(APMSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true));
assertThat(APMSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true));
assertThat(APMSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(APMSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(APMSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(APMSystemRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(APMSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true));
assertThat(APMSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
assertThat(APMSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
assertThat(APMSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
assertThat(APMSystemRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(APMSystemRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(APMSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(APMSystemRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(APMSystemRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true));
assertThat(APMSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -910,6 +925,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testAPMUserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
final RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("apm_user");
assertNotNull(roleDescriptor);
@ -917,7 +933,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
assertNoAccessAllowed(role, "foo");
@ -928,63 +944,65 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testMachineLearningAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("machine_learning_admin");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(CloseJobAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteCalendarAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteFilterAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteForecastAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteJobAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request), is(true));
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request), is(false)); // internal use only
assertThat(role.cluster().check(FindFileStructureAction.NAME, request), is(true));
assertThat(role.cluster().check(FlushJobAction.NAME, request), is(true));
assertThat(role.cluster().check(ForecastJobAction.NAME, request), is(true));
assertThat(role.cluster().check(GetBucketsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetCalendarsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetCategoriesAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDatafeedsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetFiltersAction.NAME, request), is(true));
assertThat(role.cluster().check(GetInfluencersAction.NAME, request), is(true));
assertThat(role.cluster().check(GetJobsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetJobsStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetRecordsAction.NAME, request), is(true));
assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request), is(false)); // internal use only
assertThat(role.cluster().check(KillProcessAction.NAME, request), is(false)); // internal use only
assertThat(role.cluster().check(MlInfoAction.NAME, request), is(true));
assertThat(role.cluster().check(OpenJobAction.NAME, request), is(true));
assertThat(role.cluster().check(PersistJobAction.NAME, request), is(true));
assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request), is(true));
assertThat(role.cluster().check(PostDataAction.NAME, request), is(true));
assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(PutCalendarAction.NAME, request), is(true));
assertThat(role.cluster().check(PutDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(PutFilterAction.NAME, request), is(true));
assertThat(role.cluster().check(PutJobAction.NAME, request), is(true));
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request), is(true));
assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request), is(true));
assertThat(role.cluster().check(StartDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(StopDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request), is(true));
assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(UpdateFilterAction.NAME, request), is(true));
assertThat(role.cluster().check(UpdateJobAction.NAME, request), is(true));
assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request), is(true));
assertThat(role.cluster().check(UpdateProcessAction.NAME, request), is(false)); // internal use only
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request), is(true));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteFilterAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteForecastAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(FindFileStructureAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(FlushJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(ForecastJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetBucketsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCalendarsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCategoriesAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDatafeedsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetFiltersAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetInfluencersAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetJobsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetJobsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetRecordsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(KillProcessAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(MlInfoAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(OpenJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PersistJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PostDataAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutCalendarAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutFilterAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(StartDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(StopDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateFilterAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateProcessAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertNoAccessAllowed(role, "foo");
@ -1012,63 +1030,65 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testMachineLearningUserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("machine_learning_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(CloseJobAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteCalendarAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteFilterAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteForecastAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteJobAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request), is(false));
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request), is(false));
assertThat(role.cluster().check(FindFileStructureAction.NAME, request), is(true));
assertThat(role.cluster().check(FlushJobAction.NAME, request), is(false));
assertThat(role.cluster().check(ForecastJobAction.NAME, request), is(false));
assertThat(role.cluster().check(GetBucketsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetCalendarsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetCategoriesAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDatafeedsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetFiltersAction.NAME, request), is(false));
assertThat(role.cluster().check(GetInfluencersAction.NAME, request), is(true));
assertThat(role.cluster().check(GetJobsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetJobsStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetRecordsAction.NAME, request), is(true));
assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(KillProcessAction.NAME, request), is(false));
assertThat(role.cluster().check(MlInfoAction.NAME, request), is(true));
assertThat(role.cluster().check(OpenJobAction.NAME, request), is(false));
assertThat(role.cluster().check(PersistJobAction.NAME, request), is(false));
assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request), is(false));
assertThat(role.cluster().check(PostDataAction.NAME, request), is(false));
assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(PutCalendarAction.NAME, request), is(false));
assertThat(role.cluster().check(PutDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(PutFilterAction.NAME, request), is(false));
assertThat(role.cluster().check(PutJobAction.NAME, request), is(false));
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request), is(false));
assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request), is(false));
assertThat(role.cluster().check(StartDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(StopDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request), is(false));
assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(UpdateFilterAction.NAME, request), is(false));
assertThat(role.cluster().check(UpdateJobAction.NAME, request), is(false));
assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request), is(false));
assertThat(role.cluster().check(UpdateProcessAction.NAME, request), is(false));
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request), is(false));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request), is(false));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(DeleteFilterAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(DeleteForecastAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(DeleteJobAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(FindFileStructureAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(FlushJobAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(ForecastJobAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(GetBucketsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCalendarsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCategoriesAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDatafeedsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetFiltersAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(GetInfluencersAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetJobsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetJobsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetRecordsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(KillProcessAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(MlInfoAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(OpenJobAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(PersistJobAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(PostDataAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(PutCalendarAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(PutDatafeedAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(PutFilterAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(PutJobAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(StartDatafeedAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(StopDatafeedAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(UpdateFilterAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(UpdateJobAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(UpdateProcessAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertNoAccessAllowed(role, "foo");
@ -1097,20 +1117,22 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testDataFrameTransformsAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("data_frame_transforms_admin");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request), is(true));
assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request), is(true));
assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request), is(true));
assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertOnlyReadAllowed(role, ".data-frame-notifications-1");
@ -1134,20 +1156,22 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testDataFrameTransformsUserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("data_frame_transforms_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request), is(false));
assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request), is(false));
assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request), is(false));
assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request), is(false));
assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request), is(false));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertOnlyReadAllowed(role, ".data-frame-notifications-1");
@ -1171,21 +1195,23 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testWatcherAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("watcher_admin");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(PutWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(GetWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(ExecuteWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(AckWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(ActivateWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(WatcherServiceAction.NAME, request), is(true));
assertThat(role.cluster().check(WatcherStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.cluster().check(PutWatchAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetWatchAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteWatchAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(AckWatchAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(ActivateWatchAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(WatcherServiceAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(WatcherStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
@ -1201,21 +1227,23 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testWatcherUserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("watcher_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(PutWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(GetWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(ExecuteWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(AckWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(ActivateWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(WatcherServiceAction.NAME, request), is(false));
assertThat(role.cluster().check(WatcherStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.cluster().check(PutWatchAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(GetWatchAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteWatchAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(AckWatchAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(ActivateWatchAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(WatcherServiceAction.NAME, request, authentication), is(false));
assertThat(role.cluster().check(WatcherStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
@ -1274,17 +1302,18 @@ public class ReservedRolesStoreTests extends ESTestCase {
public void testLogstashAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("logstash_admin");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role logstashAdminRole = Role.builder(roleDescriptor, null).build();
assertThat(logstashAdminRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
assertThat(logstashAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false));
assertThat(logstashAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(logstashAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -1313,7 +1342,8 @@ public class ReservedRolesStoreTests extends ESTestCase {
Role codeAdminRole = Role.builder(roleDescriptor, null).build();
assertThat(codeAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, mock(TransportRequest.class)), is(false));
assertThat(codeAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, mock(TransportRequest.class),
mock(Authentication.class)), is(false));
assertThat(codeAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
assertThat(codeAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false));
@ -1340,7 +1370,8 @@ public class ReservedRolesStoreTests extends ESTestCase {
Role codeUserRole = Role.builder(roleDescriptor, null).build();
assertThat(codeUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, mock(TransportRequest.class)), is(false));
assertThat(codeUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, mock(TransportRequest.class),
mock(Authentication.class)), is(false));
assertThat(codeUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test("foo"), is(false));
assertThat(codeUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(".reporting"), is(false));

View File

@ -9,38 +9,51 @@ package org.elasticsearch.xpack.security.action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.SecurityContext;
import org.elasticsearch.xpack.core.security.action.GetApiKeyAction;
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.security.authc.ApiKeyService;
public final class TransportGetApiKeyAction extends HandledTransportAction<GetApiKeyRequest,GetApiKeyResponse> {
private final ApiKeyService apiKeyService;
private final SecurityContext securityContext;
@Inject
public TransportGetApiKeyAction(TransportService transportService, ActionFilters actionFilters, ApiKeyService apiKeyService) {
public TransportGetApiKeyAction(TransportService transportService, ActionFilters actionFilters, ApiKeyService apiKeyService,
SecurityContext context) {
super(GetApiKeyAction.NAME, transportService, actionFilters,
(Writeable.Reader<GetApiKeyRequest>) GetApiKeyRequest::new);
this.apiKeyService = apiKeyService;
this.securityContext = context;
}
@Override
protected void doExecute(Task task, GetApiKeyRequest request, ActionListener<GetApiKeyResponse> listener) {
if (Strings.hasText(request.getRealmName()) || Strings.hasText(request.getUserName())) {
apiKeyService.getApiKeysForRealmAndUser(request.getRealmName(), request.getUserName(), listener);
} else if (Strings.hasText(request.getApiKeyId())) {
apiKeyService.getApiKeyForApiKeyId(request.getApiKeyId(), listener);
} else if (Strings.hasText(request.getApiKeyName())) {
apiKeyService.getApiKeyForApiKeyName(request.getApiKeyName(), listener);
} else {
listener.onFailure(new IllegalArgumentException("One of [api key id, api key name, username, realm name] must be specified"));
String apiKeyId = request.getApiKeyId();
String apiKeyName = request.getApiKeyName();
String username = request.getUserName();
String realm = request.getRealmName();
final Authentication authentication = securityContext.getAuthentication();
if (authentication == null) {
listener.onFailure(new IllegalStateException("authentication is required"));
}
if (request.ownedByAuthenticatedUser()) {
assert username == null;
assert realm == null;
// restrict username and realm to current authenticated user.
username = authentication.getUser().principal();
realm = ApiKeyService.getCreatorRealmName(authentication);
}
apiKeyService.getApiKeys(realm, username, apiKeyName, apiKeyId, listener);
}
}

View File

@ -9,36 +9,51 @@ package org.elasticsearch.xpack.security.action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.SecurityContext;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyAction;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.security.authc.ApiKeyService;
public final class TransportInvalidateApiKeyAction extends HandledTransportAction<InvalidateApiKeyRequest, InvalidateApiKeyResponse> {
private final ApiKeyService apiKeyService;
private final SecurityContext securityContext;
@Inject
public TransportInvalidateApiKeyAction(TransportService transportService, ActionFilters actionFilters, ApiKeyService apiKeyService) {
public TransportInvalidateApiKeyAction(TransportService transportService, ActionFilters actionFilters, ApiKeyService apiKeyService,
SecurityContext context) {
super(InvalidateApiKeyAction.NAME, transportService, actionFilters,
(Writeable.Reader<InvalidateApiKeyRequest>) InvalidateApiKeyRequest::new);
(Writeable.Reader<InvalidateApiKeyRequest>) InvalidateApiKeyRequest::new);
this.apiKeyService = apiKeyService;
this.securityContext = context;
}
@Override
protected void doExecute(Task task, InvalidateApiKeyRequest request, ActionListener<InvalidateApiKeyResponse> listener) {
if (Strings.hasText(request.getRealmName()) || Strings.hasText(request.getUserName())) {
apiKeyService.invalidateApiKeysForRealmAndUser(request.getRealmName(), request.getUserName(), listener);
} else if (Strings.hasText(request.getId())) {
apiKeyService.invalidateApiKeyForApiKeyId(request.getId(), listener);
} else {
apiKeyService.invalidateApiKeyForApiKeyName(request.getName(), listener);
String apiKeyId = request.getId();
String apiKeyName = request.getName();
String username = request.getUserName();
String realm = request.getRealmName();
final Authentication authentication = securityContext.getAuthentication();
if (authentication == null) {
listener.onFailure(new IllegalStateException("authentication is required"));
}
if (request.ownedByAuthenticatedUser()) {
assert username == null;
assert realm == null;
// restrict username and realm to current authenticated user.
username = authentication.getUser().principal();
realm = ApiKeyService.getCreatorRealmName(authentication);
}
apiKeyService.invalidateApiKeys(realm, username, apiKeyName, apiKeyId, listener);
}
}

View File

@ -71,7 +71,6 @@ import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
import javax.crypto.SecretKeyFactory;
import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
@ -95,6 +94,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.crypto.SecretKeyFactory;
import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME;
import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING;
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
@ -105,10 +106,14 @@ public class ApiKeyService {
private static final Logger logger = LogManager.getLogger(ApiKeyService.class);
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
static final String API_KEY_ID_KEY = "_security_api_key_id";
public static final String API_KEY_ID_KEY = "_security_api_key_id";
public static final String API_KEY_REALM_NAME = "_es_api_key";
public static final String API_KEY_REALM_TYPE = "_es_api_key";
public static final String API_KEY_CREATOR_REALM = "_security_api_key_creator_realm";
static final String API_KEY_ROLE_DESCRIPTORS_KEY = "_security_api_key_role_descriptors";
static final String API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY = "_security_api_key_limited_by_role_descriptors";
public static final Setting<String> PASSWORD_HASHING_ALGORITHM = new Setting<>(
"xpack.security.authc.api_key.hashing.algorithm", "pbkdf2", Function.identity(), v -> {
if (Hasher.getAvailableAlgoStoredHash().contains(v.toLowerCase(Locale.ROOT)) == false) {
@ -523,6 +528,7 @@ public class ApiKeyService {
: limitedByRoleDescriptors.keySet().toArray(Strings.EMPTY_ARRAY);
final User apiKeyUser = new User(principal, roleNames, null, null, metadata, true);
final Map<String, Object> authResultMetadata = new HashMap<>();
authResultMetadata.put(API_KEY_CREATOR_REALM, creator.get("realm"));
authResultMetadata.put(API_KEY_ROLE_DESCRIPTORS_KEY, roleDescriptors);
authResultMetadata.put(API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY, limitedByRoleDescriptors);
authResultMetadata.put(API_KEY_ID_KEY, credentials.getId());
@ -645,26 +651,34 @@ public class ApiKeyService {
}
/**
* Invalidate API keys for given realm and user name.
* Invalidate API keys for given realm, user name, API key name and id.
* @param realmName realm name
* @param userName user name
* @param username user name
* @param apiKeyName API key name
* @param apiKeyId API key id
* @param invalidateListener listener for {@link InvalidateApiKeyResponse}
*/
public void invalidateApiKeysForRealmAndUser(String realmName, String userName,
ActionListener<InvalidateApiKeyResponse> invalidateListener) {
public void invalidateApiKeys(String realmName, String username, String apiKeyName, String apiKeyId,
ActionListener<InvalidateApiKeyResponse> invalidateListener) {
ensureEnabled();
if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false) {
logger.trace("No realm name or username provided");
invalidateListener.onFailure(new IllegalArgumentException("realm name or username must be provided"));
if (Strings.hasText(realmName) == false && Strings.hasText(username) == false && Strings.hasText(apiKeyName) == false
&& Strings.hasText(apiKeyId) == false) {
logger.trace("none of the parameters [api key id, api key name, username, realm name] were specified for invalidation");
invalidateListener
.onFailure(new IllegalArgumentException("One of [api key id, api key name, username, realm name] must be specified"));
} else {
findApiKeysForUserAndRealm(userName, realmName, true, false, ActionListener.wrap(apiKeyIds -> {
if (apiKeyIds.isEmpty()) {
logger.warn("No active api keys to invalidate for realm [{}] and username [{}]", realmName, userName);
invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse());
} else {
invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), invalidateListener);
}
}, invalidateListener::onFailure));
findApiKeysForUserRealmApiKeyIdAndNameCombination(realmName, username, apiKeyName, apiKeyId, true, false,
ActionListener.wrap(apiKeys -> {
if (apiKeys.isEmpty()) {
logger.debug(
"No active api keys to invalidate for realm [{}], username [{}], api key name [{}] and api key id [{}]",
realmName, username, apiKeyName, apiKeyId);
invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse());
} else {
invalidateAllApiKeys(apiKeys.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()),
invalidateListener);
}
}, invalidateListener::onFailure));
}
}
@ -672,71 +686,6 @@ public class ApiKeyService {
indexInvalidation(apiKeyIds, invalidateListener, null);
}
/**
* Invalidate API key for given API key id
* @param apiKeyId API key id
* @param invalidateListener listener for {@link InvalidateApiKeyResponse}
*/
public void invalidateApiKeyForApiKeyId(String apiKeyId, ActionListener<InvalidateApiKeyResponse> invalidateListener) {
ensureEnabled();
if (Strings.hasText(apiKeyId) == false) {
logger.trace("No api key id provided");
invalidateListener.onFailure(new IllegalArgumentException("api key id must be provided"));
} else {
findApiKeysForApiKeyId(apiKeyId, true, false, ActionListener.wrap(apiKeyIds -> {
if (apiKeyIds.isEmpty()) {
logger.warn("No api key to invalidate for api key id [{}]", apiKeyId);
invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse());
} else {
invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), invalidateListener);
}
}, invalidateListener::onFailure));
}
}
/**
* Invalidate API key for given API key name
* @param apiKeyName API key name
* @param invalidateListener listener for {@link InvalidateApiKeyResponse}
*/
public void invalidateApiKeyForApiKeyName(String apiKeyName, ActionListener<InvalidateApiKeyResponse> invalidateListener) {
ensureEnabled();
if (Strings.hasText(apiKeyName) == false) {
logger.trace("No api key name provided");
invalidateListener.onFailure(new IllegalArgumentException("api key name must be provided"));
} else {
findApiKeyForApiKeyName(apiKeyName, true, false, ActionListener.wrap(apiKeyIds -> {
if (apiKeyIds.isEmpty()) {
logger.warn("No api key to invalidate for api key name [{}]", apiKeyName);
invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse());
} else {
invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), invalidateListener);
}
}, invalidateListener::onFailure));
}
}
private void findApiKeysForUserAndRealm(String userName, String realmName, boolean filterOutInvalidatedKeys,
boolean filterOutExpiredKeys, ActionListener<Collection<ApiKey>> listener) {
final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze();
if (frozenSecurityIndex.indexExists() == false) {
listener.onResponse(Collections.emptyList());
} else if (frozenSecurityIndex.isAvailable() == false) {
listener.onFailure(frozenSecurityIndex.getUnavailableReason());
} else {
final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("doc_type", "api_key"));
if (Strings.hasText(userName)) {
boolQuery.filter(QueryBuilders.termQuery("creator.principal", userName));
}
if (Strings.hasText(realmName)) {
boolQuery.filter(QueryBuilders.termQuery("creator.realm", realmName));
}
findApiKeys(boolQuery, filterOutInvalidatedKeys, filterOutExpiredKeys, listener);
}
}
private void findApiKeys(final BoolQueryBuilder boolQuery, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys,
ActionListener<Collection<ApiKey>> listener) {
if (filterOutInvalidatedKeys) {
@ -773,35 +722,28 @@ public class ApiKeyService {
}
}
private void findApiKeyForApiKeyName(String apiKeyName, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys,
ActionListener<Collection<ApiKey>> listener) {
private void findApiKeysForUserRealmApiKeyIdAndNameCombination(String realmName, String userName, String apiKeyName, String apiKeyId,
boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys,
ActionListener<Collection<ApiKey>> listener) {
final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze();
if (frozenSecurityIndex.indexExists() == false) {
listener.onResponse(Collections.emptyList());
} else if (frozenSecurityIndex.isAvailable() == false) {
listener.onFailure(frozenSecurityIndex.getUnavailableReason());
} else {
final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("doc_type", "api_key"));
final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("doc_type", "api_key"));
if (Strings.hasText(realmName)) {
boolQuery.filter(QueryBuilders.termQuery("creator.realm", realmName));
}
if (Strings.hasText(userName)) {
boolQuery.filter(QueryBuilders.termQuery("creator.principal", userName));
}
if (Strings.hasText(apiKeyName)) {
boolQuery.filter(QueryBuilders.termQuery("name", apiKeyName));
}
findApiKeys(boolQuery, filterOutInvalidatedKeys, filterOutExpiredKeys, listener);
}
}
private void findApiKeysForApiKeyId(String apiKeyId, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys,
ActionListener<Collection<ApiKey>> listener) {
final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze();
if (frozenSecurityIndex.indexExists() == false) {
listener.onResponse(Collections.emptyList());
} else if (frozenSecurityIndex.isAvailable() == false) {
listener.onFailure(frozenSecurityIndex.getUnavailableReason());
} else {
final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("doc_type", "api_key"))
.filter(QueryBuilders.termQuery("_id", apiKeyId));
if (Strings.hasText(apiKeyId)) {
boolQuery.filter(QueryBuilders.termQuery("_id", apiKeyId));
}
findApiKeys(boolQuery, filterOutInvalidatedKeys, filterOutExpiredKeys, listener);
}
@ -824,9 +766,9 @@ public class ApiKeyService {
BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
for (String apiKeyId : apiKeyIds) {
UpdateRequest request = client
.prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, apiKeyId)
.setDoc(Collections.singletonMap("api_key_invalidated", true))
.request();
.prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, apiKeyId)
.setDoc(Collections.singletonMap("api_key_invalidated", true))
.request();
bulkRequestBuilder.add(request);
}
bulkRequestBuilder.setRefreshPolicy(RefreshPolicy.WAIT_UNTIL);
@ -930,42 +872,26 @@ public class ApiKeyService {
}
/**
* Get API keys for given realm and user name.
* Get API key information for given realm, user, API key name and id combination
* @param realmName realm name
* @param userName user name
* @param listener listener for {@link GetApiKeyResponse}
*/
public void getApiKeysForRealmAndUser(String realmName, String userName, ActionListener<GetApiKeyResponse> listener) {
ensureEnabled();
if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false) {
logger.trace("No realm name or username provided");
listener.onFailure(new IllegalArgumentException("realm name or username must be provided"));
} else {
findApiKeysForUserAndRealm(userName, realmName, false, false, ActionListener.wrap(apiKeyInfos -> {
if (apiKeyInfos.isEmpty()) {
logger.warn("No active api keys found for realm [{}] and username [{}]", realmName, userName);
listener.onResponse(GetApiKeyResponse.emptyResponse());
} else {
listener.onResponse(new GetApiKeyResponse(apiKeyInfos));
}
}, listener::onFailure));
}
}
/**
* Get API key for given API key id
* @param username user name
* @param apiKeyName API key name
* @param apiKeyId API key id
* @param listener listener for {@link GetApiKeyResponse}
*/
public void getApiKeyForApiKeyId(String apiKeyId, ActionListener<GetApiKeyResponse> listener) {
public void getApiKeys(String realmName, String username, String apiKeyName, String apiKeyId,
ActionListener<GetApiKeyResponse> listener) {
ensureEnabled();
if (Strings.hasText(apiKeyId) == false) {
logger.trace("No api key id provided");
listener.onFailure(new IllegalArgumentException("api key id must be provided"));
if (Strings.hasText(realmName) == false && Strings.hasText(username) == false && Strings.hasText(apiKeyName) == false
&& Strings.hasText(apiKeyId) == false) {
logger.trace("none of the parameters [api key id, api key name, username, realm name] were specified for retrieval");
listener.onFailure(new IllegalArgumentException("One of [api key id, api key name, username, realm name] must be specified"));
} else {
findApiKeysForApiKeyId(apiKeyId, false, false, ActionListener.wrap(apiKeyInfos -> {
findApiKeysForUserRealmApiKeyIdAndNameCombination(realmName, username, apiKeyName, apiKeyId, false, false,
ActionListener.wrap(apiKeyInfos -> {
if (apiKeyInfos.isEmpty()) {
logger.warn("No api key found for api key id [{}]", apiKeyId);
logger.debug("No active api keys found for realm [{}], user [{}], api key name [{}] and api key id [{}]",
realmName, username, apiKeyName, apiKeyId);
listener.onResponse(GetApiKeyResponse.emptyResponse());
} else {
listener.onResponse(new GetApiKeyResponse(apiKeyInfos));
@ -975,24 +901,17 @@ public class ApiKeyService {
}
/**
* Get API key for given API key name
* @param apiKeyName API key name
* @param listener listener for {@link GetApiKeyResponse}
* Returns realm name for the authenticated user.
* If the user is authenticated by realm type {@value API_KEY_REALM_TYPE}
* then it will return the realm name of user who created this API key.
* @param authentication {@link Authentication}
* @return realm name
*/
public void getApiKeyForApiKeyName(String apiKeyName, ActionListener<GetApiKeyResponse> listener) {
ensureEnabled();
if (Strings.hasText(apiKeyName) == false) {
logger.trace("No api key name provided");
listener.onFailure(new IllegalArgumentException("api key name must be provided"));
public static String getCreatorRealmName(final Authentication authentication) {
if (authentication.getAuthenticatedBy().getType().equals(API_KEY_REALM_TYPE)) {
return (String) authentication.getMetadata().get(API_KEY_CREATOR_REALM);
} else {
findApiKeyForApiKeyName(apiKeyName, false, false, ActionListener.wrap(apiKeyInfos -> {
if (apiKeyInfos.isEmpty()) {
logger.warn("No api key found for api key name [{}]", apiKeyName);
listener.onResponse(GetApiKeyResponse.emptyResponse());
} else {
listener.onResponse(new GetApiKeyResponse(apiKeyInfos));
}
}, listener::onFailure));
return authentication.getAuthenticatedBy().getName();
}
}

View File

@ -269,7 +269,7 @@ public class AuthenticationService {
apiKeyService.authenticateWithApiKeyIfPresent(threadContext, ActionListener.wrap(authResult -> {
if (authResult.isAuthenticated()) {
final User user = authResult.getUser();
authenticatedBy = new RealmRef("_es_api_key", "_es_api_key", nodeName);
authenticatedBy = new RealmRef(ApiKeyService.API_KEY_REALM_NAME, ApiKeyService.API_KEY_REALM_TYPE, nodeName);
writeAuthToContext(new Authentication(user, authenticatedBy, null, Version.CURRENT,
Authentication.AuthenticationType.API_KEY, authResult.getMetadata()));
} else if (authResult.getStatus() == AuthenticationResult.Status.TERMINATE) {

View File

@ -64,6 +64,7 @@ import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.security.audit.AuditLevel;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.audit.AuditUtil;
import org.elasticsearch.xpack.security.authc.ApiKeyService;
import org.elasticsearch.xpack.security.authz.interceptor.RequestInterceptor;
import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
@ -572,6 +573,14 @@ public class AuthorizationService {
return authorizationError("action [{}] is unauthorized for user [{}] run as [{}]", cause, action, authUser.principal(),
authentication.getUser().principal());
}
// check for authentication by API key
if (authentication.getAuthenticatedBy().getType().equals(ApiKeyService.API_KEY_REALM_TYPE)) {
final String apiKeyId = (String) authentication.getMetadata().get(ApiKeyService.API_KEY_ID_KEY);
assert apiKeyId != null : "api key id must be present in the metadata";
logger.debug("action [{}] is unauthorized for API key id [{}] of user [{}]", action, apiKeyId, authUser.principal());
return authorizationError("action [{}] is unauthorized for API key id [{}] of user [{}]", cause, action, apiKeyId,
authUser.principal());
}
logger.debug("action [{}] is unauthorized for user [{}]", action, authUser.principal());
return authorizationError("action [{}] is unauthorized for user [{}]", cause, action, authUser.principal());
}

View File

@ -32,6 +32,8 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.transport.TransportActionProxy;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.action.GetApiKeyAction;
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction;
import org.elasticsearch.xpack.core.security.action.user.ChangePasswordAction;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction;
@ -62,6 +64,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.NamedClusterPrivile
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
import org.elasticsearch.xpack.core.security.support.Automatons;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.ApiKeyService;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
@ -86,7 +89,7 @@ import static org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail.P
public class RBACEngine implements AuthorizationEngine {
private static final Predicate<String> SAME_USER_PRIVILEGE = Automatons.predicate(
ChangePasswordAction.NAME, AuthenticateAction.NAME, HasPrivilegesAction.NAME, GetUserPrivilegesAction.NAME);
ChangePasswordAction.NAME, AuthenticateAction.NAME, HasPrivilegesAction.NAME, GetUserPrivilegesAction.NAME, GetApiKeyAction.NAME);
private static final String INDEX_SUB_REQUEST_PRIMARY = IndexAction.NAME + "[p]";
private static final String INDEX_SUB_REQUEST_REPLICA = IndexAction.NAME + "[r]";
private static final String DELETE_SUB_REQUEST_PRIMARY = DeleteAction.NAME + "[p]";
@ -137,7 +140,7 @@ public class RBACEngine implements AuthorizationEngine {
ActionListener<AuthorizationResult> listener) {
if (authorizationInfo instanceof RBACAuthorizationInfo) {
final Role role = ((RBACAuthorizationInfo) authorizationInfo).getRole();
if (role.checkClusterAction(requestInfo.getAction(), requestInfo.getRequest())) {
if (role.checkClusterAction(requestInfo.getAction(), requestInfo.getRequest(), requestInfo.getAuthentication())) {
listener.onResponse(AuthorizationResult.granted());
} else if (checkSameUserPermissions(requestInfo.getAction(), requestInfo.getRequest(), requestInfo.getAuthentication())) {
listener.onResponse(AuthorizationResult.granted());
@ -154,26 +157,39 @@ public class RBACEngine implements AuthorizationEngine {
boolean checkSameUserPermissions(String action, TransportRequest request, Authentication authentication) {
final boolean actionAllowed = SAME_USER_PRIVILEGE.test(action);
if (actionAllowed) {
if (request instanceof UserRequest == false) {
assert false : "right now only a user request should be allowed";
return false;
}
UserRequest userRequest = (UserRequest) request;
String[] usernames = userRequest.usernames();
if (usernames == null || usernames.length != 1 || usernames[0] == null) {
assert false : "this role should only be used for actions to apply to a single user";
return false;
}
final String username = usernames[0];
final boolean sameUsername = authentication.getUser().principal().equals(username);
if (sameUsername && ChangePasswordAction.NAME.equals(action)) {
return checkChangePasswordAction(authentication);
}
if (request instanceof UserRequest) {
UserRequest userRequest = (UserRequest) request;
String[] usernames = userRequest.usernames();
if (usernames == null || usernames.length != 1 || usernames[0] == null) {
assert false : "this role should only be used for actions to apply to a single user";
return false;
}
final String username = usernames[0];
final boolean sameUsername = authentication.getUser().principal().equals(username);
if (sameUsername && ChangePasswordAction.NAME.equals(action)) {
return checkChangePasswordAction(authentication);
}
assert AuthenticateAction.NAME.equals(action) || HasPrivilegesAction.NAME.equals(action)
|| GetUserPrivilegesAction.NAME.equals(action) || sameUsername == false
: "Action '" + action + "' should not be possible when sameUsername=" + sameUsername;
return sameUsername;
assert AuthenticateAction.NAME.equals(action) || HasPrivilegesAction.NAME.equals(action)
|| GetUserPrivilegesAction.NAME.equals(action) || sameUsername == false
: "Action '" + action + "' should not be possible when sameUsername=" + sameUsername;
return sameUsername;
} else if (request instanceof GetApiKeyRequest) {
GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request;
if (authentication.getAuthenticatedBy().getType().equals(ApiKeyService.API_KEY_REALM_TYPE)) {
assert authentication.getLookedUpBy() == null : "runAs not supported for api key authentication";
// if authenticated by API key then the request must also contain same API key id
String authenticatedApiKeyId = (String) authentication.getMetadata().get(ApiKeyService.API_KEY_ID_KEY);
if (Strings.hasText(getApiKeyRequest.getApiKeyId())) {
return getApiKeyRequest.getApiKeyId().equals(authenticatedApiKeyId);
} else {
return false;
}
}
} else {
assert false : "right now only a user request or get api key request should be allowed";
return false;
}
}
return false;
}

View File

@ -39,7 +39,8 @@ public final class RestGetApiKeyAction extends ApiKeyBaseRestHandler {
final String apiKeyName = request.param("name");
final String userName = request.param("username");
final String realmName = request.param("realm_name");
final GetApiKeyRequest getApiKeyRequest = new GetApiKeyRequest(realmName, userName, apiKeyId, apiKeyName);
final boolean myApiKeysOnly = request.paramAsBoolean("owner", false);
final GetApiKeyRequest getApiKeyRequest = new GetApiKeyRequest(realmName, userName, apiKeyId, apiKeyName, myApiKeysOnly);
return channel -> client.execute(GetApiKeyAction.INSTANCE, getApiKeyRequest,
new RestBuilderListener<GetApiKeyResponse>(channel) {
@Override

View File

@ -31,7 +31,8 @@ import java.io.IOException;
public final class RestInvalidateApiKeyAction extends ApiKeyBaseRestHandler {
static final ConstructingObjectParser<InvalidateApiKeyRequest, Void> PARSER = new ConstructingObjectParser<>("invalidate_api_key",
a -> {
return new InvalidateApiKeyRequest((String) a[0], (String) a[1], (String) a[2], (String) a[3]);
return new InvalidateApiKeyRequest((String) a[0], (String) a[1], (String) a[2], (String) a[3], (a[4] == null) ? false :
(Boolean) a[4]);
});
static {
@ -39,6 +40,7 @@ public final class RestInvalidateApiKeyAction extends ApiKeyBaseRestHandler {
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("username"));
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("id"));
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("name"));
PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), new ParseField("owner"));
}
public RestInvalidateApiKeyAction(Settings settings, RestController controller, XPackLicenseState licenseState) {

View File

@ -7,7 +7,6 @@
package org.elasticsearch.xpack.security.authc;
import com.google.common.collect.Sets;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
@ -87,6 +86,31 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
deleteSecurityIndex();
}
@Override
public String configRoles() {
return super.configRoles() + "\n" +
"manage_api_key_role:\n" +
" cluster: [\"manage_api_key\"]\n" +
"manage_own_api_key_role:\n" +
" cluster: [\"manage_own_api_key\"]\n";
}
@Override
public String configUsers() {
final String usersPasswdHashed = new String(
getFastStoredHashAlgoForTests().hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
return super.configUsers() +
"user_with_manage_api_key_role:" + usersPasswdHashed + "\n" +
"user_with_manage_own_api_key_role:" + usersPasswdHashed + "\n";
}
@Override
public String configUsersRoles() {
return super.configUsersRoles() +
"manage_api_key_role:user_with_manage_api_key_role\n" +
"manage_own_api_key_role:user_with_manage_own_api_key_role\n";
}
private void awaitApiKeysRemoverCompletion() throws InterruptedException {
for (ApiKeyService apiKeyService : internalCluster().getInstances(ApiKeyService.class)) {
final boolean done = awaitBusy(() -> apiKeyService.isExpirationInProgress() == false);
@ -173,7 +197,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
// Now invalidate the API key
PlainActionFuture<InvalidateApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyName(keyName), listener);
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyName(keyName, false), listener);
InvalidateApiKeyResponse invalidateResponse = listener.get();
verifyInvalidateResponse(1, responses, invalidateResponse);
@ -227,7 +251,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
SecurityClient securityClient = new SecurityClient(client);
PlainActionFuture<InvalidateApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId()), listener);
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId(), false), listener);
InvalidateApiKeyResponse invalidateResponse = listener.get();
verifyInvalidateResponse(1, responses, invalidateResponse);
}
@ -238,7 +262,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
SecurityClient securityClient = new SecurityClient(client);
PlainActionFuture<InvalidateApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyName(responses.get(0).getName()), listener);
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyName(responses.get(0).getName(), false), listener);
InvalidateApiKeyResponse invalidateResponse = listener.get();
verifyInvalidateResponse(1, responses, invalidateResponse);
}
@ -262,7 +286,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
SecurityClient securityClient = new SecurityClient(client);
PlainActionFuture<InvalidateApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(0).getId()), listener);
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(0).getId(), false), listener);
InvalidateApiKeyResponse invalidateResponse = listener.get();
assertThat(invalidateResponse.getInvalidatedApiKeys().size(), equalTo(1));
assertThat(invalidateResponse.getPreviouslyInvalidatedApiKeys().size(), equalTo(0));
@ -279,7 +303,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
// invalidate API key to trigger remover
listener = new PlainActionFuture<>();
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(1).getId()), listener);
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(1).getId(), false), listener);
assertThat(listener.get().getInvalidatedApiKeys().size(), is(1));
awaitApiKeysRemoverCompletion();
@ -354,7 +378,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
// Invalidate to trigger the remover
PlainActionFuture<InvalidateApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(2).getId()), listener);
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(2).getId(), false), listener);
assertThat(listener.get().getInvalidatedApiKeys().size(), is(1));
awaitApiKeysRemoverCompletion();
@ -403,7 +427,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
SecurityClient securityClient = new SecurityClient(client);
PlainActionFuture<InvalidateApiKeyResponse> listener = new PlainActionFuture<>();
// trigger expired keys remover
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(responses.get(1).getId()), listener);
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(responses.get(1).getId(), false), listener);
InvalidateApiKeyResponse invalidateResponse = listener.get();
assertThat(invalidateResponse.getInvalidatedApiKeys().size(), equalTo(1));
assertThat(invalidateResponse.getPreviouslyInvalidatedApiKeys().size(), equalTo(0));
@ -427,7 +451,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
Set<String> expectedValidKeyIds = null;
if (invalidate) {
PlainActionFuture<InvalidateApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId()), listener);
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId(), false), listener);
InvalidateApiKeyResponse invalidateResponse = listener.get();
invalidatedApiKeyIds = invalidateResponse.getInvalidatedApiKeys();
expectedValidKeyIds = responses.stream().filter(o -> !o.getId().equals(responses.get(0).getId())).map(o -> o.getId())
@ -475,7 +499,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
SecurityClient securityClient = new SecurityClient(client);
PlainActionFuture<GetApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(responses.get(0).getId()), listener);
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(responses.get(0).getId(), false), listener);
GetApiKeyResponse response = listener.get();
verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null);
}
@ -486,15 +510,116 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
SecurityClient securityClient = new SecurityClient(client);
PlainActionFuture<GetApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName(responses.get(0).getName()), listener);
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName(responses.get(0).getName(), false), listener);
GetApiKeyResponse response = listener.get();
verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null);
}
private void verifyGetResponse(int noOfApiKeys, List<CreateApiKeyResponse> responses, GetApiKeyResponse response,
Set<String> validApiKeyIds,
List<String> invalidatedApiKeyIds) {
assertThat(response.getApiKeyInfos().length, equalTo(noOfApiKeys));
public void testGetApiKeysOwnedByCurrentAuthenticatedUser() throws InterruptedException, ExecutionException {
int noOfSuperuserApiKeys = randomIntBetween(3, 5);
int noOfApiKeysForUserWithManageApiKeyRole = randomIntBetween(3, 5);
List<CreateApiKeyResponse> defaultUserCreatedKeys = createApiKeys(noOfSuperuserApiKeys, null);
String userWithManageApiKeyRole = randomFrom("user_with_manage_api_key_role", "user_with_manage_own_api_key_role");
List<CreateApiKeyResponse> userWithManageApiKeyRoleApiKeys = createApiKeys(userWithManageApiKeyRole,
noOfApiKeysForUserWithManageApiKeyRole, null, "monitor");
final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
.basicAuthHeaderValue(userWithManageApiKeyRole, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
SecurityClient securityClient = new SecurityClient(client);
PlainActionFuture<GetApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.getApiKey(GetApiKeyRequest.forOwnedApiKeys(), listener);
GetApiKeyResponse response = listener.get();
verifyGetResponse(userWithManageApiKeyRole, noOfApiKeysForUserWithManageApiKeyRole, userWithManageApiKeyRoleApiKeys,
response, userWithManageApiKeyRoleApiKeys.stream().map(o -> o.getId()).collect(Collectors.toSet()), null);
}
public void testInvalidateApiKeysOwnedByCurrentAuthenticatedUser() throws InterruptedException, ExecutionException {
int noOfSuperuserApiKeys = randomIntBetween(3, 5);
int noOfApiKeysForUserWithManageApiKeyRole = randomIntBetween(3, 5);
List<CreateApiKeyResponse> defaultUserCreatedKeys = createApiKeys(noOfSuperuserApiKeys, null);
String userWithManageApiKeyRole = randomFrom("user_with_manage_api_key_role", "user_with_manage_own_api_key_role");
List<CreateApiKeyResponse> userWithManageApiKeyRoleApiKeys = createApiKeys(userWithManageApiKeyRole,
noOfApiKeysForUserWithManageApiKeyRole, null, "monitor");
final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
.basicAuthHeaderValue(userWithManageApiKeyRole, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
SecurityClient securityClient = new SecurityClient(client);
PlainActionFuture<InvalidateApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.invalidateApiKey(InvalidateApiKeyRequest.forOwnedApiKeys(), listener);
InvalidateApiKeyResponse invalidateResponse = listener.get();
verifyInvalidateResponse(noOfApiKeysForUserWithManageApiKeyRole, userWithManageApiKeyRoleApiKeys, invalidateResponse);
}
public void testApiKeyAuthorizationApiKeyMustBeAbleToRetrieveItsOwnInformationButNotAnyOtherKeysCreatedBySameOwner()
throws InterruptedException, ExecutionException {
List<CreateApiKeyResponse> responses = createApiKeys(SecuritySettingsSource.TEST_SUPERUSER,2, null, (String[]) null);
final String base64ApiKeyKeyValue = Base64.getEncoder().encodeToString(
(responses.get(0).getId() + ":" + responses.get(0).getKey().toString()).getBytes(StandardCharsets.UTF_8));
Client client = client().filterWithHeader(Collections.singletonMap("Authorization", "ApiKey " + base64ApiKeyKeyValue));
SecurityClient securityClient = new SecurityClient(client);
PlainActionFuture<GetApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(responses.get(0).getId(), randomBoolean()), listener);
GetApiKeyResponse response = listener.get();
verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null);
final PlainActionFuture<GetApiKeyResponse> failureListener = new PlainActionFuture<>();
// for any other API key id, it must deny access
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(responses.get(1).getId(), randomBoolean()),
failureListener);
ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener.actionGet());
assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", SecuritySettingsSource.TEST_SUPERUSER,
responses.get(0).getId());
final PlainActionFuture<GetApiKeyResponse> failureListener1 = new PlainActionFuture<>();
securityClient.getApiKey(GetApiKeyRequest.forOwnedApiKeys(), failureListener1);
ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener1.actionGet());
assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", SecuritySettingsSource.TEST_SUPERUSER,
responses.get(0).getId());
}
public void testApiKeyWithManageOwnPrivilegeIsAbleToInvalidateItselfButNotAnyOtherKeysCreatedBySameOwner()
throws InterruptedException, ExecutionException {
List<CreateApiKeyResponse> responses = createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, 2, null, "manage_own_api_key");
final String base64ApiKeyKeyValue = Base64.getEncoder().encodeToString(
(responses.get(0).getId() + ":" + responses.get(0).getKey().toString()).getBytes(StandardCharsets.UTF_8));
Client client = client().filterWithHeader(Collections.singletonMap("Authorization", "ApiKey " + base64ApiKeyKeyValue));
SecurityClient securityClient = new SecurityClient(client);
final PlainActionFuture<InvalidateApiKeyResponse> failureListener = new PlainActionFuture<>();
// for any other API key id, it must deny access
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(responses.get(1).getId(), randomBoolean()),
failureListener);
ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener.actionGet());
assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", SecuritySettingsSource.TEST_SUPERUSER,
responses.get(0).getId());
final PlainActionFuture<InvalidateApiKeyResponse> failureListener1 = new PlainActionFuture<>();
securityClient.invalidateApiKey(InvalidateApiKeyRequest.forOwnedApiKeys(), failureListener1);
ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener1.actionGet());
assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", SecuritySettingsSource.TEST_SUPERUSER,
responses.get(0).getId());
PlainActionFuture<InvalidateApiKeyResponse> listener = new PlainActionFuture<>();
securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId(), randomBoolean()),
listener);
InvalidateApiKeyResponse invalidateResponse = listener.get();
assertThat(invalidateResponse.getInvalidatedApiKeys().size(), equalTo(1));
assertThat(invalidateResponse.getInvalidatedApiKeys(), containsInAnyOrder(responses.get(0).getId()));
assertThat(invalidateResponse.getPreviouslyInvalidatedApiKeys().size(), equalTo(0));
assertThat(invalidateResponse.getErrors().size(), equalTo(0));
}
private void verifyGetResponse(int expectedNumberOfApiKeys, List<CreateApiKeyResponse> responses,
GetApiKeyResponse response, Set<String> validApiKeyIds, List<String> invalidatedApiKeyIds) {
verifyGetResponse(SecuritySettingsSource.TEST_SUPERUSER, expectedNumberOfApiKeys, responses, response, validApiKeyIds,
invalidatedApiKeyIds);
}
private void verifyGetResponse(String user, int expectedNumberOfApiKeys, List<CreateApiKeyResponse> responses,
GetApiKeyResponse response, Set<String> validApiKeyIds, List<String> invalidatedApiKeyIds) {
assertThat(response.getApiKeyInfos().length, equalTo(expectedNumberOfApiKeys));
List<String> expectedIds = responses.stream().filter(o -> validApiKeyIds.contains(o.getId())).map(o -> o.getId())
.collect(Collectors.toList());
List<String> actualIds = Arrays.stream(response.getApiKeyInfos()).filter(o -> o.isInvalidated() == false).map(o -> o.getId())
@ -506,7 +631,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
.collect(Collectors.toList());
assertThat(actualNames, containsInAnyOrder(expectedNames.toArray(Strings.EMPTY_ARRAY)));
Set<String> expectedUsernames = (validApiKeyIds.isEmpty()) ? Collections.emptySet()
: Collections.singleton(SecuritySettingsSource.TEST_SUPERUSER);
: Collections.singleton(user);
Set<String> actualUsernames = Arrays.stream(response.getApiKeyInfos()).filter(o -> o.isInvalidated() == false)
.map(o -> o.getUsername()).collect(Collectors.toSet());
assertThat(actualUsernames, containsInAnyOrder(expectedUsernames.toArray(Strings.EMPTY_ARRAY)));
@ -515,15 +640,18 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
.map(o -> o.getId()).collect(Collectors.toList());
assertThat(invalidatedApiKeyIds, containsInAnyOrder(actualInvalidatedApiKeyIds.toArray(Strings.EMPTY_ARRAY)));
}
}
private List<CreateApiKeyResponse> createApiKeys(int noOfApiKeys, TimeValue expiration) {
return createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, noOfApiKeys, expiration, "monitor");
}
private List<CreateApiKeyResponse> createApiKeys(String user, int noOfApiKeys, TimeValue expiration, String... clusterPrivileges) {
List<CreateApiKeyResponse> responses = new ArrayList<>();
for (int i = 0; i < noOfApiKeys; i++) {
final RoleDescriptor descriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null);
final RoleDescriptor descriptor = new RoleDescriptor("role", clusterPrivileges, null, null);
Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
.basicAuthHeaderValue(user, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
SecurityClient securityClient = new SecurityClient(client);
final CreateApiKeyResponse response = securityClient.prepareCreateApiKey()
.setName("test-key-" + randomAlphaOfLengthBetween(5, 9) + i).setExpiration(expiration)
@ -535,4 +663,9 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
assertThat(responses.size(), is(noOfApiKeys));
return responses;
}
private void assertErrorMessage(final ElasticsearchSecurityException ese, String action, String userName, String apiKeyId) {
assertThat(ese.getMessage(),
is("action [" + action + "] is unauthorized for API key id [" + apiKeyId + "] of user [" + userName + "]"));
}
}

View File

@ -191,6 +191,7 @@ public class ApiKeyServiceTests extends ESTestCase {
sourceMap.put("limited_by_role_descriptors", Collections.singletonMap("limited role", Collections.singletonMap("cluster", "all")));
Map<String, Object> creatorMap = new HashMap<>();
creatorMap.put("principal", "test_user");
creatorMap.put("realm", "realm1");
creatorMap.put("metadata", Collections.emptyMap());
sourceMap.put("creator", creatorMap);
sourceMap.put("api_key_invalidated", false);
@ -209,6 +210,7 @@ public class ApiKeyServiceTests extends ESTestCase {
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("role_descriptors")));
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY),
equalTo(sourceMap.get("limited_by_role_descriptors")));
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM), is("realm1"));
sourceMap.put("expiration_time", Clock.systemUTC().instant().plus(1L, ChronoUnit.HOURS).toEpochMilli());
future = new PlainActionFuture<>();
@ -222,6 +224,7 @@ public class ApiKeyServiceTests extends ESTestCase {
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("role_descriptors")));
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY),
equalTo(sourceMap.get("limited_by_role_descriptors")));
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM), is("realm1"));
sourceMap.put("expiration_time", Clock.systemUTC().instant().minus(1L, ChronoUnit.HOURS).toEpochMilli());
future = new PlainActionFuture<>();

View File

@ -40,6 +40,7 @@ import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse;
import org.elasticsearch.xpack.core.security.action.user.ChangePasswordResponse;
import org.elasticsearch.xpack.core.security.action.user.DeleteUserResponse;
import org.elasticsearch.xpack.core.security.action.user.GetUsersResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
@ -369,10 +370,11 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
}
} else {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
GetRolesResponse getRolesResponse = c.prepareGetRoles().names("test_role").get();
assertTrue("test_role does not exist!", getRolesResponse.hasRoles());
assertTrue("any cluster permission should be authorized",
Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/foo", request));
Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/foo", request, authentication));
c.preparePutRole("test_role")
.cluster("none")
@ -383,7 +385,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
assertTrue("test_role does not exist!", getRolesResponse.hasRoles());
assertFalse("no cluster permission should be authorized",
Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/bar", request));
Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/bar", request, authentication));
}
}

View File

@ -121,7 +121,6 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivileg
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver;
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.core.security.support.Automatons;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
@ -323,9 +322,8 @@ public class AuthorizationServiceTests extends ESTestCase {
@Override
public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) {
final Predicate<TransportRequest> requestPredicate = r -> r == request;
final Predicate<String> actionPredicate =
Automatons.predicate(((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns());
builder.add(this, actionPredicate, requestPredicate);
builder.add(this, ((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns(),
requestPredicate);
return builder;
}
};
@ -350,9 +348,8 @@ public class AuthorizationServiceTests extends ESTestCase {
@Override
public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) {
final Predicate<TransportRequest> requestPredicate = r -> false;
final Predicate<String> actionPredicate =
Automatons.predicate(((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns());
builder.add(this, actionPredicate,requestPredicate);
builder.add(this, ((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns(),
requestPredicate);
return builder;
}
};

View File

@ -21,6 +21,8 @@ import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.license.GetLicenseAction;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.action.GetApiKeyAction;
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequest;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequestBuilder;
@ -51,6 +53,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.ApiKeyService;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.security.authz.RBACEngine.RBACAuthorizationInfo;
import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
@ -232,6 +235,55 @@ public class RBACEngineTests extends ESTestCase {
verifyNoMoreInteractions(authentication, lookedUpBy, authenticatedBy);
}
public void testSameUserPermissionAllowsSelfApiKeyInfoRetrievalWhenAuthenticatedByApiKey() {
final User user = new User("joe");
final String apiKeyId = randomAlphaOfLengthBetween(4, 7);
final TransportRequest request = GetApiKeyRequest.usingApiKeyId(apiKeyId, false);
final Authentication authentication = mock(Authentication.class);
final Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class);
when(authentication.getUser()).thenReturn(user);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getType()).thenReturn(ApiKeyService.API_KEY_REALM_TYPE);
when(authentication.getMetadata()).thenReturn(Collections.singletonMap(ApiKeyService.API_KEY_ID_KEY, apiKeyId));
assertTrue(engine.checkSameUserPermissions(GetApiKeyAction.NAME, request, authentication));
}
public void testSameUserPermissionDeniesApiKeyInfoRetrievalWhenAuthenticatedByADifferentApiKey() {
final User user = new User("joe");
final String apiKeyId = randomAlphaOfLengthBetween(4, 7);
final TransportRequest request = GetApiKeyRequest.usingApiKeyId(apiKeyId, false);
final Authentication authentication = mock(Authentication.class);
final Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class);
when(authentication.getUser()).thenReturn(user);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authenticatedBy.getType()).thenReturn(ApiKeyService.API_KEY_REALM_TYPE);
when(authentication.getMetadata()).thenReturn(
Collections.singletonMap(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLengthBetween(4, 7)));
assertFalse(engine.checkSameUserPermissions(GetApiKeyAction.NAME, request, authentication));
}
public void testSameUserPermissionDeniesApiKeyInfoRetrievalWhenLookedupByIsPresent() {
final User user = new User("joe");
final String apiKeyId = randomAlphaOfLengthBetween(4, 7);
final TransportRequest request = GetApiKeyRequest.usingApiKeyId(apiKeyId, false);
final Authentication authentication = mock(Authentication.class);
final Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class);
final Authentication.RealmRef lookedupBy = mock(Authentication.RealmRef.class);
when(authentication.getUser()).thenReturn(user);
when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
when(authentication.getLookedUpBy()).thenReturn(lookedupBy);
when(authenticatedBy.getType()).thenReturn(ApiKeyService.API_KEY_REALM_TYPE);
when(authentication.getMetadata()).thenReturn(
Collections.singletonMap(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLengthBetween(4, 7)));
final AssertionError assertionError = expectThrows(AssertionError.class, () -> engine.checkSameUserPermissions(GetApiKeyAction.NAME,
request, authentication));
assertNotNull(assertionError);
assertThat(assertionError.getLocalizedMessage(), is("runAs not supported for api key authentication"));
}
/**
* This tests that action names in the request are considered "matched" by the relevant named privilege
* (in this case that {@link DeleteAction} and {@link IndexAction} are satisfied by {@link IndexPrivilege#WRITE}).

View File

@ -51,7 +51,6 @@ import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
import org.elasticsearch.xpack.core.security.support.Automatons;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User;
@ -546,13 +545,13 @@ public class CompositeRolesStoreTests extends ESTestCase {
final TransportRequest request1 = mock(TransportRequest.class);
final TransportRequest request2 = mock(TransportRequest.class);
final TransportRequest request3 = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
ConfigurableClusterPrivilege ccp1 = new MockConfigurableClusterPrivilege() {
@Override
public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) {
Predicate<String> predicate1 =
Automatons.predicate(((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns());
builder.add(this, predicate1, req -> req == request1);
builder.add(this, ((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns(),
req -> req == request1);
return builder;
}
};
@ -582,9 +581,8 @@ public class CompositeRolesStoreTests extends ESTestCase {
ConfigurableClusterPrivilege ccp2 = new MockConfigurableClusterPrivilege() {
@Override
public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) {
Predicate<String> predicate2 =
Automatons.predicate(((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns());
builder.add(this, predicate2, req -> req == request2);
builder.add(this, ((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns(),
req -> req == request2);
return builder;
}
};
@ -626,12 +624,14 @@ public class CompositeRolesStoreTests extends ESTestCase {
CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(role1, role2), cache, privilegeStore, future);
Role role = future.actionGet();
assertThat(role.cluster().check(ClusterStateAction.NAME, randomFrom(request1, request2, request3)), equalTo(true));
assertThat(role.cluster().check(SamlAuthenticateAction.NAME, randomFrom(request1, request2, request3)), equalTo(true));
assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, randomFrom(request1, request2, request3)), equalTo(false));
assertThat(role.cluster().check(ClusterStateAction.NAME, randomFrom(request1, request2, request3), authentication), equalTo(true));
assertThat(role.cluster().check(SamlAuthenticateAction.NAME, randomFrom(request1, request2, request3), authentication),
equalTo(true));
assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, randomFrom(request1, request2, request3), authentication),
equalTo(false));
assertThat(role.cluster().check(PutUserAction.NAME, randomFrom(request1, request2)), equalTo(true));
assertThat(role.cluster().check(PutUserAction.NAME, request3), equalTo(false));
assertThat(role.cluster().check(PutUserAction.NAME, randomFrom(request1, request2), authentication), equalTo(true));
assertThat(role.cluster().check(PutUserAction.NAME, request3, authentication), equalTo(false));
final Predicate<String> allowedRead = role.indices().allowedIndicesMatcher(GetAction.NAME);
assertThat(allowedRead.test("abc-123"), equalTo(true));
@ -1076,7 +1076,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
PlainActionFuture<Role> roleFuture = new PlainActionFuture<>();
compositeRolesStore.getRoles(authentication.getUser(), authentication, roleFuture);
Role role = roleFuture.actionGet();
assertThat(role.checkClusterAction("cluster:admin/foo", Empty.INSTANCE), is(false));
assertThat(role.checkClusterAction("cluster:admin/foo", Empty.INSTANCE, mock(Authentication.class)), is(false));
assertThat(effectiveRoleDescriptors.get(), is(nullValue()));
verify(apiKeyService).getRoleForApiKey(eq(authentication), any(ActionListener.class));
}

View File

@ -20,6 +20,7 @@ import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.audit.logfile.CapturingLogger;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission;
import org.elasticsearch.xpack.core.security.authz.permission.IndicesPermission;
@ -351,14 +352,15 @@ public class FileRolesStoreTests extends ESTestCase {
assertEquals(1, modifiedRoles.size());
assertTrue(modifiedRoles.contains("role5"));
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
descriptors = store.roleDescriptors(Collections.singleton("role5"));
assertThat(descriptors, notNullValue());
assertEquals(1, descriptors.size());
Role role = Role.builder(descriptors.iterator().next(), null).build();
assertThat(role, notNullValue());
assertThat(role.names(), equalTo(new String[] { "role5" }));
assertThat(role.cluster().check("cluster:monitor/foo/bar", request), is(true));
assertThat(role.cluster().check("cluster:admin/foo/bar", request), is(false));
assertThat(role.cluster().check("cluster:monitor/foo/bar", request, authentication), is(true));
assertThat(role.cluster().check("cluster:admin/foo/bar", request, authentication), is(false));
// truncate to remove some
final Set<String> truncatedFileRolesModified = new HashSet<>();

View File

@ -8,11 +8,11 @@ package org.elasticsearch.xpack.security.rest.action.apikey;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.ActionType;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
@ -133,6 +133,76 @@ public class RestGetApiKeyActionTests extends ESTestCase {
}
public void testGetApiKeyOwnedByCurrentAuthenticatedUser() throws Exception {
final boolean isGetRequestForOwnedKeysOnly = randomBoolean();
final Map<String, String> param;
if (isGetRequestForOwnedKeysOnly) {
param = mapBuilder().put("owner", Boolean.TRUE.toString()).map();
} else {
param = mapBuilder().put("owner", Boolean.FALSE.toString()).put("realm_name", "realm-1").map();
}
final FakeRestRequest restRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY)
.withParams(param).build();
final SetOnce<RestResponse> responseSetOnce = new SetOnce<>();
final RestChannel restChannel = new AbstractRestChannel(restRequest, randomBoolean()) {
@Override
public void sendResponse(RestResponse restResponse) {
responseSetOnce.set(restResponse);
}
};
final Instant creation = Instant.now();
final Instant expiration = randomFrom(Arrays.asList(null, Instant.now().plus(10, ChronoUnit.DAYS)));
final ApiKey apiKey1 = new ApiKey("api-key-name-1", "api-key-id-1", creation, expiration, false,
"user-x", "realm-1");
final ApiKey apiKey2 = new ApiKey("api-key-name-2", "api-key-id-2", creation, expiration, false,
"user-y", "realm-1");
final GetApiKeyResponse getApiKeyResponseExpectedWhenOwnerFlagIsTrue = new GetApiKeyResponse(Collections.singletonList(apiKey1));
final GetApiKeyResponse getApiKeyResponseExpectedWhenOwnerFlagIsFalse = new GetApiKeyResponse(Arrays.asList(apiKey1, apiKey2));
try (NodeClient client = new NodeClient(Settings.EMPTY, threadPool) {
@SuppressWarnings("unchecked")
@Override
public <Request extends ActionRequest, Response extends ActionResponse>
void doExecute(ActionType<Response> action, Request request, ActionListener<Response> listener) {
GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request;
ActionRequestValidationException validationException = getApiKeyRequest.validate();
if (validationException != null) {
listener.onFailure(validationException);
return;
}
if (getApiKeyRequest.ownedByAuthenticatedUser()) {
listener.onResponse((Response) getApiKeyResponseExpectedWhenOwnerFlagIsTrue);
} else if (getApiKeyRequest.getRealmName() != null && getApiKeyRequest.getRealmName().equals("realm-1")) {
listener.onResponse((Response) getApiKeyResponseExpectedWhenOwnerFlagIsFalse);
}
}
}) {
final RestGetApiKeyAction restGetApiKeyAction = new RestGetApiKeyAction(Settings.EMPTY, mockRestController, mockLicenseState);
restGetApiKeyAction.handleRequest(restRequest, restChannel, client);
final RestResponse restResponse = responseSetOnce.get();
assertNotNull(restResponse);
assertThat(restResponse.status(), is(RestStatus.OK));
final GetApiKeyResponse actual = GetApiKeyResponse
.fromXContent(createParser(XContentType.JSON.xContent(), restResponse.content()));
if (isGetRequestForOwnedKeysOnly) {
assertThat(actual.getApiKeyInfos().length, is(1));
assertThat(actual.getApiKeyInfos(),
arrayContaining(apiKey1));
} else {
assertThat(actual.getApiKeyInfos().length, is(2));
assertThat(actual.getApiKeyInfos(),
arrayContaining(apiKey1, apiKey2));
}
}
}
private static MapBuilder<String, String> mapBuilder() {
return MapBuilder.newMapBuilder();
}

View File

@ -8,11 +8,11 @@ package org.elasticsearch.xpack.security.rest.action.apikey;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.ActionType;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.settings.Settings;
@ -24,15 +24,19 @@ import org.elasticsearch.rest.AbstractRestChannel;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.rest.FakeRestRequest;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse;
import java.util.Arrays;
import java.util.Collections;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -119,4 +123,71 @@ public class RestInvalidateApiKeyActionTests extends ESTestCase {
}
}
public void testInvalidateApiKeyOwnedByCurrentAuthenticatedUser() throws Exception {
final boolean isInvalidateRequestForOwnedKeysOnly = randomBoolean();
final String json;
if (isInvalidateRequestForOwnedKeysOnly) {
json = "{ \"owner\" : \"true\" }";
} else {
json = "{ \"realm_name\" : \"realm-1\", \"owner\" : \"false\" }";
}
final FakeRestRequest restRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY)
.withContent(new BytesArray(json), XContentType.JSON).build();
final SetOnce<RestResponse> responseSetOnce = new SetOnce<>();
final RestChannel restChannel = new AbstractRestChannel(restRequest, randomBoolean()) {
@Override
public void sendResponse(RestResponse restResponse) {
responseSetOnce.set(restResponse);
}
};
final InvalidateApiKeyResponse invalidateApiKeyResponseExpectedWhenOwnerFlagIsTrue = new InvalidateApiKeyResponse(
Collections.singletonList("api-key-id-1"), Collections.emptyList(), null);
final InvalidateApiKeyResponse invalidateApiKeyResponseExpectedWhenOwnerFlagIsFalse = new InvalidateApiKeyResponse(
Arrays.asList("api-key-id-1", "api-key-id-2"), Collections.emptyList(), null);
try (NodeClient client = new NodeClient(Settings.EMPTY, threadPool) {
@SuppressWarnings("unchecked")
@Override
public <Request extends ActionRequest, Response extends ActionResponse>
void doExecute(ActionType<Response> action, Request request, ActionListener<Response> listener) {
InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request;
ActionRequestValidationException validationException = invalidateApiKeyRequest.validate();
if (validationException != null) {
listener.onFailure(validationException);
return;
}
if (invalidateApiKeyRequest.ownedByAuthenticatedUser()) {
listener.onResponse((Response) invalidateApiKeyResponseExpectedWhenOwnerFlagIsTrue);
} else if (invalidateApiKeyRequest.getRealmName() != null && invalidateApiKeyRequest.getRealmName().equals("realm-1")) {
listener.onResponse((Response) invalidateApiKeyResponseExpectedWhenOwnerFlagIsFalse);
}
}
}) {
final RestInvalidateApiKeyAction restInvalidateApiKeyAction = new RestInvalidateApiKeyAction(Settings.EMPTY, mockRestController,
mockLicenseState);
restInvalidateApiKeyAction.handleRequest(restRequest, restChannel, client);
final RestResponse restResponse = responseSetOnce.get();
assertNotNull(restResponse);
assertThat(restResponse.status(), is(RestStatus.OK));
final InvalidateApiKeyResponse actual = InvalidateApiKeyResponse
.fromXContent(createParser(XContentType.JSON.xContent(), restResponse.content()));
if (isInvalidateRequestForOwnedKeysOnly) {
assertThat(actual.getInvalidatedApiKeys().size(), is(1));
assertThat(actual.getInvalidatedApiKeys(),
containsInAnyOrder("api-key-id-1"));
} else {
assertThat(actual.getInvalidatedApiKeys().size(), is(2));
assertThat(actual.getInvalidatedApiKeys(),
containsInAnyOrder("api-key-id-1", "api-key-id-2"));
}
}
}
}

View File

@ -15,5 +15,5 @@ setup:
# This is fragile - it needs to be updated every time we add a new cluster/index privilege
# I would much prefer we could just check that specific entries are in the array, but we don't have
# an assertion for that
- length: { "cluster" : 29 }
- length: { "cluster" : 30 }
- length: { "index" : 16 }