From 9a1d33d863fbdc08f74f6eaf02876f6276900c61 Mon Sep 17 00:00:00 2001 From: Jay Modi Date: Wed, 12 Oct 2016 06:52:24 -0400 Subject: [PATCH] security: include anonymous roles when building the global permission The anonymous role was being applied to other users for index access control but was not being applied in terms of action level access control. This change makes the minimum required change to apply the anonymous role for all users when anonymous is enabled. Additionally, some minor changes were made to the native roles store to not lookup roles before the service is started. Closes elastic/elasticsearch#3711 Original commit: elastic/x-pack-elasticsearch@a9398e178de72ec5539029406e438eabdde222f4 --- .../security/authz/AuthorizationService.java | 16 ++++---- .../authz/store/NativeRolesStore.java | 7 ++++ .../authc/esnative/NativeRealmIntegTests.java | 40 ++++++++++++------- .../authz/AuthorizationServiceTests.java | 27 +++++++++++++ 4 files changed, 68 insertions(+), 22 deletions(-) diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index 1e56b1717cd..85fac3c9543 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -53,6 +53,7 @@ import org.elasticsearch.xpack.security.user.XPackUser; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -308,19 +309,20 @@ public class AuthorizationService extends AbstractComponent { } private GlobalPermission permission(String[] roleNames) { + final String[] anonymousRoles = isAnonymousEnabled ? anonymousUser.roles() : Strings.EMPTY_ARRAY; if (roleNames.length == 0) { - return DefaultRole.INSTANCE; - } - - if (roleNames.length == 1) { - Role role = rolesStore.role(roleNames[0]); - return role == null ? DefaultRole.INSTANCE : GlobalPermission.Compound.builder().add(DefaultRole.INSTANCE).add(role).build(); + if (anonymousRoles.length == 0) { + assert isAnonymousEnabled == false : "anonymous is only enabled when the anonymous user has roles"; + return DefaultRole.INSTANCE; + } } // we'll take all the roles and combine their associated permissions + final Set uniqueNames = new HashSet<>(Arrays.asList(roleNames)); + uniqueNames.addAll(Arrays.asList(anonymousRoles)); GlobalPermission.Compound.Builder roles = GlobalPermission.Compound.builder().add(DefaultRole.INSTANCE); - for (String roleName : roleNames) { + for (String roleName : uniqueNames) { Role role = rolesStore.role(roleName); if (role != null) { roles.add(role); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java index 75e4c3df5d0..1d25ffaf8c0 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java @@ -354,6 +354,9 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C @Override public Role role(String roleName) { + if (state() != State.STARTED) { + return null; + } RoleAndVersion roleAndVersion = getRoleAndVersion(roleName); return roleAndVersion == null ? null : roleAndVersion.getRole(); } @@ -443,6 +446,10 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C } private RoleAndVersion getRoleAndVersion(final String roleId) { + if (securityIndexExists == false) { + return null; + } + RoleAndVersion roleAndVersion = null; final AtomicReference getRef = new AtomicReference<>(null); final CountDownLatch latch = new CountDownLatch(1); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index 45540150e92..06ae853661f 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -49,6 +49,7 @@ import java.util.List; import java.util.Map; import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.containsString; @@ -316,10 +317,12 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { .addIndices(new String[]{"*"}, new String[]{"read"}, new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}")) .get(); - try { - client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get(); - fail("user should not be able to execute any cluster actions!"); - } catch (ElasticsearchSecurityException e) { + if (anonymousEnabled) { + assertNoTimeout(client() + .filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get()); + } else { + ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> client() + .filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get()); assertThat(e.status(), is(RestStatus.FORBIDDEN)); } } else { @@ -358,10 +361,12 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { .prepareHealth().get(); assertFalse(response.isTimedOut()); c.prepareDeleteRole("test_role").get(); - try { - client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get(); - fail("user should not be able to execute any actions!"); - } catch (ElasticsearchSecurityException e) { + if (anonymousEnabled) { + assertNoTimeout( + client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get()); + } else { + ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> + client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get()); assertThat(e.status(), is(RestStatus.FORBIDDEN)); } } @@ -406,11 +411,14 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { assertThat(joe.roles(), arrayContaining("read_role")); assertThat(joe.fullName(), is("Joe Smith")); - // test that role change took effect - try { - client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get(); - fail("test_role does not have permission to get health"); - } catch (ElasticsearchSecurityException e) { + // test that role change took effect if anonymous is disabled as anonymous grants monitoring permissions... + if (anonymousEnabled) { + assertNoTimeout( + client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get()); + } else { + ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> + client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get()); + assertThat(e.status(), is(RestStatus.FORBIDDEN)); assertThat(e.getMessage(), containsString("authorized")); } @@ -434,7 +442,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { // test with new password and role response = client() .filterWithHeader( - Collections.singletonMap("Authorization",basicAuthHeaderValue("joe", new SecuredString("changeme2".toCharArray())))) + Collections.singletonMap("Authorization", + basicAuthHeaderValue("joe", new SecuredString("changeme2".toCharArray())))) .admin().cluster().prepareHealth().get(); assertFalse(response.isTimedOut()); } @@ -558,7 +567,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { response = client() .filterWithHeader( - Collections.singletonMap("Authorization", basicAuthHeaderValue("joe", new SecuredString("changeme".toCharArray())))) + Collections.singletonMap("Authorization", + basicAuthHeaderValue("joe", new SecuredString("changeme".toCharArray())))) .admin().cluster().prepareHealth().get(); assertThat(response.isTimedOut(), is(false)); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 5a8888240f9..356cd4192b5 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions; import org.elasticsearch.action.admin.indices.create.CreateIndexAction; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsAction; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest; import org.elasticsearch.action.admin.indices.recovery.RecoveryAction; import org.elasticsearch.action.admin.indices.recovery.RecoveryRequest; @@ -614,6 +615,32 @@ public class AuthorizationServiceTests extends ESTestCase { } } + public void testAnonymousRolesAreAppliedToOtherUsers() { + TransportRequest request = new ClusterHealthRequest(); + ClusterState state = mock(ClusterState.class); + Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "anonymous_user_role").build(); + final AnonymousUser anonymousUser = new AnonymousUser(settings); + authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, + new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser); + + when(rolesStore.role("anonymous_user_role")) + .thenReturn(Role.builder("anonymous_user_role") + .cluster(ClusterPrivilege.ALL) + .add(IndexPrivilege.ALL, "a") + .build()); + when(clusterService.state()).thenReturn(state); + when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); + + // sanity check the anonymous user + authorizationService.authorize(createAuthentication(anonymousUser), ClusterHealthAction.NAME, request); + authorizationService.authorize(createAuthentication(anonymousUser), IndicesExistsAction.NAME, new IndicesExistsRequest("a")); + + // test the no role user + final User userWithNoRoles = new User("no role user"); + authorizationService.authorize(createAuthentication(userWithNoRoles), ClusterHealthAction.NAME, request); + authorizationService.authorize(createAuthentication(userWithNoRoles), IndicesExistsAction.NAME, new IndicesExistsRequest("a")); + } + private Authentication createAuthentication(User user) { RealmRef lookedUpBy = user.runAs() == null ? null : new RealmRef("looked", "up", "by"); return new Authentication(user, new RealmRef("test", "test", "foo"), lookedUpBy);