shield: disable document and field level security by default

This change disables document and field level security by default so that we are able to maintain
bulk update functionality. Users that enable DLS/FLS will not have this functionality. Additionally,
if a user tries to configure DLS/FLS in a role without enabling it, the role will be skipped during
parsing and a log message will be logged at the error level.

See elastic/elasticsearch#938

Original commit: elastic/x-pack-elasticsearch@60c7519092
This commit is contained in:
jaymode 2015-11-17 16:28:45 -05:00
parent cc2096b4f9
commit 16848c6043
11 changed files with 185 additions and 19 deletions

View File

@ -309,7 +309,7 @@ public class ShieldPlugin extends Plugin {
}
public static boolean flsDlsEnabled(Settings settings) {
return settings.getAsBoolean(DLS_FLS_ENABLED_SETTING, true);
return settings.getAsBoolean(DLS_FLS_ENABLED_SETTING, false);
}
private void failIfShieldQueryCacheIsNotActive(Settings settings, boolean nodeSettings) {

View File

@ -90,7 +90,7 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
} catch (IOException e) {
throw new ElasticsearchException("failed to setup roles file watcher", e);
}
permissions = parseFile(file, reservedRoles, logger);
permissions = parseFile(file, reservedRoles, logger, settings);
}
@Override
@ -116,18 +116,18 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
}
public static Set<String> parseFileForRoleNames(Path path, ESLogger logger) {
Map<String, Permission.Global.Role> roleMap = parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger, false);
Map<String, Permission.Global.Role> roleMap = parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger, false, Settings.EMPTY);
if (roleMap == null) {
return emptySet();
}
return roleMap.keySet();
}
public static Map<String, Permission.Global.Role> parseFile(Path path, Set<Permission.Global.Role> reservedRoles, ESLogger logger) {
return parseFile(path, reservedRoles, logger, true);
public static Map<String, Permission.Global.Role> parseFile(Path path, Set<Permission.Global.Role> reservedRoles, ESLogger logger, Settings settings) {
return parseFile(path, reservedRoles, logger, true, settings);
}
public static Map<String, Permission.Global.Role> parseFile(Path path, Set<Permission.Global.Role> reservedRoles, ESLogger logger, boolean resolvePermission) {
public static Map<String, Permission.Global.Role> parseFile(Path path, Set<Permission.Global.Role> reservedRoles, ESLogger logger, boolean resolvePermission, Settings settings) {
if (logger == null) {
logger = NoOpLogger.INSTANCE;
}
@ -138,7 +138,7 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
try {
List<String> roleSegments = roleSegments(path);
for (String segment : roleSegments) {
Permission.Global.Role role = parseRole(segment, path, logger, resolvePermission);
Permission.Global.Role role = parseRole(segment, path, logger, resolvePermission, settings);
if (role != null) {
if (SystemRole.NAME.equals(role.name())) {
logger.warn("role [{}] is reserved to the system. the relevant role definition in the mapping file will be ignored", SystemRole.NAME);
@ -164,7 +164,7 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
return unmodifiableMap(roles);
}
private static Permission.Global.Role parseRole(String segment, Path path, ESLogger logger, boolean resolvePermissions) {
private static Permission.Global.Role parseRole(String segment, Path path, ESLogger logger, boolean resolvePermissions, Settings settings) {
String roleName = null;
try {
XContentParser parser = YamlXContent.yamlXContent.createParser(segment);
@ -301,6 +301,11 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
}
}
if (name != null) {
if ((query != null || (fields != null && fields.isEmpty() == false)) && ShieldPlugin.flsDlsEnabled(settings) == false) {
logger.error("invalid role definition [{}] in roles file [{}]. document and field level security is not enabled. set [{}] to [true] in the configuration file. skipping role...", roleName, path.toAbsolutePath(), ShieldPlugin.DLS_FLS_ENABLED_SETTING);
return null;
}
try {
permission.add(fields, query, Privilege.Index.get(name), indices);
} catch (IllegalArgumentException e) {
@ -417,7 +422,7 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
public void onFileChanged(Path file) {
if (file.equals(FileRolesStore.this.file)) {
try {
permissions = parseFile(file, reservedRoles, logger);
permissions = parseFile(file, reservedRoles, logger, settings);
logger.info("updated roles (roles file [{}] changed)", file.toAbsolutePath());
} catch (Throwable t) {
logger.error("could not reload roles file [{}]. Current roles remain unmodified", t, file.toAbsolutePath());

View File

@ -10,7 +10,6 @@ import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.Node;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.test.ShieldIntegTestCase;
@ -30,7 +29,7 @@ public class BulkUpdateTests extends ShieldIntegTestCase {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(Node.HTTP_ENABLED, true)
.put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, false) //FIXME randomize once DLS/FLS works with Bulk updates...
//.put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, false) //FIXME randomize once DLS/FLS works with Bulk updates...
.build();
}

View File

@ -8,6 +8,7 @@ package org.elasticsearch.integration;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexModule;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.test.ShieldIntegTestCase;
@ -39,6 +40,7 @@ public class DocumentAndFieldLevelSecurityTests extends ShieldIntegTestCase {
"role2:user2\n" +
"role3:user3\n";
}
@Override
protected String configRoles() {
return super.configRoles() +
@ -65,6 +67,14 @@ public class DocumentAndFieldLevelSecurityTests extends ShieldIntegTestCase {
" query: '{\"term\" : {\"field2\" : \"value2\"}}'\n";
}
@Override
public Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, true)
.build();
}
public void testSimpleQuery() throws Exception {
assertAcked(client().admin().indices().prepareCreate("test")
.addMapping("type1", "field1", "type=string", "field2", "type=string")

View File

@ -8,7 +8,9 @@ package org.elasticsearch.integration;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.test.ShieldIntegTestCase;
@ -49,6 +51,7 @@ public class DocumentLevelSecurityRandomTests extends ShieldIntegTestCase {
}
return builder.toString();
}
@Override
protected String configRoles() {
StringBuilder builder = new StringBuilder(super.configRoles());
@ -66,6 +69,14 @@ public class DocumentLevelSecurityRandomTests extends ShieldIntegTestCase {
return builder.toString();
}
@Override
public Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, true)
.build();
}
public void testDuelWithAliasFilters() throws Exception {
assertAcked(client().admin().indices().prepareCreate("test")
.addMapping("type1", "field1", "type=string", "field2", "type=string")

View File

@ -13,12 +13,14 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.termvectors.MultiTermVectorsResponse;
import org.elasticsearch.action.termvectors.TermVectorsRequest;
import org.elasticsearch.action.termvectors.TermVectorsResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.children.Children;
import org.elasticsearch.search.aggregations.bucket.global.Global;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.test.ShieldIntegTestCase;
@ -69,6 +71,14 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase {
" query: '{\"term\" : {\"field2\" : \"value2\"}}'"; // <-- query defined as json in a string
}
@Override
public Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, true)
.build();
}
public void testSimpleQuery() throws Exception {
assertAcked(client().admin().indices().prepareCreate("test")
.addMapping("type1", "field1", "type=string", "field2", "type=string")

View File

@ -7,8 +7,10 @@ package org.elasticsearch.integration;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.test.ShieldIntegTestCase;
@ -47,6 +49,7 @@ public class FieldLevelSecurityRandomTests extends ShieldIntegTestCase {
"role3:user3\n" +
"role4:user4\n";
}
@Override
protected String configRoles() {
if (allowedFields == null) {
@ -98,6 +101,14 @@ public class FieldLevelSecurityRandomTests extends ShieldIntegTestCase {
" - field3\n";
}
@Override
public Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, true)
.build();
}
public void testRandom() throws Exception {
int j = 0;
Map<String, Object> doc = new HashMap<>();

View File

@ -23,6 +23,7 @@ import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.test.ESIntegTestCase;
@ -102,6 +103,14 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase {
" fields: 'field*'\n";
}
@Override
public Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, true)
.build();
}
public void testQuery() throws Exception {
assertAcked(client().admin().indices().prepareCreate("test")
.addMapping("type1", "field1", "type=string", "field2", "type=string")

View File

@ -7,6 +7,8 @@ package org.elasticsearch.integration;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.test.ShieldIntegTestCase;
@ -34,6 +36,7 @@ public class IndicesPermissionsWithAliasesWildcardsAndRegexsTests extends Shield
return super.configUsersRoles() +
"role1:user1\n";
}
@Override
protected String configRoles() {
return super.configRoles() +
@ -51,6 +54,14 @@ public class IndicesPermissionsWithAliasesWildcardsAndRegexsTests extends Shield
" fields: field3\n";
}
@Override
public Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, true)
.build();
}
public void testResolveWildcardsRegexs() throws Exception {
assertAcked(client().admin().indices().prepareCreate("test")
.addMapping("type1", "field1", "type=string", "field2", "type=string")

View File

@ -7,6 +7,7 @@ package org.elasticsearch.shield.authz.store;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.audit.logfile.CapturingLogger;
import org.elasticsearch.shield.authc.support.RefreshListener;
import org.elasticsearch.shield.authz.Permission;
@ -29,6 +30,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static java.util.Collections.singleton;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
@ -46,9 +48,10 @@ import static org.hamcrest.Matchers.startsWith;
public class FileRolesStoreTests extends ESTestCase {
public void testParseFile() throws Exception {
Path path = getDataPath("roles.yml");
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(),
logger, Settings.builder().put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, true).build());
assertThat(roles, notNullValue());
assertThat(roles.size(), is(7));
assertThat(roles.size(), is(10));
Permission.Global.Role role = roles.get("role1");
assertThat(role, notNullValue());
@ -140,6 +143,80 @@ public class FileRolesStoreTests extends ESTestCase {
assertThat(role.runAs().check("user1"), is(true));
assertThat(role.runAs().check("user2"), is(true));
assertThat(role.runAs().check("user" + randomIntBetween(3, 9)), is(false));
role = roles.get("role_fields");
assertThat(role, notNullValue());
assertThat(role.name(), equalTo("role_fields"));
assertThat(role.cluster(), notNullValue());
assertThat(role.cluster(), is(Permission.Cluster.Core.NONE));
assertThat(role.runAs(), is(Permission.RunAs.Core.NONE));
assertThat(role.indices(), notNullValue());
assertThat(role.indices().groups(), notNullValue());
assertThat(role.indices().groups().length, is(1));
group = role.indices().groups()[0];
assertThat(group.indices(), notNullValue());
assertThat(group.indices().length, is(1));
assertThat(group.indices()[0], equalTo("field_idx"));
assertThat(group.privilege(), notNullValue());
assertThat(group.privilege().isAlias(Privilege.Index.READ), is(true));
assertThat(group.getFields(), contains("foo", "boo"));
role = roles.get("role_query");
assertThat(role, notNullValue());
assertThat(role.name(), equalTo("role_query"));
assertThat(role.cluster(), notNullValue());
assertThat(role.cluster(), is(Permission.Cluster.Core.NONE));
assertThat(role.runAs(), is(Permission.RunAs.Core.NONE));
assertThat(role.indices(), notNullValue());
assertThat(role.indices().groups(), notNullValue());
assertThat(role.indices().groups().length, is(1));
group = role.indices().groups()[0];
assertThat(group.indices(), notNullValue());
assertThat(group.indices().length, is(1));
assertThat(group.indices()[0], equalTo("query_idx"));
assertThat(group.privilege(), notNullValue());
assertThat(group.privilege().isAlias(Privilege.Index.READ), is(true));
assertThat(group.getFields(), nullValue());
assertThat(group.getQuery(), notNullValue());
role = roles.get("role_query_fields");
assertThat(role, notNullValue());
assertThat(role.name(), equalTo("role_query_fields"));
assertThat(role.cluster(), notNullValue());
assertThat(role.cluster(), is(Permission.Cluster.Core.NONE));
assertThat(role.runAs(), is(Permission.RunAs.Core.NONE));
assertThat(role.indices(), notNullValue());
assertThat(role.indices().groups(), notNullValue());
assertThat(role.indices().groups().length, is(1));
group = role.indices().groups()[0];
assertThat(group.indices(), notNullValue());
assertThat(group.indices().length, is(1));
assertThat(group.indices()[0], equalTo("query_fields_idx"));
assertThat(group.privilege(), notNullValue());
assertThat(group.privilege().isAlias(Privilege.Index.READ), is(true));
assertThat(group.getFields(), contains("foo", "boo"));
assertThat(group.getQuery(), notNullValue());
}
public void testParseFileWithFLSAndDLSDisabled() throws Exception {
Path path = getDataPath("roles.yml");
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.ERROR);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(),
logger, randomBoolean() ? Settings.builder().put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, false).build() : Settings.EMPTY);
assertThat(roles, notNullValue());
assertThat(roles.size(), is(7));
assertThat(roles.get("role_fields"), nullValue());
assertThat(roles.get("role_query"), nullValue());
assertThat(roles.get("role_query_fields"), nullValue());
List<CapturingLogger.Msg> entries = logger.output(CapturingLogger.Level.ERROR);
assertThat(entries, hasSize(3));
assertThat(entries.get(0).text, startsWith("invalid role definition [role_fields] in roles file [" + path.toAbsolutePath() + "]. document and field level security is not enabled."));
assertThat(entries.get(1).text, startsWith("invalid role definition [role_query] in roles file [" + path.toAbsolutePath() + "]. document and field level security is not enabled."));
assertThat(entries.get(2).text, startsWith("invalid role definition [role_query_fields] in roles file [" + path.toAbsolutePath() + "]. document and field level security is not enabled."));
}
/**
@ -147,7 +224,7 @@ public class FileRolesStoreTests extends ESTestCase {
*/
public void testDefaultRolesFile() throws Exception {
Path path = getDataPath("default_roles.yml");
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger, Settings.EMPTY);
assertThat(roles, notNullValue());
assertThat(roles.size(), is(8));
@ -225,14 +302,14 @@ public class FileRolesStoreTests extends ESTestCase {
public void testThatEmptyFileDoesNotResultInLoop() throws Exception {
Path file = createTempFile();
Files.write(file, Collections.singletonList("#"), StandardCharsets.UTF_8);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(file, Collections.<Permission.Global.Role>emptySet(), logger);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(file, Collections.<Permission.Global.Role>emptySet(), logger, Settings.EMPTY);
assertThat(roles.keySet(), is(empty()));
}
public void testThatInvalidRoleDefinitions() throws Exception {
Path path = getDataPath("invalid_roles.yml");
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.ERROR);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger, Settings.EMPTY);
assertThat(roles.size(), is(1));
assertThat(roles, hasKey("valid_role"));
Permission.Global.Role role = roles.get("valid_role");
@ -268,7 +345,7 @@ public class FileRolesStoreTests extends ESTestCase {
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO);
Path path = getDataPath("reserved_roles.yml");
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, reservedRoles, logger);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, reservedRoles, logger, Settings.EMPTY);
assertThat(roles, notNullValue());
assertThat(roles.size(), is(2));
@ -300,7 +377,7 @@ public class FileRolesStoreTests extends ESTestCase {
Path path = createTempFile();
Files.delete(path);
assertThat(Files.exists(path), is(false));
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, reservedRoles, logger);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, reservedRoles, logger, Settings.EMPTY);
assertThat(roles, notNullValue());
assertThat(roles.size(), is(1));

View File

@ -29,4 +29,27 @@ role_run_as:
# role with more than run_as
role_run_as1:
run_as: [user1, user2]
run_as: [user1, user2]
role_fields:
indices:
'field_idx':
privileges: READ
fields:
- foo
- boo
role_query:
indices:
'query_idx':
privileges: READ
query: '{ "match_all": {} }'
role_query_fields:
indices:
'query_fields_idx':
privileges: READ
query: '{ "match_all": {} }'
fields:
- foo
- boo