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@a9398e178d
This commit is contained in:
Jay Modi 2016-10-12 06:52:24 -04:00 committed by GitHub
parent 4e00ab2f2b
commit 9a1d33d863
4 changed files with 68 additions and 22 deletions

View File

@ -53,6 +53,7 @@ import org.elasticsearch.xpack.security.user.XPackUser;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -308,19 +309,20 @@ public class AuthorizationService extends AbstractComponent {
} }
private GlobalPermission permission(String[] roleNames) { private GlobalPermission permission(String[] roleNames) {
final String[] anonymousRoles = isAnonymousEnabled ? anonymousUser.roles() : Strings.EMPTY_ARRAY;
if (roleNames.length == 0) { if (roleNames.length == 0) {
return DefaultRole.INSTANCE; if (anonymousRoles.length == 0) {
} assert isAnonymousEnabled == false : "anonymous is only enabled when the anonymous user has roles";
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();
} }
// we'll take all the roles and combine their associated permissions // we'll take all the roles and combine their associated permissions
final Set<String> uniqueNames = new HashSet<>(Arrays.asList(roleNames));
uniqueNames.addAll(Arrays.asList(anonymousRoles));
GlobalPermission.Compound.Builder roles = GlobalPermission.Compound.builder().add(DefaultRole.INSTANCE); GlobalPermission.Compound.Builder roles = GlobalPermission.Compound.builder().add(DefaultRole.INSTANCE);
for (String roleName : roleNames) { for (String roleName : uniqueNames) {
Role role = rolesStore.role(roleName); Role role = rolesStore.role(roleName);
if (role != null) { if (role != null) {
roles.add(role); roles.add(role);

View File

@ -354,6 +354,9 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C
@Override @Override
public Role role(String roleName) { public Role role(String roleName) {
if (state() != State.STARTED) {
return null;
}
RoleAndVersion roleAndVersion = getRoleAndVersion(roleName); RoleAndVersion roleAndVersion = getRoleAndVersion(roleName);
return roleAndVersion == null ? null : roleAndVersion.getRole(); return roleAndVersion == null ? null : roleAndVersion.getRole();
} }
@ -443,6 +446,10 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C
} }
private RoleAndVersion getRoleAndVersion(final String roleId) { private RoleAndVersion getRoleAndVersion(final String roleId) {
if (securityIndexExists == false) {
return null;
}
RoleAndVersion roleAndVersion = null; RoleAndVersion roleAndVersion = null;
final AtomicReference<GetResponse> getRef = new AtomicReference<>(null); final AtomicReference<GetResponse> getRef = new AtomicReference<>(null);
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);

View File

@ -49,6 +49,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; 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.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
@ -316,10 +317,12 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
.addIndices(new String[]{"*"}, new String[]{"read"}, .addIndices(new String[]{"*"}, new String[]{"read"},
new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}")) new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}"))
.get(); .get();
try { if (anonymousEnabled) {
client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get(); assertNoTimeout(client()
fail("user should not be able to execute any cluster actions!"); .filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get());
} catch (ElasticsearchSecurityException e) { } else {
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> client()
.filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get());
assertThat(e.status(), is(RestStatus.FORBIDDEN)); assertThat(e.status(), is(RestStatus.FORBIDDEN));
} }
} else { } else {
@ -358,10 +361,12 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
.prepareHealth().get(); .prepareHealth().get();
assertFalse(response.isTimedOut()); assertFalse(response.isTimedOut());
c.prepareDeleteRole("test_role").get(); c.prepareDeleteRole("test_role").get();
try { if (anonymousEnabled) {
client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get(); assertNoTimeout(
fail("user should not be able to execute any actions!"); client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get());
} catch (ElasticsearchSecurityException e) { } else {
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () ->
client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get());
assertThat(e.status(), is(RestStatus.FORBIDDEN)); assertThat(e.status(), is(RestStatus.FORBIDDEN));
} }
} }
@ -406,11 +411,14 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
assertThat(joe.roles(), arrayContaining("read_role")); assertThat(joe.roles(), arrayContaining("read_role"));
assertThat(joe.fullName(), is("Joe Smith")); assertThat(joe.fullName(), is("Joe Smith"));
// test that role change took effect // test that role change took effect if anonymous is disabled as anonymous grants monitoring permissions...
try { if (anonymousEnabled) {
client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get(); assertNoTimeout(
fail("test_role does not have permission to get health"); client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get());
} catch (ElasticsearchSecurityException e) { } 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")); assertThat(e.getMessage(), containsString("authorized"));
} }
@ -434,7 +442,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
// test with new password and role // test with new password and role
response = client() response = client()
.filterWithHeader( .filterWithHeader(
Collections.singletonMap("Authorization",basicAuthHeaderValue("joe", new SecuredString("changeme2".toCharArray())))) Collections.singletonMap("Authorization",
basicAuthHeaderValue("joe", new SecuredString("changeme2".toCharArray()))))
.admin().cluster().prepareHealth().get(); .admin().cluster().prepareHealth().get();
assertFalse(response.isTimedOut()); assertFalse(response.isTimedOut());
} }
@ -558,7 +567,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
response = client() response = client()
.filterWithHeader( .filterWithHeader(
Collections.singletonMap("Authorization", basicAuthHeaderValue("joe", new SecuredString("changeme".toCharArray())))) Collections.singletonMap("Authorization",
basicAuthHeaderValue("joe", new SecuredString("changeme".toCharArray()))))
.admin().cluster().prepareHealth().get(); .admin().cluster().prepareHealth().get();
assertThat(response.isTimedOut(), is(false)); assertThat(response.isTimedOut(), is(false));
} }

View File

@ -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.alias.IndicesAliasesRequest.AliasActions;
import org.elasticsearch.action.admin.indices.create.CreateIndexAction; import org.elasticsearch.action.admin.indices.create.CreateIndexAction;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; 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.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.admin.indices.recovery.RecoveryAction; import org.elasticsearch.action.admin.indices.recovery.RecoveryAction;
import org.elasticsearch.action.admin.indices.recovery.RecoveryRequest; 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) { private Authentication createAuthentication(User user) {
RealmRef lookedUpBy = user.runAs() == null ? null : new RealmRef("looked", "up", "by"); RealmRef lookedUpBy = user.runAs() == null ? null : new RealmRef("looked", "up", "by");
return new Authentication(user, new RealmRef("test", "test", "foo"), lookedUpBy); return new Authentication(user, new RealmRef("test", "test", "foo"), lookedUpBy);