Use case: User with `create_doc` index privilege will be allowed to only index new documents either via Index API or Bulk API. There are two cases that we need to think: - **User indexing a new document without specifying an Id.** For this ES auto generates an Id and now ES version 7.5.0 onwards defaults to `op_type` `create` we just need to authorize on the `op_type`. - **User indexing a new document with an Id.** This is problematic as we do not know whether a document with Id exists or not. If the `op_type` is `create` then we can assume the user is trying to add a document, if it exists it is going to throw an error from the index engine. Given these both cases, we can safely authorize based on the `op_type` value. If the value is `create` then the user with `create_doc` privilege is authorized to index new documents. In the `AuthorizationService` when authorizing a bulk request, we check the implied action. This code changes that to append the `:op_type/index` or `:op_type/create` to indicate the implied index action.
This commit is contained in:
parent
7c862fe71f
commit
b6d1d2e6ec
|
@ -345,8 +345,9 @@ public final class Role {
|
|||
public static final String VIEW_INDEX_METADATA = "view_index_metadata";
|
||||
public static final String MANAGE_FOLLOW_INDEX = "manage_follow_index";
|
||||
public static final String MANAGE_ILM = "manage_ilm";
|
||||
public static final String CREATE_DOC = "create_doc";
|
||||
public static final String[] ALL_ARRAY = new String[] { NONE, ALL, READ, READ_CROSS, CREATE, INDEX, DELETE, WRITE, MONITOR, MANAGE,
|
||||
DELETE_INDEX, CREATE_INDEX, VIEW_INDEX_METADATA, MANAGE_FOLLOW_INDEX, MANAGE_ILM };
|
||||
DELETE_INDEX, CREATE_INDEX, VIEW_INDEX_METADATA, MANAGE_FOLLOW_INDEX, MANAGE_ILM, CREATE_DOC };
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -96,6 +96,7 @@ A successful call returns an object with "cluster" and "index" fields.
|
|||
"index" : [
|
||||
"all",
|
||||
"create",
|
||||
"create_doc",
|
||||
"create_index",
|
||||
"delete",
|
||||
"delete_index",
|
||||
|
|
|
@ -51,6 +51,8 @@ public final class IndexPrivilege extends Privilege {
|
|||
ClusterSearchShardsAction.NAME);
|
||||
private static final Automaton CREATE_AUTOMATON = patterns("indices:data/write/index*", "indices:data/write/bulk*",
|
||||
PutMappingAction.NAME);
|
||||
private static final Automaton CREATE_DOC_AUTOMATON = patterns("indices:data/write/index", "indices:data/write/index[*",
|
||||
"indices:data/write/index:op_type/create", "indices:data/write/bulk*", PutMappingAction.NAME);
|
||||
private static final Automaton INDEX_AUTOMATON =
|
||||
patterns("indices:data/write/index*", "indices:data/write/bulk*", "indices:data/write/update*", PutMappingAction.NAME);
|
||||
private static final Automaton DELETE_AUTOMATON = patterns("indices:data/write/delete*", "indices:data/write/bulk*");
|
||||
|
@ -77,6 +79,7 @@ public final class IndexPrivilege extends Privilege {
|
|||
public static final IndexPrivilege INDEX = new IndexPrivilege("index", INDEX_AUTOMATON);
|
||||
public static final IndexPrivilege DELETE = new IndexPrivilege("delete", DELETE_AUTOMATON);
|
||||
public static final IndexPrivilege WRITE = new IndexPrivilege("write", WRITE_AUTOMATON);
|
||||
public static final IndexPrivilege CREATE_DOC = new IndexPrivilege("create_doc", CREATE_DOC_AUTOMATON);
|
||||
public static final IndexPrivilege MONITOR = new IndexPrivilege("monitor", MONITOR_AUTOMATON);
|
||||
public static final IndexPrivilege MANAGE = new IndexPrivilege("manage", MANAGE_AUTOMATON);
|
||||
public static final IndexPrivilege DELETE_INDEX = new IndexPrivilege("delete_index", DELETE_INDEX_AUTOMATON);
|
||||
|
@ -97,6 +100,7 @@ public final class IndexPrivilege extends Privilege {
|
|||
.put("delete", DELETE)
|
||||
.put("write", WRITE)
|
||||
.put("create", CREATE)
|
||||
.put("create_doc", CREATE_DOC)
|
||||
.put("delete_index", DELETE_INDEX)
|
||||
.put("view_index_metadata", VIEW_METADATA)
|
||||
.put("read_cross_cluster", READ_CROSS_CLUSTER)
|
||||
|
|
|
@ -93,6 +93,8 @@ public class AuthorizationService {
|
|||
public static final String AUTHORIZATION_INFO_KEY = "_authz_info";
|
||||
private static final AuthorizationInfo SYSTEM_AUTHZ_INFO =
|
||||
() -> Collections.singletonMap(PRINCIPAL_ROLES_FIELD_NAME, new String[] { SystemUser.ROLE_NAME });
|
||||
private static final String IMPLIED_INDEX_ACTION = IndexAction.NAME + ":op_type/index";
|
||||
private static final String IMPLIED_CREATE_ACTION = IndexAction.NAME + ":op_type/create";
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(AuthorizationService.class);
|
||||
|
||||
|
@ -536,8 +538,9 @@ public class AuthorizationService {
|
|||
final DocWriteRequest<?> docWriteRequest = item.request();
|
||||
switch (docWriteRequest.opType()) {
|
||||
case INDEX:
|
||||
return IMPLIED_INDEX_ACTION;
|
||||
case CREATE:
|
||||
return IndexAction.NAME;
|
||||
return IMPLIED_CREATE_ACTION;
|
||||
case UPDATE:
|
||||
return UpdateAction.NAME;
|
||||
case DELETE:
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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.integration;
|
||||
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class CreateDocsIndexPrivilegeTests extends AbstractPrivilegeTestCase {
|
||||
private static final String INDEX_NAME = "index-1";
|
||||
private static final String CREATE_DOC_USER = "create_doc_user";
|
||||
private String jsonDoc = "{ \"name\" : \"elasticsearch\", \"body\": \"foo bar\" }";
|
||||
private static final String ROLES =
|
||||
"all_indices_role:\n" +
|
||||
" indices:\n" +
|
||||
" - names: '*'\n" +
|
||||
" privileges: [ all ]\n" +
|
||||
"create_doc_role:\n" +
|
||||
" indices:\n" +
|
||||
" - names: '*'\n" +
|
||||
" privileges: [ create_doc ]\n";
|
||||
|
||||
private static final String USERS_ROLES =
|
||||
"all_indices_role:admin\n" +
|
||||
"create_doc_role:" + CREATE_DOC_USER + "\n";
|
||||
|
||||
@Override
|
||||
protected boolean addMockHttpTransport() {
|
||||
return false; // enable http
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String configRoles() {
|
||||
return super.configRoles() + "\n" + ROLES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String configUsers() {
|
||||
final String usersPasswdHashed = new String(Hasher.resolve(
|
||||
randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")).hash(new SecureString("passwd".toCharArray())));
|
||||
|
||||
return super.configUsers() +
|
||||
"admin:" + usersPasswdHashed + "\n" +
|
||||
CREATE_DOC_USER + ":" + usersPasswdHashed + "\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String configUsersRoles() {
|
||||
return super.configUsersRoles() + USERS_ROLES;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void insertBaseDocumentsAsAdmin() throws Exception {
|
||||
Request request = new Request("PUT", "/" + INDEX_NAME + "/_doc/1");
|
||||
request.setJsonEntity(jsonDoc);
|
||||
request.addParameter("refresh", "true");
|
||||
assertAccessIsAllowed("admin", request);
|
||||
}
|
||||
|
||||
public void testCreateDocUserCanIndexNewDocumentsWithAutoGeneratedId() throws IOException {
|
||||
assertAccessIsAllowed(CREATE_DOC_USER, "POST", "/" + INDEX_NAME + "/_doc", "{ \"foo\" : \"bar\" }");
|
||||
}
|
||||
|
||||
public void testCreateDocUserCanIndexNewDocumentsWithExternalIdAndOpTypeIsCreate() throws IOException {
|
||||
assertAccessIsAllowed(CREATE_DOC_USER, randomFrom("PUT", "POST"), "/" + INDEX_NAME + "/_doc/2?op_type=create", "{ \"foo\" : " +
|
||||
"\"bar\" }");
|
||||
}
|
||||
|
||||
public void testCreateDocUserIsDeniedToIndexNewDocumentsWithExternalIdAndOpTypeIsIndex() throws IOException {
|
||||
assertAccessIsDenied(CREATE_DOC_USER, randomFrom("PUT", "POST"), "/" + INDEX_NAME + "/_doc/3", "{ \"foo\" : \"bar\" }");
|
||||
}
|
||||
|
||||
public void testCreateDocUserIsDeniedToIndexUpdatesToExistingDocument() throws IOException {
|
||||
assertAccessIsDenied(CREATE_DOC_USER, "POST", "/" + INDEX_NAME + "/_doc/1/_update", "{ \"doc\" : { \"foo\" : \"baz\" } }");
|
||||
assertAccessIsDenied(CREATE_DOC_USER, "PUT", "/" + INDEX_NAME + "/_doc/1", "{ \"foo\" : \"baz\" }");
|
||||
}
|
||||
|
||||
public void testCreateDocUserCanIndexNewDocumentsWithAutoGeneratedIdUsingBulkApi() throws IOException {
|
||||
assertAccessIsAllowed(CREATE_DOC_USER, randomFrom("PUT", "POST"),
|
||||
"/" + INDEX_NAME + "/_bulk", "{ \"index\" : { } }\n{ \"foo\" : \"bar\" }\n");
|
||||
}
|
||||
|
||||
public void testCreateDocUserCanIndexNewDocumentsWithAutoGeneratedIdAndOpTypeCreateUsingBulkApi() throws IOException {
|
||||
assertAccessIsAllowed(CREATE_DOC_USER, randomFrom("PUT", "POST"),
|
||||
"/" + INDEX_NAME + "/_bulk", "{ \"create\" : { } }\n{ \"foo\" : \"bar\" }\n");
|
||||
}
|
||||
|
||||
public void testCreateDocUserCanIndexNewDocumentsWithExternalIdAndOpTypeIsCreateUsingBulkApi() throws IOException {
|
||||
assertAccessIsAllowed(CREATE_DOC_USER, randomFrom("PUT", "POST"),
|
||||
"/" + INDEX_NAME + "/_bulk", "{ \"create\" : { \"_id\" : \"4\" } }\n{ \"foo\" : \"bar\" }\n");
|
||||
}
|
||||
|
||||
public void testCreateDocUserIsDeniedToIndexNewDocumentsWithExternalIdAndOpTypeIsIndexUsingBulkApi() throws IOException {
|
||||
assertBodyHasAccessIsDenied(CREATE_DOC_USER, randomFrom("PUT", "POST"),
|
||||
"/" + INDEX_NAME + "/_bulk", "{ \"index\" : { \"_id\" : \"5\" } }\n{ \"foo\" : \"bar\" }\n");
|
||||
}
|
||||
|
||||
public void testCreateDocUserIsDeniedToIndexUpdatesToExistingDocumentUsingBulkApi() throws IOException {
|
||||
assertBodyHasAccessIsDenied(CREATE_DOC_USER, randomFrom("PUT", "POST"),
|
||||
"/" + INDEX_NAME + "/_bulk", "{ \"index\" : { \"_id\" : \"1\" } }\n{ \"doc\" : {\"foo\" : \"bazbaz\"} }\n");
|
||||
assertBodyHasAccessIsDenied(CREATE_DOC_USER, randomFrom("PUT", "POST"),
|
||||
"/" + INDEX_NAME + "/_bulk", "{ \"update\" : { \"_id\" : \"1\" } }\n{ \"doc\" : {\"foo\" : \"bazbaz\"} }\n");
|
||||
}
|
||||
|
||||
}
|
|
@ -1193,16 +1193,16 @@ public class AuthorizationServiceTests extends ESTestCase {
|
|||
eq(DeleteAction.NAME), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() }));
|
||||
verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication),
|
||||
eq(IndexAction.NAME), eq("concrete-index"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(IndexAction.NAME + ":op_type/index"), eq("concrete-index"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() }));
|
||||
verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication),
|
||||
eq(IndexAction.NAME), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(IndexAction.NAME + ":op_type/index"), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() }));
|
||||
verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication),
|
||||
eq(DeleteAction.NAME), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() }));
|
||||
verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication),
|
||||
eq(IndexAction.NAME), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(IndexAction.NAME + ":op_type/index"), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() }));
|
||||
verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request),
|
||||
authzInfoRoles(new String[] { role.getName() })); // bulk request is allowed
|
||||
|
@ -1236,7 +1236,7 @@ public class AuthorizationServiceTests extends ESTestCase {
|
|||
eq(DeleteAction.NAME), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() }));
|
||||
verify(auditTrail, times(2)).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication),
|
||||
eq(IndexAction.NAME), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(IndexAction.NAME + ":op_type/index"), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()),
|
||||
eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() }));
|
||||
// bulk request is allowed
|
||||
verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request),
|
||||
|
|
|
@ -16,4 +16,4 @@ setup:
|
|||
# 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" : 30 }
|
||||
- length: { "index" : 16 }
|
||||
- length: { "index" : 17 }
|
||||
|
|
Loading…
Reference in New Issue