Repository: cassandra Updated Branches: refs/heads/trunk 56348ea7b -> b32ce687e
Make LIST USERS display inherited superuser status patch by Sam Tunnicliffe; reviewed by Aleksey Yeschenko for CASSANDRA-8849 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/b32ce687 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/b32ce687 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/b32ce687 Branch: refs/heads/trunk Commit: b32ce687e713de8d8535b0607c9edd9b55a7b6aa Parents: 56348ea Author: Sam Tunnicliffe <s...@beobal.com> Authored: Tue Mar 3 15:58:48 2015 -0800 Committer: Aleksey Yeschenko <alek...@apache.org> Committed: Tue Mar 3 15:58:48 2015 -0800 ---------------------------------------------------------------------- CHANGES.txt | 2 +- conf/cassandra.yaml | 8 ++ .../cassandra/auth/AuthenticatedUser.java | 89 +-------------- src/java/org/apache/cassandra/auth/Roles.java | 59 ++++++++++ .../org/apache/cassandra/auth/RolesCache.java | 109 +++++++++++++++++++ .../org/apache/cassandra/config/Config.java | 2 + .../cassandra/config/DatabaseDescriptor.java | 12 ++ .../statements/AuthenticationStatement.java | 20 ---- .../cql3/statements/DropRoleStatement.java | 6 +- .../cql3/statements/ListUsersStatement.java | 6 +- .../apache/cassandra/service/ClientState.java | 2 +- 11 files changed, 200 insertions(+), 115 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index cc3658d..b877cbe 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ 3.0 - * Add role based access control (CASSANDRA-7653, 8650, 7216, 8760) + * Add role based access control (CASSANDRA-7653, 8650, 7216, 8760, 8849) * Avoid accessing partitioner through StorageProxy (CASSANDRA-8244, 8268) * Upgrade Metrics library and remove depricated metrics (CASSANDRA-5657) * Serializing Row cache alternative, fully off heap (CASSANDRA-7438) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/conf/cassandra.yaml ---------------------------------------------------------------------- diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index 3643986..240f37a 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -93,6 +93,14 @@ role_manager: CassandraRoleManager # Will be disabled automatically for AllowAllAuthenticator. roles_validity_in_ms: 2000 +# Refresh interval for roles cache (if enabled). +# After this interval, cache entries become eligible for refresh. Upon next +# access, an async reload is scheduled and the old value returned until it +# completes. If roles_validity_in_ms is non-zero, then this must be +# also. +# Defaults to the same value as roles_validity_in_ms. +# roles_update_interval_in_ms: 1000 + # Validity period for permissions cache (fetching permissions can be an # expensive operation depending on the authorizer, CassandraAuthorizer is # one example). Defaults to 2000, set to 0 to disable. http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/auth/AuthenticatedUser.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java index e4a065d..ee62503 100644 --- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java +++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java @@ -18,20 +18,10 @@ package org.apache.cassandra.auth; import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; import com.google.common.base.Objects; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListenableFutureTask; -import org.apache.cassandra.concurrent.ScheduledExecutors; import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.exceptions.RequestExecutionException; -import org.apache.cassandra.exceptions.RequestValidationException; /** * Returned from IAuthenticator#authenticate(), represents an authenticated user everywhere internally. @@ -47,9 +37,6 @@ public class AuthenticatedUser public static final String ANONYMOUS_USERNAME = "anonymous"; public static final AuthenticatedUser ANONYMOUS_USER = new AuthenticatedUser(ANONYMOUS_USERNAME); - // User-level roles cache - private static final LoadingCache<RoleResource, Set<RoleResource>> rolesCache = initRolesCache(); - // User-level permissions cache. private static final PermissionsCache permissionsCache = new PermissionsCache(DatabaseDescriptor.getPermissionsValidity(), DatabaseDescriptor.getPermissionsUpdateInterval(), @@ -84,16 +71,7 @@ public class AuthenticatedUser */ public boolean isSuper() { - return !isAnonymous() && hasSuperuserRole(); - } - - private boolean hasSuperuserRole() - { - IRoleManager roleManager = DatabaseDescriptor.getRoleManager(); - for (RoleResource role : getRoles()) - if (roleManager.isSuper(role)) - return true; - return false; + return !isAnonymous() && Roles.hasSuperuserStatus(role); } /** @@ -121,71 +99,12 @@ public class AuthenticatedUser */ public Set<RoleResource> getRoles() { - if (rolesCache == null) - return loadRoles(role); - - try - { - return rolesCache.get(role); - } - catch (Exception e) - { - throw new RuntimeException(e); - } + return Roles.getRoles(role); } - public static Set<Permission> getPermissions(AuthenticatedUser user, IResource resource) - { - return permissionsCache.getPermissions(user, resource); - } - - private static Set<RoleResource> loadRoles(RoleResource primary) - { - try - { - return DatabaseDescriptor.getRoleManager().getRoles(primary, true); - } - catch (RequestValidationException e) - { - throw new AssertionError(e); // not supposed to happen - } - catch (RequestExecutionException e) - { - throw new RuntimeException(e); - } - } - - private static LoadingCache<RoleResource, Set<RoleResource>> initRolesCache() + public Set<Permission> getPermissions(IResource resource) { - if (DatabaseDescriptor.getAuthenticator() instanceof AllowAllAuthenticator) - return null; - - int validityPeriod = DatabaseDescriptor.getRolesValidity(); - if (validityPeriod <= 0) - return null; - - return CacheBuilder.newBuilder() - .refreshAfterWrite(validityPeriod, TimeUnit.MILLISECONDS) - .build(new CacheLoader<RoleResource, Set<RoleResource>>() - { - public Set<RoleResource> load(RoleResource primary) - { - return loadRoles(primary); - } - - public ListenableFuture<Set<RoleResource>> reload(final RoleResource primary, Set<RoleResource> oldValue) - { - ListenableFutureTask<Set<RoleResource>> task = ListenableFutureTask.create(new Callable<Set<RoleResource>>() - { - public Set<RoleResource> call() - { - return loadRoles(primary); - } - }); - ScheduledExecutors.optionalTasks.execute(task); - return task; - } - }); + return permissionsCache.getPermissions(this, resource); } @Override http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/auth/Roles.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/Roles.java b/src/java/org/apache/cassandra/auth/Roles.java new file mode 100644 index 0000000..c73f882 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/Roles.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cassandra.auth; + +import java.util.Set; + +import org.apache.cassandra.config.DatabaseDescriptor; + +public class Roles +{ + private static final RolesCache cache = new RolesCache(DatabaseDescriptor.getRolesValidity(), + DatabaseDescriptor.getRolesUpdateInterval(), + DatabaseDescriptor.getRolesCacheMaxEntries(), + DatabaseDescriptor.getRoleManager()); + + /** + * Get all roles granted to the supplied Role, including both directly granted + * and inherited roles. + * The returned roles may be cached if roles_validity_in_ms > 0 + * + * @param primaryRole the Role + * @return set of all granted Roles for the primary Role + */ + public static Set<RoleResource> getRoles(RoleResource primaryRole) + { + return cache.getRoles(primaryRole); + } + + /** + * Returns true if the supplied role or any other role granted to it + * (directly or indirectly) has superuser status. + * + * @param role the primary role + * @return true if the role has superuser status, false otherwise + */ + public static boolean hasSuperuserStatus(RoleResource role) + { + IRoleManager roleManager = DatabaseDescriptor.getRoleManager(); + for (RoleResource r : cache.getRoles(role)) + if (roleManager.isSuper(r)) + return true; + return false; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/auth/RolesCache.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/RolesCache.java b/src/java/org/apache/cassandra/auth/RolesCache.java new file mode 100644 index 0000000..0e9e134 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/RolesCache.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cassandra.auth; + +import java.util.Set; +import java.util.concurrent.*; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListenableFutureTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.concurrent.DebuggableThreadPoolExecutor; +import org.apache.cassandra.config.DatabaseDescriptor; + +public class RolesCache +{ + private static final Logger logger = LoggerFactory.getLogger(RolesCache.class); + + private final ThreadPoolExecutor cacheRefreshExecutor = new DebuggableThreadPoolExecutor("RolesCacheRefresh", + Thread.NORM_PRIORITY); + private final IRoleManager roleManager; + private final LoadingCache<RoleResource, Set<RoleResource>> cache; + + public RolesCache(int validityPeriod, int updateInterval, int maxEntries, IRoleManager roleManager) + { + this.roleManager = roleManager; + this.cache = initCache(validityPeriod, updateInterval, maxEntries); + } + + public Set<RoleResource> getRoles(RoleResource role) + { + if (cache == null) + return roleManager.getRoles(role, true); + + try + { + return cache.get(role); + } + catch (ExecutionException e) + { + throw new RuntimeException(e); + } + } + + private LoadingCache<RoleResource, Set<RoleResource>> initCache(int validityPeriod, + int updateInterval, + int maxEntries) + { + if (DatabaseDescriptor.getAuthenticator() instanceof AllowAllAuthenticator) + return null; + + if (validityPeriod <= 0) + return null; + + return CacheBuilder.newBuilder() + .refreshAfterWrite(updateInterval, TimeUnit.MILLISECONDS) + .expireAfterWrite(validityPeriod, TimeUnit.MILLISECONDS) + .maximumSize(maxEntries) + .build(new CacheLoader<RoleResource, Set<RoleResource>>() + { + public Set<RoleResource> load(RoleResource primaryRole) + { + return roleManager.getRoles(primaryRole, true); + } + + public ListenableFuture<Set<RoleResource>> reload(final RoleResource primaryRole, + final Set<RoleResource> oldValue) + { + ListenableFutureTask<Set<RoleResource>> task; + task = ListenableFutureTask.create(new Callable<Set<RoleResource>>() + { + public Set<RoleResource> call() throws Exception + { + try + { + return roleManager.getRoles(primaryRole, true); + } + catch (Exception e) + { + logger.debug("Error performing async refresh of user roles", e); + throw e; + } + } + }); + cacheRefreshExecutor.execute(task); + return task; + } + }); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/config/Config.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java index 806c7b4..eec6826 100644 --- a/src/java/org/apache/cassandra/config/Config.java +++ b/src/java/org/apache/cassandra/config/Config.java @@ -48,6 +48,8 @@ public class Config public int permissions_cache_max_entries = 1000; public int permissions_update_interval_in_ms = -1; public int roles_validity_in_ms = 2000; + public int roles_cache_max_entries = 1000; + public int roles_update_interval_in_ms = -1; /* Hashing strategy Random or OPHF */ public String partitioner; http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/config/DatabaseDescriptor.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index 2891e9a..92d5d5b 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -640,6 +640,18 @@ public class DatabaseDescriptor return conf.roles_validity_in_ms; } + public static int getRolesCacheMaxEntries() + { + return conf.roles_cache_max_entries; + } + + public static int getRolesUpdateInterval() + { + return conf.roles_update_interval_in_ms == -1 + ? conf.roles_validity_in_ms + : conf.roles_update_interval_in_ms; + } + public static int getThriftFramedTransportSize() { return conf.thrift_framed_transport_size_in_mb * 1024 * 1024; http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java b/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java index b0ea246..151e4f0 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java @@ -17,11 +17,8 @@ */ package org.apache.cassandra.cql3.statements; - -import org.apache.cassandra.auth.IRoleManager; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.auth.RoleResource; -import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.exceptions.RequestExecutionException; @@ -72,22 +69,5 @@ public abstract class AuthenticationStatement extends ParsedStatement implements state.getUser().getName())); } } - - protected boolean hasSuperuserStatus(RoleResource role) - { - IRoleManager roleManager = DatabaseDescriptor.getRoleManager(); - try - { - for (RoleResource r : roleManager.getRoles(role, true)) - if (roleManager.isSuper(r)) - return true; - return false; - } - catch(RequestValidationException | RequestExecutionException e) - { - throw new RuntimeException(e); - } - } - } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java index 44c749b..ec4bde7 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java @@ -17,9 +17,7 @@ */ package org.apache.cassandra.cql3.statements; -import org.apache.cassandra.auth.AuthenticatedUser; -import org.apache.cassandra.auth.Permission; -import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.auth.*; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.RoleName; import org.apache.cassandra.exceptions.*; @@ -40,7 +38,7 @@ public class DropRoleStatement extends AuthenticationStatement public void checkAccess(ClientState state) throws UnauthorizedException { super.checkPermission(state, Permission.DROP, role); - if (hasSuperuserStatus(role) && !state.getUser().isSuper()) + if (Roles.hasSuperuserStatus(role) && !state.getUser().isSuper()) throw new UnauthorizedException("Only superusers can drop a role with superuser status"); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java b/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java index 3edf3a4..7251980 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java @@ -21,9 +21,7 @@ import java.util.List; import com.google.common.collect.ImmutableList; -import org.apache.cassandra.auth.AuthKeyspace; -import org.apache.cassandra.auth.IRoleManager; -import org.apache.cassandra.auth.RoleResource; +import org.apache.cassandra.auth.*; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnSpecification; @@ -53,7 +51,7 @@ public class ListUsersStatement extends ListRolesStatement if (!roleManager.canLogin(role)) continue; result.addColumnValue(UTF8Type.instance.decompose(role.getRoleName())); - result.addColumnValue(BooleanType.instance.decompose(roleManager.isSuper(role))); + result.addColumnValue(BooleanType.instance.decompose(Roles.hasSuperuserStatus(role))); } return new ResultMessage.Rows(result); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b32ce687/src/java/org/apache/cassandra/service/ClientState.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/service/ClientState.java b/src/java/org/apache/cassandra/service/ClientState.java index 57a57e4..1bc6e9d 100644 --- a/src/java/org/apache/cassandra/service/ClientState.java +++ b/src/java/org/apache/cassandra/service/ClientState.java @@ -327,6 +327,6 @@ public class ClientState private Set<Permission> authorize(IResource resource) { - return AuthenticatedUser.getPermissions(user, resource); + return user.getPermissions(resource); } }