From cc12665bb7645d17ba70edcf952ee6a1ea63127b Mon Sep 17 00:00:00 2001 From: Sam Tunnicliffe Date: Wed, 25 Apr 2018 12:11:34 +0100 Subject: [PATCH] Improve RolesCache to include detailed role info Patch by Sam Tunnicliffe; reviewed by Jay Zhuang --- CHANGES.txt | 1 + .../org/apache/cassandra/auth/AuthCache.java | 13 + .../cassandra/auth/AuthenticatedUser.java | 31 ++- .../cassandra/auth/CassandraAuthorizer.java | 7 +- .../cassandra/auth/CassandraRoleManager.java | 228 ++++++++---------- .../apache/cassandra/auth/IRoleManager.java | 18 ++ src/java/org/apache/cassandra/auth/Role.java | 72 ++++++ src/java/org/apache/cassandra/auth/Roles.java | 132 +++++++++- .../org/apache/cassandra/auth/RolesCache.java | 34 ++- .../apache/cassandra/service/ClientState.java | 5 +- .../auth/CassandraNetworkAuthorizerTest.java | 36 +-- .../auth/CassandraRoleManagerTest.java | 88 +++++++ .../apache/cassandra/auth/RoleTestUtils.java | 85 +++++++ .../org/apache/cassandra/auth/RolesTest.java | 95 ++++++++ 14 files changed, 668 insertions(+), 177 deletions(-) create mode 100644 src/java/org/apache/cassandra/auth/Role.java create mode 100644 test/unit/org/apache/cassandra/auth/CassandraRoleManagerTest.java create mode 100644 test/unit/org/apache/cassandra/auth/RoleTestUtils.java create mode 100644 test/unit/org/apache/cassandra/auth/RolesTest.java diff --git a/CHANGES.txt b/CHANGES.txt index 1ba9975738..a7468f4d90 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.0 + * Extend RolesCache to include detailed role info (CASSANDRA-14497) * Add fqltool compare (CASSANDRA-14619) * Add fqltool replay (CASSANDRA-14618) * Log keyspace in full query log (CASSANDRA-14656) diff --git a/src/java/org/apache/cassandra/auth/AuthCache.java b/src/java/org/apache/cassandra/auth/AuthCache.java index 39542305a5..d6ff0b0eab 100644 --- a/src/java/org/apache/cassandra/auth/AuthCache.java +++ b/src/java/org/apache/cassandra/auth/AuthCache.java @@ -89,6 +89,19 @@ public class AuthCache implements AuthCacheMBean } } + protected void unregisterMBean() + { + try + { + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + mbs.unregisterMBean(getObjectName()); + } + catch (Exception e) + { + logger.warn("Error unregistering {} cache mbean", name, e); + } + } + protected ObjectName getObjectName() throws MalformedObjectNameException { return new ObjectName(MBEAN_NAME_BASE + name); diff --git a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java index 3d7c078c7d..9f22beaa31 100644 --- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java +++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java @@ -94,18 +94,46 @@ public class AuthenticatedUser /** * Get the roles that have been granted to the user via the IRoleManager * - * @return a list of roles that have been granted to the user + * @return a set of identifiers for the roles that have been granted to the user */ public Set getRoles() { return Roles.getRoles(role); } + /** + * Get the detailed info on roles granted to the user via IRoleManager + * + * @return a set of Role objects detailing the roles granted to the user + */ + public Set getRoleDetails() + { + return Roles.getRoleDetails(role); + } + public Set getPermissions(IResource resource) { return permissionsCache.getPermissions(this, resource); } + /** + * Check whether this user has login privileges. + * LOGIN is not inherited from granted roles, so must be directly granted to the primary role for this user + * + * @return true if the user is permitted to login, false otherwise. + */ + public boolean canLogin() + { + return Roles.canLogin(getPrimaryRole()); + } + + /** + * Verify that there is not DC level restriction on this user accessing this node. + * Further extends the login privilege check by verifying that the primary role for this user is permitted + * to perform operations in the local (to this node) datacenter. Like LOGIN, this is not inherited from + * granted roles. + * @return true if the user is permitted to access nodes in this node's datacenter, false otherwise + */ public boolean hasLocalAccess() { return networkAuthCache.get(this.getPrimaryRole()).canAccess(Datacenters.thisDatacenter()); @@ -136,4 +164,5 @@ public class AuthenticatedUser { return Objects.hashCode(name); } + } diff --git a/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java b/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java index cebde13116..238b5b506e 100644 --- a/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java +++ b/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java @@ -68,9 +68,10 @@ public class CassandraAuthorizer implements IAuthorizer Set permissions = EnumSet.noneOf(Permission.class); - for (RoleResource role: user.getRoles()) - addPermissionsForRole(permissions, resource, role); - + // Even though we only care about the RoleResource here, we use getRoleDetails as + // it saves a Set creation in RolesCache + for (Role role: user.getRoleDetails()) + addPermissionsForRole(permissions, resource, role.resource); return permissions; } diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java index f5dd457c0d..ebb7d5ff99 100644 --- a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java +++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java @@ -20,12 +20,14 @@ package org.apache.cassandra.auth; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.*; -import com.google.common.base.Objects; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,27 +81,24 @@ public class CassandraRoleManager implements IRoleManager static final String DEFAULT_SUPERUSER_PASSWORD = "cassandra"; // Transform a row in the AuthKeyspace.ROLES to a Role instance - private static final Function ROW_TO_ROLE = new Function() + private static final Function ROW_TO_ROLE = row -> { - public Role apply(UntypedResultSet.Row row) + try { - try - { - return new Role(row.getString("role"), - row.getBoolean("is_superuser"), - row.getBoolean("can_login"), - row.has("member_of") ? row.getSet("member_of", UTF8Type.instance) - : Collections.emptySet()); - } - // Failing to deserialize a boolean in is_superuser or can_login will throw an NPE - catch (NullPointerException e) - { - logger.warn("An invalid value has been detected in the {} table for role {}. If you are " + - "unable to login, you may need to disable authentication and confirm " + - "that values in that table are accurate", AuthKeyspace.ROLES, row.getString("role")); - throw new RuntimeException(String.format("Invalid metadata has been detected for role %s", row.getString("role")), e); - } - + return new Role(row.getString("role"), + row.getBoolean("is_superuser"), + row.getBoolean("can_login"), + Collections.emptyMap(), + row.has("member_of") ? row.getSet("member_of", UTF8Type.instance) + : Collections.emptySet()); + } + // Failing to deserialize a boolean in is_superuser or can_login will throw an NPE + catch (NullPointerException e) + { + logger.warn("An invalid value has been detected in the {} table for role {}. If you are " + + "unable to login, you may need to disable authentication and confirm " + + "that values in that table are accurate", AuthKeyspace.ROLES, row.getString("role")); + throw new RuntimeException(String.format("Invalid metadata has been detected for role %s", row.getString("role")), e); } }; @@ -117,9 +116,6 @@ public class CassandraRoleManager implements IRoleManager return rounds; } - // NullObject returned when a supplied role name not found in AuthKeyspace.ROLES - private static final Role NULL_ROLE = new Role(null, false, false, Collections.emptySet()); - private SelectStatement loadRoleStatement; private final Set