Add a new async search security origin (#52141)
This commit adds a new security origin, and an associated reserved user and role, named `_async_search`, which can be used by internal clients to manage the `.async-search-*` restricted index namespace.
This commit is contained in:
parent
d68a4ec82e
commit
cc1fce96ba
|
@ -52,6 +52,7 @@ public final class ClientHelper {
|
|||
public static final String ROLLUP_ORIGIN = "rollup";
|
||||
public static final String ENRICH_ORIGIN = "enrich";
|
||||
public static final String TRANSFORM_ORIGIN = "transform";
|
||||
public static final String ASYNC_SEARCH_ORIGIN = "async_search";
|
||||
|
||||
private ClientHelper() {}
|
||||
|
||||
|
|
|
@ -275,7 +275,8 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
|
|||
}
|
||||
|
||||
public static boolean isReserved(String role) {
|
||||
return RESERVED_ROLES.containsKey(role) || UsernamesField.SYSTEM_ROLE.equals(role) || UsernamesField.XPACK_ROLE.equals(role);
|
||||
return RESERVED_ROLES.containsKey(role) || UsernamesField.SYSTEM_ROLE.equals(role) ||
|
||||
UsernamesField.XPACK_ROLE.equals(role) || UsernamesField.ASYNC_SEARCH_ROLE.equals(role);
|
||||
}
|
||||
|
||||
public Map<String, Object> usageStats() {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.user;
|
||||
|
||||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.core.security.authz.permission.Role;
|
||||
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
|
||||
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
|
||||
|
||||
public class AsyncSearchUser extends User {
|
||||
|
||||
public static final String NAME = UsernamesField.ASYNC_SEARCH_NAME;
|
||||
public static final AsyncSearchUser INSTANCE = new AsyncSearchUser();
|
||||
public static final String ROLE_NAME = UsernamesField.ASYNC_SEARCH_ROLE;
|
||||
public static final Role ROLE = Role.builder(new RoleDescriptor(ROLE_NAME,
|
||||
null,
|
||||
new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
.indices(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + "*")
|
||||
.privileges("all")
|
||||
.allowRestrictedIndices(true).build(),
|
||||
},
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
MetadataUtils.DEFAULT_RESERVED_METADATA,
|
||||
null), null).build();
|
||||
|
||||
private AsyncSearchUser() {
|
||||
super(NAME, ROLE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return INSTANCE == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
public static boolean is(User user) {
|
||||
return INSTANCE.equals(user);
|
||||
}
|
||||
|
||||
public static boolean is(String principal) {
|
||||
return NAME.equals(principal);
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ public final class UsernamesField {
|
|||
public static final String BEATS_ROLE = "beats_system";
|
||||
public static final String APM_NAME = "apm_system";
|
||||
public static final String APM_ROLE = "apm_system";
|
||||
public static final String ASYNC_SEARCH_NAME = "_async_search";
|
||||
public static final String ASYNC_SEARCH_ROLE = "_async_search";
|
||||
|
||||
public static final String REMOTE_MONITORING_NAME = "remote_monitoring_user";
|
||||
public static final String REMOTE_MONITORING_COLLECTION_ROLE = "remote_monitoring_collector";
|
||||
|
|
|
@ -133,6 +133,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivileg
|
|||
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
|
||||
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
|
||||
import org.elasticsearch.xpack.core.security.user.APMSystemUser;
|
||||
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
|
||||
import org.elasticsearch.xpack.core.security.user.BeatsSystemUser;
|
||||
import org.elasticsearch.xpack.core.security.user.LogstashSystemUser;
|
||||
import org.elasticsearch.xpack.core.security.user.RemoteMonitoringUser;
|
||||
|
@ -201,6 +202,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
|
|||
assertThat(ReservedRolesStore.isReserved("kibana_dashboard_only_user"), is(true));
|
||||
assertThat(ReservedRolesStore.isReserved("beats_admin"), is(true));
|
||||
assertThat(ReservedRolesStore.isReserved(XPackUser.ROLE_NAME), is(true));
|
||||
assertThat(ReservedRolesStore.isReserved(AsyncSearchUser.ROLE_NAME), is(true));
|
||||
assertThat(ReservedRolesStore.isReserved(LogstashSystemUser.ROLE_NAME), is(true));
|
||||
assertThat(ReservedRolesStore.isReserved(BeatsSystemUser.ROLE_NAME), is(true));
|
||||
assertThat(ReservedRolesStore.isReserved(APMSystemUser.ROLE_NAME), is(true));
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.elasticsearch.xpack.core.security.SecurityContext;
|
|||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
|
||||
import org.elasticsearch.xpack.core.security.support.Automatons;
|
||||
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
|
||||
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
|
||||
import org.elasticsearch.xpack.core.security.user.XPackUser;
|
||||
|
||||
|
@ -19,6 +20,7 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Predicate;
|
||||
|
||||
import static org.elasticsearch.action.admin.cluster.node.tasks.get.GetTaskAction.TASKS_ORIGIN;
|
||||
import static org.elasticsearch.xpack.core.ClientHelper.ASYNC_SEARCH_ORIGIN;
|
||||
import static org.elasticsearch.xpack.core.ClientHelper.ENRICH_ORIGIN;
|
||||
import static org.elasticsearch.xpack.core.ClientHelper.TRANSFORM_ORIGIN;
|
||||
import static org.elasticsearch.xpack.core.ClientHelper.DEPRECATION_ORIGIN;
|
||||
|
@ -116,6 +118,9 @@ public final class AuthorizationUtils {
|
|||
case TASKS_ORIGIN: // TODO use a more limited user for tasks
|
||||
securityContext.executeAsUser(XPackUser.INSTANCE, consumer, Version.CURRENT);
|
||||
break;
|
||||
case ASYNC_SEARCH_ORIGIN:
|
||||
securityContext.executeAsUser(AsyncSearchUser.INSTANCE, consumer, Version.CURRENT);
|
||||
break;
|
||||
default:
|
||||
assert false : "action.origin [" + actionOrigin + "] is unknown!";
|
||||
throw new IllegalStateException("action.origin [" + actionOrigin + "] should always be a known value");
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;
|
|||
import org.elasticsearch.xpack.core.security.support.CacheIteratorHelper;
|
||||
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
|
||||
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
|
||||
import org.elasticsearch.xpack.core.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
|
||||
|
@ -212,6 +213,10 @@ public class CompositeRolesStore {
|
|||
roleActionListener.onResponse(ReservedRolesStore.SUPERUSER_ROLE);
|
||||
return;
|
||||
}
|
||||
if (AsyncSearchUser.is(user)) {
|
||||
roleActionListener.onResponse(AsyncSearchUser.ROLE);
|
||||
return;
|
||||
}
|
||||
|
||||
final Authentication.AuthenticationType authType = authentication.getAuthenticationType();
|
||||
if (authType == Authentication.AuthenticationType.API_KEY) {
|
||||
|
|
|
@ -14,12 +14,14 @@ import org.elasticsearch.xpack.core.security.SecurityContext;
|
|||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef;
|
||||
import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
|
||||
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
|
||||
import org.elasticsearch.xpack.core.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
|
||||
import org.elasticsearch.xpack.core.security.user.XPackUser;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
@ -95,9 +97,15 @@ public class AuthorizationUtilsTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testSwitchAndExecuteXpackUser() throws Exception {
|
||||
String origin = randomFrom(ClientHelper.ML_ORIGIN, ClientHelper.WATCHER_ORIGIN, ClientHelper.DEPRECATION_ORIGIN,
|
||||
ClientHelper.MONITORING_ORIGIN, ClientHelper.PERSISTENT_TASK_ORIGIN, ClientHelper.INDEX_LIFECYCLE_ORIGIN);
|
||||
assertSwitchBasedOnOriginAndExecute(origin, XPackUser.INSTANCE);
|
||||
for (String origin : Arrays.asList(ClientHelper.ML_ORIGIN, ClientHelper.WATCHER_ORIGIN, ClientHelper.DEPRECATION_ORIGIN,
|
||||
ClientHelper.MONITORING_ORIGIN, ClientHelper.PERSISTENT_TASK_ORIGIN, ClientHelper.INDEX_LIFECYCLE_ORIGIN)) {
|
||||
assertSwitchBasedOnOriginAndExecute(origin, XPackUser.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
public void testSwitchAndExecuteAsyncSearchUser() throws Exception {
|
||||
String origin = ClientHelper.ASYNC_SEARCH_ORIGIN;
|
||||
assertSwitchBasedOnOriginAndExecute(origin, AsyncSearchUser.INSTANCE);
|
||||
}
|
||||
|
||||
public void testSwitchWithTaskOrigin() throws Exception {
|
||||
|
@ -124,10 +132,10 @@ public class AuthorizationUtilsTests extends ESTestCase {
|
|||
latch.countDown();
|
||||
listener.onResponse(null);
|
||||
};
|
||||
|
||||
threadContext.putHeader(headerName, headerValue);
|
||||
try (ThreadContext.StoredContext ignored = threadContext.stashWithOrigin(origin)) {
|
||||
AuthorizationUtils.switchUserBasedOnActionOriginAndExecute(threadContext, securityContext, consumer);
|
||||
|
||||
latch.await();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;
|
|||
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
|
||||
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
|
||||
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
|
||||
import org.elasticsearch.xpack.core.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
import org.elasticsearch.xpack.core.security.user.XPackUser;
|
||||
|
@ -937,7 +938,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|||
assertThat(Arrays.asList(roles.names()), hasItem("anonymous_user_role"));
|
||||
}
|
||||
|
||||
public void testDoesNotUseRolesStoreForXPackUser() {
|
||||
public void testDoesNotUseRolesStoreForXPacAndAsyncSearchUser() {
|
||||
final FileRolesStore fileRolesStore = mock(FileRolesStore.class);
|
||||
doCallRealMethod().when(fileRolesStore).accept(any(Set.class), any(ActionListener.class));
|
||||
final NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class);
|
||||
|
@ -959,13 +960,23 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|||
rds -> effectiveRoleDescriptors.set(rds));
|
||||
verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor
|
||||
|
||||
// test Xpack user short circuits to its own reserved role
|
||||
PlainActionFuture<Role> rolesFuture = new PlainActionFuture<>();
|
||||
Authentication auth = new Authentication(XPackUser.INSTANCE, new RealmRef("name", "type", "node"), null);
|
||||
compositeRolesStore.getRoles(XPackUser.INSTANCE, auth, rolesFuture);
|
||||
final Role roles = rolesFuture.actionGet();
|
||||
Role roles = rolesFuture.actionGet();
|
||||
assertThat(roles, equalTo(XPackUser.ROLE));
|
||||
assertThat(effectiveRoleDescriptors.get(), is(nullValue()));
|
||||
verifyNoMoreInteractions(fileRolesStore, nativeRolesStore, reservedRolesStore);
|
||||
|
||||
// test AyncSearch user short circuits to its own reserved role
|
||||
rolesFuture = new PlainActionFuture<>();
|
||||
auth = new Authentication(AsyncSearchUser.INSTANCE, new RealmRef("name", "type", "node"), null);
|
||||
compositeRolesStore.getRoles(AsyncSearchUser.INSTANCE, auth, rolesFuture);
|
||||
roles = rolesFuture.actionGet();
|
||||
assertThat(roles, equalTo(AsyncSearchUser.ROLE));
|
||||
assertThat(effectiveRoleDescriptors.get(), is(nullValue()));
|
||||
verifyNoMoreInteractions(fileRolesStore, nativeRolesStore, reservedRolesStore);
|
||||
}
|
||||
|
||||
public void testGetRolesForSystemUserThrowsException() {
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.security.user;
|
||||
|
||||
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsAction;
|
||||
import org.elasticsearch.action.admin.cluster.state.ClusterStateAction;
|
||||
import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction;
|
||||
import org.elasticsearch.action.delete.DeleteAction;
|
||||
import org.elasticsearch.action.get.GetAction;
|
||||
import org.elasticsearch.action.index.IndexAction;
|
||||
import org.elasticsearch.action.search.SearchAction;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
import org.elasticsearch.xpack.core.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
|
||||
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
|
||||
import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchAction;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class AsyncSearchUserTests extends ESTestCase {
|
||||
|
||||
public void testAsyncSearchUserCannotAccessNonRestrictedIndices() {
|
||||
for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
|
||||
Predicate<String> predicate = AsyncSearchUser.ROLE.indices().allowedIndicesMatcher(action);
|
||||
String index = randomAlphaOfLengthBetween(3, 12);
|
||||
if (false == RestrictedIndicesNames.isRestricted(index)) {
|
||||
assertThat(predicate.test(index), Matchers.is(false));
|
||||
}
|
||||
index = "." + randomAlphaOfLengthBetween(3, 12);
|
||||
if (false == RestrictedIndicesNames.isRestricted(index)) {
|
||||
assertThat(predicate.test(index), Matchers.is(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testAsyncSearchUserCanAccessOnlyAsyncSearchRestrictedIndices() {
|
||||
for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
|
||||
final Predicate<String> predicate = AsyncSearchUser.ROLE.indices().allowedIndicesMatcher(action);
|
||||
for (String index : RestrictedIndicesNames.RESTRICTED_NAMES) {
|
||||
assertThat(predicate.test(index), Matchers.is(false));
|
||||
}
|
||||
assertThat(predicate.test(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 3)), Matchers.is(true));
|
||||
}
|
||||
}
|
||||
|
||||
public void testAsyncSearchUserHasNoClusterPrivileges() {
|
||||
for (String action : Arrays.asList(ClusterStateAction.NAME, GetWatchAction.NAME, ClusterStatsAction.NAME, NodesStatsAction.NAME)) {
|
||||
assertThat(AsyncSearchUser.ROLE.cluster().check(action, mock(TransportRequest.class), mock(Authentication.class)),
|
||||
Matchers.is(false));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.user;
|
||||
|
||||
import org.elasticsearch.action.delete.DeleteAction;
|
||||
import org.elasticsearch.action.get.GetAction;
|
||||
import org.elasticsearch.action.index.IndexAction;
|
||||
import org.elasticsearch.action.search.SearchAction;
|
||||
|
@ -17,24 +18,33 @@ import org.elasticsearch.xpack.security.audit.index.IndexNameResolver;
|
|||
import org.hamcrest.Matchers;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class XPackUserTests extends ESTestCase {
|
||||
|
||||
public void testXPackUserCanAccessNonSecurityIndices() {
|
||||
final String action = randomFrom(GetAction.NAME, SearchAction.NAME, IndexAction.NAME);
|
||||
final Predicate<String> predicate = XPackUser.ROLE.indices().allowedIndicesMatcher(action);
|
||||
final String index = randomBoolean() ? randomAlphaOfLengthBetween(3, 12) : "." + randomAlphaOfLength(8);
|
||||
assertThat(predicate.test(index), Matchers.is(true));
|
||||
for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
|
||||
Predicate<String> predicate = XPackUser.ROLE.indices().allowedIndicesMatcher(action);
|
||||
String index = randomAlphaOfLengthBetween(3, 12);
|
||||
if (false == RestrictedIndicesNames.isRestricted(index)) {
|
||||
assertThat(predicate.test(index), Matchers.is(true));
|
||||
}
|
||||
index = "." + randomAlphaOfLengthBetween(3, 12);
|
||||
if (false == RestrictedIndicesNames.isRestricted(index)) {
|
||||
assertThat(predicate.test(index), Matchers.is(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testXPackUserCannotAccessRestrictedIndices() {
|
||||
final String action = randomFrom(GetAction.NAME, SearchAction.NAME, IndexAction.NAME);
|
||||
final Predicate<String> predicate = XPackUser.ROLE.indices().allowedIndicesMatcher(action);
|
||||
for (String index : RestrictedIndicesNames.RESTRICTED_NAMES) {
|
||||
assertThat(predicate.test(index), Matchers.is(false));
|
||||
for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
|
||||
Predicate<String> predicate = XPackUser.ROLE.indices().allowedIndicesMatcher(action);
|
||||
for (String index : RestrictedIndicesNames.RESTRICTED_NAMES) {
|
||||
assertThat(predicate.test(index), Matchers.is(false));
|
||||
}
|
||||
assertThat(predicate.test(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2)), Matchers.is(false));
|
||||
}
|
||||
assertThat(predicate.test(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2)), Matchers.is(false));
|
||||
}
|
||||
|
||||
public void testXPackUserCanReadAuditTrail() {
|
||||
|
|
Loading…
Reference in New Issue